-
Notifications
You must be signed in to change notification settings - Fork 447
SwiftSyntax support for module selectors #3091
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
base: main
Are you sure you want to change the base?
Conversation
🥳 |
This capability is currently unused, but it’ll matter soon.
1f47aa2
to
eaa6956
Compare
And make sure it doesn’t break Objective-C selector lexing.
Treating introducers as keywords is now always conditional on whether `shouldParsePatternBinding(introducer:)` returns `true`. That method has also been modified to correctly handle an edge case with wildcard patterns.
da6eff6
to
f94b58a
Compare
@swift-ci please test |
@swift-ci please test |
1 similar comment
@swift-ci please test |
@swift-ci please test |
@swift-ci please test macOS |
@swift-ci please test Linux |
This build failure seems to be real:
|
I can't actually reproduce that failure locally, but I'm pushing a speculative fix. |
@swift-ci please test |
@swift-ci please test macOS |
@swift-ci please test Linux |
@swift-ci please test Windows |
We are good now with macOS and Linux. Let's try Windows again. @swift-ci please test Windows. |
hmm, the Windows CI hit a compiler crasher:
|
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.
Apologies I didn't review this sooner. I still haven't look into the implementation closely, but here's the first round 🙇
Sources/SwiftParser/Names.swift
Outdated
// Technically the current token *should* be an identifier, but we also want to diagnose other tokens that might be | ||
// used by accident (particularly keywords and `_`). However, we don't want to consume tokens which would make the | ||
// surrounding structure mis-parse. | ||
return self.at(anyIn: StructuralTokens.self) == nil |
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 don't think it's a good idea to (almost) always consider <token>
::
as a module qualifier. E..g.
class
::
This looks to me an incomplete class
declaration and just an orphan ::
. Even if it's on the same line with no-space, I don't think we need to parse it as a module selector.
Okay, this is a dramatically simpler implementation that doesn't try to do anything special to handle module selectors at invalid locations. I can't say I'm happy with some of the recovery behavior I'm seeing—in particular, parameter lists and tuple patterns seem to just throw up their hands in a pretty ugly way instead of recovering to a |
Sources/SwiftParser/Names.swift
Outdated
|
||
extension Parser { | ||
/// Parses one or more module selectors, if present. | ||
mutating func parseModuleSelector() -> RawModuleSelectorSyntax? { |
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 know there're several parsing functions that doesn't follow this convention, but:
mutating func parseModuleSelector() -> RawModuleSelectorSyntax? { | |
mutating func parseModuleSelectorIfPresent() -> RawModuleSelectorSyntax? { |
or check self.isAtModuleSelector()
before calling this and make this non-optional.
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.
A module selector is never mandatory, so I'm going to rename the method.
Sources/SwiftParser/Names.swift
Outdated
@@ -68,6 +213,9 @@ extension Parser { | |||
} | |||
|
|||
mutating func parseDeclReferenceExpr(_ flags: DeclNameOptions = []) -> RawDeclReferenceExprSyntax { | |||
// Consume a module selector if present. | |||
let moduleSelector = self.parseModuleSelector() |
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 think presence of the module selector should affect flags
(DeclNameOptions.keywords
).
It would be weird if ModName::default
is rejected, but foo.ModName::default
is allowed.
related: https://forums.swift.org/t/pitch-module-selectors/80835/82
Sources/SwiftParser/Names.swift
Outdated
} | ||
|
||
// Technically the current token *should* be an identifier, but we also want to diagnose other tokens that might be | ||
// used by accident (particularly keywords and `_`). However, we don't want to consume tokens which would make the |
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.
Would you show me some examples where consuming keywords as a invalid module identifiers improves the diagnostics?
IMO, keyword as a module name rule should follow existing keyword rules should be like:
- Bare keywords as module names are allowed after
.
(following SE-0071). Otherwise it's not a module selector even if there's::
after that - Some special keywords such as
Self
,self
,_
andAny
should be parsed as reserved names (even without.
). And they should be rejected/diagnosed accordingly, either in the parser or the type checker.
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'll narrow this to accept .wildcard
, .binaryOperator
, and the keywords self
, Self
, super
, and Any
in addition to identifiers. (These extra tokens are still not valid—they're just assumed to be attempts to write an identifier.) Does that sound like a reasonable list?
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.
Hmm I'm not 100% sure about .binaryOperator
. Actually that brought up another question which is whether :
should be a "bound" character. (i.e. *::foo
is "binary" or "prefix" operator). Either way, for example for foo + ::bar
, for me, this look like a missing module identifier before ::
. Maybe special casing *
with any operator kind would be more reasonable.
(though I personally can't imagine *::ignite()
would be a thing because it can't be use after .
I.e. for foo.*::ignite()
, .*
is an operator)
.wildcard
, self
, Self
, super
and Any
sounds good to me.
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.
Ah foo + ::bar
is parsed as a regular binary operator expression either way. Hmm maybe fine then... 🤔
@@ -1473,13 +1473,12 @@ extension ConsumeExprSyntax { | |||
} | |||
|
|||
extension DeclReferenceExprSyntax { | |||
@available(*, deprecated, renamed: "unexpectedBeforeBaseName") | |||
public var unexpectedBeforeIdentifier: UnexpectedNodesSyntax? { |
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 accessor is removed now? I think it's okay, but any idea how much effort would it take to preserve this?
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.
Ah, there was a bug in the changes I made to hide experimental children in compatibility layers. Will fix.
|
||
// If we got `::` when it's not allowed, reject it | ||
var unexpectedBeforeTrailingPeriod: RawUnexpectedNodesSyntax? | ||
if keepGoing?.tokenKind == .colonColon && !hasImportKind { |
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 allows:
import struct Foo::Bar::Baz
which I don't think intentional?
Also nit: If you use optional var
please initialize it with nil
explicitly. Or make it let
and make else
branch to initialize it.
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.
import struct Foo.Bar.Baz
is also semantically invalid but permitted by SwiftParser. ASTGen needs to diagnose both, and the same check would handle both of them.
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.
Clang submodules are valid and accepted in import-path
, so ASTGen won't diagnose it.
E.g. import class Foundation.NSData.NSData
works. I know this is equivalent with import class Foundation.NSData
, but we cannot just reject it.
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.
Huh—that fact dropped out of my memory, but I must have known it when I created ImportPath
in the compiler back in 2020, because it's clearly designed to support it. Weird how that happens sometimes.
I'll have to decide if I want to support import class Foundation.NSData::NSData
or not. (I'm thinking no—we allow ::
there so that we allow the same syntax that's valid at a use site, and in the current proposal a submodule wouldn't be valid at a use site.)
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 agree we should NOT support import class Foundation.NSData::NSData
.
If isAtModuleSpecifier()
after import-kind
, I think it's okay to just parse the module specifier and the following identifier, then return it.
Initializers for nodes with experimental node children need to be marked `@_spi`. This PR: • Adds that attribute. • Generates an alternative which *doesn’t* use SPI as part of the compatibility layer. • As a side effect, adds a `Child.Refactoring.introduced` case that can be used to generate compatibility `unexpected` properties. No functional change in this commit, but it will affect the code generation in the next one.
Changes the syntax tree to represent module selectors: • A `ModuleSelectorSyntax` node represents a module selector abstractly. • The following nodes now have an optional `moduleSelector` child: • `DeclReferenceExprSyntax` • `IdentifierTypeSyntax` • `MacroExpansionExprSyntax` • `MemberTypeSyntax` • BasicFormat knows the preferred format for module selectors. Other components, particularly the parser, were also updated to continue building, though without any changes in behavior. Parser implementation will come in a future commit.
Changes it to share code with `parseTypeIdentifier()` and clean up the member type parsing a little. Also tweaks call sites of `parseTypeIdentifier()`.
This commit ports over tests from the compiler’s (future) `test/NameLookup/module_selector.swift` file and makes sure the correct uses parse as expected. It also tests that ill-formed module selectors (ones with a missing or non-identifier module name) are diagnosed correctly. This commit doesn’t handle module selectors in scoped `import` statements; the related test has been XFAILed.
This restricts the code styles developers can use, but makes it less likely to eat syntax that was meant to belong to a different statement after an incomplete line.
This PR adds module selectors to SwiftSyntax and SwiftParser (draft of matching compiler feature in swiftlang/swift#34556). This feature was pitched ages ago; a proper proposal is
on my todo listnow available.Note: This PR is still a work in progress—in particular, I need to go back and improve the tests, both by expanding their coverage and by adding assertions about the resulting syntax trees—but I'd appreciate feedback on the design while I'm working on that.Reviewers: If you review this commit-by-commit, I've separated out the big dumb mechanical changes from the ones that actually change parsing logic.