Skip to content

Conversation

beccadax
Copy link
Contributor

This PR adds support for module selectors to the legacy parser and also updates ASTGen to lower the module selector-related syntax nodes introduced by swiftlang/swift-syntax#3091. Although the compiler parses module selectors with this PR, they don't have any actual effect on name lookup or other compiler behavior.

Makes progress on rdar://problem/19481048. Extracted from #34556. Currently under review as SE-0491.

Try to avoid calling `getSourceRange()` on a decl where that will form an invalid range.
When closing paren is missing, point at the location where it should be found, not the opening paren.
A PatternBindingEntry formed from a MissingPatternSyntax has no valid SourceLocs in it, and a PatternBindingDecl containing only PatternBindingEntries with no valid SourceLocs trips an assertion during availability checking. (Generating dummy SourceLocs can cause invalid overlaps between AvailabilityScopes, so that’s not a workable solution.) The fix is to:

• Refuse to generate a PatternBindingEntry from a PatternBindingSyntax with a MissingPatternSyntax (MissingPatternSyntaxes at nested positions don’t have this problem)
• Refuse to generate a PatternBindingDecl with no PatternBindingEntries

This ensures that the invalid AST nodes are never formed.

No current test cases hit this problem, but certain invalid module selector tests can run into this situation when run with ASTGen.
@beccadax
Copy link
Contributor Author

beccadax commented Oct 7, 2025

@swift-ci smoke test

@inline(__always)
func generateSourceLoc(_ node: some SyntaxProtocol) -> SourceLoc {
SourceLoc(at: node.positionAfterSkippingLeadingTrivia, in: self.base)
precondition(node.positionAfterSkippingLeadingTrivia.utf8Offset < 0x0000000100000000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was something I added to chase down a bug caused by missing tokens having very large, invalid offsets that turned into out-of-bounds SourceLocs. It probably doesn't belong in this PR; the constant here is a fairly arbitrary "big enough" value.

Comment on lines +277 to +281
arguments: (
leftParen: TokenSyntax,
labels: some Collection<TokenSyntax>,
rightParen: TokenSyntax
)? = Optional<(leftParen: TokenSyntax, labels: [TokenSyntax], rightParen: TokenSyntax)>.none
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be just DeclNameArgumentsSyntax ?

Suggested change
arguments: (
leftParen: TokenSyntax,
labels: some Collection<TokenSyntax>,
rightParen: TokenSyntax
)? = Optional<(leftParen: TokenSyntax, labels: [TokenSyntax], rightParen: TokenSyntax)>.none
arguments: DeclNameArgumentsSyntax? = nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it can be. Good catch!

// The availability checker requires declarations to have valid SourceLocs, but MissingPatternSyntax lowers to
// an implicit AnyPattern with invalid SourceLocs. A top-level MissingPatternSyntax could therefore cause us to
// construct an invalid AST. Drop the whole binding instead.
// No need to diagnose; SwiftParser should have already diagnosed the `MissingPatternSyntax`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little unfortunate that we're missing possible diagnostics for the initializer expressions.
Can't we set the SourceLoc if the generated pattern is AnyPattern with invalid SourceLocs?

(Not sure MissingPatternSyntax + non-nil initializer is possible in practice. though)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we rejiggered some things in the bridging layer, I suppose we could create an implicit AnyPattern with an explicit SourceLoc. Where would we get that SourceLoc from, though? The MissingPatternSyntax won't give us a usable one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateSourceLoc(pattern) would return the location of the next token of var/let or ,. I assume that is not usable because it would overwrap with the next decl? If so, generateSourceLoc(pattern.previousToken(viewMode: .sourceAccurate)) might work, but I'm fine with leaving this as-is for now.

diagnose(Tok, diag::replace_module_selector_with_member_lookup)
.fixItReplace(Tok.getLoc(), ".");
}
HasNext = consumeIf(tok::period) || consumeIf(tok::colon_colon);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you align the implementation with SwiftParser?

DeclNameRef nameRef =
parseDeclNameRef(nameLoc, diag::impossible_parse,
DeclNameFlag::AllowAnonymousParamNames |
DeclNameFlag::ModuleSelectorUnsupported);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? I believe this is only called when Tok is tok::dollarident so there's no chance to have a module selector here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I could roll this back. It was more important back when parseDeclNameRef() always parsed a module selector and just diagnosed it when you passed ModuleSelectorUnsupported.

Comment on lines +3339 to +3340
if (P.Tok.is(tok::identifier) && P.peekToken().is(tok::colon_colon)) {
return P.lookahead(2, [&](auto &scope) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making isAtModuleSelector() returning number of module selector tokens and

Suggested change
if (P.Tok.is(tok::identifier) && P.peekToken().is(tok::colon_colon)) {
return P.lookahead(2, [&](auto &scope) {
if (auto numTokens = P.isAtModuleSelector()) {
return P.lookahead(*numTokens, [&](auto &scope) {

// the same as those of the result node, plus any junk consumed
// afterwards
auto Brace = BraceStmt::create(Context, Result.getStartLoc(),
auto Brace = BraceStmt::create(Context, StartLoc,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this change?

Copy link
Contributor Author

@beccadax beccadax Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, there are (invalid code) situations where Result.getStartLoc() will be an invalid SourceLoc. When BraceStmt::create is passed an invalid left brace location and a valid right brace location, calling Stmt::getSourceRange() will trip an assertion. Using StartLoc here fixes the issue because, like PreviousLoc, it is always valid.

break;
case tok::kw_protocol:
if (startsWithLess(peekToken())) {
if (Tok.is(tok::kw_protocol) && startsWithLess(peekToken())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right; I think it was briefly needed until I factored out diagnoseAndRecover() into a helper lambda.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants