[swift-evolution] Pitch: Omit deprecation warnings for same-file references

BJ Homer bjhomer at gmail.com
Fri May 5 13:37:25 CDT 2017


I’ve run into this problem as well, when dealing with legacy file formats (similar to the example in the proposal), and I agree it would be nice to be able to address it. We’ve got a few “permanent” warnings in our code base right now due to this. I’m not sure whether the deprecation should be disabled for the entire file, though; perhaps it should be disabled just within the type itself? I don’t know how that would contribute to the complexity of the implementation, but it seems like we would still want to warn about deprecated accesses between two types declared in the same file.

-BJ Homer

> On May 5, 2017, at 12:12 PM, Tony Allevato via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Hey Swift Evolvers,
> 
> I'd like to propose a change that would suppress deprecation warnings when the reference to the deprecated declaration is in the same file as that declaration.
> 
> This personally affects one of my projects (Swift protocol buffers) because .proto files allow declarations to be declared as deprecated, and we'd like to surface that by deprecating the equivalent declaration in the Swift code that we generate. We can't do this without generating noisy build logs, because we still need to reference the deprecated declarations to encode/decode the messages correctly; and the workarounds have significant performance penalties as described in the draft.
> 
> I'd love some feedback from folks to know whether this—or something like it—is something that seems desirable/undesirable for the language. I've tinkered with an implementation in a branch <https://github.com/apple/swift/compare/master...allevato:omit-deprecation-same-file> and it's fairly straightforward.
> 
> Gist link: https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb>
> 
> 
> Omit deprecation warnings for same-file references
> 
> Proposal: SE-0000 <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
> Author(s): Tony Allevato <https://github.com/allevato>
> Status: Awaiting review <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#rationale>
> Review manager: TBD
>  <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#introduction>Introduction
> 
> Public API developers can use the @available attribute in Swift to mark APIs as deprecated, which will emit warnings when a user references that API to notify them that that particular API is no longer preferred or may disappear in the future.
> 
> These warnings are emitted for any reference to a deprecated entity, including those in the same file. In some cases, however, it may be necessary and correct to continue referring to the deprecated entity privately while discouraging its external use. In these scenarios, the warnings are superfluous and generate noise in the build logs. We propose eliminating these warnings for references made in the same file.
> 
> Swift-evolution thread: TBD <http://thread.gmane.org/gmane.comp.lang.swift.evolution/>
>  <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#motivation>Motivation
> 
> As APIs evolve, declarations may become deprecated but still need to be referenced privately in order to function correctly. For example, consider an object that is serialized/parsed between a client and server. The first iteration of such a type might look like this (the examples use a strawman API to avoid tying the discussion too closely with the new coding APIs, which is not the focus):
> 
> public class Person {
>   public var name: String
>   public var phoneNumber: String?
> 
>   public init(name: String, phoneNumber: String? = nil) {
>     self.name = name
>     self.phoneNumber = phoneNumber
>   }
> 
>   public convenience init?(from reader: Reader) {
>     guard let name = reader.readString(withKey: "name") else {
>       return nil
>     }
>     let phoneNumber = reader.readString(withKey: "phoneNumber")
>     self.init(name: name, phoneNumber: phoneNumber)
>   }
> 
>   public func write(to writer: Writer) {
>     writer.write(name, key: "name")
>     if let phoneNumber = phoneNumber {
>       writer.write(phoneNumber, key: "phoneNumber")
>     }
>   }
> }
> Later, we decide that we need to support storing multiple phone numbers for a Person. To avoid breaking source compatibility, we deprecate the old property and add a new one. We still wish the encoding/decoding process to preserve the integrity of the old data, however—for example, a middleman process used for logging should not modify the data stream, so the encoding/decoding process should not be also migrating the data. Thus, we update the class to the following:
> 
> public class Person {
>   public var name: String
>   @available(*, deprecated, message: "use 'phoneNumbers' instead")
>   public var phoneNumber: String?
>   public var phoneNumbers: [String]
> 
>   @available(*, deprecated, message: "use 'init(name:phoneNumbers:)' instead")
>   public convenience init(name: String, phoneNumber: String?) {
>     self.phoneNumber = phoneNumber
>     self.init(name: name, phoneNumbers: [])
>   }
> 
>   public init(name: String, phoneNumbers: [String] = []) {
>     self.name = name
>     self.phoneNumber = nil
>     self.phoneNumbers = phoneNumbers
>   }
> 
>   public convenience init?(from reader: Reader) {
>     guard let name = reader.readString(withKey: "name") else {
>       return nil
>     }
>     let phoneNumbers = reader.readStringArray(withKey: "phoneNumbers") ?? []
>     self.phoneNumber = reader.readString(withKey: "phoneNumber")
>     self.init(name: name, phoneNumbers: phoneNumbers)
>   }
> 
>   public func write(to writer: Writer) {
>     writer.write(name, key: "name")
>     if let phoneNumber = phoneNumber {
>       writer.write(phoneNumber, key: "phoneNumber")
>     }
>     if !phoneNumbers.isEmpty {
>       writer.write(phoneNumbers, key: "phoneNumbers")
>     }
>   }
> }
> This type is backwards-compatible with the previous version and will warn external users that they should migrate to the new version of the API; it will also have its values preserved exactly if a middleman process parses and then reserializes it.
> 
> The problem, however, is that the references to phoneNumber in Person's own methods also emit deprecation warnings. This is undesirable because the warnings cannot be easily avoided (see workarounds below). The crux of the issue is that one of the two statements is likely to be true:
> 
> At the time the user deprecates something, they also have the opportunity to migrate all references to it within the same file, thus eliminating the warnings there and making the file build cleanly.
> 
> At the time the user deprecates something, they must continue to reference it internally to achieve correct and performant behavior, thus making it currently impossible to achieve a clean build for the file containing that declaration.
> 
> This proposal aims to solve the second issue.
> 
>  <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#workarounds-and-their-problems>Workarounds and their problems
> 
> One workaround would be to shadow deprecated declarations by making the declaration itself private (and prepending something like an underscore to the name) and then using a public passthrough property or method. The private declaration would not be deprecated while the public one would be.
> 
> There are two main problems with this approach. First, it increases the development and maintanence cost of the deprecation because the new declaration has to be added and all internal references to the declaration must be updated.
> 
> Secondly, and more importantly, this shadowing can have significant performance penalties. Consider this example:
> 
> class DirectArrayHolder {
>   @available(*, deprecated, message: "Use something else instead")
>   var array: [String] = []
> }
> 
> class IndirectArrayHolder {
>   var _array: [String] = []
> 
>   @available(*, deprecated, message: "Use something else instead")
>   var array: [String] {
>     get { return _array }
>     set { _array = newValue }
>   }
> }
> 
> func useDirectHolder() -> DirectArrayHolder {
>   let holder = DirectArrayHolder()
>   for _ in 0...10_000 {
>     holder.array.append("foo")
>   }
>   return holder
> }
> 
> func useIndirectHolder() -> IndirectArrayHolder {
>   let holder = IndirectArrayHolder()
>   for _ in 0...10_000 {
>     holder.array.append("foo")
>   }
>   return holder
> }
> Behaviorally, these are the same. However, the generated code is different because the memory management semantics change due to the passthrough property. With full optimizations on, useDirectHolder executes in 1.360 sec while useIndirectHolder executes in 235.8 sec—over two orders of magnitude slower!
> 
> It could be argued that this performance issue is something that could be solved separately, but this should be considered just one motivating example and not the sole motivation for this proposed change.
> 
>  <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#proposed-solution>Proposed solution
> 
> Do not print deprecation warnings for references to deprecated declarations that are in the same file.
> 
> There would be no change for declarations marked unavailable. A declaration marked unavailable is making a stronger statement about its existence such that referencing it is an error; unlike marking a declaration deprecated, marking it unavailable is a breaking change for downstream clients, so the behavior of that attribute value should be unchanged whether references are in the same file or elsewhere.
> 
>  <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#implementation-details>Implementation details
> 
> A small addition would be made to TypeChecker::diagnoseIfDeprecated so that it returns early if the declaration is in the same source file as the reference.
> 
>  <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#impact-on-existing-code>Impact on existing code
> 
> This is not a source-breaking change. The only side effect for existing code is that deprecation warnings that were emitted for same-file references in the past will no longer be emitted.
> 
>  <https://gist.github.com/allevato/76f098a761147f3be5017d10a6f4ecbb#alternatives-considered>Alternatives considered
> 
> Omitting deprecation warnings for references in the same module, not only the same file, was also considered. An argument could be made that deprecation warnings are intended as messaging for external users of an API, so drawing the line at the module level would be more appropriate than at the file level. However, modules can be large and span a broad set of APIs contributed by many developers on multi-person teams, and they may want to use the @available attribute to phase out particular APIs for internal use as well. Omitting deprecation warnings for anything in the same module takes that useful tool away for those users.
> 
> Another idea that was brought up in a feature request <https://bugs.swift.org/browse/SR-3357> filed for this was to allow a "deprecated block" that would suppress all deprecation warnings inside it. This would also solve the problem motivating this proposal and is similar to the approach taken by other languages (such as Java's @SuppressWarnings("deprecation") annotation), but this approach has drawbacks as well. It would not distinguish between deprecation warnings in the same file or module vs. those in other files or modules (unless modifiers were added to it, which runs the risk of adding too much complexity for too little gain). Furthermore, it has the potential to be harmful if abused—developers could surround large code blocks with the deprecation suppressor and then lose any deprecation warnings that would have been emitted for APIs used inside that block.
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170505/516320e3/attachment.html>


More information about the swift-evolution mailing list