Skip to content

Commit e2d3c6d

Browse files
author
Nathan Hawes
committed
[CodeCompletion] Handle member completions within the OriginalExprs of ErrorExprs if present.
We weren't walking into ErrorExprs in typeCheckForCodeCompletion previously, so never found the CodeCompletionExpr.
1 parent a5f6a67 commit e2d3c6d

File tree

2 files changed

+53
-6
lines changed

2 files changed

+53
-6
lines changed

lib/Sema/TypeCheckCodeCompletion.cpp

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ bool TypeChecker::typeCheckForCodeCompletion(
603603
return false;
604604

605605
// First of all, let's check whether given target expression
606-
// does indeed have a code completion token in it.
606+
// does indeed have the code completion location in it.
607607
{
608608
auto range = expr->getSourceRange();
609609
if (range.isInvalid() ||
@@ -623,7 +623,8 @@ bool TypeChecker::typeCheckForCodeCompletion(
623623
Application,
624624
StringInterpolation,
625625
SingleStmtClosure,
626-
MultiStmtClosure
626+
MultiStmtClosure,
627+
ErrorExpression
627628
};
628629

629630
class ContextFinder : public ASTWalker {
@@ -632,7 +633,7 @@ bool TypeChecker::typeCheckForCodeCompletion(
632633
// Stack of all "interesting" contexts up to code completion expression.
633634
llvm::SmallVector<Context, 4> Contexts;
634635

635-
Expr *CompletionExpr;
636+
Expr *CompletionExpr = nullptr;
636637

637638
public:
638639
ContextFinder(Expr *E) {
@@ -660,12 +661,20 @@ bool TypeChecker::typeCheckForCodeCompletion(
660661
return std::make_pair(false, nullptr);
661662
}
662663

664+
if (auto *Error = dyn_cast<ErrorExpr>(E)) {
665+
Contexts.push_back(std::make_pair(ContextKind::ErrorExpression, E));
666+
if (auto *OrigExpr = Error->getOriginalExpr()) {
667+
OrigExpr->walk(*this);
668+
return std::make_pair(false, hasCompletionExpr() ? nullptr : E);
669+
}
670+
}
671+
663672
return std::make_pair(true, E);
664673
}
665674

666675
Expr *walkToExprPost(Expr *E) override {
667676
if (isa<ClosureExpr>(E) || isa<InterpolatedStringLiteralExpr>(E) ||
668-
isa<ApplyExpr>(E))
677+
isa<ApplyExpr>(E) || isa<ErrorExpr>(E))
669678
Contexts.pop_back();
670679
return E;
671680
}
@@ -680,11 +689,23 @@ bool TypeChecker::typeCheckForCodeCompletion(
680689
return hasContext(ContextKind::StringInterpolation);
681690
}
682691

692+
bool hasCompletionExpr() const {
693+
return CompletionExpr;
694+
}
695+
683696
Expr *getCompletionExpr() const {
684697
assert(CompletionExpr);
685698
return CompletionExpr;
686699
}
687700

701+
ErrorExpr *getInnermostErrorExpr() const {
702+
for (const Context &curr : llvm::reverse(Contexts)) {
703+
if (curr.first == ContextKind::ErrorExpression)
704+
return cast<ErrorExpr>(curr.second);
705+
}
706+
return nullptr;
707+
}
708+
688709
ClosureExpr *getOutermostMultiStmtClosure() const {
689710
for (const Context &curr : Contexts) {
690711
if (curr.first == ContextKind::MultiStmtClosure)
@@ -717,6 +738,25 @@ bool TypeChecker::typeCheckForCodeCompletion(
717738
ContextFinder contextAnalyzer(expr);
718739
expr->walk(contextAnalyzer);
719740

741+
// If there was no completion expr (e.g. if the code completion location is
742+
// within an ErrorExpr without an valid subexpr due to parser error recovery)
743+
// bail.
744+
if (!contextAnalyzer.hasCompletionExpr())
745+
return false;
746+
747+
// If the completion expression is in a valid subexpression of an ErrorExpr,
748+
// fallback to trying the valid subexpression without any context. This can
749+
// happen for cases like `expectsBoolArg(foo.<complete>).` which becomes an
750+
// ErrorExpr due to the missing member name after the final dot.
751+
if (auto *errorExpr = contextAnalyzer.getInnermostErrorExpr()) {
752+
if (auto *origExpr = errorExpr->getOriginalExpr()) {
753+
SolutionApplicationTarget completionTarget(origExpr, DC, CTP_Unused,
754+
/*contextualType=*/Type(),
755+
/*isDiscarded=*/true);
756+
return typeCheckForCodeCompletion(completionTarget, callback);
757+
}
758+
}
759+
720760
// Interpolation components are type-checked separately.
721761
if (contextAnalyzer.locatedInStringIterpolation())
722762
return false;

test/IDE/complete_ambiguous.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=SIMPLE_MEMBERS | %FileCheck %s --check-prefix=SIMPLE
44
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=RELATED | %FileCheck %s --check-prefix=RELATED
55
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=RELATED_EXTRAARG | %FileCheck %s --check-prefix=RELATED
6+
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=RELATED_INERROREXPR | %FileCheck %s --check-prefix=RELATED
67
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=GENERIC | %FileCheck %s --check-prefix=GENERIC
8+
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=GENERIC_MISSINGARG | %FileCheck %s --check-prefix=NORESULTS
79
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=CLOSURE_MISSINGARG | %FileCheck %s --check-prefix=POINT_MEMBER
810
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=CLOSURE_NORETURN | %FileCheck %s --check-prefix=POINT_MEMBER
911
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=CLOSURE_FUNCBUILDER | %FileCheck %s --check-prefix=POINT_MEMBER
@@ -46,6 +48,10 @@ let x: A = overloadedReturn(1).#^RELATED_EXTRAARG^#
4648
// RELATED-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: doBThings()[#Void#]{{; name=.+$}}
4749
// RELATED: End completions
4850

51+
func takesA(_ callback: () -> A) -> B {}
52+
func takesB(_ item: B) {}
53+
54+
takesB((takesA { return overloadedReturn().#^RELATED_INERROREXPR^# }).)
4955

5056
protocol C {
5157
associatedtype Element
@@ -78,9 +84,10 @@ genericReturn(CDStruct()).#^GENERIC^#
7884
// GENERIC-DAG: Decl[InstanceMethod]/CurrNominal: doAThings()[#A#]{{; name=.+$}}
7985
// GENERIC: End completions
8086

81-
// FIXME: this takes 19 seconds to determine there are no results.
8287
genericReturn().#^GENERIC_MISSINGARG^#
8388

89+
// NORESULTS-NOT: Begin completions
90+
8491
struct Point {
8592
let x: Int
8693
let y: Int
@@ -120,7 +127,7 @@ struct ThingBuilder {
120127
}
121128
func CreateThings(@ThingBuilder makeThings: () -> [Thing]) {}
122129

123-
// FIXME: only works in single expression closure
130+
// FIXME: only works if the first call to Thing is passed a single expression closure
124131
CreateThings {
125132
Thing { point in
126133
point.#^CLOSURE_FUNCBUILDER^#

0 commit comments

Comments
 (0)