-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add rule to infer property types from the right-hand-side value rather than writing the type explicitly on the left-hand side #263
Conversation
@@ -333,25 +333,72 @@ _You can enable the following settings in Xcode by running [this script](resourc | |||
|
|||
```swift | |||
// WRONG | |||
let host: Host = Host() | |||
let sun: Star = Star(mass: 1.989e30) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I was here I expanded the list of examples used by the "Don't include types where they can be easily inferred." rule. These updates reflect the current behavior of the SwiftFormat redundantType
rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
|
||
// RIGHT | ||
return .left |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice example and is good advice in general, but doesn't reflect the behavior of the SwiftFormat redundantType
rule that we use. I don't believe we have autocorrect support for this specific example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an asana task to our internal board for us to look at adding autocorrect for this sort of case
…r than writing the type explicitly on the left-hand side
README.md
Outdated
let myShape1: any ShapeStyle = .saturnOutline | ||
|
||
// WRONG: fails with "error: static member 'saturnOutline' cannot be used on protocol metatype '(any ShapeStyle).Type'" | ||
let myShape2 = (any ShapeStyle).myShape |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way -- for this we have the ability to tell SwiftFormat a list of symbols to exclude from this rule (--preservesymbols ShapeStyle,OtherType,etc
). Inside the apps repo we could exclude any type or property name where we frequently use this syntax. Two relevant examples are:
UnresolvedShapeStyle
- all existing callsites use
any UnresolvedShapeStyle
so this is already handled properly
- all existing callsites use
canonicalFake
- there are about a dozen examples of
let experimentsService: ExperimentsService = .canonicalFake
(etc). It seems like a good idea to exclude this using--preservesymbols canonicalFake
. Updating the callsites tolet experimentsService: any ExperimentsService = .canonicalFake
also works.
- there are about a dozen examples of
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for flagging this @calda . I was wondering about this... My recollection is that eventually it's going to be required that we use any
for any existential/protocol types but that this requirement is being slowly rolled out. Is your understanding also that eventually all existential/protocol type will need to be preceded by any
? If so, I think it's less problematic if we need to work around certain situations like the above ones you highlighted for the time being.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to understand if there would be cases when we still need --preservesymbols
if/when the language requires that all existential/protocol types are preceded by any
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw on the Swift Evolution forums that they decided to not make any
mandatory in Swift 6 mode after all. Although after a quick search on the forums I can't actually find where they said that.
This doesn't necessarily confirm it, but if you build code like this using a nightly Swift 6 build it doesn't require any
.
I am trying to understand if there would be cases when we still need --preservesymbols if/when the language requires that all existential/protocol types are preceded by any.
If all call sites used the existential any
syntax, we wouldn't need to use --preservesymbols
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found the quote: https://forums.swift.org/t/progress-toward-the-swift-6-language-mode/68315
The Language Steering Group has decided that one previously-accepted upcoming feature, ExistentialAny, will not be enabled by default in Swift 6. SE-0335: Introduce existential any introduces the any keyword to identify existential types. The proposal also specifies that "bare" protocol names will no longer be permitted as types---they must either use any or some, as appropriate---under the upcoming feature flag ExistentialAny. Given the concerns about migration to consistent use of existential any in the language, and the expectation that additional language improvements will be coming that might affect the end result of that migration, the Language Steering Group is deferring the source-incompatible changes in SE-0335 to a future language revision.
It sounds like they didn't rule out this direction, but rather just deferred it for longer (probably a very long time, if we have to wait for Swift 7!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the apps repo PR that I merged today, I included --preservesymbols canonicalFake
. This is a common and reasonable pattern that I think we should continue supporting. It's not common to use the any ExperimentService
spelling today, so it felt too weird to require it in this specific case due to this limitation of the linter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@calda this is looking great! I was hoping to talk through some comments before approving if that's OK but I feel like we are very close 👍
README.md
Outdated
let myShape1: any ShapeStyle = .saturnOutline | ||
|
||
// WRONG: fails with "error: static member 'saturnOutline' cannot be used on protocol metatype '(any ShapeStyle).Type'" | ||
let myShape2 = (any ShapeStyle).myShape |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for flagging this @calda . I was wondering about this... My recollection is that eventually it's going to be required that we use any
for any existential/protocol types but that this requirement is being slowly rolled out. Is your understanding also that eventually all existential/protocol type will need to be preceded by any
? If so, I think it's less problematic if we need to work around certain situations like the above ones you highlighted for the time being.
README.md
Outdated
let myShape1: any ShapeStyle = .saturnOutline | ||
|
||
// WRONG: fails with "error: static member 'saturnOutline' cannot be used on protocol metatype '(any ShapeStyle).Type'" | ||
let myShape2 = (any ShapeStyle).myShape |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to understand if there would be cases when we still need --preservesymbols
if/when the language requires that all existential/protocol types are preceded by any
.
Thanks for the review @bachand! I updated the rule to clarify that it applies to both local variables and instance properties, improved the main example to show both cases, and simplified some of the other examples related to edge cases. |
…operties, simplify examples
// to the right-hand side will result in invalid code if the value is defined in an | ||
// extension like `extension ShapeStyle where Self == SaturnOutline`. | ||
// SwiftFormat autocorrect detects this case by checking for the existential `any` keyword. | ||
let myShape1: any ShapeStyle = .saturnOutline |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent a bit of time thinking about this case in more detail, that let myShape1: ShapeStyle = .saturnOutline
is allowed but let myShape1: = ShapeStyle.saturnOutline
isn't. I couldn't understand why this wasn't allowed.
This syntax was introduced in SE-0299. This question came up briefly in the review thread, and Pavel mentioned that there was more discussion in the pitch thread. A found the most compelling discussion here in the pitch thread.
They intentionally don't frame this as adding a static member to the protocol type, but rather a shorthand syntax that lets you omit the name of the concrete type.
Basically, the thinking is that this code:
let myShape1: ShapeStyle = .saturnOutline
is actually implicit shorthand for this code:
let myShape1: ShapeStyle = SaturnOutline.saturnOutline
not shorthand for this code:
let myShape1: ShapeStyle = ShapeStyle.saturnOutline
This is subtle but distinct, and is a reasonable explanation for why ShapeStyle.saturnOutline
isn't allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This problem should also go away eventually, once any
is required in a future language mode (just not Swift 6).
Please react with 👍/👎 below if you agree or disagree with this proposal.
Summary
This PR proposes a new rule to prefer letting the type of a property be inferred from the right-hand-side value rather than writing the type explicitly on the left-hand side:
Autocorrect support for this rule is implemented in nicklockwood/SwiftFormat#1640.
Reasoning
We already have a rule to omit redundant types. It converts
let sun: Star = Star()
tolet sun = Star()
, rather than tolet sun: Star = .init()
.The syntax with inferred types is more idiomatic, and is much more common, than the syntax with explicit types.
I briefly checked the frequency of these two styles in our codebase with a regex search:
let sun = Star()
orlet earth = Planet.earth
, etc):((let)|(var)) \w+ = [A-Z]+\w+(\.|\()
let sun: Star = .init()
, etc)((let)|(var)) \w+: \w+ = \.
.This rule only applies to simple cases where the right-hand side starts with a leading dot (meaning we know that the RHS value refers to a static member of the type written on the LHS). For other cases, we don't state a preference.