Skip to content

Commit 278ecef

Browse files
committed
[CodeCompletion] Don’t report a call pattern as convertible if its result type doesn’t match
To compute the expected type of a call pattern (which is the return type of the function if that call pattern is being used), we called `getTypeForCompletion` for the entire call, in the same way that we do for the code completion token. However, this pattern does not generally work. For the code completion token it worked because the code completion expression doesn’t have an inherent type and it inherits the type solely from its context. Calls, however, have an inherent return type and that type gets assigned as the `typeForCompletion`. Implement targeted checks for the two most common cases where an expected type exists: If the call that we suggest call patterns for is itself an argument to another function or if it is used in a place that has a contextual type in the constraint system (eg. a variable binding or a `return` statement). This means that we no longer return `Convertible` for call patterns in some more complex scenarios. But given that this information was computed based on incorrect results and that in those cases all call patterns had a `Convertible` type relation, I think that’s acceptable. Fixing this would require recording more information in the constraints system, which is out-of-scope for now.
1 parent 2ef1aa5 commit 278ecef

9 files changed

+52
-62
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ class FunctionArgApplyInfo {
908908
bool lookThroughAutoclosure) const {
909909
auto param = fnTy->getParams()[ParamIdx];
910910
auto paramTy = param.getPlainType();
911-
if (lookThroughAutoclosure && param.isAutoClosure())
911+
if (lookThroughAutoclosure && param.isAutoClosure() && paramTy->is<FunctionType>())
912912
paramTy = paramTy->castTo<FunctionType>()->getResult();
913913
return paramTy;
914914
}

lib/IDE/ArgumentCompletion.cpp

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -89,43 +89,6 @@ bool ArgumentTypeCheckCompletionCallback::addPossibleParams(
8989
return ShowGlobalCompletions;
9090
}
9191

92-
/// Applies heuristic to determine whether the result type of \p E is
93-
/// unconstrained, that is if the constraint system is satisfiable for any
94-
/// result type of \p E.
95-
static bool isExpressionResultTypeUnconstrained(const Solution &S, Expr *E) {
96-
ConstraintSystem &CS = S.getConstraintSystem();
97-
if (auto ParentExpr = CS.getParentExpr(E)) {
98-
if (auto Assign = dyn_cast<AssignExpr>(ParentExpr)) {
99-
if (isa<DiscardAssignmentExpr>(Assign->getDest())) {
100-
// _ = <expr> is unconstrained
101-
return true;
102-
}
103-
} else if (isa<RebindSelfInConstructorExpr>(ParentExpr)) {
104-
// super.init() is unconstrained (it always produces the correct result
105-
// by definition)
106-
return true;
107-
}
108-
}
109-
auto target = S.getTargetFor(E);
110-
if (!target)
111-
return false;
112-
113-
assert(target->kind == SyntacticElementTarget::Kind::expression);
114-
switch (target->getExprContextualTypePurpose()) {
115-
case CTP_Unused:
116-
// If we aren't using the contextual type, its unconstrained by definition.
117-
return true;
118-
case CTP_Initialization: {
119-
// let x = <expr> is unconstrained
120-
auto contextualType = target->getExprContextualType();
121-
return !contextualType || contextualType->is<UnresolvedType>();
122-
}
123-
default:
124-
// Assume that it's constrained by default.
125-
return false;
126-
}
127-
}
128-
12992
/// Returns whether `E` has a parent expression with arguments.
13093
static bool hasParentCallLikeExpr(Expr *E, ConstraintSystem &CS) {
13194
E = CS.getParentExpr(E);
@@ -172,10 +135,21 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
172135
auto ArgIdx = ArgInfo->completionIdx;
173136

174137
Type ExpectedCallType;
175-
if (!isExpressionResultTypeUnconstrained(S, ParentCall)) {
176-
ExpectedCallType = getTypeForCompletion(S, ParentCall);
138+
if (auto ArgLoc = S.getConstraintSystem().getArgumentLocator(ParentCall)) {
139+
if (auto FuncArgApplyInfo = S.getFunctionArgApplyInfo(ArgLoc)) {
140+
Type ParamType = FuncArgApplyInfo->getParamInterfaceType();
141+
ExpectedCallType = S.simplifyTypeForCodeCompletion(ParamType);
142+
}
177143
}
178-
144+
if (!ExpectedCallType) {
145+
if (auto ContextualType = S.getContextualType(ParentCall)) {
146+
ExpectedCallType = ContextualType;
147+
}
148+
}
149+
if (ExpectedCallType && ExpectedCallType->hasUnresolvedType()) {
150+
ExpectedCallType = Type();
151+
}
152+
179153
auto *CallLocator = CS.getConstraintLocator(ParentCall);
180154
auto *CalleeLocator = S.getCalleeLocator(CallLocator);
181155

test/IDE/complete_call_arg.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -576,17 +576,17 @@ func testStaticMemberCall() {
576576
func testImplicitMember() {
577577
let _: TestStaticMemberCall = .create1(#^IMPLICIT_MEMBER_AFTERPAREN_1^#)
578578
// IMPLICIT_MEMBER_AFTERPAREN_1: Begin completions, 1 items
579-
// IMPLICIT_MEMBER_AFTERPAREN_1: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#arg1: Int#}[')'][#TestStaticMemberCall#]; name=arg1:
579+
// IMPLICIT_MEMBER_AFTERPAREN_1: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#arg1: Int#}[')'][#TestStaticMemberCall#]; name=arg1:
580580

581581
let _: TestStaticMemberCall = .create2(#^IMPLICIT_MEMBER_AFTERPAREN_2^#)
582-
// IMPLICIT_MEMBER_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#(arg1): Int#}[')'][#TestStaticMemberCall#];
583-
// IMPLICIT_MEMBER_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#(arg1): Int#}, {#arg2: Int#}, {#arg3: Int#}, {#arg4: Int#}[')'][#TestStaticMemberCall#];
582+
// IMPLICIT_MEMBER_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#(arg1): Int#}[')'][#TestStaticMemberCall#];
583+
// IMPLICIT_MEMBER_AFTERPAREN_2-DAG: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#(arg1): Int#}, {#arg2: Int#}, {#arg3: Int#}, {#arg4: Int#}[')'][#TestStaticMemberCall#];
584584
// IMPLICIT_MEMBER_AFTERPAREN_2-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem/TypeRelation[Convertible]: Int[#Int#];
585585
// IMPLICIT_MEMBER_AFTERPAREN_2-DAG: Literal[Integer]/None/TypeRelation[Convertible]: 0[#Int#];
586586

587587
let _: TestStaticMemberCall? = .create1(#^IMPLICIT_MEMBER_AFTERPAREN_3^#)
588588
// IMPLICIT_MEMBER_AFTERPAREN_3: Begin completions, 1 items
589-
// IMPLICIT_MEMBER_AFTERPAREN_3: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#arg1: Int#}[')'][#TestStaticMemberCall#]; name=arg1:
589+
// IMPLICIT_MEMBER_AFTERPAREN_3: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#arg1: Int#}[')'][#TestStaticMemberCall#]; name=arg1:
590590

591591
let _: TestStaticMemberCall = .create2(1, #^IMPLICIT_MEMBER_SECOND^#)
592592
// IMPLICIT_MEMBER_SECOND: Begin completions, 3 items
@@ -600,7 +600,7 @@ func testImplicitMember() {
600600

601601
let _: TestStaticMemberCall = .createOverloaded(#^IMPLICIT_MEMBER_OVERLOADED^#)
602602
// IMPLICIT_MEMBER_OVERLOADED: Begin completions, 1 item
603-
// IMPLICIT_MEMBER_OVERLOADED: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#arg1: Int#}[')'][#TestStaticMemberCall#]; name=arg1:
603+
// IMPLICIT_MEMBER_OVERLOADED: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#arg1: Int#}[')'][#TestStaticMemberCall#]; name=arg1:
604604
}
605605
func testImplicitMemberInArrayLiteral() {
606606
struct Receiver {
@@ -1325,8 +1325,8 @@ func testSubscriptWithExistingRhs(someString: String) {
13251325

13261326
// SUBSCRIPT_WITH_EXISTING_RHS: Begin completions
13271327
// SUBSCRIPT_WITH_EXISTING_RHS-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<[String : Any], Value>#}[']'][#Value#];
1328-
// SUBSCRIPT_WITH_EXISTING_RHS-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem/TypeRelation[Convertible]: ['[']{#(key): String#}[']'][#@lvalue Any?#];
1329-
// SUBSCRIPT_WITH_EXISTING_RHS-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem/TypeRelation[Convertible]: ['[']{#(key): String#}, {#default: Any#}[']'][#@lvalue Any#];
1328+
// SUBSCRIPT_WITH_EXISTING_RHS-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem: ['[']{#(key): String#}[']'][#@lvalue Any?#];
1329+
// SUBSCRIPT_WITH_EXISTING_RHS-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem: ['[']{#(key): String#}, {#default: Any#}[']'][#@lvalue Any#];
13301330
// SUBSCRIPT_WITH_EXISTING_RHS-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: someString[#String#];
13311331
// SUBSCRIPT_WITH_EXISTING_RHS: End completions
13321332
}
@@ -1348,8 +1348,8 @@ func testOptionalConversionInSrcOfAssignment(myArray: [Int]) {
13481348
var optInt: Int?
13491349
optInt = myArray[#^OPTIONAL_CONVERSION_IN_ASSIGNMENT^#]
13501350
// OPTIONAL_CONVERSION_IN_ASSIGNMENT: Begin completions
1351-
// OPTIONAL_CONVERSION_IN_ASSIGNMENT-DAG: Pattern/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['[']{#keyPath: KeyPath<[Int], Value>#}[']'][#Value#]; name=keyPath:
1352-
// OPTIONAL_CONVERSION_IN_ASSIGNMENT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem/TypeRelation[Convertible]: ['[']{#(index): Int#}[']'][#Int#]; name=:
1351+
// OPTIONAL_CONVERSION_IN_ASSIGNMENT-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<[Int], Value>#}[']'][#Value#]; name=keyPath:
1352+
// OPTIONAL_CONVERSION_IN_ASSIGNMENT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem: ['[']{#(index): Int#}[']'][#Int#]; name=:
13531353
// OPTIONAL_CONVERSION_IN_ASSIGNMENT-DAG: Literal[Integer]/None/TypeRelation[Convertible]: 0[#Int#]; name=0
13541354
// OPTIONAL_CONVERSION_IN_ASSIGNMENT: End completions
13551355
}
@@ -1359,7 +1359,7 @@ func testAnyConversionInDestOfAssignment(_ message: String) {
13591359
userInfo[#^ANY_CONVERSION_IN_ASSIGNMENT^#] = message
13601360
// ANY_CONVERSION_IN_ASSIGNMENT: Begin completions
13611361
// ANY_CONVERSION_IN_ASSIGNMENT-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<[String : Any], Value>#}[']'][#Value#]; name=keyPath:
1362-
// ANY_CONVERSION_IN_ASSIGNMENT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem/TypeRelation[Convertible]: ['[']{#(key): String#}, {#default: Any#}[']'][#@lvalue Any#]; name=:default:
1362+
// ANY_CONVERSION_IN_ASSIGNMENT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/IsSystem: ['[']{#(key): String#}, {#default: Any#}[']'][#@lvalue Any#]; name=:default:
13631363
// ANY_CONVERSION_IN_ASSIGNMENT-DAG: Decl[LocalVar]/Local: userInfo[#[String : Any]#]; name=userInfo
13641364
// ANY_CONVERSION_IN_ASSIGNMENT-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: message[#String#]; name=message
13651365
// ANY_CONVERSION_IN_ASSIGNMENT: End completions
@@ -1427,18 +1427,19 @@ struct NestedCallsWithoutClosingParen {
14271427
func testInDictionaryLiteral() {
14281428
let a = 1
14291429
let b = 2
1430-
_ = [a: foo(#^IN_DICTIONARY_LITERAL?check=NESTED_CALL_AT_START^#, b: 1]
1430+
_ = [a: foo(#^IN_DICTIONARY_LITERAL?check=NESTED_CALL_WITHOUT_TYPE_RELATION^#, b: 1]
1431+
// NESTED_CALL_WITHOUT_TYPE_RELATION-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#arg: Int#}, {#arg2: Int#}[')'][#Int#];
14311432
}
14321433

14331434
func testInArrayLiteral() {
1434-
_ = [foo(#^IN_ARRAY_LITERAL?check=NESTED_CALL_AT_START^#, 1]
1435+
_ = [foo(#^IN_ARRAY_LITERAL?check=NESTED_CALL_WITHOUT_TYPE_RELATION^#, 1]
14351436
}
14361437

14371438
func testInParen() {
1438-
_ = (foo(#^IN_PAREN?check=NESTED_CALL_AT_START^#)
1439+
_ = (foo(#^IN_PAREN?check=NESTED_CALL_WITHOUT_TYPE_RELATION^#)
14391440
}
14401441

14411442
func testInTuple() {
1442-
_ = (foo(#^IN_TUPLE?check=NESTED_CALL_AT_START^#, 1)
1443+
_ = (foo(#^IN_TUPLE?check=NESTED_CALL_WITHOUT_TYPE_RELATION^#, 1)
14431444
}
14441445
}

test/IDE/complete_dont_filter_overloads_with_cc_token.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func test(text: String) {
1818
}
1919

2020
// COMPLETE: Begin completions
21-
// COMPLETE-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#verbatim: String#}[')'][#MyText#];
22-
// COMPLETE-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#(content): StringProtocol#}[')'][#MyText#];
21+
// COMPLETE-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#verbatim: String#}[')'][#MyText#];
22+
// COMPLETE-DAG: Decl[Constructor]/CurrNominal/Flair[ArgLabels]: ['(']{#(content): StringProtocol#}[')'][#MyText#];
2323
// COMPLETE-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: text[#String#];
2424
// COMPLETE: End completions

test/IDE/complete_enum_unresolved_dot_argument_labels.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ func testInit() {
1010
var state = DragState.inactive
1111
state = .dragging(#^SIGNATURE^#)
1212
// SIGNATURE: Begin completions, 1 item
13-
// SIGNATURE: Pattern/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#translationX: Int#}, {#translationY: Int#}[')'][#DragState#];
13+
// SIGNATURE: Pattern/CurrNominal/Flair[ArgLabels]: ['(']{#translationX: Int#}, {#translationY: Int#}[')'][#DragState#];
1414

1515
state = .dragging(translationX: 2, #^ARGUMENT^#)
1616
// ARGUMENT: Begin completions, 1 item
1717
// ARGUMENT: Pattern/Local/Flair[ArgLabels]: {#translationY: Int#}[#Int#];
1818

1919
state = .defaulted(#^DEFAULTED^#)
2020
// DEFAULTED: Begin completions, 1 items
21-
// DEFAULTED: Pattern/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#x: Int#}, {#y: Int#}, {#z: Int#}, {#extra: Int#}[')'][#DragState#];
21+
// DEFAULTED: Pattern/CurrNominal/Flair[ArgLabels]: ['(']{#x: Int#}, {#y: Int#}, {#z: Int#}, {#extra: Int#}[')'][#DragState#];
2222

2323
state = .defaulted(x: 1, #^DEFAULTEDARG^#)
2424
state = .defaulted(x: "Wrong type", #^ERRORTYPE?check=DEFAULTEDARG^#)

test/IDE/complete_in_result_builder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func testCompleteFunctionArgumentLabels() {
134134
@TupleBuilder<String> var x1 {
135135
StringFactory.makeString(#^FUNCTION_ARGUMENT_LABEL^#)
136136
// FUNCTION_ARGUMENT_LABEL: Begin completions, 1 item
137-
// FUNCTION_ARGUMENT_LABEL: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#x: String#}[')'][#String#];
137+
// FUNCTION_ARGUMENT_LABEL: Decl[StaticMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#x: String#}[')'][#String#];
138138
}
139139
}
140140

test/IDE/complete_subscript.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,5 @@ func testSettableSub(x: inout HasSettableSub) {
150150
x[#^SETTABLE_SUBSCRIPT^#] = 32
151151
}
152152
// SETTABLE_SUBSCRIPT-DAG: Pattern/CurrNominal/Flair[ArgLabels]: ['[']{#keyPath: KeyPath<HasSettableSub, Value>#}[']'][#Value#];
153-
// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['[']{#(a): String#}[']'][#@lvalue Int#];
153+
// SETTABLE_SUBSCRIPT-DAG: Decl[Subscript]/CurrNominal/Flair[ArgLabels]: ['[']{#(a): String#}[']'][#@lvalue Int#];
154154
// SETTABLE_SUBSCRIPT-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: local[#String#]; name=local
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %batch-code-completion
2+
3+
func foo(_ x: Int) {}
4+
5+
struct Bar {
6+
func bar(withString: String) -> String {}
7+
func bar(withInt: Int) -> Int {}
8+
}
9+
10+
func test() {
11+
foo(Bar().bar(#^COMPLETE^#))
12+
}
13+
// Ensure that we don't report the call pattern for `bar` as `Convertible`
14+
// COMPLETE-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]: ['(']{#withString: String#}[')'][#String#]; name=withString:
15+
// COMPLETE-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#withInt: Int#}[')'][#Int#]; name=withInt:

validation-test/IDE/issues_fixed/issue-57149.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ struct ContentView: View {
2727
}
2828
}
2929

30-
// COMPLETE: Decl[InstanceMethod]/Super/Flair[ArgLabels]/TypeRelation[Convertible]: ['(']{#foo: Int#}[')'][#Never#]; name=foo:
30+
// COMPLETE: Decl[InstanceMethod]/Super/Flair[ArgLabels]: ['(']{#foo: Int#}[')'][#Never#]; name=foo:

0 commit comments

Comments
 (0)