SE-0443 introduced control over compiler warnings with command-line flags which control behaviors of specific warning groups.
This is a pitch for introducing a new declaration attribute for controlling compiler warning behavior in specific code regions: to be emitted as warnings, errors, or fully suppressed:
@warn(Deprecate, as: error)
public func foo() {
...
}
As with the access modifier debate, there are probably a very large number of variations related to controlling diagnostics that have legitimate use cases, and it'll be a matter of both taste and judgment as to which ones to offer in the language and ecosystem. An important guiding principle on this matter, I think, is whether having a particular option for controlling diagnostics makes it easier to write correct code (that is, by letting users minimize false positives and turn true positives into full errors) rather than incorrect code (that is, by letting users inadvertently suppress true positives or turn false positives into full errors).
There are probably many good use cases for lexical control of warnings as pitched here. I think ultimately some form of this pitch is an inevitable addition to the language and a good one. However, I continue to believe (1) that dealing with deprecation warnings, as you illustrate, will be the first use case that people think of and reach for; (2) that folks will want to silence them more often than upgrade them to errors; and (3) that it is the wrong choice for that particular use.
As I recall from prior discussions, acknowledging (by silencing) a deprecation warning is more likely to be related to one specific API or one specific import. For example, someone mentioned certain deprecated UIWebView APIs, which as I understand had no suitable replacements among the newer WKWebView APIs. Users of those deprecated APIs may well want to silence the deprecation warning throughout at least a file, if not an entire directory of files.
The pitched solution here has two disadvantages in that use case: (1) they'd have to sprinkle the attribute in many places—annoying, but fine; (2) they'd no longer see deprecation warnings within those scopes that arise from any dependency and not just UIWebView. The latter disadvantage, to my mind, is the major reason that the feature as pitched is the wrong choice for that use case: the cost of eliminating false positives (i.e., acknowledging and silencing a deprecation warning that the user is aware of but can't do anything else about) would be to also eliminate other, true positives—including deprecations that haven't occurred yet but that could be very important for the user to know about in the future.
Since I believe, as reflected in your go-to example, that users will want to reach for the feature you're pitching as a first choice for dealing with the deprecation warning problem, I am leery about introducing it without first having in place a solution that more suitably addresses the use case above: silence all deprecation warnings related to a particular API or import (perhaps at the import site; perhaps in the package config).
I’m sure everyone will have their own style here. However, in my experience with ObjC/C, I use the pragma directive to suppress deprecation warnings very narrowly. e.g.:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
// one line of code here
#pragma GCC diagnostic pop
Given the features in this proposal, I would create “deprecated wrappers” for deprecated API to call and annotate that with @warn(Deprecate, as: ignored). I don’t mind the additional boilerplate if if means we get this. Adding something to import or project settings to deprecate a specific API for the entire project sounds pretty complicated to design and engineer. As a framework author, there are times where I simply cannot just migrate code because something that our API calls was deprecated. One in particular in swift-foundation has been bugging me for a while, because there is no alternative to switch to - and the warning shows up when building the package.
Silencing all deprecation warnings from a single import seems like the wrong granularity to me. Different deprecations from the same module may have different justifications and remedies. It seems just as ill-advised to me to suppress all deprecations from a single module as it does to suppress all deprecation warnings in a large lexical scope. Why should I be encouraged to ignore all deprecations of standard library APIs when I set out to ignore just one? I don't think the fact that the APIs come from the same module makes it likely that that is the appropriate response. Silencing all deprecation warnings related to use of a single API does make more sense to me, but it's hard for me to imagine how that would be accomplished in source.
What I like about the proposed design is that lexical control is quite flexible. Judicious developers can use the proposed @warn attribute in a fine-grained manner if they're willing to restructure code by introducing wrappers as Tony described. It's also fairly clear to me that we could extend this design later to add statement or expression level control in a way that would compose quite well. This seems like the right starting place to me.
This sounds like exactly the point I'm trying to make: the example you give is of a single API deprecation (mktemp). There is no way in Swift either now or with this pitched feature to acknowledge (as you do in the comments very eloquently for human readers) and silence that one particular warning about mktemp in one go at the callsite. In Obj-C/C, as you outline above, you can bracket a single line of code in-place with #pragma directives as the next best thing. But the pitched feature cannot enable that next-best option in Swift. Instead, to "hold it right" as it were, you'll have to create one-liner wrappers for each deprecated API you use (essentially, manually re-declaring deprecated APIs as non-deprecated).
My concern isn't merely that it's boilerplate (which on its own would be tolerable, just annoying—but amenable to later improvement). My concern is rather that the boilerplate approach isn't the only way to achieve the end goal of making the warning go away: the alternative, which is much lower friction with this pitch, is to silence deprecation warnings for everything in the function that currently calls mktemp without creating a wrapper. Given the universe of possible designs for this feature, I think one that enables doing the wrong thing as the path of least resistance and doing the right thing as something the user has to go out of their way to re-create with boilerplate is not the ideal.
Certainly, I'd agree that silencing all deprecation warnings for the entire standard library is not ideal. Vendors can and do, however, deprecate an entire swath of APIs all at once (cf. the UIWebView example raised before) and to my mind it would be of exactly the correct granularity that what can be deprecated in one go can be acknowledged in one go (or, at least, more sensible than silencing anything deprecated anywhere, come what may, within one lexical scope).
But in any case, my sense would be that something like @deprecated import func Darwin.mktemp would be an acceptable spelling for silencing that warning on a file-wide basis. But point well taken that the argument could be made that one might want to be even more granular than that.
Yeah, I think this is where we disagree: I feel very strongly that the right starting place cannot require judicious use in order to avoid untoward consequences. I think we should start where imperfect users just doing their best can rely on the feature to improve their code without unwittingly finding themselves worse off down the road, and I worry about that when it comes to silencing any and all deprecation warnings in a scope.
Again I do agree, though, that something like the pitched feature will be nice for very many other use cases and ultimately will be a good addition to the language.
Fair enough opinion. In this case, mine is that I trust the developer knows what is right for their own code here, and it’s not the same category as memory unsafe API.
Linters and other code analysis tools usually define their own measures to disable (and re-enable) certain diagnostics within a range of code. Typically, these measures are comments with a specific syntax.
For example in SwiftLint, we support
// swiftlint:disable rule1
// swiftlint:disable:next rule2
class C { // swiftlint:disable:this rule3
// swiftlint:disable:previous rule4
}
// swiftlint:enable rule1
struct S {}
which allows for much more flexibility as exclusions are not bound to source-level declarations only.
That doesn't mean I'm advocating against this compiler feature, just wanted to point this out, perhaps as a "future direction".
But mentioning other linters, what I would really like to see is that the new @warn attributes not only work for compiler diagnostics, but also for any other kind of tool. That means @warn should either allow a string as its first argument, so that other tools can define their own syntax and watch for that, or there should be a third (optional) argument that allows for a kind of categorization, like @warn(Deprecate, from: Swift, as: error) or @warn(unused_parameter, from: SwiftLint, as: ignored).
Finally, SwiftLint may warn if a rule is disabled in a certain source range, but the rule wouldn't actually trigger there. Is this planned for compiler warnings as well? So if you ignored Deprecation warnings in a certain scope without actual deprecation warnings, would the compiler complain? And how to refuse it from complaining if you really wanted to ignore the diagnostic for potential future violations?