Skip to content

Commit a01692a

Browse files
committed
[CSOptimizer] Skip ?? if it's involved in optional chain with unresolved result type
`??` operator is overloaded on optionality of its result. When the first argument matches exactly, the ranking is going to be skewed towards selecting an overload choice that returns a non-optional type. This is not always correct i.e. when operator is involved in optional chaining. To avoid producing an incorrect favoring, let's skip the this disjunction when constraints associated with result type indicate that it should be optional. Simply adding it as a binding won't work because if the second argument is non-optional the overload that returns `T?` would still have a lower score. Resolves: rdar://164201746
1 parent a604d99 commit a01692a

File tree

2 files changed

+41
-0
lines changed

2 files changed

+41
-0
lines changed

lib/Sema/CSOptimizer.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,14 @@ static bool isStandardInfixLogicalOperator(Constraint *disjunction) {
261261
return false;
262262
}
263263

264+
static bool isNilCoalescingOperator(Constraint *disjunction) {
265+
auto *choice = disjunction->getNestedConstraints()[0];
266+
if (auto *decl = getOverloadChoiceDecl(choice))
267+
return decl->isOperator() &&
268+
decl->getBaseIdentifier().isNilCoalescingOperator();
269+
return false;
270+
}
271+
264272
static bool isArithmeticOperator(ValueDecl *decl) {
265273
return decl->isOperator() && decl->getBaseIdentifier().isArithmeticOperator();
266274
}
@@ -1086,6 +1094,30 @@ static void determineBestChoicesInContext(
10861094
if (auto *typeVar = resultType->getAs<TypeVariableType>()) {
10871095
auto bindingSet = cs.getBindingsFor(typeVar);
10881096

1097+
// `??` operator is overloaded on optionality of its result. When the
1098+
// first argument matches exactly, the ranking is going to be skewed
1099+
// towards selecting an overload choice that returns a non-optional type.
1100+
// This is not always correct i.e. when operator is involved in optional
1101+
// chaining. To avoid producing an incorrect favoring, let's skip the this
1102+
// disjunction when constraints associated with result type indicate
1103+
// that it should be optional.
1104+
//
1105+
// Simply adding it as a binding won't work because if the second argument
1106+
// is non-optional the overload that returns `T?` would still have a lower
1107+
// score.
1108+
if (!bindingSet && isNilCoalescingOperator(disjunction)) {
1109+
auto &cg = cs.getConstraintGraph();
1110+
if (llvm::any_of(cg[typeVar].getConstraints(),
1111+
[&typeVar](Constraint *constraint) {
1112+
if (constraint->getKind() !=
1113+
ConstraintKind::OptionalObject)
1114+
return false;
1115+
return constraint->getFirstType()->isEqual(typeVar);
1116+
})) {
1117+
continue;
1118+
}
1119+
}
1120+
10891121
for (const auto &binding : bindingSet.Bindings) {
10901122
resultTypes.push_back(binding.BindingType);
10911123
}

test/Constraints/nil-coalescing-favoring.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,12 @@ func test_no_incorrect_favoring(v: Int?, o: Int) {
6262
sameType(s1, as: Int?.self)
6363
sameType(s2, as: Int?.self)
6464
}
65+
66+
extension Int {
67+
func test() -> Int { 42 }
68+
}
69+
70+
func test_optional_chaining(v: Int?, defaultV: Int) {
71+
// CHECK: declref_expr type="(consuming Int?, @autoclosure () throws -> Int?) throws -> Int?" {{.*}} decl="Swift.(file).??
72+
_ = (v ?? defaultV)?.test() // Ok (no warnings)
73+
}

0 commit comments

Comments
 (0)