Skip to content

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

Merged
merged 6 commits into from
Oct 2, 2020

Conversation

slavapestov
Copy link
Contributor

@slavapestov slavapestov commented Sep 30, 2020

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.

@slavapestov
Copy link
Contributor Author

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.

@slavapestov slavapestov requested a review from xedin September 30, 2020 21:22
@slavapestov
Copy link
Contributor Author

@swift-ci Please test source compatibility

@slavapestov slavapestov force-pushed the simulate-parser-lookup branch 2 times, most recently from 7dbfa86 to 330f2e9 Compare October 1, 2020 05:49
Copy link
Contributor

@davidungar davidungar left a 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!

Comment on lines +670 to +687
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);
}
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice and clear!

Comment on lines 371 to 380
if (consumer.consume(localFuncsAndTypes))
return true;

if (consumer.consumePossiblyNotInScope(localVars))
return true;

if (consumer.finishLookupInBraceStmt(stmt))
return true;

return false;
Copy link
Contributor

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.

@slavapestov
Copy link
Contributor Author

@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.
@slavapestov slavapestov force-pushed the simulate-parser-lookup branch from 330f2e9 to d219674 Compare October 2, 2020 03:48
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.
@slavapestov slavapestov force-pushed the simulate-parser-lookup branch from d219674 to 3ec4ced Compare October 2, 2020 03:50
@slavapestov
Copy link
Contributor Author

@swift-ci Please smoke test

@slavapestov
Copy link
Contributor Author

@swift-ci Please test source compatibility

@davidungar
Copy link
Contributor

Still looks fine!

@slavapestov slavapestov merged commit 95c4a97 into swiftlang:main Oct 2, 2020
@compnerd
Copy link
Member

compnerd commented Oct 2, 2020

@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).

@slavapestov
Copy link
Contributor Author

Oops, my bad. Thanks for reverting. I'll take a look.

Comment on lines +607 to +621
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;
}

Copy link
Collaborator

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?

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.

4 participants