Skip to content

Commit 1fd4725

Browse files
authored
Merge pull request #32283 from rintaro/ide-completion-postfixfallback-rdar64176730
[CodeCompletion] Typecheck without CodeCompletionExpr
2 parents 73af91e + 5db0f1a commit 1fd4725

File tree

4 files changed

+132
-8
lines changed

4 files changed

+132
-8
lines changed

lib/IDE/CodeCompletion.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6172,15 +6172,33 @@ void CodeCompletionCallbacksImpl::doneParsing() {
61726172
if (analyzedExpr->getEndLoc() != CodeCompleteTokenExpr->getLoc())
61736173
break;
61746174

6175-
// If the call expression doesn't have a type, infer it from the
6176-
// possible callee info.
61776175
Type resultTy = analyzedExpr->getType();
6178-
if (!resultTy) {
6179-
if (ContextInfo.getPossibleCallees().empty())
6180-
break;
6181-
auto calleeInfo = ContextInfo.getPossibleCallees()[0];
6182-
resultTy = calleeInfo.Type->getResult();
6183-
analyzedExpr->setType(resultTy);
6176+
// If the call expression doesn't have a type, fallback to:
6177+
if (!resultTy || resultTy->is<ErrorType>()) {
6178+
// 1) Try to type check removing CodeCompletionExpr from the call.
6179+
Expr *removedExpr = analyzedExpr;
6180+
removeCodeCompletionExpr(CurDeclContext->getASTContext(),
6181+
removedExpr);
6182+
ConcreteDeclRef referencedDecl;
6183+
auto optT = getTypeOfCompletionContextExpr(
6184+
CurDeclContext->getASTContext(), CurDeclContext,
6185+
CompletionTypeCheckKind::Normal, removedExpr, referencedDecl);
6186+
if (optT) {
6187+
resultTy = *optT;
6188+
analyzedExpr->setType(resultTy);
6189+
}
6190+
}
6191+
if (!resultTy || resultTy->is<ErrorType>()) {
6192+
// 2) Infer it from the possible callee info.
6193+
if (!ContextInfo.getPossibleCallees().empty()) {
6194+
auto calleeInfo = ContextInfo.getPossibleCallees()[0];
6195+
resultTy = calleeInfo.Type->getResult();
6196+
analyzedExpr->setType(resultTy);
6197+
}
6198+
}
6199+
if (!resultTy || resultTy->is<ErrorType>()) {
6200+
// 3) Give up providing postfix completions.
6201+
break;
61846202
}
61856203

61866204
auto &SM = CurDeclContext->getASTContext().SourceMgr;

lib/IDE/ExprContextAnalysis.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "ExprContextAnalysis.h"
1414
#include "swift/AST/ASTContext.h"
15+
#include "swift/AST/ASTVisitor.h"
1516
#include "swift/AST/ASTWalker.h"
1617
#include "swift/AST/Decl.h"
1718
#include "swift/AST/DeclContext.h"
@@ -181,6 +182,81 @@ Expr *swift::ide::findParsedExpr(const DeclContext *DC,
181182
return finder.get();
182183
}
183184

185+
//===----------------------------------------------------------------------===//
186+
// removeCodeCompletionExpr(ASTContext, Expr)
187+
//===----------------------------------------------------------------------===//
188+
189+
namespace {
190+
// TODO: Implement other expressions?
191+
class CCExprRemover: public ASTWalker, public ExprVisitor<CCExprRemover, Expr *> {
192+
ASTContext &Ctx;
193+
194+
public:
195+
bool Removed = false;
196+
197+
CCExprRemover(ASTContext &Ctx) : Ctx(Ctx) {}
198+
199+
Expr *visitCallExpr(CallExpr *E) {
200+
SourceLoc lParenLoc, rParenLoc;
201+
SmallVector<Identifier, 2> argLabels;
202+
SmallVector<SourceLoc, 2> argLabelLocs;
203+
SmallVector<Expr *, 2> args;
204+
SmallVector<TrailingClosure, 2> trailingClosures;
205+
bool removing = false;
206+
207+
if (auto paren = dyn_cast<ParenExpr>(E->getArg())) {
208+
if (isa<CodeCompletionExpr>(paren->getSubExpr())) {
209+
lParenLoc = paren->getLParenLoc();
210+
rParenLoc = paren->getRParenLoc();
211+
removing = true;
212+
}
213+
} else if (auto tuple = dyn_cast<TupleExpr>(E->getArg())) {
214+
lParenLoc = tuple->getLParenLoc();
215+
rParenLoc = tuple->getRParenLoc();
216+
for (unsigned i = 0, e = tuple->getNumElements(); i != e; ++i) {
217+
if (isa<CodeCompletionExpr>(tuple->getElement(i))) {
218+
removing = true;
219+
continue;
220+
}
221+
222+
if (i < E->getUnlabeledTrailingClosureIndex()) {
223+
// Normal arguments.
224+
argLabels.push_back(E->getArgumentLabels()[i]);
225+
argLabelLocs.push_back(E->getArgumentLabelLocs()[i]);
226+
args.push_back(tuple->getElement(i));
227+
} else {
228+
// Trailing closure arguments.
229+
trailingClosures.emplace_back(E->getArgumentLabels()[i],
230+
E->getArgumentLabelLocs()[i],
231+
tuple->getElement(i));
232+
}
233+
}
234+
}
235+
if (removing) {
236+
Removed = true;
237+
return CallExpr::create(Ctx, E->getFn(), lParenLoc, args, argLabels,
238+
argLabelLocs, rParenLoc, trailingClosures,
239+
E->isImplicit());
240+
}
241+
return E;
242+
}
243+
244+
Expr *visitExpr(Expr *E) {
245+
return E;
246+
}
247+
248+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
249+
return {true, visit(E)};
250+
}
251+
};
252+
}
253+
254+
bool swift::ide::removeCodeCompletionExpr(ASTContext &Ctx, Expr *&expr) {
255+
CCExprRemover remover(Ctx);
256+
expr = expr->walk(remover);
257+
return remover.Removed;
258+
}
259+
184260
//===----------------------------------------------------------------------===//
185261
// getReturnTypeFromContext(DeclContext)
186262
//===----------------------------------------------------------------------===//

lib/IDE/ExprContextAnalysis.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ void typeCheckContextAt(DeclContext *DC, SourceLoc Loc);
3434
/// exact the same as \p TargetRange. Returns \c nullptr if not found.
3535
Expr *findParsedExpr(const DeclContext *DC, SourceRange TargetRange);
3636

37+
/// Remove \c CodeCompletionExpr from \p expr . Returns \c true if it actually
38+
/// mutated the expression.
39+
///
40+
/// NOTE: Currently, this only removes CodeCompletionExpr at call argument
41+
/// position.
42+
bool removeCodeCompletionExpr(ASTContext &Ctx, Expr *&expr);
43+
3744
/// Returns expected return type of the given decl context.
3845
/// \p DC should be an \c AbstractFunctionDecl or an \c AbstractClosureExpr.
3946
Type getReturnTypeFromContext(const DeclContext *DC);

test/IDE/complete_multiple_trailingclosure.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INIT_REQUIRED_NEWLINE_2 | %FileCheck %s -check-prefix=INIT_REQUIRED_NEWLINE_2
1414
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INIT_REQUIRED_SAMELINE_3 | %FileCheck %s -check-prefix=INIT_REQUIRED_SAMELINE_3
1515
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INIT_REQUIRED_NEWLINE_3 | %FileCheck %s -check-prefix=INIT_REQUIRED_NEWLINE_3
16+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INIT_FALLBACK_1 | %FileCheck %s -check-prefix=INIT_FALLBACK
17+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INIT_FALLBACK_2 | %FileCheck %s -check-prefix=INIT_FALLBACK
1618

1719
func globalFunc1(fn1: () -> Int, fn2: () -> String) {}
1820
func testGlobalFunc() {
@@ -184,3 +186,24 @@ func testOptionalInit() {
184186
// INIT_REQUIRED_NEWLINE_3-NOT: name=fn3
185187
// INIT_REQUIRED_NEWLINE_3: End completions
186188
}
189+
190+
struct MyStruct4<T> {
191+
init(arg1: Int = 0, arg2: () -> T) {}
192+
init(name: String, arg2: () -> String, arg3: () -> T) {}
193+
194+
func testStructMethod() {}
195+
}
196+
func testFallbackPostfix() {
197+
let _ = MyStruct4 {
198+
1
199+
} #^INIT_FALLBACK_1^#
200+
// INIT_FALLBACK: Begin completions, 2 items
201+
// INIT_FALLBACK-DAG: Decl[InstanceMethod]/CurrNominal: .testStructMethod()[#Void#];
202+
// INIT_FALLBACK-DAG: Keyword[self]/CurrNominal: .self[#MyStruct4<Int>#];
203+
// INIT_FALLBACK: End completions
204+
let _ = MyStruct4(name: "test") {
205+
""
206+
} arg3: {
207+
1
208+
} #^INIT_FALLBACK_2^#
209+
}

0 commit comments

Comments
 (0)