Skip to content

[5.1-04-24-2019] [code-completion] Add type context for single-expression function bodies #24288

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
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
16 changes: 15 additions & 1 deletion include/swift/IDE/CodeCompletion.h
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,21 @@ class CodeCompletionContext {
public:
CodeCompletionCache &Cache;
CompletionKind CodeCompletionKind = CompletionKind::None;
bool HasExpectedTypeRelation = false;

enum class TypeContextKind {
/// There is no known contextual type. All types are equally good.
None,

/// There is a contextual type from a single-expression closure/function
/// body. The context is a hint, and enables unresolved member completion,
/// but should not hide any results.
SingleExpressionBody,

/// There are known contextual types.
Required,
};

TypeContextKind typeContextKind = TypeContextKind::None;

/// Whether there may be members that can use implicit member syntax,
/// e.g. `x = .foo`.
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Parse/CodeCompletionCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class CodeCompletionCallbacks {
SmallVectorImpl<StringRef> &Keywords, SourceLoc introducerLoc) {};

/// Complete at the beginning of accessor in a accessor block.
virtual void completeAccessorBeginning() {};
virtual void completeAccessorBeginning(CodeCompletionExpr *E) {};

/// Complete the keyword in attribute, for instance, @available.
virtual void completeDeclAttrKeyword(Decl *D, bool Sil, bool Param) {};
Expand Down
40 changes: 32 additions & 8 deletions lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,18 @@ calculateMaxTypeRelationForDecl(
bool IsImplicitlyCurriedInstanceMethod = false) {
auto Result = CodeCompletionResult::ExpectedTypeRelation::Unrelated;
for (auto Type : typeContext.possibleTypes) {
// Do not use Void type context for a single-expression body, since the
// implicit return does not constrain the expression.
//
// { ... -> () in x } // x can be anything
//
// This behaves differently from explicit return, and from non-Void:
//
// { ... -> Int in x } // x must be Int
// { ... -> () in return x } // x must be Void
if (typeContext.isSingleExpressionBody && Type->isVoid())
continue;

Result = std::max(Result, calculateTypeRelationForDecl(
D, Type, IsImplicitlyCurriedInstanceMethod));

Expand Down Expand Up @@ -1359,7 +1371,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
void completeInPrecedenceGroup(SyntaxKind SK) override;
void completeNominalMemberBeginning(
SmallVectorImpl<StringRef> &Keywords, SourceLoc introducerLoc) override;
void completeAccessorBeginning() override;
void completeAccessorBeginning(CodeCompletionExpr *E) override;

void completePoundAvailablePlatform() override;
void completeImportDecl(std::vector<std::pair<Identifier, SourceLoc>> &Path) override;
Expand Down Expand Up @@ -1650,7 +1662,15 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
expectedTypeContext.possibleTypes.push_back(T);
}

bool hasExpectedTypes() const { return !expectedTypeContext.empty(); }
CodeCompletionContext::TypeContextKind typeContextKind() const {
if (expectedTypeContext.empty()) {
return CodeCompletionContext::TypeContextKind::None;
} else if (expectedTypeContext.isSingleExpressionBody) {
return CodeCompletionContext::TypeContextKind::SingleExpressionBody;
} else {
return CodeCompletionContext::TypeContextKind::Required;
}
}

bool needDot() const {
return NeedLeadingDot;
Expand Down Expand Up @@ -3548,9 +3568,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
builder.addSimpleNamedParameter("values");
builder.addRightParen();
for (auto T : expectedTypeContext.possibleTypes) {
if (!T)
continue;
if (T->is<TupleType>()) {
if (T && T->is<TupleType>() && !T->isVoid()) {
addTypeAnnotation(builder, T);
builder.setExpectedTypeRelation(CodeCompletionResult::Identical);
break;
Expand Down Expand Up @@ -4619,9 +4637,11 @@ void CodeCompletionCallbacksImpl::completeNominalMemberBeginning(
CurDeclContext = P.CurDeclContext;
}

void CodeCompletionCallbacksImpl::completeAccessorBeginning() {
void CodeCompletionCallbacksImpl::completeAccessorBeginning(
CodeCompletionExpr *E) {
Kind = CompletionKind::AccessorBeginning;
CurDeclContext = P.CurDeclContext;
CodeCompleteTokenExpr = E;
}

static bool isDynamicLookup(Type T) {
Expand Down Expand Up @@ -5204,8 +5224,12 @@ void CodeCompletionCallbacksImpl::doneParsing() {
}

case CompletionKind::AccessorBeginning: {
if (isa<AccessorDecl>(ParsedDecl))
if (isa<AccessorDecl>(ParsedDecl)) {
ExprContextInfo ContextInfo(CurDeclContext, CodeCompleteTokenExpr);
Lookup.setExpectedTypes(ContextInfo.getPossibleTypes(),
ContextInfo.isSingleExpressionBody());
DoPostfixExprBeginning();
}
break;
}

Expand Down Expand Up @@ -5394,7 +5418,7 @@ void CodeCompletionCallbacksImpl::doneParsing() {
Lookup.RequestedCachedResults.clear();
}

CompletionContext.HasExpectedTypeRelation = Lookup.hasExpectedTypes();
CompletionContext.typeContextKind = Lookup.typeContextKind();

deliverCompletionResults();
}
Expand Down
25 changes: 18 additions & 7 deletions lib/IDE/ExprContextAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,12 @@ class ExprContextAnalyzer {
break;
}
default:
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
assert(isSingleExpressionBodyForCodeCompletion(AFD->getBody()));
singleExpressionBody = true;
recordPossibleType(getReturnTypeFromContext(AFD));
break;
}
llvm_unreachable("Unhandled decl kind.");
}
}
Expand All @@ -731,6 +737,14 @@ class ExprContextAnalyzer {
}
}

/// Whether the given \c BraceStmt, which must be the body of a function or
/// closure, should be treated as a single-expression return for the purposes
/// of code-completion.
///
/// We cannot use hasSingleExpressionBody, because we explicitly do not use
/// the single-expression-body when there is code-completion in the expression
/// in order to avoid a base expression affecting the type. However, now that
/// we've typechecked, we will take the context type into account.
static bool isSingleExpressionBodyForCodeCompletion(BraceStmt *body) {
return body->getNumElements() == 1 && body->getElements()[0].is<Expr *>();
}
Expand Down Expand Up @@ -775,15 +789,9 @@ class ExprContextAnalyzer {
(!isa<CallExpr>(ParentE) && !isa<SubscriptExpr>(ParentE) &&
!isa<BinaryExpr>(ParentE) && !isa<ArgumentShuffleExpr>(ParentE));
}
case ExprKind::Closure: {
// Note: we cannot use hasSingleExpressionBody, because we explicitly
// do not use the single-expression-body when there is code-completion
// in the expression in order to avoid a base expression affecting
// the type. However, now that we've typechecked, we will take the
// context type into account.
case ExprKind::Closure:
return isSingleExpressionBodyForCodeCompletion(
cast<ClosureExpr>(E)->getBody());
}
default:
return false;
}
Expand All @@ -804,6 +812,9 @@ class ExprContextAnalyzer {
case DeclKind::PatternBinding:
return true;
default:
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D))
if (auto *body = AFD->getBody())
return isSingleExpressionBodyForCodeCompletion(body);
return false;
}
} else if (auto P = Node.getAsPattern()) {
Expand Down
12 changes: 11 additions & 1 deletion lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4430,20 +4430,30 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags,

if (Tok.is(tok::code_complete)) {
if (CodeCompletion) {
CodeCompletionExpr *CCE = nullptr;
if (IsFirstAccessor && !parsingLimitedSyntax) {
// If CC token is the first token after '{', it might be implicit
// getter. Set up dummy accessor as the decl context to populate
// 'self' decl.

// FIXME: if there is already code inside the body, we should fall
// through to parseImplicitGetter and handle the completion there so
// that we can differentiate a single-expression body from the first
// expression in a multi-statement body.
auto getter = createAccessorFunc(
accessors.LBLoc, /*ValueNamePattern*/ nullptr, GenericParams,
Indices, ElementTy, StaticLoc, Flags, AccessorKind::Get,
storage, this, /*AccessorKeywordLoc*/ SourceLoc());
CCE = new (Context) CodeCompletionExpr(Tok.getLoc());
getter->setBody(BraceStmt::create(Context, Tok.getLoc(),
ASTNode(CCE), Tok.getLoc(),
/*implicit*/ true));
accessors.add(getter);
CodeCompletion->setParsedDecl(getter);
} else {
CodeCompletion->setParsedDecl(storage);
}
CodeCompletion->completeAccessorBeginning();
CodeCompletion->completeAccessorBeginning(CCE);
}
consumeToken(tok::code_complete);
accessorHasCodeCompletion = true;
Expand Down
2 changes: 1 addition & 1 deletion test/IDE/complete_accessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
// NO_OBSERVER-NOT: willSet
// NO_OBSERVER-NOT: didSet

// WITH_GLOBAL: Decl[GlobalVar]/CurrModule: globalValue[#String#];
// WITH_GLOBAL: Decl[GlobalVar]/CurrModule{{(/TypeRelation\[Identical\])?}}: globalValue[#String#];
// NO_GLOBAL-NOT: globalValue;

// WITH_SELF: Decl[LocalVar]/Local: self[#{{.+}}#]; name=self
Expand Down
2 changes: 1 addition & 1 deletion test/IDE/complete_at_top_level.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ _ = {
}()
// TOP_LEVEL_CLOSURE_1: Begin completions
// TOP_LEVEL_CLOSURE_1-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]{{; name=.+$}}
// TOP_LEVEL_CLOSURE_1-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Identical]: fooFunc1()[#Void#]{{; name=.+$}}
// TOP_LEVEL_CLOSURE_1-DAG: Decl[FreeFunction]/CurrModule: fooFunc1()[#Void#]{{; name=.+$}}
// TOP_LEVEL_CLOSURE_1-DAG: Decl[GlobalVar]/Local: fooObject[#FooStruct#]{{; name=.+$}}
// TOP_LEVEL_CLOSURE_1: End completions

Expand Down
2 changes: 1 addition & 1 deletion test/IDE/complete_crashes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ while true {
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=GENERIC_PARAM_AND_ASSOC_TYPE | %FileCheck %s -check-prefix=GENERIC_PARAM_AND_ASSOC_TYPE
struct CustomGenericCollection<Key> : ExpressibleByDictionaryLiteral {
// GENERIC_PARAM_AND_ASSOC_TYPE: Begin completions
// GENERIC_PARAM_AND_ASSOC_TYPE-DAG: Decl[InstanceVar]/CurrNominal: count[#Int#]; name=count
// GENERIC_PARAM_AND_ASSOC_TYPE-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Identical]: count[#Int#]; name=count
// GENERIC_PARAM_AND_ASSOC_TYPE-DAG: Decl[GenericTypeParam]/Local: Key[#Key#]; name=Key
// GENERIC_PARAM_AND_ASSOC_TYPE-DAG: Decl[TypeAlias]/CurrNominal: Value[#CustomGenericCollection<Key>.Value#]; name=Value
// GENERIC_PARAM_AND_ASSOC_TYPE: End completions
Expand Down
10 changes: 5 additions & 5 deletions test/IDE/complete_in_accessors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func returnsInt() -> Int {}

// WITH_GLOBAL_DECLS: Begin completions
// WITH_GLOBAL_DECLS-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]{{; name=.+$}}
// WITH_GLOBAL_DECLS-DAG: Decl[FreeFunction]/CurrModule: returnsInt()[#Int#]{{; name=.+$}}
// WITH_GLOBAL_DECLS-DAG: Decl[FreeFunction]/CurrModule{{(/TypeRelation\[Identical\])?}}: returnsInt()[#Int#]{{; name=.+$}}
// WITH_GLOBAL_DECLS: End completions

// WITH_GLOBAL_DECLS1: Begin completions
Expand All @@ -142,7 +142,7 @@ func returnsInt() -> Int {}

// WITH_MEMBER_DECLS: Begin completions
// WITH_MEMBER_DECLS-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]{{; name=.+$}}
// WITH_MEMBER_DECLS-DAG: Decl[FreeFunction]/CurrModule: returnsInt()[#Int#]{{; name=.+$}}
// WITH_MEMBER_DECLS-DAG: Decl[FreeFunction]/CurrModule{{(/TypeRelation\[Identical\])?}}: returnsInt()[#Int#]{{; name=.+$}}
// WITH_MEMBER_DECLS-DAG: Decl[LocalVar]/Local: self[#MemberAccessors#]{{; name=.+$}}
// WITH_MEMBER_DECLS-DAG: Decl[InstanceVar]/CurrNominal: instanceVar[#Double#]{{; name=.+$}}
// WITH_MEMBER_DECLS-DAG: Decl[InstanceMethod]/CurrNominal: instanceFunc({#(a): Int#})[#Float#]{{; name=.+$}}
Expand All @@ -159,8 +159,8 @@ func returnsInt() -> Int {}

// WITH_LOCAL_DECLS: Begin completions
// WITH_LOCAL_DECLS-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]{{; name=.+$}}
// WITH_LOCAL_DECLS-DAG: Decl[FreeFunction]/CurrModule: returnsInt()[#Int#]{{; name=.+$}}
// WITH_LOCAL_DECLS-DAG: Decl[LocalVar]/Local: functionParam[#Int#]{{; name=.+$}}
// WITH_LOCAL_DECLS-DAG: Decl[FreeFunction]/CurrModule{{(/TypeRelation\[Identical\])?}}: returnsInt()[#Int#]{{; name=.+$}}
// WITH_LOCAL_DECLS-DAG: Decl[LocalVar]/Local{{(/TypeRelation\[Identical\])?}}: functionParam[#Int#]{{; name=.+$}}
// WITH_LOCAL_DECLS-DAG: Decl[FreeFunction]/Local: localFunc({#(a): Int#})[#Float#]{{; name=.+$}}
// WITH_LOCAL_DECLS: End completions

Expand Down Expand Up @@ -456,7 +456,7 @@ func accessorsInFunction(_ functionParam: Int) {

// ACCESSORS_IN_MEMBER_FUNC_2: Begin completions
// ACCESSORS_IN_MEMBER_FUNC_2-DAG: Decl[LocalVar]/Local: self[#AccessorsInMemberFunction#]
// ACCESSORS_IN_MEMBER_FUNC_2-DAG: Decl[LocalVar]/Local: functionParam[#Int#]
// ACCESSORS_IN_MEMBER_FUNC_2-DAG: Decl[LocalVar]/Local{{(/TypeRelation\[Identical\])?}}: functionParam[#Int#]
// ACCESSORS_IN_MEMBER_FUNC_2-DAG: Decl[InstanceVar]/OutNominal: instanceVar[#Double#]
// ACCESSORS_IN_MEMBER_FUNC_2-DAG: Decl[InstanceMethod]/OutNominal: instanceFunc({#(a): Int#})[#Float#]
// ACCESSORS_IN_MEMBER_FUNC_2: End completions
Expand Down
Loading