From 33f78388d76fae301e63e89d3c689a00b425f87e Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 15 Jun 2020 16:28:54 +0000 Subject: [PATCH 1/2] test: enable ObjC interop for test which requires it This test requires ObjC interop, ensure that we explicitly enable the ObjC interop. --- test/IDE/range_info_declattr.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/IDE/range_info_declattr.swift b/test/IDE/range_info_declattr.swift index a821791b4299f..6daddeeb75417 100644 --- a/test/IDE/range_info_declattr.swift +++ b/test/IDE/range_info_declattr.swift @@ -23,16 +23,16 @@ class Derived : ObjCBase { } } -// RUN: %target-swift-ide-test -range -pos=4:1 -end-pos=9:2 -source-filename %s | %FileCheck %s -check-prefix=CHECK1 -// RUN: %target-swift-ide-test -range -pos=5:3 -end-pos=7:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK2 -// RUN: %target-swift-ide-test -range -pos=5:25 -end-pos=7:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK3 -// RUN: %target-swift-ide-test -range -pos=8:3 -end-pos=8:31 -source-filename %s | %FileCheck %s -check-prefix=CHECK4 -// RUN: %target-swift-ide-test -range -pos=13:5 -end-pos=13:32 -source-filename %s | %FileCheck %s -check-prefix=CHECK5 -// RUN: %target-swift-ide-test -range -pos=13:16 -end-pos=13:32 -source-filename %s | %FileCheck %s -check-prefix=CHECK6 -// RUN: %target-swift-ide-test -range -pos=12:26 -end-pos=14:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK7 -// RUN: %target-swift-ide-test -range -pos=17:5 -end-pos=18:21 -source-filename %s | %FileCheck %s -check-prefix=CHECK8 -// RUN: %target-swift-ide-test -range -pos=20:5 -end-pos=22:12 -source-filename %s | %FileCheck %s -check-prefix=CHECK9 -// RUN: %target-swift-ide-test -range -pos=21:5 -end-pos=22:12 -source-filename %s | %FileCheck %s -check-prefix=CHECK10 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=4:1 -end-pos=9:2 -source-filename %s | %FileCheck %s -check-prefix=CHECK1 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=5:3 -end-pos=7:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK2 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=5:25 -end-pos=7:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK3 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=8:3 -end-pos=8:31 -source-filename %s | %FileCheck %s -check-prefix=CHECK4 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=13:5 -end-pos=13:32 -source-filename %s | %FileCheck %s -check-prefix=CHECK5 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=13:16 -end-pos=13:32 -source-filename %s | %FileCheck %s -check-prefix=CHECK6 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=12:26 -end-pos=14:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK7 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=17:5 -end-pos=18:21 -source-filename %s | %FileCheck %s -check-prefix=CHECK8 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=20:5 -end-pos=22:12 -source-filename %s | %FileCheck %s -check-prefix=CHECK9 +// RUN: %target-swift-ide-test -enable-objc-interop -disable-objc-attr-requires-foundation-module -range -pos=21:5 -end-pos=22:12 -source-filename %s | %FileCheck %s -check-prefix=CHECK10 // CHECK1: SingleDecl // CHECK1-NEXT: @objc class ObjCClass : ObjCBase { From 5db0f1a40b76618d918f0050053f240e9ac27234 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 9 Jun 2020 14:24:55 -0700 Subject: [PATCH 2/2] [CodeCompletion] Typecheck without CodeCompletionExpr when falling back to "postfix expression completion" after labeled trailing closure completion. rdar://problem/64176730 --- lib/IDE/CodeCompletion.cpp | 34 +++++++-- lib/IDE/ExprContextAnalysis.cpp | 76 +++++++++++++++++++ lib/IDE/ExprContextAnalysis.h | 7 ++ .../complete_multiple_trailingclosure.swift | 23 ++++++ 4 files changed, 132 insertions(+), 8 deletions(-) diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 09e5cab31d771..4a146d66afb5e 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -6172,15 +6172,33 @@ void CodeCompletionCallbacksImpl::doneParsing() { if (analyzedExpr->getEndLoc() != CodeCompleteTokenExpr->getLoc()) break; - // If the call expression doesn't have a type, infer it from the - // possible callee info. Type resultTy = analyzedExpr->getType(); - if (!resultTy) { - if (ContextInfo.getPossibleCallees().empty()) - break; - auto calleeInfo = ContextInfo.getPossibleCallees()[0]; - resultTy = calleeInfo.Type->getResult(); - analyzedExpr->setType(resultTy); + // If the call expression doesn't have a type, fallback to: + if (!resultTy || resultTy->is()) { + // 1) Try to type check removing CodeCompletionExpr from the call. + Expr *removedExpr = analyzedExpr; + removeCodeCompletionExpr(CurDeclContext->getASTContext(), + removedExpr); + ConcreteDeclRef referencedDecl; + auto optT = getTypeOfCompletionContextExpr( + CurDeclContext->getASTContext(), CurDeclContext, + CompletionTypeCheckKind::Normal, removedExpr, referencedDecl); + if (optT) { + resultTy = *optT; + analyzedExpr->setType(resultTy); + } + } + if (!resultTy || resultTy->is()) { + // 2) Infer it from the possible callee info. + if (!ContextInfo.getPossibleCallees().empty()) { + auto calleeInfo = ContextInfo.getPossibleCallees()[0]; + resultTy = calleeInfo.Type->getResult(); + analyzedExpr->setType(resultTy); + } + } + if (!resultTy || resultTy->is()) { + // 3) Give up providing postfix completions. + break; } auto &SM = CurDeclContext->getASTContext().SourceMgr; diff --git a/lib/IDE/ExprContextAnalysis.cpp b/lib/IDE/ExprContextAnalysis.cpp index 05aeef6bef665..602f5f32b19c3 100644 --- a/lib/IDE/ExprContextAnalysis.cpp +++ b/lib/IDE/ExprContextAnalysis.cpp @@ -12,6 +12,7 @@ #include "ExprContextAnalysis.h" #include "swift/AST/ASTContext.h" +#include "swift/AST/ASTVisitor.h" #include "swift/AST/ASTWalker.h" #include "swift/AST/Decl.h" #include "swift/AST/DeclContext.h" @@ -181,6 +182,81 @@ Expr *swift::ide::findParsedExpr(const DeclContext *DC, return finder.get(); } +//===----------------------------------------------------------------------===// +// removeCodeCompletionExpr(ASTContext, Expr) +//===----------------------------------------------------------------------===// + +namespace { +// TODO: Implement other expressions? +class CCExprRemover: public ASTWalker, public ExprVisitor { + ASTContext &Ctx; + +public: + bool Removed = false; + + CCExprRemover(ASTContext &Ctx) : Ctx(Ctx) {} + + Expr *visitCallExpr(CallExpr *E) { + SourceLoc lParenLoc, rParenLoc; + SmallVector argLabels; + SmallVector argLabelLocs; + SmallVector args; + SmallVector trailingClosures; + bool removing = false; + + if (auto paren = dyn_cast(E->getArg())) { + if (isa(paren->getSubExpr())) { + lParenLoc = paren->getLParenLoc(); + rParenLoc = paren->getRParenLoc(); + removing = true; + } + } else if (auto tuple = dyn_cast(E->getArg())) { + lParenLoc = tuple->getLParenLoc(); + rParenLoc = tuple->getRParenLoc(); + for (unsigned i = 0, e = tuple->getNumElements(); i != e; ++i) { + if (isa(tuple->getElement(i))) { + removing = true; + continue; + } + + if (i < E->getUnlabeledTrailingClosureIndex()) { + // Normal arguments. + argLabels.push_back(E->getArgumentLabels()[i]); + argLabelLocs.push_back(E->getArgumentLabelLocs()[i]); + args.push_back(tuple->getElement(i)); + } else { + // Trailing closure arguments. + trailingClosures.emplace_back(E->getArgumentLabels()[i], + E->getArgumentLabelLocs()[i], + tuple->getElement(i)); + } + } + } + if (removing) { + Removed = true; + return CallExpr::create(Ctx, E->getFn(), lParenLoc, args, argLabels, + argLabelLocs, rParenLoc, trailingClosures, + E->isImplicit()); + } + return E; + } + + Expr *visitExpr(Expr *E) { + return E; + } + + std::pair walkToExprPre(Expr *E) override { + return {true, visit(E)}; + } +}; +} + +bool swift::ide::removeCodeCompletionExpr(ASTContext &Ctx, Expr *&expr) { + CCExprRemover remover(Ctx); + expr = expr->walk(remover); + return remover.Removed; +} + //===----------------------------------------------------------------------===// // getReturnTypeFromContext(DeclContext) //===----------------------------------------------------------------------===// diff --git a/lib/IDE/ExprContextAnalysis.h b/lib/IDE/ExprContextAnalysis.h index 38c9443c46a1d..d6b039693fb70 100644 --- a/lib/IDE/ExprContextAnalysis.h +++ b/lib/IDE/ExprContextAnalysis.h @@ -34,6 +34,13 @@ void typeCheckContextAt(DeclContext *DC, SourceLoc Loc); /// exact the same as \p TargetRange. Returns \c nullptr if not found. Expr *findParsedExpr(const DeclContext *DC, SourceRange TargetRange); +/// Remove \c CodeCompletionExpr from \p expr . Returns \c true if it actually +/// mutated the expression. +/// +/// NOTE: Currently, this only removes CodeCompletionExpr at call argument +/// position. +bool removeCodeCompletionExpr(ASTContext &Ctx, Expr *&expr); + /// Returns expected return type of the given decl context. /// \p DC should be an \c AbstractFunctionDecl or an \c AbstractClosureExpr. Type getReturnTypeFromContext(const DeclContext *DC); diff --git a/test/IDE/complete_multiple_trailingclosure.swift b/test/IDE/complete_multiple_trailingclosure.swift index f75dab6080939..7dd053a267841 100644 --- a/test/IDE/complete_multiple_trailingclosure.swift +++ b/test/IDE/complete_multiple_trailingclosure.swift @@ -13,6 +13,8 @@ // 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 // 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 // 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 +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INIT_FALLBACK_1 | %FileCheck %s -check-prefix=INIT_FALLBACK +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INIT_FALLBACK_2 | %FileCheck %s -check-prefix=INIT_FALLBACK func globalFunc1(fn1: () -> Int, fn2: () -> String) {} func testGlobalFunc() { @@ -184,3 +186,24 @@ func testOptionalInit() { // INIT_REQUIRED_NEWLINE_3-NOT: name=fn3 // INIT_REQUIRED_NEWLINE_3: End completions } + +struct MyStruct4 { + init(arg1: Int = 0, arg2: () -> T) {} + init(name: String, arg2: () -> String, arg3: () -> T) {} + + func testStructMethod() {} +} +func testFallbackPostfix() { + let _ = MyStruct4 { + 1 + } #^INIT_FALLBACK_1^# +// INIT_FALLBACK: Begin completions, 2 items +// INIT_FALLBACK-DAG: Decl[InstanceMethod]/CurrNominal: .testStructMethod()[#Void#]; +// INIT_FALLBACK-DAG: Keyword[self]/CurrNominal: .self[#MyStruct4#]; +// INIT_FALLBACK: End completions + let _ = MyStruct4(name: "test") { + "" + } arg3: { + 1 + } #^INIT_FALLBACK_2^# +}