Skip to content

Commit 36a9628

Browse files
authored
Merge pull request #77537 from hamishknight/complete-func
[Completion] Type-check parent closures for local functions
2 parents dd7ec7f + 27995ee commit 36a9628

12 files changed

+124
-50
lines changed

include/swift/AST/DeclContext.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -515,12 +515,12 @@ class alignas(1 << DeclContextAlignInBits) DeclContext
515515
const_cast<DeclContext *>(this)->getInnermostSkippedFunctionContext();
516516
}
517517

518-
/// Returns the innermost context that is a ClosureExpr, which defines how
519-
/// self behaves, unless within a type context that redefines self.
518+
/// Returns the innermost ClosureExpr context that can propagate its captures
519+
/// to this DeclContext.
520520
LLVM_READONLY
521-
ClosureExpr *getInnermostClosureForSelfCapture();
522-
const ClosureExpr *getInnermostClosureForSelfCapture() const {
523-
return const_cast<DeclContext *>(this)->getInnermostClosureForSelfCapture();
521+
ClosureExpr *getInnermostClosureForCaptures();
522+
const ClosureExpr *getInnermostClosureForCaptures() const {
523+
return const_cast<DeclContext *>(this)->getInnermostClosureForCaptures();
524524
}
525525

526526
/// Returns the semantic parent of this context. A context has a

include/swift/Sema/CompletionContextFinder.h

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ class CompletionContextFinder : public ASTWalker {
5252
Expr *InitialExpr = nullptr;
5353
DeclContext *InitialDC;
5454

55+
/// Whether we're looking for any viable fallback expression.
56+
bool ForFallback = false;
57+
58+
/// Finder for fallback completion contexts within the outermost non-closure
59+
/// context of the code completion expression's direct context.
60+
CompletionContextFinder(DeclContext *completionDC)
61+
: InitialDC(completionDC), ForFallback(true) {
62+
while (auto *ACE = dyn_cast<AbstractClosureExpr>(InitialDC))
63+
InitialDC = ACE->getParent();
64+
InitialDC->walkContext(*this);
65+
}
66+
5567
public:
5668
MacroWalking getMacroWalkingBehavior() const override {
5769
return MacroWalking::Arguments;
@@ -61,18 +73,16 @@ class CompletionContextFinder : public ASTWalker {
6173
CompletionContextFinder(constraints::SyntacticElementTarget target,
6274
DeclContext *DC);
6375

64-
/// Finder for completion contexts within the outermost non-closure context of
65-
/// the code completion expression's direct context.
66-
CompletionContextFinder(DeclContext *completionDC) : InitialDC(completionDC) {
67-
while (auto *ACE = dyn_cast<AbstractClosureExpr>(InitialDC))
68-
InitialDC = ACE->getParent();
69-
InitialDC->walkContext(*this);
76+
static CompletionContextFinder forFallback(DeclContext *DC) {
77+
return CompletionContextFinder(DC);
7078
}
7179

7280
PreWalkResult<Expr *> walkToExprPre(Expr *E) override;
7381

7482
PostWalkResult<Expr *> walkToExprPost(Expr *E) override;
7583

84+
PreWalkAction walkToDeclPre(Decl *D) override;
85+
7686
bool locatedInStringInterpolation() const {
7787
return hasContext(ContextKind::StringInterpolation);
7888
}

lib/AST/DeclContext.cpp

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -280,21 +280,21 @@ DeclContext *DeclContext::getInnermostSkippedFunctionContext() {
280280
return nullptr;
281281
}
282282

283-
ClosureExpr *DeclContext::getInnermostClosureForSelfCapture() {
284-
auto dc = this;
285-
if (auto closure = dyn_cast<ClosureExpr>(dc)) {
286-
return closure;
287-
}
288-
289-
// Stop searching if we find a type decl, since types always
290-
// redefine what 'self' means, even when nested inside a closure.
291-
if (dc->isTypeContext()) {
292-
return nullptr;
293-
}
283+
ClosureExpr *DeclContext::getInnermostClosureForCaptures() {
284+
auto *DC = this;
285+
do {
286+
if (auto *CE = dyn_cast<ClosureExpr>(DC))
287+
return CE;
294288

295-
if (auto parent = dc->getParent()) {
296-
return parent->getInnermostClosureForSelfCapture();
297-
}
289+
// Autoclosures and AbstractFunctionDecls can propagate captures.
290+
switch (DC->getContextKind()) {
291+
case DeclContextKind::AbstractClosureExpr:
292+
case DeclContextKind::AbstractFunctionDecl:
293+
continue;
294+
default:
295+
return nullptr;
296+
}
297+
} while ((DC = DC->getParent()));
298298

299299
return nullptr;
300300
}

lib/AST/UnqualifiedLookup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ ValueDecl *UnqualifiedLookupFactory::lookupBaseDecl(const DeclContext *baseDC) c
362362
// Perform an unqualified lookup for the base decl of this result. This
363363
// handles cases where self was rebound (e.g. `guard let self = self`)
364364
// earlier in this closure or some outer closure.
365-
auto closureExpr = DC->getInnermostClosureForSelfCapture();
365+
auto closureExpr = DC->getInnermostClosureForCaptures();
366366
if (!closureExpr) {
367367
return nullptr;
368368
}

lib/IDE/PostfixCompletion.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void PostfixCompletionCallback::fallbackTypeCheck(DeclContext *DC) {
7474
Expr *fallbackExpr = CompletionExpr;
7575
DeclContext *fallbackDC = DC;
7676

77-
CompletionContextFinder finder(DC);
77+
auto finder = CompletionContextFinder::forFallback(DC);
7878
if (finder.hasCompletionExpr()) {
7979
if (auto fallback = finder.getFallbackCompletionExpr()) {
8080
fallbackExpr = fallback->E;

lib/IDE/TypeCheckCompletionCallback.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ using namespace swift::constraints;
2424
void TypeCheckCompletionCallback::fallbackTypeCheck(DeclContext *DC) {
2525
assert(!GotCallback);
2626

27-
CompletionContextFinder finder(DC);
27+
auto finder = CompletionContextFinder::forFallback(DC);
2828
if (!finder.hasCompletionExpr())
2929
return;
3030

lib/Sema/CSApply.cpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5620,16 +5620,20 @@ namespace {
56205620
.fixItInsert(coercion->getStartLoc(), "consume ");
56215621
}
56225622

5623-
// Type-check any local decls encountered.
5624-
for (auto *D : LocalDeclsToTypeCheck)
5625-
TypeChecker::typeCheckDecl(D);
5626-
5627-
// Expand any macros encountered.
5628-
// FIXME: Expansion should be lazy.
5629-
auto &eval = cs.getASTContext().evaluator;
5630-
for (auto *E : MacrosToExpand) {
5631-
(void)evaluateOrDefault(eval, ExpandMacroExpansionExprRequest{E},
5632-
std::nullopt);
5623+
// If we're doing code completion, avoid doing any further type-checking,
5624+
// that should instead be handled by TypeCheckASTNodeAtLocRequest.
5625+
if (!ctx.CompletionCallback) {
5626+
// Type-check any local decls encountered.
5627+
for (auto *D : LocalDeclsToTypeCheck)
5628+
TypeChecker::typeCheckDecl(D);
5629+
5630+
// Expand any macros encountered.
5631+
// FIXME: Expansion should be lazy.
5632+
auto &eval = cs.getASTContext().evaluator;
5633+
for (auto *E : MacrosToExpand) {
5634+
(void)evaluateOrDefault(eval, ExpandMacroExpansionExprRequest{E},
5635+
std::nullopt);
5636+
}
56335637
}
56345638
}
56355639

lib/Sema/CompletionContextFinder.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,17 @@ CompletionContextFinder::walkToExprPost(Expr *E) {
8686
return Action::Continue(E);
8787
}
8888

89+
ASTWalker::PreWalkAction CompletionContextFinder::walkToDeclPre(Decl *D) {
90+
// Look through any decl if we're looking for any viable fallback expression.
91+
if (ForFallback)
92+
return Action::Continue();
93+
94+
// Otherwise, follow the same rule as the ConstraintSystem, where only
95+
// nested PatternBindingDecls are solved as part of the system. Local decls
96+
// are handled by TypeCheckASTNodeAtLocRequest.
97+
return Action::VisitNodeIf(isa<PatternBindingDecl>(D));
98+
}
99+
89100
size_t CompletionContextFinder::getKeyPathCompletionComponentIndex() const {
90101
size_t ComponentIndex = 0;
91102
auto Components = getKeyPathContainingCompletionComponent()->getComponents();

lib/Sema/MiscDiagnostics.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2221,7 +2221,7 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
22212221
return nullptr;
22222222
}
22232223

2224-
return parentContext->getInnermostClosureForSelfCapture();
2224+
return parentContext->getInnermostClosureForCaptures();
22252225
}
22262226

22272227
bool shouldRecordClosure(const AbstractClosureExpr *E) {

lib/Sema/TypeCheckStmt.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2628,15 +2628,26 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
26282628
}
26292629
}
26302630

2631-
// If the context is a closure, type check the entire surrounding closure.
2632-
// Conjunction constraints ensure that statements unrelated to the one that
2633-
// contains the code completion token are not type checked.
2634-
if (auto CE = dyn_cast<ClosureExpr>(DC)) {
2631+
// If we're within a ClosureExpr that can propagate its captures to this
2632+
// DeclContext, we need to type-check the entire surrounding closure. If the
2633+
// completion token is contained within the closure itself, conjunction
2634+
// constraints ensure that statements unrelated to the one that contains the
2635+
// code completion token are not type checked. If it's in a nested local
2636+
// function, we unfortunately need to type-check everything since we need to
2637+
// apply the solution.
2638+
// FIXME: We ought to see if we can do better in that case.
2639+
if (auto *CE = DC->getInnermostClosureForCaptures()) {
26352640
if (CE->getBodyState() == ClosureExpr::BodyState::Parsed) {
26362641
swift::typeCheckASTNodeAtLoc(
26372642
TypeCheckASTNodeAtLocContext::declContext(CE->getParent()),
26382643
CE->getLoc());
2639-
return false;
2644+
2645+
// If the context itself is a ClosureExpr, we should have type-checked
2646+
// the completion expression now. If it's a nested local declaration,
2647+
// fall through to type-check the AST node now that we've type-checked
2648+
// the surrounding closure.
2649+
if (isa<ClosureExpr>(DC))
2650+
return false;
26402651
}
26412652
}
26422653

test/IDE/complete_if_switch_expr.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,22 @@ func testSkipTypeChecking9() -> E {
252252
}
253253

254254
func testSkipTypeChecking10() -> E {
255-
// We only need to type-check the inner-most function for this.
255+
// Similar to the above case, we need to type-check everything for this since
256+
// the type-checking of 'takesArgAndClosure' is required to correctly handle
257+
// any potential captures in 'foo'.
256258
if Bool.random() {
257-
NO.TYPECHECK
259+
.e
258260
} else {
259-
takesArgAndClosure(NO.TYPECHECK) {
261+
takesArgAndClosure(0) {
260262
func foo() {
263+
// We can however skip unrelated elements in the local function.
264+
let x = NO.TYPECHECK
265+
if NO.TYPECHECK {
266+
takesE(NO.TYPECHECK)
267+
}
261268
takesE(.#^DOT21?check=DOT^#)
262269
}
263-
return NO.TYPECHECK
270+
return .e
264271
}
265272
}
266273
}
@@ -320,7 +327,7 @@ func testSkipTypeChecking14() -> E {
320327
}
321328
}
322329

323-
func testSkipTypeChecking14() -> E {
330+
func testSkipTypeChecking15() -> E {
324331
switch Bool.random() {
325332
case true:
326333
.#^DOT26?check=DOT^#
@@ -336,7 +343,7 @@ func testSkipTypeChecking14() -> E {
336343
}
337344
}
338345

339-
func testSkipTypechecking15(_ x: inout Int) -> E {
346+
func testSkipTypechecking16(_ x: inout Int) -> E {
340347
switch Bool.random() {
341348
case true:
342349
.#^DOT27?check=DOT^#

test/IDE/complete_issue-77305.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %batch-code-completion
2+
3+
// https://github.com/swiftlang/swift/issues/77305
4+
5+
struct S {
6+
var x: Int
7+
}
8+
9+
func withFoo(_ x: (S) -> Void) {}
10+
11+
withFoo { foo in
12+
func bar() {
13+
foo.#^FN_IN_CLOSURE^#
14+
// FN_IN_CLOSURE: Decl[InstanceVar]/CurrNominal: x[#Int#]; name=x
15+
}
16+
}
17+
18+
withFoo { x in
19+
_ = { y in
20+
func bar() {
21+
_ = { z in
22+
func baz() {
23+
func qux() {
24+
z.#^VERY_NESTED_FN_IN_CLOSURE^#
25+
// VERY_NESTED_FN_IN_CLOSURE: Decl[InstanceVar]/CurrNominal: x[#Int#]; name=x
26+
}
27+
}
28+
}(y)
29+
}
30+
}(x)
31+
}

0 commit comments

Comments
 (0)