-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Implement backward-compatible closure capture behavior with parser lookup disabled #34135
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
Implement backward-compatible closure capture behavior with parser lookup disabled #34135
Conversation
Note: The large diff is primarily because of the commit that moves preCheckExpression() into it's own file. There are no other changes in that specific commit, so you can review the others individually instead. |
@swift-ci Please test source compatibility |
7dbfa86
to
330f2e9
Compare
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 focused on the name lookup part, and that looks very good! As you warned, there is a huge, artificial diff in PreCheckExpr. I'm not sure what if anything changed there. Great to be cleaning things up this way!
SmallVector<ValueDecl *, 2> localFuncsAndTypes; | ||
SmallVector<VarDecl *, 2> localVars; | ||
|
||
// All types and functions are visible anywhere within a brace statement | ||
// scope. When ordering matters (i.e. var decl) we will have split the brace | ||
// statement into nested scopes. | ||
for (auto braceElement : bs->getElements()) { | ||
if (auto localBinding = braceElement.dyn_cast<Decl *>()) { | ||
if (auto *vd = dyn_cast<ValueDecl>(localBinding)) { | ||
if (isa<FuncDecl>(vd) || isa<TypeDecl>(vd)) { | ||
localFuncsAndTypes.push_back(vd); | ||
} else if (auto *var = dyn_cast<VarDecl>(localBinding)) { | ||
localVars.push_back(var); | ||
} | ||
} | ||
} | ||
} | ||
|
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.
Nice and clear!
lib/AST/ASTScopeLookup.cpp
Outdated
if (consumer.consume(localFuncsAndTypes)) | ||
return true; | ||
|
||
if (consumer.consumePossiblyNotInScope(localVars)) | ||
return true; | ||
|
||
if (consumer.finishLookupInBraceStmt(stmt)) | ||
return true; | ||
|
||
return false; |
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.
Very interesting! Looks good here.
@davidungar I split out that change in #34152 |
The parser's own re-declaration checking for local declarations has slightly different behavior than Sema's re-declaration check. Whereas Sema rejects this: class C { let x = 123 func x() {} } The parser accepts this: func f() { let x = 123 func x() {} } With Sema's check now handling both cases, fudge the behavior to match the parser in the local case.
…indings when IncludeOuterResults is set The old behavior was that ASTScope would introduce all VarDecls defined in a BraceStmt at the beginning of the BraceStmt. I recently enabled the use of PatternEntryDeclScopes, which introduce the binding at its actual source location instead of at the beginning of the parent statement. This patch now makes use of the new information by having UnqualifiedLookupFlags::IncludeOuterResults toggle between the two behaviors. When searching for outer results, we also consider all VarDecls in a BraceStmt, not just those in scope. This is implemented by giving AbstractASTScopeDeclConsumer a new entry point, consumePossiblyNotInScope(). When looking up into a BraceStmt, all VarDecls are passed in to this entry point. The default implementation does nothing, which means that ASTScope::lookupSingleLocalDecl() now respects source locations when searching for bindings, just like parse-time lookup. However, Sema's preCheckExpression() pass, which sets Flags::IgnoreOuterResults, will continue to find forward-referenced VarDecls, just as it did with the old context-based DeclContext lookup.
330f2e9
to
d219674
Compare
Before performing an UnqualifiedLookup with Flags::IncludeOuterResults turned on, call ASTScope::lookupSingleLocalDecl() to find local bindings that precede the current source location. If this fails, we perform an unqualified lookup to try to find forward references to captures, type members, and top-level declarations.
…clConsumer::consume() It wasn't used for anything, and it was always set based on whether the declaration in question was a GenericTypeParamDecl, a ParamDecl, or something else.
d219674
to
3ec4ced
Compare
@swift-ci Please smoke test |
@swift-ci Please test source compatibility |
Still looks fine! |
@slavapestov I think that this change is causing some issues with the latest changes that @hborla did: https://ci-external.swift.org/job/oss-swift-windows-x86_64-vs2019/3047/console shows a couple of test failures (that I also saw on Linux - I don't think that this is Windows specific). |
Oops, my bad. Thanks for reverting. I'll take a look. |
bool ASTScopeDeclConsumerForUnqualifiedLookup::consumePossiblyNotInScope( | ||
ArrayRef<VarDecl *> vars) { | ||
if (factory.hasPreciseScopingOfVarDecls()) | ||
return false; | ||
|
||
for (auto *var : vars) { | ||
if (!factory.Name.getFullName().isSimpleName(var->getName())) | ||
continue; | ||
|
||
factory.Results.push_back(LookupResultEntry(var)); | ||
} | ||
|
||
return false; | ||
} | ||
|
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.
Was it intentional to always return false here?
I wrote about the existing Swift scoping rules for local functions here: https://forums.swift.org/t/clarify-scoping-behavior-with-local-functions/40558
The quick summary is that today, parser lookup runs first, and only finds names that precede the use location in the source file. The old context-based lookup done from
preCheckExpression()
finds all local declarations, even those not in scope. A set of heuristics are used to find the relevant local declaration.Since ASTScope has to replace parse-time lookup, it has to model both behaviors. This PR wires it all up.
In the future, we can consider simplifying pre-check's name lookup rules by banning forward references to captured bindings, as I wrote in my forum post. But that's not in the critical path for removing parser lookup anymore.
Builds upon #34125.