Skip to content

Commit cdcaa11

Browse files
author
Nathan Hawes
committed
[CodeCompletion][Sema] Refine logic that ignores missing arguments after the code completion position when solving
If the completion loc was in a trailing closure arg, it only makes sense to ignore later missing args if they have function type, as there's no way for the user to complete the call for overload being matched otherwise. Also fix a bug where we were incorrectly penalizing solutions with holes that were ultimately due to missing arguments after the completion position. Resolves rdar://problem/72412302
1 parent 77d828d commit cdcaa11

File tree

4 files changed

+133
-40
lines changed

4 files changed

+133
-40
lines changed

include/swift/Sema/ConstraintLocator.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,14 @@ class LocatorPathElt::ApplyArgToParam final : public StoredIntegerElement<3> {
511511
}
512512
};
513513

514-
class LocatorPathElt::SynthesizedArgument final : public StoredIntegerElement<1> {
514+
class LocatorPathElt::SynthesizedArgument final : public StoredIntegerElement<2> {
515515
public:
516-
SynthesizedArgument(unsigned index)
517-
: StoredIntegerElement(ConstraintLocator::SynthesizedArgument, index) {}
516+
SynthesizedArgument(unsigned index, bool afterCodeCompletionLoc = false)
517+
: StoredIntegerElement(ConstraintLocator::SynthesizedArgument, index,
518+
afterCodeCompletionLoc) {}
518519

519-
unsigned getIndex() const { return getValue(); }
520+
unsigned getIndex() const { return getValue<0>(); }
521+
bool isAfterCodeCompletionLoc() const { return getValue<1>(); }
520522

521523
static bool classof(const LocatorPathElt *elt) {
522524
return elt->getKind() == ConstraintLocator::SynthesizedArgument;

lib/Sema/CSBindings.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,20 +1652,32 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const {
16521652

16531653
cs.addConstraint(ConstraintKind::Bind, TypeVar, type, srcLocator);
16541654

1655+
auto reportHole = [&]() {
1656+
if (cs.isForCodeCompletion()) {
1657+
// Don't penalize solutions with unresolved generics.
1658+
if (TypeVar->getImpl().getGenericParameter())
1659+
return false;
1660+
// Don't penalize solutions with holes due to missing arguments after the
1661+
// code completion position.
1662+
auto argLoc = srcLocator->findLast<LocatorPathElt::SynthesizedArgument>();
1663+
if (argLoc && argLoc->isAfterCodeCompletionLoc())
1664+
return false;
1665+
}
1666+
cs.increaseScore(SK_Hole);
1667+
1668+
if (auto fix = fixForHole(cs)) {
1669+
if (cs.recordFix(/*fix=*/fix->first, /*impact=*/fix->second))
1670+
return true;
1671+
}
1672+
return false;
1673+
};
1674+
16551675
// If this was from a defaultable binding note that.
16561676
if (Binding.isDefaultableBinding()) {
16571677
cs.DefaultedConstraints.push_back(srcLocator);
16581678

1659-
if (type->isHole()) {
1660-
// Reflect in the score that this type variable couldn't be
1661-
// resolved and had to be bound to a placeholder "hole" type.
1662-
cs.increaseScore(SK_Hole);
1663-
1664-
if (auto fix = fixForHole(cs)) {
1665-
if (cs.recordFix(/*fix=*/fix->first, /*impact=*/fix->second))
1666-
return true;
1667-
}
1668-
}
1679+
if (type->isHole() && reportHole())
1680+
return true;
16691681
}
16701682

16711683
return !cs.failedConstraint && !cs.simplify();

lib/Sema/CSSimplify.cpp

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -986,8 +986,35 @@ constraints::matchCallArguments(
986986
};
987987
}
988988

989-
static Optional<unsigned>
990-
getCompletionArgIndex(ASTNode anchor, ConstraintSystem &CS) {
989+
struct CompletionArgInfo {
990+
unsigned completionIdx;
991+
Optional<unsigned> firstTrailingIdx;
992+
993+
bool isAllowableMissingArg(unsigned argInsertIdx,
994+
AnyFunctionType::Param param) {
995+
// If the argument is before or at the index of the argument containing the
996+
// completion, the user would likely have already written it if they
997+
// intended this overload.
998+
if (completionIdx >= argInsertIdx)
999+
return false;
1000+
1001+
// If the argument is after the first trailing closure, the user can only
1002+
// continue on to write more trailing arguments, so only allow this overload
1003+
// if the missing argument is of function type.
1004+
if (firstTrailingIdx && argInsertIdx > *firstTrailingIdx) {
1005+
if (param.isInOut())
1006+
return false;
1007+
1008+
Type expectedTy = param.getPlainType()->lookThroughAllOptionalTypes();
1009+
return expectedTy->is<FunctionType>() || expectedTy->isAny() ||
1010+
expectedTy->isTypeVariableOrMember();
1011+
}
1012+
return true;
1013+
}
1014+
};
1015+
1016+
static Optional<CompletionArgInfo>
1017+
getCompletionArgInfo(ASTNode anchor, ConstraintSystem &CS) {
9911018
Expr *arg = nullptr;
9921019
if (auto *CE = getAsExpr<CallExpr>(anchor))
9931020
arg = CE->getArg();
@@ -999,16 +1026,15 @@ getCompletionArgIndex(ASTNode anchor, ConstraintSystem &CS) {
9991026
if (!arg)
10001027
return None;
10011028

1002-
if (auto *TE = dyn_cast<TupleExpr>(arg)) {
1003-
auto elems = TE->getElements();
1004-
auto idx = llvm::find_if(elems, [&](Expr *elem) {
1005-
return CS.containsCodeCompletionLoc(elem);
1006-
});
1007-
if (idx != elems.end())
1008-
return std::distance(elems.begin(), idx);
1009-
} else if (auto *PE = dyn_cast<ParenExpr>(arg)) {
1029+
auto trailingIdx = arg->getUnlabeledTrailingClosureIndexOfPackedArgument();
1030+
if (auto *PE = dyn_cast<ParenExpr>(arg)) {
10101031
if (CS.containsCodeCompletionLoc(PE->getSubExpr()))
1011-
return 0;
1032+
return CompletionArgInfo{ 0, trailingIdx };
1033+
} else if (auto *TE = dyn_cast<TupleExpr>(arg)) {
1034+
for (unsigned i: indices(TE->getElements())) {
1035+
if (CS.containsCodeCompletionLoc(TE->getElement(i)))
1036+
return CompletionArgInfo{ i, trailingIdx };
1037+
}
10121038
}
10131039
return None;
10141040
}
@@ -1021,7 +1047,7 @@ class ArgumentFailureTracker : public MatchCallArgumentListener {
10211047

10221048
SmallVector<SynthesizedArg, 4> MissingArguments;
10231049
SmallVector<std::pair<unsigned, AnyFunctionType::Param>, 4> ExtraArguments;
1024-
Optional<unsigned> CompletionArgIdx;
1050+
Optional<CompletionArgInfo> CompletionArgInfo;
10251051

10261052
public:
10271053
ArgumentFailureTracker(ConstraintSystem &cs,
@@ -1048,10 +1074,19 @@ class ArgumentFailureTracker : public MatchCallArgumentListener {
10481074
const auto &param = Parameters[paramIdx];
10491075

10501076
unsigned newArgIdx = Arguments.size();
1077+
1078+
bool isAfterCodeCompletionLoc = false;
1079+
if (CS.isForCodeCompletion()) {
1080+
if (!CompletionArgInfo)
1081+
CompletionArgInfo = getCompletionArgInfo(Locator.getAnchor(), CS);
1082+
isAfterCodeCompletionLoc = CompletionArgInfo &&
1083+
CompletionArgInfo->isAllowableMissingArg(argInsertIdx, param);
1084+
}
1085+
10511086
auto *argLoc = CS.getConstraintLocator(
10521087
Locator, {LocatorPathElt::ApplyArgToParam(newArgIdx, paramIdx,
10531088
param.getParameterFlags()),
1054-
LocatorPathElt::SynthesizedArgument(newArgIdx)});
1089+
LocatorPathElt::SynthesizedArgument(newArgIdx, isAfterCodeCompletionLoc)});
10551090

10561091
auto *argType =
10571092
CS.createTypeVariable(argLoc, TVO_CanBindToInOut | TVO_CanBindToLValue |
@@ -1060,16 +1095,12 @@ class ArgumentFailureTracker : public MatchCallArgumentListener {
10601095
auto synthesizedArg = param.withType(argType);
10611096
Arguments.push_back(synthesizedArg);
10621097

1063-
if (CS.isForCodeCompletion()) {
1064-
// When solving for code completion, if any argument contains the
1065-
// completion location, later arguments shouldn't be considered missing
1066-
// (causing the solution to have a worse score) as the user just hasn't
1067-
// written them yet. Early exit to avoid recording them in this case.
1068-
if (!CompletionArgIdx)
1069-
CompletionArgIdx = getCompletionArgIndex(Locator.getAnchor(), CS);
1070-
if (CompletionArgIdx && *CompletionArgIdx < argInsertIdx)
1071-
return newArgIdx;
1072-
}
1098+
// When solving for code completion, if any argument contains the
1099+
// completion location, later arguments shouldn't be considered missing
1100+
// (causing the solution to have a worse score) as the user just hasn't
1101+
// written them yet. Early exit to avoid recording them in this case.
1102+
if (isAfterCodeCompletionLoc)
1103+
return newArgIdx;
10731104

10741105
MissingArguments.push_back(SynthesizedArg{paramIdx, synthesizedArg});
10751106

test/IDE/complete_ambiguous.swift

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=REGULAR_MULTICLOSURE_APPLIED | %FileCheck %s --check-prefix=POINT_MEMBER
4949
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=BEST_SOLUTION_FILTER | %FileCheck %s --check-prefix=BEST_SOLUTION_FILTER
5050
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=BEST_SOLUTION_FILTER2 | %FileCheck %s --check-prefix=BEST_SOLUTION_FILTER
51+
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=MISSINGARG_INLINE | %FileCheck %s --check-prefix=MISSINGARG_INLINE
52+
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=MISSINGARG_TRAILING | %FileCheck %s --check-prefix=MISSINGARG_TRAILING
5153

5254

5355
struct A {
@@ -313,9 +315,55 @@ func testMissingArgs() {
313315

314316
test3(after: Test.#^OVERLOADEDFUNC_MISSINGARG_BEFORE^#);
315317
test4(both: Test.#^OVERLOADEDFUNC_MISSINGARG_BEFOREANDAFTER^#)
316-
}
317-
318318

319+
enum Bop { case bop }
320+
enum Bix { case bix }
321+
enum Blu { case blu }
322+
enum Baz { case baz }
323+
enum Boy { case boy }
324+
325+
func trailing(x: Int, _ y: () -> Foo, z: () -> ()) {}
326+
func trailing(x: Int, _ y: () -> Bar, z: (() -> ())?) {}
327+
func trailing(x: Int, _ y: () -> Bop, z: Any) {}
328+
func trailing<T>(x: Int, _ y: () -> Bix, z: T) {}
329+
func trailing<T>(x: Int, _ y: () -> Boy, z: T?) {}
330+
func trailing<T>(x: Int, _ y: () -> Blu, z: [T]?) {}
331+
func trailing(x: Int, _ y: () -> Blu, z: inout Any) {}
332+
func trailing(x: Int, _ y: () -> Baz, z: Int) {}
333+
334+
trailing(x: 2, { .#^MISSINGARG_INLINE^# })
335+
trailing(x: 2) { .#^MISSINGARG_TRAILING^# }
336+
337+
// MISSINGARG_INLINE: Begin completions, 14 items
338+
// MISSINGARG_INLINE-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: foo[#Foo#]; name=foo
339+
// MISSINGARG_INLINE-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Foo#})[#(into: inout Hasher) -> Void#]; name=hash(self: Foo)
340+
// MISSINGARG_INLINE-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: bar[#Bar#]; name=bar
341+
// MISSINGARG_INLINE-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Bar#})[#(into: inout Hasher) -> Void#]; name=hash(self: Bar)
342+
// MISSINGARG_INLINE-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: bop[#Bop#]; name=bop
343+
// MISSINGARG_INLINE-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Bop#})[#(into: inout Hasher) -> Void#]; name=hash(self: Bop)
344+
// MISSINGARG_INLINE-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: bix[#Bix#]; name=bix
345+
// MISSINGARG_INLINE-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Bix#})[#(into: inout Hasher) -> Void#]; name=hash(self: Bix)
346+
// MISSINGARG_INLINE-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: boy[#Boy#]; name=boy
347+
// MISSINGARG_INLINE-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Boy#})[#(into: inout Hasher) -> Void#]; name=hash(self: Boy)
348+
// MISSINGARG_INLINE-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: blu[#Blu#]; name=blu
349+
// MISSINGARG_INLINE-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Blu#})[#(into: inout Hasher) -> Void#]; name=hash(self: Blu)
350+
// MISSINGARG_INLINE-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: baz[#Baz#]; name=baz
351+
// MISSINGARG_INLINE-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Baz#})[#(into: inout Hasher) -> Void#]; name=hash(self: Baz)
352+
// MISSINGARG_INLINE: End completions
353+
354+
// MISSINGARG_TRAILING: Begin completions, 10 items
355+
// MISSINGARG_TRAILING-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: foo[#Foo#]; name=foo
356+
// MISSINGARG_TRAILING-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Foo#})[#(into: inout Hasher) -> Void#]; name=hash(self: Foo)
357+
// MISSINGARG_TRAILING-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: bar[#Bar#]; name=bar
358+
// MISSINGARG_TRAILING-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Bar#})[#(into: inout Hasher) -> Void#]; name=hash(self: Bar)
359+
// MISSINGARG_TRAILING-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: bop[#Bop#]; name=bop
360+
// MISSINGARG_TRAILING-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Bop#})[#(into: inout Hasher) -> Void#]; name=hash(self: Bop)
361+
// MISSINGARG_TRAILING-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: bix[#Bix#]; name=bix
362+
// MISSINGARG_TRAILING-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Bix#})[#(into: inout Hasher) -> Void#]; name=hash(self: Bix)
363+
// MISSINGARG_TRAILING-DAG: Decl[EnumElement]/ExprSpecific/TypeRelation[Identical]: boy[#Boy#]; name=boy
364+
// MISSINGARG_TRAILING-DAG: Decl[InstanceMethod]/CurrNominal: hash({#(self): Boy#})[#(into: inout Hasher) -> Void#]; name=hash(self: Boy)
365+
// MISSINGARG_TRAILING: End completions
366+
}
319367

320368
protocol C {
321369
associatedtype Element
@@ -450,7 +498,7 @@ struct Struct123: Equatable {
450498
func testNoBestSolutionFilter() {
451499
let a = Struct123();
452500
let b = [Struct123]().first(where: { $0 == a && 1 + 90 * 5 / 8 == 45 * -10 })?.structMem != .#^BEST_SOLUTION_FILTER^#
453-
let c = min(10.3, 10 / 10.4) < 6 + 5 / (10 - 3) ? true : Optional(a)?.structMem != .#^BEST_SOLUTION_FILTER2^#
501+
let c = min(10.3, 10 / 10.4) < 6 / 7 ? true : Optional(a)?.structMem != .#^BEST_SOLUTION_FILTER2^#
454502
}
455503

456504
// BEST_SOLUTION_FILTER: Begin completions

0 commit comments

Comments
 (0)