Skip to content

[CodeCompletion] Wrap base expression with CodeCompletionExpr #32184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 9 additions & 1 deletion lib/AST/ASTWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,15 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
} while (false)

Expr *visitErrorExpr(ErrorExpr *E) { return E; }
Expr *visitCodeCompletionExpr(CodeCompletionExpr *E) { return E; }
Expr *visitCodeCompletionExpr(CodeCompletionExpr *E) {
if (Expr *baseExpr = E->getBase()) {
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) {
Expand Down
7 changes: 3 additions & 4 deletions lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1132,10 +1132,9 @@ Parser::parseExprPostfixSuffix(ParserResult<Expr> 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;
Expand Down
4 changes: 2 additions & 2 deletions lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ ParserResult<Stmt> 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);
Expand Down Expand Up @@ -818,7 +818,7 @@ ParserResult<Stmt> 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) {
Expand Down
21 changes: 18 additions & 3 deletions lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.<HERE>)`
// 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) {
Expand Down
13 changes: 12 additions & 1 deletion test/IDE/complete_enum_elements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions test/IDE/complete_rdar63965160.swift
Original file line number Diff line number Diff line change
@@ -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: View>(_ c0: C0) -> C0 {}
static func buildBlock<C0: View, C1: View>(_ c0: C0, _ c1: C1) -> C1 {}
static func buildBlock<C0: View, C1: View, C2: View>(_ c0: C0, _ c1: C1, _ c2: C2) -> C1 {}
}

struct ForEach<Data, Content>: 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
6 changes: 3 additions & 3 deletions test/IDE/complete_skipbody.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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