SE-0522: Source-Level Control Over Compiler Warnings

Hello, Swift community!

The review of SE-0522: Source-Level Control Over Compiler Warnings begins now and runs through April 2nd, 2026.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by DM. When contacting the review manager directly, please put "SE-0522" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available in the Swift evolution repository.

With thanks in advance for your consideration,

Tony Allevato
Review Manager

17 Likes

OMG yes!

I would use this extensively for working with legacy API’s and for silencing warnings for situations I otherwise verify.

I read the proposal and it clearly addressed almost every issue I could think of.

Two issues I had were unstated, but readers might appreciate the clarification:

(1) Default parameter expressions are outside the lexical scope of a function, so they cannot be re-ranked in this manner. (Any other unreachable (non-top-level) code?)

(2) I assume the compiler first generates the warning and then re-ranks the level, so this cannot be used to work around bugs in compiler diagnostics. Otherwise, I’d have more use-cases; would they be valid or reliable?

As a future direction, it could help to condition such a declaration on a range of compiler versions and/or package traits, in order to target compiler behaviors or to progressively increase strictness of one’s code for target versions.

2 Likes

bikeshedding, wouldn't this read more fluently as?

@diagnose(<DiagnosticGroup>, as: {.warning, .error}, ...)
11 Likes

Another motivation for spelling the attribute @diagnose would be to open up a future direction where it also offers control over remarks. I've been thinking about how it would be useful to be able to opt-in to remarks in a particular region of code when trying to understand compiler behavior and it seems like this attribute could be a natural fit. There could of course be another attribute for this purpose, e.g. @remark(<DiagnosticGroup>), but it seems worth exploring whether a single diagnostic control attribute would make sense.

4 Likes

Having an official way to suppress warnings is a very welcome addition!

I agree that the proposed spelling doesn’t seem very fluent, particularly as: ignored.

Ultimately warning, error, and ignored are not the same part of speech, so using them as arguments in the same position will create some slightly-off phrasings. warn, error and ignore are all verbs, so could work together more fluently (for example).

What if we instead had separate attributes for each use case?

@ignore(DiagnosticGroup, reason: ...)

@error(DiagnosticGroup, reason: ...)

@warn(DiagnosticGroup, reason: ...)

Or, what if we used different argument labels for each use case?

@warning(ignore: DiagnosticGroup, reason: ...)

@warning(error: DiagnosticGroup, reason: ...)

@warning(warn: DiagnosticGroup, reason: ...)

// or

@warning(ignore: DiagnosticGroup, reason: ...)

@warning(asError: DiagnosticGroup, reason: ...)

@warning(asWarning: DiagnosticGroup, reason: ...)

// or

@warning(ignore: DiagnosticGroup, reason: ...)

@warning(DiagnosticGroup, as: error, reason: ...)

@warning(DiagnosticGroup, as: warning, reason: ...)

I also like the way these spellings put the ā€œinstructionā€ part before the diagnostic group name rather than after. e.g. ā€œIgnore deprecation warningsā€ is more fluent than: ā€œDeprecation warnings: ignoreā€)

1 Like

I was thinking very similar, it would be nice to control performance-diagnostics / opt-remarks with this same mechanism.

e.g. @diagnose(ImplicitAllocations, as: {.remark, .warning, .error})

3 Likes

Overall the feature seems quite useful and I expect it to be widely adopted. I agree with others that the naming could probably be improved; one use that came to mind, as I expect it will be somewhat common in codebases that aspire to enable -warnings-as-errors by default, is @warn(<diag>, as: warning).

A few other assorted thoughts after an initial read through:


The proposal text currently says:

when -suppress-warnings is in effect, it suppresses all warning diagnostics module-wide, and @warn attributes have no observable effect, including @warn attributes that specify as: error behavior.

This appears to be at odds with the current implementation, which was recently changed to allow warnings that are lexically escalated to errors to "escape" from being suppressed (enabled in this PR). Is this text still accurate as to the intended interaction here?


Maybe a "future direction" sort of thing, but was support for a variadic list of diagnostic groups considered? I don't have a great sense if the desire for that would arise much in practice, and I assume such functionality could be introduced later if desired.


How are unknown diagnostic groups handled by this mechanism? I believe the existing infrastructure emits an UnknownWarningGroup warning if command line diagnostic groups are specified that do not match a known value. Will this use the same mechanism (experimentation suggests it may not at the moment)? For targets that upgrade warnings to errors by default, it may be useful to allow @warn(FutureCompilerWarning, as: warning) without that becoming an error when building with older compilers (or requiring additional source-level or build configuration to deal with it).


This is sort of an aside, but in doing some cursory research into how other ecosystems implement this sort of thing, I learned that Rust has some affordance for declaring that a diagnostic is expected to be produced, which will then warn if the compiler doesn't emit it. That seems like a nice possible feature – curious if anyone has thought at all about anything along those lines.

1 Like

One thing that’s a bit awkward about the idea is that remarks really reside on a different axis from warnings and errors; they are just enabled or disabled. It wouldn’t make sense to diagnose a category of remarks as warnings or errors since they are designed to be informational. So the acceptable arguments for @diagnose when operating on remarks would need to be different.

This was buried a bit in the pre-review discussion of the proposal PR, but see [Source Warning Control] Small misc. improvements to `@warn` support by artemcm Ā· Pull Request #87927 Ā· swiftlang/swift Ā· GitHub for an updated (but not yet merged) implementation PR.

2 Likes

Had it not been suggested by @rauhul, I too would have suggested @diagnosed. I'd still riff and say that changing the argument order so we have @diagnose(as: error, DiagnosticGroup) reads even more fluently.

Yes, it makes ignored read weirdly, but I don't think we need to redesign the whole scheme around that one option: it's of-a-kind with @available(*, unavailable)—and, in my view, much more intuitive than that. And for reasons I'll expound on below, I continue to believe that the likely-to-be most sought-after ignored warning ought to be actively discouraged anyway.


To me, this is really a key future direction that would get us to a better resting place. Yes, declarations are logical units of code and it's reasonable enough for one to want to upgrade or downgrade warnings for that entire unit.

As a matter of encouraging code authors to do the right thing, though, being able to scope diagnostic overrides to a smaller region of code (in the spirit of unsafe blocks in Rust) seems right. As precedent, recall that (for reasons we don't need to get into here) we tacked on throws(some Error) annotations to do blocks where previously they were exclusive to declarations.

I think it's regrettable that we're limited to having "inward-facing" functionality exclusively usable on "outward-facing" parts of the language: by which I mean, the caller of a function is not the primary audience—and indeed, sometimes (often?) has no need to know at all—when a warning diagnosed inside a function's implementation is being upgraded or suppressed.


@warn(DeprecatedDeclaration, as: ignored)

This brings me to my "broken record" comment. I continue to believe that it is the wrong thing to offer users an option to suppress all deprecation warnings as the only way to suppress any deprecation warnings.

Semantically, the diagnostic group for deprecated declarations isn't really one or a small handful of warnings but is an unbounded number of them which can be added to by anyone. My sense is that users will frequently want to use a specific API or a specific suite of APIs vended in a single library or package despite deprecation—but what's being offered here as the exclusive way to accomplish that goal is telling the compiler that you never want to be warned again about any deprecations in a scope whatsoever.

For that reason, I continue to believe that a feature designed like @preconcurrency, which allows for targeted suppression of some diagnostics with respect to a specific import, is the right way of expressing that you want to use a deprecated API or package. Of course, that does not have to be (and shouldn't be) designed here: but at a minimum, I'd want to remove text which suggests to users that @warn(DeprecatedDeclaration, as: ignored) is a good idea and achieve consensus that it's not; and perhaps more severely, I'd want to consider whether we want to actually disable this specific usage.

1 Like

Hmm. I have to say that I think this is exactly backwards; "diagnose [problems like] X as [an] error" is natural English word order, while "diagnose as [an] error [problems like] X" is not.

Now, if you've got a ton of these attributes, your order does give a nice table-like effect as long as the user sorts them by kind:

@diagnose(as: warning, Jabberwock)
@diagnose(as: warning, Jubjub)
@diagnose(as: warning, Bandersnatch)
@diagnose(as: error, VorpalSword)
@diagnose(as: error, SnickerSnack)

On the other hand, I think if people find themselves writing that sort of thing so much that it becomes something to factor into the design, we've probably failed on multiple levels.

4 Likes

Heh, personally, I strongly prefer the latter word order when writing in English :)

-0.5

This proposal treats compilation diagnostic policy as declaration metadata, but diagnostic policy is better understood in the tooling or build config space. For instance, I’m already using SwiftLint and custom Danger plugins to get what I need from elevating or suppressing diagnostics, and I can tune those independently or org-wide for an entire team of developers, without disrupting source code.

This also implies (to me) that we may have to wait for a future version of Swift rather than simply update to a new Xcode version that contains more pragmatic knobs and buttons to make warning and error diagnostics more flexible.

1 Like

I would love to see some options to customize warnings generated in generated Objective-C header as well. While back I faced an issue with it and had to rely on an ugly hack: Module-Swift.h: Property type 'Type2Details * _Nonnull' is incompatible with type 'Type1Details * _Nonnull' inherited from 'Type1' - #6 by tera

I'm really glad we're finally doing this, and am definitely supportive of the language feature as a whole. There used to be a time where the need for this wasn't treated as seriously, so I'm glad we've realized the reality of library maintainers who e.g. maintain libraries across many Swift versions that such feature really is necessary to not drown in a flood of warnings that "we know of but can't do anything about" -- I've encountered many such cases personally, so I'm really looking forward to this! :partying_face:

Yeah, most definitely @diagnose/@diagnostic please

The name does need improvement though, the @warn isn't good since it's not

Prior art not mentioned in the proposal in other languages would be: the well known @SuppressWarnings(identifier) in Java, or the @nowarn("msg=regex") in Scala. They're both focused on suppressing warnings and the proposal here offers actually more since we can raise the severity level e.g. @warn(DeprecatedDeclaration, as: error).

This is a welcome addition, however it results in a very misleading name ("warn"), as it is not warning at all, it is causing errors (or nothing, where the suppress word would have worked). Saying "warn as error" is very very weird.

Long story short, the only reasonable word I can think of here @diagnose(Group, as: ...), or I'd personally prefer @diagnostic(Group, as: ...), because the "diagnose" (or "warn" make it sound like its presence in source should cause a warning to be emitted tbh).

Others have also already voiced their preference of this spelling, so I hope we can converge one of those forms, that doesn't confuse the meaning of the word warning.

Clarify handling of unknown group identifiers

The proposal doesn't really specify how unknown identifiers are handled, and I really do not want to be littering my code with:

#if compiler(>=6.6) // because BadIdeaWarning was introduced in 6.6
@diagnostic(BadIdeaWarning, ignored)
#endif
public func foo() { }

... only because that warning group was introduced in 6.6 but I'm also maintaining my library for 6.5 and 6.4 etc. It should of course be allowed to #if for a specific version or platform, but I would not want to be forced into doing this only because an identifier was introduced in a recent Swift.

Can we please make sure unknown identifiers are allowed and silently ignored?

There could be some flag to make this a warning or error to make sure e.g. in CI that on latest swift all the identifiers are actually valid.

Alternatively I suppose this could emit a warning... but I feel like then I'd be going around and adding @diagnostic(UnknownWarningGroup, ignored) whenever facing this situation where suppressing a warning new in Swift X, while I'm maintaining X-2 still.

What's the proposal authors ideas here?

Document interaction with --warnings-as-errors

It would be good to clarify the interaction here, it "seems obvious" but it would be better to spell it out here since this will become the reference to the behavior in the future.

I'd expect:

  • --warnings-as-errors causes all warnings to become errors
  • except:
    • if a warning was downgraded to ignored: @diagnostic(Something, ignore) -> ignored
    • if a warning was kept "definitely a warning" @diagnostic(SomethingWarning, warning) -> stays a warning (?)

It'd be good to put this clarification next to the -suppress-warnings section

Clarify interaction with peer macros

The proposal just shows interaction with #macros but doesn't discuss macros which introduce new declarations.

For example:

@diagnostic(DeprecatedSomething, as: error)
@Resolvable protocol Kappa ... { ... } 

// generates:

// @diagnostic(DeprecatedSomething, as: error) ??? 
distributed actor $Kappa ... { ... } 

Are we leaving it to those decl macros to copy diagnostic attributes OR are we giving control to silence warnings to end users?

Arguably, this is a tool for end users, and if they're using a macro that produces warnings, they may need to silence it? Unless I'm misremembering how warnings are handled in emitted peers.

9 Likes

I do like the @diagnose spelling, as it does read a bit cleaner to state ā€œdiagnose such-and-such as an errorā€ compared to ā€œwarn … as an errorā€, so I can see this being an improvement. @warn was chosen primarily to distinguish the feature being applicable only to warnings belonging to a diagnostic group and to make it clear that this control cannot be applied to errors belonging to a diagnostic group. The name @diagnose loses that meaning at-a-glance though we can arguably make up for that by emitting an error when this attribute is used with a diagnostic group which contains errors.

It does read much more intuitive to me to have the attribute be prescriptive (a verb) rather than descriptive (a noun) though, since diagnostic seems to me to read a bit awkward as it feels as though it is referring to a very specific diagnostic which I can see being confusing. I could see a stronger argument for @diagnosticGroup but that is starting to get pretty verbose.

I had some similar thoughts on this topic recently here. I agree that @diagnose potentially means we can keep a door open to a design that includes control over remarks; but, as you pointed out, remarks would require a different set of arguments because none of the existing error|warning|ignored behaviours make sense. Which then means @diagnose is potentially confusing if it starts to expect different arguments based on what kind of group is specified, which would be an argument towards considering also adding @remark(<Diagnostic Group ID>) (without any behavior specifier). I don’t think that means that we must stick with @warn though because @diagnose and @remark coexisting seems just fine to me due to remarks generally being a fully separate diagnostic kind. Still, it seems pretty undeniable that having a similar control to enable remarks in a fine-grained fashion seems very valuable. It is definitely worth at least including it as a future direction.

I think it valuable to have a single common mechanism for controlling warning behavior that the user can reach for, it’s just fewer things to remember; both the attribute name and argument labels very much feel like different mechanisms, rather than different controls over the same mechanism.

I can see this being a future direction or an alternative considered, though I don’t think the intent of the proposal quite justifies having variadic diagnostic groups. As already mentioned, if the user is specifying this attribute with longer and longer collections of diagnostic groups - we have failed on many different levels here. In practice, I expect applications of this attribute to be more-targeted.

Unknown diagnostic groups inside the @warn attribute should result in an UnknownWarningGroupwarning, yes.

As mentioned above by @jamieQ, I think consistency with -W flags is important here, so I think unknown identifiers should be treated with UnknownWarningGroupwarning. I do think though the workaround you mention of @warn(UnknownWarningGroup, as: ignored) is the right one to have. I could see this being argued as an exception to not having a module-level -Wsuppress. Silently ignoring unknown identifiers also sounds like an unpleasant tradeoff though - make a typo in a group name and you will end up quite confused.

Sorry, I am not sure I follow this example, how would this work? Do you see this as a mechanism where any unknown diagnostic group is treated as FutureCompilerWarning?

We have a similar mechanism used primarily for compiler testing though I’ve seen it used in the wild for the kind of validation you mention: swift/docs/Diagnostics.md at main Ā· swiftlang/swift Ā· GitHub

Interaction with compiler options and evaluation order section clarifies this:

The @warn attribute overrides diagnostic behavior for the specified diagnostic group, relative to its enclosing scope - with command-line behavior specifiers representing global (module-wide) scope: -warnings-as-errors, -Werror, -Wwarning.

and

At the top-level file scope, a @warn attribute overrides the specified diagnostic group's global behavior within the lexical scope of the declaration it is applied to.

So the overall model is stated to be uniform, with CLI flags (including -warnings-as-errors) define the module-wide default, and @warn overrides that default within its lexical scope. -suppress-warnings section is, on the contrary, about a flag which is treated as an exception to the overall model.

Diagnostic configuration as understood by tooling or build config space is something we have today with -Werror, -Wwarning and -warnings-as-errors. This proposal is specifically about finer-grained controls than those which can be stablished as team-wide policies.

2 Likes

That's a bit tricky because sometimes you want the compiler to verify that the expected diagnostic is actually emitted to maintain strict code hygiene, and sometimes you don't. One possible approach to address this would be a new warning (in a dedicated group) that could itself be silenced or escalated:

@warn(MissingExpectedDiagnostic, as: error/ignored)
@warn(DeprecatedDeclaration, as: ignored)

MissingExpectedDiagnostic would be emitted for any @warn attribute that fails to find a match within the declaration, excluding @warn(MissingExpectedDiagnostic, as: ...) itself to avoid language paradoxes.

Replying a bit inversed:

My thinking is that if you're doing this annotation it is to silence something you're seeing, so a typo is easy to spot by not causing the silencing you expected.

The consistency argument is good though, if we're already warning about unknown groups this makes sense to keep the behavior... I can also see that doing the explicit ignore on UnknownWarningGroup is actually a nice useful signal for the sources and something to be audited in cleanups by itself...

This sounds good then, thanks for discussing.

Ah not sure how I missed that, I was searching for -warnings-as-errors, must have typo-ed somehow. Thank you, that is sufficiently clear :+1:

@ArtemC what about the peer macros though? That's something we're not explored yet AFAICS?

This was my secret wish for Swift (just barely behind getting parameter labels for all closures included stored ones back… @Chris_Lattner3 you promised ;)… well, kind of did not say it was really impossible for it to perhaps happen one day maybe you guess…).

Do we think there might be restrictions such as around import statements? Warnings emitted by Apple’s own framework? Specific warnings that are set to be unsuppressable?

Otherwise this is almost too good to be true, thanks :).

Agreed on all but the last point here, I think it would be a bit confusing creating two classes of warnings where all warnings are equal but some are more equal than others so to speak. I would treat them as errors if you have Treat Warnings as Errors enabled.

Yes please :folded_hands:! I think the flag to elevate this to a warning or error would be a perfect compromise.

1 Like