Skip to content

Commit c509c03

Browse files
committed
[CSSimplify] Transfer conformance requirements of a parameter to an argument
Performance optimization to help rule out generic overload choices sooner. This is based on an observation of the constraint solver behavior, consider following example: ```swift func test<U: ExpressibleByStringLiteral>(_: U) {} test(42) ``` Constraint System for call to `test` is going to look like this: ``` $T_test := ($T_U) -> $T_U $T_U <conforms to> ExpressibleByStringLiteral $T_42 <argument conversion> $T_U $T_42 <literal conforms to> ExpressibleByIntegerLiteral ``` Currently to find out that `test` doesn't match, solver would have to first find a binding for `$T_42`, then find a binding for `$T_U` (through `<argument conversion>` constraint) and only later fail while checking conformance of `Int` to `ExpressibleByStringLiteral`. Instead of waiting for parameter to be bound, let's transfer conformance requirements through a conversion constraint directly to an argument type, since it has to conform to the same set of protocols as parameter to be convertible (restrictions apply i.e. protocol composition types). With that change it's possible to verify conformances early and reject `test<U: ExpressibleByStringLiteral>` overload as soon as type of an argument has been determined. This especially powerful for chained calls because solver would only have to check as deep as first invald argument binding.
1 parent 985843a commit c509c03

File tree

4 files changed

+107
-6
lines changed

4 files changed

+107
-6
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,40 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
14331433
assert(!argsWithLabels[argIdx].isAutoClosure() ||
14341434
isSynthesizedArgument(argument));
14351435

1436+
// If parameter is a generic parameter, let's copy its
1437+
// conformance requirements (if any), to the argument
1438+
// be able to filter mismatching choices earlier.
1439+
if (auto *typeVar = paramTy->getAs<TypeVariableType>()) {
1440+
auto *locator = typeVar->getImpl().getLocator();
1441+
if (locator->isForGenericParameter()) {
1442+
auto &CG = cs.getConstraintGraph();
1443+
auto *repr = cs.getRepresentative(typeVar);
1444+
for (auto *constraint : CG[repr].getConstraints()) {
1445+
if (constraint->getKind() != ConstraintKind::ConformsTo)
1446+
continue;
1447+
1448+
// This is not a direct requirement.
1449+
if (!constraint->getFirstType()->isEqual(typeVar))
1450+
continue;
1451+
1452+
// If the composition consists of a class + protocol,
1453+
// we can't attach conformance to the argument because
1454+
// parameter would have to pick one of the components.
1455+
if (argTy.findIf([](Type type) {
1456+
return type->is<ProtocolCompositionType>();
1457+
}))
1458+
continue;
1459+
1460+
auto protocolTy = constraint->getSecondType();
1461+
if (!protocolTy->is<ProtocolType>())
1462+
continue;
1463+
1464+
cs.addConstraint(ConstraintKind::TransitivelyConformsTo, argTy,
1465+
protocolTy, constraint->getLocator());
1466+
}
1467+
}
1468+
}
1469+
14361470
cs.addConstraint(
14371471
subKind, argTy, paramTy,
14381472
matchingAutoClosureResult
@@ -6193,7 +6227,79 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
61936227
ConstraintSystem::SolutionKind ConstraintSystem::simplifyTransitivelyConformsTo(
61946228
Type type, Type protocolTy, ConstraintLocatorBuilder locator,
61956229
TypeMatchOptions flags) {
6196-
return SolutionKind::Error;
6230+
// Since this is a performance optimization, let's ignore it
6231+
// in diagnostic mode.
6232+
if (shouldAttemptFixes())
6233+
return SolutionKind::Solved;
6234+
6235+
auto formUnsolved = [&]() {
6236+
// If we're supposed to generate constraints, do so.
6237+
if (flags.contains(TMF_GenerateConstraints)) {
6238+
auto *conformance =
6239+
Constraint::create(*this, ConstraintKind::TransitivelyConformsTo,
6240+
type, protocolTy, getConstraintLocator(locator));
6241+
6242+
addUnsolvedConstraint(conformance);
6243+
return SolutionKind::Solved;
6244+
}
6245+
6246+
return SolutionKind::Unsolved;
6247+
};
6248+
6249+
auto resolvedTy = getFixedTypeRecursive(type, /*wantRValue=*/true);
6250+
if (resolvedTy->isTypeVariableOrMember())
6251+
return formUnsolved();
6252+
6253+
auto *protocol = protocolTy->castTo<ProtocolType>()->getDecl();
6254+
6255+
// First, let's check whether the type itself conforms,
6256+
// if it does - we are done.
6257+
if (TypeChecker::conformsToProtocol(resolvedTy, protocol, DC))
6258+
return SolutionKind::Solved;
6259+
6260+
// If the type doesn't conform, let's check whether
6261+
// an Optional or Unsafe{Mutable}Pointer from it would.
6262+
6263+
SmallVector<Type, 4> typesToCheck;
6264+
6265+
// Optional<T>
6266+
typesToCheck.push_back(
6267+
OptionalType::get(resolvedTy->getWithoutSpecifierType()));
6268+
6269+
// Unsafe{Mutable}Pointer<T>
6270+
{
6271+
auto &ctx = getASTContext();
6272+
6273+
auto *ptrDecl = ctx.getUnsafePointerDecl();
6274+
6275+
// String -> UnsafePointer<Void>
6276+
if (resolvedTy->isEqual(ctx.getStringDecl()->getDeclaredInterfaceType())) {
6277+
typesToCheck.push_back(BoundGenericType::get(ptrDecl, /*parent=*/Type(),
6278+
{ctx.TheEmptyTupleType}));
6279+
}
6280+
6281+
// Array<T> -> UnsafePointer<T>
6282+
if (auto elt = isArrayType(resolvedTy)) {
6283+
typesToCheck.push_back(
6284+
BoundGenericType::get(ptrDecl, /*parent=*/Type(), {*elt}));
6285+
}
6286+
6287+
// inout argument -> UnsafePointer<T>, UnsafeMutablePointer<T>
6288+
if (type->is<InOutType>()) {
6289+
typesToCheck.push_back(
6290+
BoundGenericType::get(ptrDecl, /*parent=*/Type(), {resolvedTy}));
6291+
typesToCheck.push_back(BoundGenericType::get(
6292+
ctx.getUnsafeMutablePointerDecl(), /*parent=*/Type(), {resolvedTy}));
6293+
}
6294+
}
6295+
6296+
return llvm::any_of(
6297+
typesToCheck,
6298+
[&](Type type) {
6299+
return bool(TypeChecker::conformsToProtocol(type, protocol, DC));
6300+
})
6301+
? SolutionKind::Solved
6302+
: SolutionKind::Error;
61976303
}
61986304

61996305
/// Determine the kind of checked cast to perform from the given type to

validation-test/Sema/type_checker_perf/slow/rdar31439825.swift renamed to validation-test/Sema/type_checker_perf/fast/rdar31439825.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
let a = 1
55

66
_ = -a + -a - -a + -a - -a
7-
// expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}}

validation-test/Sema/type_checker_perf/slow/rdar46713933.swift renamed to validation-test/Sema/type_checker_perf/fast/rdar46713933_concrete_args.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,5 @@ func wrap<T: ExpressibleByFloatLiteral>(_ key: String, _ value: T) -> T { return
77
func wrap<T: ExpressibleByStringLiteral>(_ key: String, _ value: T) -> T { return value }
88

99
func wrapped(i: Int) -> Int {
10-
// FIXME: When this stops being "too complex", turn the integer value into
11-
// an integer literal.
12-
// expected-error@+1{{reasonable time}}
1310
return wrap("1", i) + wrap("1", i) + wrap("1", i) + wrap("1", i)
1411
}

validation-test/Sema/type_checker_perf/slow/fast-operator-typechecking.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,4 @@ func f(tail: UInt64, byteCount: UInt64) {
1919
func size(count: Int) {
2020
// Size of the buffer we need to allocate
2121
let _ = count * MemoryLayout<Float>.size * (4 + 3 + 3 + 2 + 4)
22-
// expected-error@-1 {{the compiler is unable to type-check this expression in reasonable time}}
2322
}

0 commit comments

Comments
 (0)