From b5b9eed9df1c8866995250586c3d7b31b39b2f15 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Wed, 5 Nov 2025 22:32:09 -0500 Subject: [PATCH] IRGen: Narrow fix for type equivalence problem in searchNominalTypeMetadata() This fixes a regression introduced by e3c8f423bc0d2fac9998c2b60da793921fc69ad4, but the root cause was actually a subtle invariant violation in IRGen. FulfillmentMap's use of canonical types as keys assumes that canonical type equality is sufficient for checking if two types "are the same". However, this is not true when those types contain type parameters, because two distinct type parameters might belong to the same equivalence class. Thus, when we take the type's context substitution map, and apply it to each type parameter in our given list of requirements, the substitution operation could output a different but equivalent type parameter. However, it turns out that in practice, we only end up here with a type that is either fully substituted, or is exactly the declared interface type of their nominal. In the latter case, the type's substitution map is the identity substitution map, so we can just detect this case and skip the call to subst(). This avoids the problem. I added an assertion that will fire if this assumption is ever violated in the future. To support the general case, we will need to pass down a generic signature that describes the type parameters that appear in the given type, and use this signature to reduce the type of each lookup key before we proceed. Fixes rdar://160649141. --- lib/IRGen/Fulfillment.cpp | 35 +++++++++++++++- test/IRGen/fulfillment_map_key_equality.swift | 40 +++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 test/IRGen/fulfillment_map_key_equality.swift diff --git a/lib/IRGen/Fulfillment.cpp b/lib/IRGen/Fulfillment.cpp index 13cce27cc03a..feb9547a3111 100644 --- a/lib/IRGen/Fulfillment.cpp +++ b/lib/IRGen/Fulfillment.cpp @@ -374,13 +374,44 @@ bool FulfillmentMap::searchNominalTypeMetadata(IRGenModule &IGM, bool hadFulfillment = false; - auto subs = type->getContextSubstitutionMap(); + std::optional subs = type->getContextSubstitutionMap(); + + // FIXME: Today, the type is either a contextual type for a generic + // environment (in which case the substitution map's replacement types + // do not contain any type parameters), or it is *exactly* the declared + // interface type of its nominal type declaration (in which case we can + // skip the substitution and use the original type parameter as the + // lookup key in our fulfillment map). + // + // If this invariant no longer holds in the future and we legitimately + // end up here with a substitution map whose replacement types also + // contain type parameters, please do not comment out the assertion, + // because if it fails to hold, the lookup below will no longer work. + // Indeed, types that contain type parameters might be equivalent with + // respect to a generic signature, but not equal as canonical types. + // + // The correct fix in that case would be to plumb through the generic + // signature that describes the substitution map's replacement types + // (*not* the substitution map's input generic signature), so that we can + // reduce the result of the call to subst() below with respect to this + // signature before forming the key. + if (type->hasTypeParameter()) { + ASSERT(subs->isIdentity()); + subs = std::nullopt; + } GenericTypeRequirements requirements(IGM, nominal); for (unsigned reqtIndex : indices(requirements.getRequirements())) { auto requirement = requirements.getRequirements()[reqtIndex]; - auto arg = requirement.getTypeParameter().subst(subs)->getCanonicalType(); + auto arg = requirement.getTypeParameter(); + // Only substitute if we have to. + if (subs) { + arg = arg.subst(*subs)->getCanonicalType(); + // Otherwise, we cannot guarantee that two equivalent types + // are actually going to be canonically equal. + ASSERT(!arg->hasTypeParameter()); + } // Skip uninteresting type arguments. if (!keys.hasInterestingType(arg)) diff --git a/test/IRGen/fulfillment_map_key_equality.swift b/test/IRGen/fulfillment_map_key_equality.swift new file mode 100644 index 000000000000..88c68ccb85a1 --- /dev/null +++ b/test/IRGen/fulfillment_map_key_equality.swift @@ -0,0 +1,40 @@ +// RUN: %target-swift-frontend -emit-ir %s -enable-library-evolution | %FileCheck %s + +// rdar://160649141 + +public protocol P1 {} + +public protocol P2 { + associatedtype A1 +} + +public protocol P5 { + associatedtype A2: P2 +} + +public protocol P3 where A4.A1 == A3.A2.A1 { + associatedtype A3: P5 + associatedtype A4: P2 + + var x: Int { get } +} + +public protocol P6: P3 {} + +public protocol P4 { + associatedtype A4: P2 +} + +public struct G1: P2 {} + +public struct G2: P5 {} + +public struct G3: P3 where T.A4.A1: P1 { + public typealias A4 = G1 + public typealias A3 = G2 + + // Make sure this witness thunk doesn't have any additional bogus parameters. + + // CHECK-LABEL: define internal swiftcc i64 @"$s28fulfillment_map_key_equality2G3VyxGAA2P3A2aEP1xSivgTW"(ptr noalias swiftself captures(none) %0, ptr %Self, ptr %SelfWitnessTable) {{.*}} { + public var x: Int { fatalError() } +}