diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 34f94465a8ec0..112e31368a2ce 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -598,13 +598,23 @@ class ErrorExpr : public Expr { /// CodeCompletionExpr - Represents the code completion token in the AST, this /// can help us preserve the context of the code completion position. class CodeCompletionExpr : public Expr { - SourceRange Range; + Expr *Base; + SourceLoc Loc; public: - CodeCompletionExpr(SourceRange Range, Type Ty = Type()) - : Expr(ExprKind::CodeCompletion, /*Implicit=*/true, Ty), Range(Range) {} + CodeCompletionExpr(Expr *Base, SourceLoc Loc) + : Expr(ExprKind::CodeCompletion, /*Implicit=*/true, Type()), + Base(Base), Loc(Loc) {} - SourceRange getSourceRange() const { return Range; } + CodeCompletionExpr(SourceLoc Loc) + : CodeCompletionExpr(/*Base=*/nullptr, Loc) {} + + Expr *getBase() const { return Base; } + void setBase(Expr *E) { Base = E; } + + SourceLoc getLoc() const { return Loc; } + SourceLoc getStartLoc() const { return Base ? Base->getStartLoc() : Loc; } + SourceLoc getEndLoc() const { return Loc; } static bool classof(const Expr *E) { return E->getKind() == ExprKind::CodeCompletion; diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index e3340143015ec..fd27068e79a6b 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -476,7 +476,15 @@ class Traversal : public ASTVisitorgetBase()) { + Expr *newBaseExpr = doIt(baseExpr); + if (!newBaseExpr) + return nullptr; + E->setBase(newBaseExpr); + } + return E; + } Expr *visitLiteralExpr(LiteralExpr *E) { return E; } Expr *visitDiscardAssignmentExpr(DiscardAssignmentExpr *E) { return E; } Expr *visitTypeExpr(TypeExpr *E) { diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 3922c9e2d1569..28d9d0c7ec328 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -1132,10 +1132,9 @@ Parser::parseExprPostfixSuffix(ParserResult Result, bool isExprBasic, if (CodeCompletion) { CodeCompletion->completeDotExpr(Result.get(), /*DotLoc=*/TokLoc); } - // Eat the code completion token because we handled it. - consumeToken(tok::code_complete); - Result.setHasCodeCompletion(); - return Result; + auto CCExpr = new (Context) CodeCompletionExpr(Result.get(), + consumeToken(tok::code_complete)); + return makeParserCodeCompletionResult(CCExpr); } DeclNameLoc NameLoc; diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 6cd20d3dfbf4f..c0cfc7da3de43 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -750,7 +750,7 @@ ParserResult Parser::parseStmtReturn(SourceLoc tryLoc) { SourceLoc ReturnLoc = consumeToken(tok::kw_return); if (Tok.is(tok::code_complete)) { - auto CCE = new (Context) CodeCompletionExpr(SourceRange(Tok.getLoc())); + auto CCE = new (Context) CodeCompletionExpr(Tok.getLoc()); auto Result = makeParserResult(new (Context) ReturnStmt(ReturnLoc, CCE)); if (CodeCompletion) { CodeCompletion->completeReturnStmt(CCE); @@ -818,7 +818,7 @@ ParserResult Parser::parseStmtYield(SourceLoc tryLoc) { SourceLoc yieldLoc = consumeToken(tok::kw_yield); if (Tok.is(tok::code_complete)) { - auto cce = new (Context) CodeCompletionExpr(SourceRange(Tok.getLoc())); + auto cce = new (Context) CodeCompletionExpr(Tok.getLoc()); auto result = makeParserResult( YieldStmt::create(Context, yieldLoc, SourceLoc(), cce, SourceLoc())); if (CodeCompletion) { diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 86cacf5c8afbf..e5cf5e3cf5031 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -1176,9 +1176,24 @@ namespace { virtual Type visitCodeCompletionExpr(CodeCompletionExpr *E) { CS.Options |= ConstraintSystemFlags::SuppressDiagnostics; - return CS.createTypeVariable(CS.getConstraintLocator(E), - TVO_CanBindToLValue | - TVO_CanBindToNoEscape); + auto locator = CS.getConstraintLocator(E); + auto ty = CS.createTypeVariable(locator, + TVO_CanBindToLValue | + TVO_CanBindToNoEscape); + + // Defaults to the type of the base expression if we have a base + // expression. + // FIXME: This is just to keep the old behavior where `foo(base.)` + // the argument is type checked to the type of the 'base'. Ideally, code + // completion expression should be defauled to 'UnresolvedType' + // regardless of the existence of the base expression. But the constraint + // system is simply not ready for that. + if (auto base = E->getBase()) { + CS.addConstraint(ConstraintKind::Defaultable, ty, CS.getType(base), + locator); + } + + return ty; } Type visitNilLiteralExpr(NilLiteralExpr *expr) { diff --git a/test/IDE/complete_enum_elements.swift b/test/IDE/complete_enum_elements.swift index 01a5138abee84..7f4097efb0a9c 100644 --- a/test/IDE/complete_enum_elements.swift +++ b/test/IDE/complete_enum_elements.swift @@ -29,7 +29,7 @@ // RUN: %FileCheck %s -check-prefix=FOO_ENUM_DOT_ELEMENTS < %t.enum.txt // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ENUM_SW_WITH_QUAL_1 > %t.enum.txt -// RUN: %FileCheck %s -check-prefix=FOO_ENUM_DOT < %t.enum.txt +// RUN: %FileCheck %s -check-prefix=FOO_ENUM_DOT_CONTEXT < %t.enum.txt // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ENUM_SW_EXPR_ERROR_1 > %t.enum.txt // RUN: %FileCheck %s -check-prefix=FOO_ENUM_DOT < %t.enum.txt @@ -116,6 +116,17 @@ enum FooEnum: CaseIterable { // FOO_ENUM_DOT-NEXT: Decl[StaticVar]/CurrNominal: allCases[#[FooEnum]#]{{; name=.+$}} // FOO_ENUM_DOT-NEXT: End completions +// FOO_ENUM_DOT_CONTEXT: Begin completions +// FOO_ENUM_DOT_CONTEXT-NEXT: Keyword[self]/CurrNominal: self[#FooEnum.Type#]; name=self +// FOO_ENUM_DOT_CONTEXT-NEXT: Keyword/CurrNominal: Type[#FooEnum.Type#]; name=Type +// FOO_ENUM_DOT_CONTEXT-NEXT: Decl[EnumElement]/CurrNominal/TypeRelation[Identical]: Foo1[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_DOT_CONTEXT-NEXT: Decl[EnumElement]/CurrNominal/TypeRelation[Identical]: Foo2[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_DOT_CONTEXT-NEXT: Decl[StaticVar]/CurrNominal/TypeRelation[Identical]: alias1[#FooEnum#]{{; name=.+$}} +// FOO_ENUM_DOT_CONTEXT-NEXT: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): FooEnum#})[#(into: inout Hasher) -> Void#]{{; name=.+$}} +// FOO_ENUM_DOT_CONTEXT-NEXT: Decl[TypeAlias]/CurrNominal: AllCases[#[FooEnum]#]{{; name=.+$}} +// FOO_ENUM_DOT_CONTEXT-NEXT: Decl[StaticVar]/CurrNominal: allCases[#[FooEnum]#]{{; name=.+$}} +// FOO_ENUM_DOT_CONTEXT-NEXT: End completions + // FOO_ENUM_DOT_INVALID: Begin completions // FOO_ENUM_DOT_INVALID-NEXT: Keyword[self]/CurrNominal: self[#FooEnum.Type#]; name=self // FOO_ENUM_DOT_INVALID-NEXT: Keyword/CurrNominal: Type[#FooEnum.Type#]; name=Type diff --git a/test/IDE/complete_rdar63965160.swift b/test/IDE/complete_rdar63965160.swift new file mode 100644 index 0000000000000..41dd721ffb8d6 --- /dev/null +++ b/test/IDE/complete_rdar63965160.swift @@ -0,0 +1,38 @@ +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=STRINGLITERAL | %FileCheck %s +// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=NORMAL | %FileCheck %s + +protocol View {} + +@_functionBuilder +struct Builder { + static func buildBlock(_ c0: C0) -> C0 {} + static func buildBlock(_ c0: C0, _ c1: C1) -> C1 {} + static func buildBlock(_ c0: C0, _ c1: C1, _ c2: C2) -> C1 {} +} + +struct ForEach: View where Data: RandomAccessCollection { + init(_ dat: Data, @Builder content: (Data.Element) -> Content) {} +} + +struct Text: View { + init(_ text: String) {} +} + +struct Value { + var name: String +} + +func test(values: [Value]) { + _ = ForEach(values) { value in + Text("foobar") + Text("value \(value.#^STRINGLITERAL^#)") + } + _ = ForEach(values) { value in + Text("foobar") + Text(value.#^NORMAL^#) + } +} +// CHECK: Begin completions, 2 items +// CHECK-DAG: Keyword[self]/CurrNominal: self[#Value#]; +// CHECK-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[{{Convertible|Identical}}]: name[#String#]; +// CHECK: End completions diff --git a/test/IDE/complete_skipbody.swift b/test/IDE/complete_skipbody.swift index 9b9d3f61e13b2..728e9f342f2f4 100644 --- a/test/IDE/complete_skipbody.swift +++ b/test/IDE/complete_skipbody.swift @@ -49,7 +49,7 @@ let globalValue = globalValueOpt! let FORBIDDEN_globalVar = 1 -switch glovalValue.x { +switch globalValue.x { case let x where x < 2: if x == globalValue.#^TOPLEVEL^# {} default: @@ -58,6 +58,6 @@ default: // CHECK: Begin completions, 3 items // CHECK-DAG: Keyword[self]/CurrNominal: self[#MyStruct#]; name=self -// CHECK-DAG: Decl[InstanceVar]/CurrNominal: x[#Int#]; name=x -// CHECK-DAG: Decl[InstanceVar]/CurrNominal: y[#Int#]; name=y +// CHECK-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Identical]: x[#Int#]; name=x +// CHECK-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Identical]: y[#Int#]; name=y // CHECK: End completions