Skip to content

Commit 6c6d3c9

Browse files
committed
[CS] Add special case to preserve compatibility for rdar://84279742
When ranking constructor parameter lists, we compose them as tuples or parens, and check if they are subtypes or unlabeled versions of each other. Previously this was done with the parameter flags intact, but recently I changed the logic to explicitly strip parameter flags in preparation for no longer storing the flags on these types. This caused a slight behavior change, as it turns out we have a special case in `TupleType::get` that allows an unlabeled single parameter to be composed as a tuple type if its variadic bit is set. With the parameter flags now stripped, we produce a paren type. This means that when comparing the parameter lists e.g `(x: Int...)` and `(Int...)`, instead of comparing two tuple types end up comparing a tuple with a paren and fail. To preserve the old behavior, implement a special case for when we have an unlabeled and labeled variadic comparison for a single parameter. In this case, add the parameter types directly to the type diff, and track which one had the label. The ranking logic can then use this to prefer the unlabeled variant. This is only needed in the single parameter case, as other cases will compare as tuples the same as before. In cases where variadics aren't used, we may end up trying to compare parens with tuples, but that's consistent with what we previously did. rdar://84279742
1 parent 9415bec commit 6c6d3c9

File tree

3 files changed

+150
-14
lines changed

3 files changed

+150
-14
lines changed

lib/Sema/CSRanking.cpp

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -766,10 +766,32 @@ static void addKeyPathDynamicMemberOverloads(
766766
}
767767
}
768768

769+
namespace {
770+
/// A set of type variable bindings to compare for ranking.
771+
struct TypeBindingsToCompare {
772+
Type Type1;
773+
Type Type2;
774+
775+
// These bits are used in the case where we need to compare a lone unlabeled
776+
// parameter with a labeled parameter, and allow us to prefer the unlabeled
777+
// one.
778+
bool Type1WasLabeled = false;
779+
bool Type2WasLabeled = false;
780+
781+
TypeBindingsToCompare(Type type1, Type type2)
782+
: Type1(type1), Type2(type2) {}
783+
784+
/// Whether the type bindings to compare are known to be the same.
785+
bool areSameTypes() const {
786+
return !Type1WasLabeled && !Type2WasLabeled && Type1->isEqual(Type2);
787+
}
788+
};
789+
}; // end anonymous namespace
790+
769791
/// Given the bound types of two constructor overloads, returns their parameter
770792
/// list types as tuples to compare for solution ranking, or \c None if they
771793
/// shouldn't be compared.
772-
static Optional<std::pair<Type, Type>>
794+
static Optional<TypeBindingsToCompare>
773795
getConstructorParamsAsTuples(ASTContext &ctx, Type boundTy1, Type boundTy2) {
774796
auto choiceTy1 =
775797
boundTy1->lookThroughAllOptionalTypes()->getAs<FunctionType>();
@@ -793,11 +815,43 @@ getConstructorParamsAsTuples(ASTContext &ctx, Type boundTy1, Type boundTy2) {
793815
if (initParams1[idx].isVariadic() != initParams2[idx].isVariadic())
794816
return None;
795817
}
818+
819+
// Awful hack needed to preserve source compatibility: If we have single
820+
// variadic parameters to compare, where one has a label and the other does
821+
// not, e.g (x: Int...) and (Int...), compare the parameter types by
822+
// themselves, and make a note of which one has the label.
823+
//
824+
// This is needed because previously we would build a TupleType for a single
825+
// unlabeled variadic parameter (Int...), which would let us compare it with
826+
// a labeled parameter (x: Int...) and prefer the unlabeled version. With the
827+
// parameter flags stripped however, (Int...) would become a paren type,
828+
// which we wouldn't compare with the tuple type (x: Int...). To preserve the
829+
// previous behavior in this case, just do a type comparison for the param
830+
// types, and record where we stripped a label. The ranking logic can then use
831+
// this to prefer the unlabeled variant. This is only needed in the single
832+
// parameter case, as other cases will compare as tuples the same as before.
833+
// In cases where variadics aren't used, we may end up trying to compare
834+
// parens with tuples, but that's consistent with what we previously did.
835+
//
836+
// Note we can just do checks on initParams1, as we've already established
837+
// sizes and variadic bits are consistent.
838+
if (initParams1.size() == 1 && initParams1[0].isVariadic() &&
839+
initParams1[0].hasLabel() != initParams2[0].hasLabel()) {
840+
TypeBindingsToCompare bindings(initParams1[0].getParameterType(),
841+
initParams2[0].getParameterType());
842+
if (initParams1[0].hasLabel()) {
843+
bindings.Type1WasLabeled = true;
844+
} else {
845+
bindings.Type2WasLabeled = true;
846+
}
847+
return bindings;
848+
}
849+
796850
auto tuple1 = AnyFunctionType::composeTuple(ctx, initParams1,
797851
/*wantParamFlags*/ false);
798852
auto tuple2 = AnyFunctionType::composeTuple(ctx, initParams2,
799853
/*wantParamFlags*/ false);
800-
return std::make_pair(tuple1, tuple2);
854+
return TypeBindingsToCompare(tuple1, tuple2);
801855
}
802856

803857
SolutionCompareResult ConstraintSystem::compareSolutions(
@@ -1154,7 +1208,7 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
11541208
}
11551209

11561210
// Compare the type variable bindings.
1157-
llvm::DenseMap<TypeVariableType *, std::pair<Type, Type>> typeDiff;
1211+
llvm::DenseMap<TypeVariableType *, TypeBindingsToCompare> typeDiff;
11581212

11591213
const auto &bindings1 = solutions[idx1].typeBindings;
11601214
const auto &bindings2 = solutions[idx2].typeBindings;
@@ -1185,8 +1239,7 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
11851239
if (binding2 == bindings2.end())
11861240
continue;
11871241

1188-
auto concreteType1 = binding1.second;
1189-
auto concreteType2 = binding2->second;
1242+
TypeBindingsToCompare typesToCompare(binding1.second, binding2->second);
11901243

11911244
// For short-form and self-delegating init calls, we want to prefer
11921245
// parameter lists with subtypes over supertypes. To do this, compose tuples
@@ -1197,20 +1250,21 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
11971250
// have some ranking and subtyping rules specific to tuples that we may need
11981251
// to preserve to avoid breaking source.
11991252
if (isShortFormOrSelfDelegatingConstructorBinding) {
1200-
auto diffs = getConstructorParamsAsTuples(cs.getASTContext(),
1201-
concreteType1, concreteType2);
1253+
auto diffs = getConstructorParamsAsTuples(
1254+
cs.getASTContext(), typesToCompare.Type1, typesToCompare.Type2);
12021255
if (!diffs)
12031256
continue;
1204-
std::tie(concreteType1, concreteType2) = *diffs;
1257+
typesToCompare = *diffs;
12051258
}
12061259

1207-
if (!concreteType1->isEqual(concreteType2))
1208-
typeDiff.insert({typeVar, {concreteType1, concreteType2}});
1260+
if (!typesToCompare.areSameTypes())
1261+
typeDiff.insert({typeVar, typesToCompare});
12091262
}
12101263

12111264
for (auto &binding : typeDiff) {
1212-
auto type1 = binding.second.first;
1213-
auto type2 = binding.second.second;
1265+
auto types = binding.second;
1266+
auto type1 = types.Type1;
1267+
auto type2 = types.Type2;
12141268

12151269
// If either of the types still contains type variables, we can't
12161270
// compare them.
@@ -1251,11 +1305,11 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
12511305
auto unlabeled1 = getUnlabeledType(type1, cs.getASTContext());
12521306
auto unlabeled2 = getUnlabeledType(type2, cs.getASTContext());
12531307
if (unlabeled1->isEqual(unlabeled2)) {
1254-
if (type1->isEqual(unlabeled1)) {
1308+
if (type1->isEqual(unlabeled1) && !types.Type1WasLabeled) {
12551309
++score1;
12561310
continue;
12571311
}
1258-
if (type2->isEqual(unlabeled2)) {
1312+
if (type2->isEqual(unlabeled2) && !types.Type2WasLabeled) {
12591313
++score2;
12601314
continue;
12611315
}

test/Constraints/ranking.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,55 @@ struct S4 {
343343
}
344344
}
345345

346+
// rdar://84279742 – Make sure we prefer the unlabeled init here.
347+
struct S5 {
348+
init(x: Int...) {}
349+
init(_ x: Int...) {}
350+
351+
// CHECK-LABEL: sil hidden [ossa] @$s7ranking2S5V15testInitRankingyyF
352+
func testInitRanking() {
353+
// CHECK: function_ref @$s7ranking2S5VyACSid_tcfC : $@convention(method) (@owned Array<Int>, @thin S5.Type) -> S5
354+
_ = S5()
355+
}
356+
}
357+
358+
// We should also prefer the unlabeled case here.
359+
struct S6 {
360+
init(_: Int = 0, x: Int...) {}
361+
init(_: Int = 0, _: Int...) {}
362+
363+
// CHECK-LABEL: sil hidden [ossa] @$s7ranking2S6V15testInitRankingyyF
364+
func testInitRanking() {
365+
// CHECK: function_ref @$s7ranking2S6VyACSi_SidtcfC : $@convention(method) (Int, @owned Array<Int>, @thin S6.Type) -> S6
366+
_ = S6()
367+
}
368+
}
369+
370+
// However subtyping rules take precedence over labeling rules, so we should
371+
// prefer the labeled init here.
372+
struct S7 {
373+
init(x: Int...) {}
374+
init(_: Int?...) {}
375+
376+
// CHECK-LABEL: sil hidden [ossa] @$s7ranking2S7V15testInitRankingyyF
377+
func testInitRanking() {
378+
// CHECK: function_ref @$s7ranking2S7V1xACSid_tcfC : $@convention(method) (@owned Array<Int>, @thin S7.Type) -> S7
379+
_ = S7()
380+
}
381+
}
382+
383+
// Subtyping rules also let us prefer the Int... init here.
384+
struct S8 {
385+
init(_: Int?...) {}
386+
init(_: Int...) {}
387+
388+
// CHECK-LABEL: sil hidden [ossa] @$s7ranking2S8V15testInitRankingyyF
389+
func testInitRanking() {
390+
// CHECK: function_ref @$s7ranking2S8VyACSid_tcfC : $@convention(method) (@owned Array<Int>, @thin S8.Type) -> S8
391+
_ = S8()
392+
}
393+
}
394+
346395
//--------------------------------------------------------------------
347396
// Pointer conversions
348397
//--------------------------------------------------------------------

test/Constraints/ranking_ambiguities.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,36 @@ struct S1 {
2525
_ = S1.init() // expected-error {{ambiguous use of 'init'}}
2626
}
2727
}
28+
29+
// Ambiguous because we don't prefer one label over the other.
30+
struct S2 {
31+
init(x: Int...) {} // expected-note {{found this candidate}}
32+
init(y: Int...) {} // expected-note {{found this candidate}}
33+
34+
func testInitRanking() {
35+
_ = S2() // expected-error {{ambiguous use of 'init'}}
36+
}
37+
}
38+
39+
// Ambiguous because we don't apply the prefer-unlabeled rule if the types
40+
// aren't compatible.
41+
struct S3 {
42+
init(x: Int...) {} // expected-note {{found this candidate}}
43+
init(_: String...) {} // expected-note {{found this candidate}}
44+
45+
func testInitRanking() {
46+
_ = S3() // expected-error {{ambiguous use of 'init'}}
47+
}
48+
}
49+
50+
// Ambiguous because this ends up being a comparison between a paren and tuple
51+
// parameter list, and we don't have a special case for it. Ideally we would
52+
// align this behavior with the variadic behavior.
53+
struct S4 {
54+
init(x: Int = 0) {} // expected-note {{found this candidate}}
55+
init(_: Int = 0) {} // expected-note {{found this candidate}}
56+
57+
func testInitRanking() {
58+
_ = S4() // expected-error {{ambiguous use of 'init'}}
59+
}
60+
}

0 commit comments

Comments
 (0)