Skip to content

Commit 22e12e8

Browse files
committed
[ConstraintSystem] Switch to pair-wise type binding differentiation for ranking
Instead of trying to hold a "global" set of type variable differences let's use pair-wise comparison instead because in presence of generic overloads such would be more precise. If there are N solutions for a single generic overload we currently relied on "local" comparison to detect the difference, but it's not always possible to split the system to do one. Which means higher level comparisons have to account for "local" (per overload choice) differences as well otherwise ranking would loose precision.
1 parent af82e6f commit 22e12e8

File tree

3 files changed

+53
-69
lines changed

3 files changed

+53
-69
lines changed

lib/Sema/CSRanking.cpp

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,19 +1040,37 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
10401040
}
10411041

10421042
// Compare the type variable bindings.
1043-
for (auto &binding : diff.typeBindings) {
1043+
llvm::DenseMap<TypeVariableType *, std::pair<Type, Type>> typeDiff;
1044+
1045+
const auto &bindings1 = solutions[idx1].typeBindings;
1046+
const auto &bindings2 = solutions[idx2].typeBindings;
1047+
1048+
for (const auto &binding1 : bindings1) {
1049+
auto *typeVar = binding1.first;
1050+
10441051
// If the type variable isn't one for which we should be looking at the
10451052
// bindings, don't.
1046-
if (!binding.typeVar->getImpl().prefersSubtypeBinding())
1053+
if (!typeVar->getImpl().prefersSubtypeBinding())
10471054
continue;
10481055

1049-
auto type1 = binding.bindings[idx1];
1050-
auto type2 = binding.bindings[idx2];
1051-
1052-
// If the types are equivalent, there's nothing more to do.
1053-
if (type1->isEqual(type2))
1056+
// If both solutions have a binding for this type variable
1057+
// let's consider it.
1058+
auto binding2 = bindings2.find(typeVar);
1059+
if (binding2 == bindings2.end())
10541060
continue;
1055-
1061+
1062+
auto concreteType1 = binding1.second;
1063+
auto concreteType2 = binding2->second;
1064+
1065+
if (!concreteType1->isEqual(concreteType2)) {
1066+
typeDiff.insert({typeVar, {concreteType1, concreteType2}});
1067+
}
1068+
}
1069+
1070+
for (auto &binding : typeDiff) {
1071+
auto type1 = binding.second.first;
1072+
auto type2 = binding.second.second;
1073+
10561074
// If either of the types still contains type variables, we can't
10571075
// compare them.
10581076
// FIXME: This is really unfortunate. More type variable sharing
@@ -1128,7 +1146,7 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
11281146
}
11291147
}
11301148
}
1131-
1149+
11321150
// All other things considered equal, if any overload choice is more
11331151
// more constrained than the other, increment the score.
11341152
if (score1 == score2) {
@@ -1315,12 +1333,6 @@ SolutionDiff::SolutionDiff(ArrayRef<Solution> solutions) {
13151333
if (solutions.size() <= 1)
13161334
return;
13171335

1318-
// Populate the type bindings with the first solution.
1319-
llvm::DenseMap<TypeVariableType *, SmallVector<Type, 2>> typeBindings;
1320-
for (auto binding : solutions[0].typeBindings) {
1321-
typeBindings[binding.first].push_back(binding.second);
1322-
}
1323-
13241336
// Populate the overload choices with the first solution.
13251337
llvm::DenseMap<ConstraintLocator *, SmallVector<OverloadChoice, 2>>
13261338
overloadChoices;
@@ -1331,27 +1343,6 @@ SolutionDiff::SolutionDiff(ArrayRef<Solution> solutions) {
13311343
// Find the type variables and overload locators common to all of the
13321344
// solutions.
13331345
for (auto &solution : solutions.slice(1)) {
1334-
// For each type variable bound in all of the previous solutions, check
1335-
// whether we have a binding for this type variable in this solution.
1336-
SmallVector<TypeVariableType *, 4> removeTypeBindings;
1337-
for (auto &binding : typeBindings) {
1338-
auto known = solution.typeBindings.find(binding.first);
1339-
if (known == solution.typeBindings.end()) {
1340-
removeTypeBindings.push_back(binding.first);
1341-
continue;
1342-
}
1343-
1344-
// Add this solution's binding to the results.
1345-
binding.second.push_back(known->second);
1346-
}
1347-
1348-
// Remove those type variables for which this solution did not have a
1349-
// binding.
1350-
for (auto typeVar : removeTypeBindings) {
1351-
typeBindings.erase(typeVar);
1352-
}
1353-
removeTypeBindings.clear();
1354-
13551346
// For each overload locator for which we have an overload choice in
13561347
// all of the previous solutions. Check whether we have an overload choice
13571348
// in this solution.
@@ -1374,26 +1365,6 @@ SolutionDiff::SolutionDiff(ArrayRef<Solution> solutions) {
13741365
}
13751366
}
13761367

1377-
// Look through the type variables that have bindings in all of the
1378-
// solutions, and add those that have differences to the diff.
1379-
for (auto &binding : typeBindings) {
1380-
Type singleType;
1381-
for (auto type : binding.second) {
1382-
if (!singleType)
1383-
singleType = type;
1384-
else if (!singleType->isEqual(type)) {
1385-
// We have a difference. Add this binding to the diff.
1386-
this->typeBindings.push_back(
1387-
SolutionDiff::TypeBindingDiff{
1388-
binding.first,
1389-
std::move(binding.second)
1390-
});
1391-
1392-
break;
1393-
}
1394-
}
1395-
}
1396-
13971368
for (auto &overloadChoice : overloadChoices) {
13981369
OverloadChoice singleChoice = overloadChoice.second[0];
13991370
for (auto choice : overloadChoice.second) {

lib/Sema/ConstraintSystem.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -936,23 +936,10 @@ class SolutionDiff {
936936
SmallVector<OverloadChoice, 2> choices;
937937
};
938938

939-
/// A difference between two type variable bindings.
940-
struct TypeBindingDiff {
941-
/// The type variable.
942-
TypeVariableType *typeVar;
943-
944-
/// The bindings that each solution made.
945-
SmallVector<Type, 2> bindings;
946-
};
947-
948939
/// The differences between the overload choices between the
949940
/// solutions.
950941
SmallVector<OverloadDiff, 4> overloads;
951942

952-
/// The differences between the type variable bindings of the
953-
/// solutions.
954-
SmallVector<TypeBindingDiff, 4> typeBindings;
955-
956943
/// Compute the differences between the given set of solutions.
957944
///
958945
/// \param solutions The set of solutions.

test/Constraints/closures.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,3 +916,29 @@ func test_trailing_closure_with_defaulted_last() {
916916
foo { 42 } // Ok
917917
foo(fn: { 42 }) // Ok
918918
}
919+
920+
// Test that even in multi-statement closure case we still pick up `(Action) -> Void` over `Optional<(Action) -> Void>`.
921+
// Such behavior used to rely on ranking of partial solutions but with delayed constraint generation of closure bodies
922+
// it's no longer the case, so we need to make sure that even in case of complete solutions we still pick the right type.
923+
924+
protocol Action { }
925+
protocol StateType { }
926+
927+
typealias Fn = (Action) -> Void
928+
typealias Middleware<State> = (@escaping Fn, @escaping () -> State?) -> (@escaping Fn) -> Fn
929+
930+
class Foo<State: StateType> {
931+
var state: State!
932+
var fn: Fn!
933+
934+
init(middleware: [Middleware<State>]) {
935+
self.fn = middleware
936+
.reversed()
937+
.reduce({ action in },
938+
{ (fun, middleware) in // Ok, to type-check result type has to be `(Action) -> Void`
939+
let dispatch: (Action) -> Void = { _ in }
940+
let getState = { [weak self] in self?.state }
941+
return middleware(dispatch, getState)(fun)
942+
})
943+
}
944+
}

0 commit comments

Comments
 (0)