Skip to content

Commit 390d7a6

Browse files
committed
[CodeCompletion] Compute type relations for the function calls selected by ArgumentCompletion
1 parent f4986f9 commit 390d7a6

File tree

5 files changed

+61
-7
lines changed

5 files changed

+61
-7
lines changed

include/swift/IDE/ArgumentCompletion.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
2525
struct Result {
2626
/// The type associated with the code completion expression itself.
2727
Type ExpectedType;
28+
/// The expected return type of the function call.
29+
Type ExpectedCallType;
2830
/// True if this is a subscript rather than a function call.
2931
bool IsSubscript;
3032
/// The FuncDecl or SubscriptDecl associated with the call.

lib/IDE/ArgumentCompletion.cpp

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,44 @@ bool ArgumentTypeCheckCompletionCallback::addPossibleParams(
7878
return ShowGlobalCompletions;
7979
}
8080

81+
/// Applies heuristic to determine whether the result type of \p E is
82+
/// unconstrained, that is if the constraint system is satisfiable for any
83+
/// result type of \p E.
84+
static bool isExpressionResultTypeUnconstrained(const Solution &S, Expr *E) {
85+
ConstraintSystem &CS = S.getConstraintSystem();
86+
if (auto ParentExpr = CS.getParentExpr(E)) {
87+
if (auto Assign = dyn_cast<AssignExpr>(ParentExpr)) {
88+
if (isa<DiscardAssignmentExpr>(Assign->getDest())) {
89+
// _ = <expr> is unconstrained
90+
return true;
91+
}
92+
} else if (isa<RebindSelfInConstructorExpr>(ParentExpr)) {
93+
// super.init() is unconstrained (it always produces the correct result
94+
// by definition)
95+
return true;
96+
}
97+
}
98+
auto targetIt = S.solutionApplicationTargets.find(E);
99+
if (targetIt == S.solutionApplicationTargets.end()) {
100+
return false;
101+
}
102+
auto target = targetIt->second;
103+
assert(target.kind == SolutionApplicationTarget::Kind::expression);
104+
switch (target.getExprContextualTypePurpose()) {
105+
case CTP_Unused:
106+
// If we aren't using the contextual type, its unconstrained by definition.
107+
return true;
108+
case CTP_Initialization: {
109+
// let x = <expr> is unconstrained
110+
auto contextualType = target.getExprContextualType();
111+
return !contextualType || contextualType->is<UnresolvedType>();
112+
}
113+
default:
114+
// Assume that it's constrained by default.
115+
return false;
116+
}
117+
}
118+
81119
void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
82120
Type ExpectedTy = getTypeForCompletion(S, CompletionExpr);
83121

@@ -111,6 +149,11 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
111149
}
112150
auto ArgIdx = ArgInfo->completionIdx;
113151

152+
Type ExpectedCallType;
153+
if (!isExpressionResultTypeUnconstrained(S, ParentCall)) {
154+
ExpectedCallType = getTypeForCompletion(S, ParentCall);
155+
}
156+
114157
auto *CallLocator = CS.getConstraintLocator(ParentCall);
115158
auto *CalleeLocator = S.getCalleeLocator(CallLocator);
116159

@@ -189,10 +232,10 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
189232
if (Info.ValueTy) {
190233
FuncTy = Info.ValueTy->lookThroughAllOptionalTypes()->getAs<AnyFunctionType>();
191234
}
192-
Results.push_back({ExpectedTy, isa<SubscriptExpr>(ParentCall), Info.Value,
193-
FuncTy, ArgIdx, ParamIdx, std::move(ClaimedParams),
194-
IsNoninitialVariadic, Info.BaseTy, HasLabel, IsAsync,
195-
SolutionSpecificVarTypes});
235+
Results.push_back({ExpectedTy, ExpectedCallType,
236+
isa<SubscriptExpr>(ParentCall), Info.Value, FuncTy, ArgIdx,
237+
ParamIdx, std::move(ClaimedParams), IsNoninitialVariadic,
238+
Info.BaseTy, HasLabel, IsAsync, SolutionSpecificVarTypes});
196239
}
197240

198241
void ArgumentTypeCheckCompletionCallback::deliverResults(
@@ -205,10 +248,18 @@ void ArgumentTypeCheckCompletionCallback::deliverResults(
205248

206249
// Perform global completion as a fallback if we don't have any results.
207250
bool shouldPerformGlobalCompletion = Results.empty();
251+
SmallVector<Type, 4> ExpectedCallTypes;
252+
for (auto &Result : Results) {
253+
ExpectedCallTypes.push_back(Result.ExpectedCallType);
254+
}
255+
208256
SmallVector<Type, 8> ExpectedTypes;
209257

210258
if (IncludeSignature && !Results.empty()) {
211259
Lookup.setHaveLParen(true);
260+
Lookup.setExpectedTypes(ExpectedCallTypes,
261+
/*isImplicitSingleExpressionReturn=*/false);
262+
212263
for (auto &Result : Results) {
213264
auto SemanticContext = SemanticContextKind::None;
214265
NominalTypeDecl *BaseNominal = nullptr;

lib/Sema/TypeCheckCodeCompletion.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@ bool TypeChecker::typeCheckForCodeCompletion(
623623

624624
// If solve failed to generate constraints or with some other
625625
// issue, we need to fallback to type-checking a sub-expression.
626+
cs.setSolutionApplicationTarget(target.getAsExpr(), target);
626627
if (!cs.solveForCodeCompletion(target, solutions))
627628
return CompletionResult::Fallback;
628629

test/IDE/complete_call_arg.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1231,7 +1231,7 @@ private extension Sequence {
12311231
func SubstitutableBaseTyOfSubscript<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
12321232
return sorted { a, b in a[#^GENERICBASE_SUB^#] }
12331233
// GENERICBASE_SUB: Begin completions, 1 item
1234-
// GENERICBASE_SUB: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<Self.Element, Value>#}[']'][#Value#];
1234+
// GENERICBASE_SUB: Pattern/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['[']{#keyPath: KeyPath<Self.Element, Value>#}[']'][#Value#];
12351235
// GENERICBASE_SUB: End completions
12361236
}
12371237
}

test/IDE/complete_subscript.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func testSubcscriptTuple(val: (x: Int, String)) {
162162
}
163163

164164
struct HasSettableSub {
165-
subscript(a: String) -> Any {
165+
subscript(a: String) -> Int {
166166
get { return 1 }
167167
set { }
168168
}
@@ -174,6 +174,6 @@ func testSettableSub(x: inout HasSettableSub) {
174174
}
175175
// SETTABLE_SUBSCRIPT: Begin completions
176176
// SETTABLE_SUBSCRIPT-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<HasSettableSub, Value>#}[']'][#Value#];
177-
// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#(a): String#}[']'][#@lvalue Any#];
177+
// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['[']{#(a): String#}[']'][#@lvalue Int#];
178178
// SETTABLE_SUBSCRIPT-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: local[#String#]; name=local
179179
// SETTABLE_SUBSCRIPT: End completions

0 commit comments

Comments
 (0)