Merge pull request #77537 from hamishknight/complete-func

[Completion] Type-check parent closures for local functions
This commit is contained in:
Hamish Knight
2024-11-12 10:40:53 +00:00
committed by GitHub
12 changed files with 123 additions and 49 deletions

View File

@@ -515,12 +515,12 @@ public:
const_cast<DeclContext *>(this)->getInnermostSkippedFunctionContext(); const_cast<DeclContext *>(this)->getInnermostSkippedFunctionContext();
} }
/// Returns the innermost context that is a ClosureExpr, which defines how /// Returns the innermost ClosureExpr context that can propagate its captures
/// self behaves, unless within a type context that redefines self. /// to this DeclContext.
LLVM_READONLY LLVM_READONLY
ClosureExpr *getInnermostClosureForSelfCapture(); ClosureExpr *getInnermostClosureForCaptures();
const ClosureExpr *getInnermostClosureForSelfCapture() const { const ClosureExpr *getInnermostClosureForCaptures() const {
return const_cast<DeclContext *>(this)->getInnermostClosureForSelfCapture(); return const_cast<DeclContext *>(this)->getInnermostClosureForCaptures();
} }
/// Returns the semantic parent of this context. A context has a /// Returns the semantic parent of this context. A context has a

View File

@@ -52,6 +52,18 @@ class CompletionContextFinder : public ASTWalker {
Expr *InitialExpr = nullptr; Expr *InitialExpr = nullptr;
DeclContext *InitialDC; DeclContext *InitialDC;
/// Whether we're looking for any viable fallback expression.
bool ForFallback = false;
/// Finder for fallback completion contexts within the outermost non-closure
/// context of the code completion expression's direct context.
CompletionContextFinder(DeclContext *completionDC)
: InitialDC(completionDC), ForFallback(true) {
while (auto *ACE = dyn_cast<AbstractClosureExpr>(InitialDC))
InitialDC = ACE->getParent();
InitialDC->walkContext(*this);
}
public: public:
MacroWalking getMacroWalkingBehavior() const override { MacroWalking getMacroWalkingBehavior() const override {
return MacroWalking::Arguments; return MacroWalking::Arguments;
@@ -61,18 +73,16 @@ public:
CompletionContextFinder(constraints::SyntacticElementTarget target, CompletionContextFinder(constraints::SyntacticElementTarget target,
DeclContext *DC); DeclContext *DC);
/// Finder for completion contexts within the outermost non-closure context of static CompletionContextFinder forFallback(DeclContext *DC) {
/// the code completion expression's direct context. return CompletionContextFinder(DC);
CompletionContextFinder(DeclContext *completionDC) : InitialDC(completionDC) {
while (auto *ACE = dyn_cast<AbstractClosureExpr>(InitialDC))
InitialDC = ACE->getParent();
InitialDC->walkContext(*this);
} }
PreWalkResult<Expr *> walkToExprPre(Expr *E) override; PreWalkResult<Expr *> walkToExprPre(Expr *E) override;
PostWalkResult<Expr *> walkToExprPost(Expr *E) override; PostWalkResult<Expr *> walkToExprPost(Expr *E) override;
PreWalkAction walkToDeclPre(Decl *D) override;
bool locatedInStringInterpolation() const { bool locatedInStringInterpolation() const {
return hasContext(ContextKind::StringInterpolation); return hasContext(ContextKind::StringInterpolation);
} }

View File

@@ -280,21 +280,21 @@ DeclContext *DeclContext::getInnermostSkippedFunctionContext() {
return nullptr; return nullptr;
} }
ClosureExpr *DeclContext::getInnermostClosureForSelfCapture() { ClosureExpr *DeclContext::getInnermostClosureForCaptures() {
auto dc = this; auto *DC = this;
if (auto closure = dyn_cast<ClosureExpr>(dc)) { do {
return closure; if (auto *CE = dyn_cast<ClosureExpr>(DC))
} return CE;
// Stop searching if we find a type decl, since types always // Autoclosures and AbstractFunctionDecls can propagate captures.
// redefine what 'self' means, even when nested inside a closure. switch (DC->getContextKind()) {
if (dc->isTypeContext()) { case DeclContextKind::AbstractClosureExpr:
return nullptr; case DeclContextKind::AbstractFunctionDecl:
} continue;
default:
if (auto parent = dc->getParent()) { return nullptr;
return parent->getInnermostClosureForSelfCapture(); }
} } while ((DC = DC->getParent()));
return nullptr; return nullptr;
} }

View File

@@ -362,7 +362,7 @@ ValueDecl *UnqualifiedLookupFactory::lookupBaseDecl(const DeclContext *baseDC) c
// Perform an unqualified lookup for the base decl of this result. This // Perform an unqualified lookup for the base decl of this result. This
// handles cases where self was rebound (e.g. `guard let self = self`) // handles cases where self was rebound (e.g. `guard let self = self`)
// earlier in this closure or some outer closure. // earlier in this closure or some outer closure.
auto closureExpr = DC->getInnermostClosureForSelfCapture(); auto closureExpr = DC->getInnermostClosureForCaptures();
if (!closureExpr) { if (!closureExpr) {
return nullptr; return nullptr;
} }

View File

@@ -74,7 +74,7 @@ void PostfixCompletionCallback::fallbackTypeCheck(DeclContext *DC) {
Expr *fallbackExpr = CompletionExpr; Expr *fallbackExpr = CompletionExpr;
DeclContext *fallbackDC = DC; DeclContext *fallbackDC = DC;
CompletionContextFinder finder(DC); auto finder = CompletionContextFinder::forFallback(DC);
if (finder.hasCompletionExpr()) { if (finder.hasCompletionExpr()) {
if (auto fallback = finder.getFallbackCompletionExpr()) { if (auto fallback = finder.getFallbackCompletionExpr()) {
fallbackExpr = fallback->E; fallbackExpr = fallback->E;

View File

@@ -24,7 +24,7 @@ using namespace swift::constraints;
void TypeCheckCompletionCallback::fallbackTypeCheck(DeclContext *DC) { void TypeCheckCompletionCallback::fallbackTypeCheck(DeclContext *DC) {
assert(!GotCallback); assert(!GotCallback);
CompletionContextFinder finder(DC); auto finder = CompletionContextFinder::forFallback(DC);
if (!finder.hasCompletionExpr()) if (!finder.hasCompletionExpr())
return; return;

View File

@@ -5620,16 +5620,20 @@ namespace {
.fixItInsert(coercion->getStartLoc(), "consume "); .fixItInsert(coercion->getStartLoc(), "consume ");
} }
// Type-check any local decls encountered. // If we're doing code completion, avoid doing any further type-checking,
for (auto *D : LocalDeclsToTypeCheck) // that should instead be handled by TypeCheckASTNodeAtLocRequest.
TypeChecker::typeCheckDecl(D); if (!ctx.CompletionCallback) {
// Type-check any local decls encountered.
for (auto *D : LocalDeclsToTypeCheck)
TypeChecker::typeCheckDecl(D);
// Expand any macros encountered. // Expand any macros encountered.
// FIXME: Expansion should be lazy. // FIXME: Expansion should be lazy.
auto &eval = cs.getASTContext().evaluator; auto &eval = cs.getASTContext().evaluator;
for (auto *E : MacrosToExpand) { for (auto *E : MacrosToExpand) {
(void)evaluateOrDefault(eval, ExpandMacroExpansionExprRequest{E}, (void)evaluateOrDefault(eval, ExpandMacroExpansionExprRequest{E},
std::nullopt); std::nullopt);
}
} }
} }

View File

@@ -86,6 +86,17 @@ CompletionContextFinder::walkToExprPost(Expr *E) {
return Action::Continue(E); return Action::Continue(E);
} }
ASTWalker::PreWalkAction CompletionContextFinder::walkToDeclPre(Decl *D) {
// Look through any decl if we're looking for any viable fallback expression.
if (ForFallback)
return Action::Continue();
// Otherwise, follow the same rule as the ConstraintSystem, where only
// nested PatternBindingDecls are solved as part of the system. Local decls
// are handled by TypeCheckASTNodeAtLocRequest.
return Action::VisitNodeIf(isa<PatternBindingDecl>(D));
}
size_t CompletionContextFinder::getKeyPathCompletionComponentIndex() const { size_t CompletionContextFinder::getKeyPathCompletionComponentIndex() const {
size_t ComponentIndex = 0; size_t ComponentIndex = 0;
auto Components = getKeyPathContainingCompletionComponent()->getComponents(); auto Components = getKeyPathContainingCompletionComponent()->getComponents();

View File

@@ -2221,7 +2221,7 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
return nullptr; return nullptr;
} }
return parentContext->getInnermostClosureForSelfCapture(); return parentContext->getInnermostClosureForCaptures();
} }
bool shouldRecordClosure(const AbstractClosureExpr *E) { bool shouldRecordClosure(const AbstractClosureExpr *E) {

View File

@@ -2628,15 +2628,26 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
} }
} }
// If the context is a closure, type check the entire surrounding closure. // If we're within a ClosureExpr that can propagate its captures to this
// Conjunction constraints ensure that statements unrelated to the one that // DeclContext, we need to type-check the entire surrounding closure. If the
// contains the code completion token are not type checked. // completion token is contained within the closure itself, conjunction
if (auto CE = dyn_cast<ClosureExpr>(DC)) { // constraints ensure that statements unrelated to the one that contains the
// code completion token are not type checked. If it's in a nested local
// function, we unfortunately need to type-check everything since we need to
// apply the solution.
// FIXME: We ought to see if we can do better in that case.
if (auto *CE = DC->getInnermostClosureForCaptures()) {
if (CE->getBodyState() == ClosureExpr::BodyState::Parsed) { if (CE->getBodyState() == ClosureExpr::BodyState::Parsed) {
swift::typeCheckASTNodeAtLoc( swift::typeCheckASTNodeAtLoc(
TypeCheckASTNodeAtLocContext::declContext(CE->getParent()), TypeCheckASTNodeAtLocContext::declContext(CE->getParent()),
CE->getLoc()); CE->getLoc());
return false;
// If the context itself is a ClosureExpr, we should have type-checked
// the completion expression now. If it's a nested local declaration,
// fall through to type-check the AST node now that we've type-checked
// the surrounding closure.
if (isa<ClosureExpr>(DC))
return false;
} }
} }

View File

@@ -252,15 +252,22 @@ func testSkipTypeChecking9() -> E {
} }
func testSkipTypeChecking10() -> E { func testSkipTypeChecking10() -> E {
// We only need to type-check the inner-most function for this. // Similar to the above case, we need to type-check everything for this since
// the type-checking of 'takesArgAndClosure' is required to correctly handle
// any potential captures in 'foo'.
if Bool.random() { if Bool.random() {
NO.TYPECHECK .e
} else { } else {
takesArgAndClosure(NO.TYPECHECK) { takesArgAndClosure(0) {
func foo() { func foo() {
// We can however skip unrelated elements in the local function.
let x = NO.TYPECHECK
if NO.TYPECHECK {
takesE(NO.TYPECHECK)
}
takesE(.#^DOT21?check=DOT^#) takesE(.#^DOT21?check=DOT^#)
} }
return NO.TYPECHECK return .e
} }
} }
} }
@@ -320,7 +327,7 @@ func testSkipTypeChecking14() -> E {
} }
} }
func testSkipTypeChecking14() -> E { func testSkipTypeChecking15() -> E {
switch Bool.random() { switch Bool.random() {
case true: case true:
.#^DOT26?check=DOT^# .#^DOT26?check=DOT^#
@@ -336,7 +343,7 @@ func testSkipTypeChecking14() -> E {
} }
} }
func testSkipTypechecking15(_ x: inout Int) -> E { func testSkipTypechecking16(_ x: inout Int) -> E {
switch Bool.random() { switch Bool.random() {
case true: case true:
.#^DOT27?check=DOT^# .#^DOT27?check=DOT^#

View File

@@ -0,0 +1,31 @@
// RUN: %batch-code-completion
// https://github.com/swiftlang/swift/issues/77305
struct S {
var x: Int
}
func withFoo(_ x: (S) -> Void) {}
withFoo { foo in
func bar() {
foo.#^FN_IN_CLOSURE^#
// FN_IN_CLOSURE: Decl[InstanceVar]/CurrNominal: x[#Int#]; name=x
}
}
withFoo { x in
_ = { y in
func bar() {
_ = { z in
func baz() {
func qux() {
z.#^VERY_NESTED_FN_IN_CLOSURE^#
// VERY_NESTED_FN_IN_CLOSURE: Decl[InstanceVar]/CurrNominal: x[#Int#]; name=x
}
}
}(y)
}
}(x)
}