From 9bb6673bbdc4c60e3de93edb4fe4ef55e403ad2d Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 29 Apr 2025 17:11:13 -0400 Subject: [PATCH] AST: Fix existential erasure of long member types Suppose protocol P has a primary associated type A, and we have a `any P` value. We form the generalization signature with substitution map {T := S}, and the existential signature . Now, if we call a protocol requirement that takes Self.A.A.A, we see this is fixed concrete type, because the reduced type of Self.A.A.A is T.A.A in the existential signature. However, this type parameter is not formed from the conformance requirements of the generalization signature (there aren't any), so we cannot directly apply the outer substitution map. Instead, change the outer substitution conformance lookup callback to check if the reduced type parameter is valid in the generalization signature, and not just rooted in a generic parameter of the generalization signature. If it isn't, fall back to global conformance lookup. A better fix would introduce new requirements into the generalization signature to handle this, or store them separately in the generic environment itself. But this is fine for now. - Fixes https://github.com/swiftlang/swift/issues/79763. - Fixes rdar://problem/146111083. --- include/swift/AST/SubstitutionMap.h | 1 - lib/AST/GenericEnvironment.cpp | 10 ++++-- lib/AST/SubstitutionMap.cpp | 33 ++++++++++--------- lib/Sema/OpenedExistentials.cpp | 9 ++--- .../SILGen/existential_erasure_length_2.swift | 25 ++++++++++++++ 5 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 test/SILGen/existential_erasure_length_2.swift diff --git a/include/swift/AST/SubstitutionMap.h b/include/swift/AST/SubstitutionMap.h index 961d87d87bcd7..c46d7ffd18235 100644 --- a/include/swift/AST/SubstitutionMap.h +++ b/include/swift/AST/SubstitutionMap.h @@ -340,7 +340,6 @@ struct OuterSubstitutions { SubstitutionMap subs; unsigned depth; - bool isUnsubstitutedTypeParameter(Type type) const; Type operator()(SubstitutableType *type) const; ProtocolConformanceRef operator()(CanType dependentType, Type conformingReplacementType, diff --git a/lib/AST/GenericEnvironment.cpp b/lib/AST/GenericEnvironment.cpp index 915c19f5ea34e..4cda36c2512e2 100644 --- a/lib/AST/GenericEnvironment.cpp +++ b/lib/AST/GenericEnvironment.cpp @@ -317,9 +317,13 @@ GenericEnvironment::maybeApplyOuterContextSubstitutions(Type type) const { case Kind::OpenedExistential: case Kind::OpenedElement: case Kind::Opaque: { - OuterSubstitutions replacer{ - getOuterSubstitutions(), getGenericSignature()->getMaxDepth()}; - return type.subst(replacer, replacer); + if (auto subs = getOuterSubstitutions()) { + OuterSubstitutions replacer{subs, + getGenericSignature()->getMaxDepth()}; + return type.subst(replacer, replacer); + } + + return type; } } } diff --git a/lib/AST/SubstitutionMap.cpp b/lib/AST/SubstitutionMap.cpp index eb2410760426a..dca03e2d6efa7 100644 --- a/lib/AST/SubstitutionMap.cpp +++ b/lib/AST/SubstitutionMap.cpp @@ -669,21 +669,8 @@ SubstitutionMap SubstitutionMap::mapIntoTypeExpansionContext( SubstFlags::PreservePackExpansionLevel); } -bool OuterSubstitutions::isUnsubstitutedTypeParameter(Type type) const { - if (!type->isTypeParameter()) - return false; - - if (auto depMemTy = type->getAs()) - return isUnsubstitutedTypeParameter(depMemTy->getBase()); - - if (auto genericParam = type->getAs()) - return genericParam->getDepth() >= depth; - - return false; -} - Type OuterSubstitutions::operator()(SubstitutableType *type) const { - if (isUnsubstitutedTypeParameter(type)) + if (cast(type)->getDepth() >= depth) return Type(type); return QuerySubstitutionMap{subs}(type); @@ -693,9 +680,23 @@ ProtocolConformanceRef OuterSubstitutions::operator()( CanType dependentType, Type conformingReplacementType, ProtocolDecl *conformedProtocol) const { - if (isUnsubstitutedTypeParameter(dependentType)) - return ProtocolConformanceRef::forAbstract( + auto sig = subs.getGenericSignature(); + if (!sig->isValidTypeParameter(dependentType) || + !sig->requiresProtocol(dependentType, conformedProtocol)) { + // FIXME: We need the isValidTypeParameter() check instead of just looking + // at the root generic parameter because in the case of an existential + // environment, the reduced type of a member type of Self might be an outer + // type parameter that is not formed from the outer generic signature's + // conformance requirements. Ideally, we'd either add these supplementary + // conformance requirements to the generalization signature, or we would + // store the supplementary conformances directly in the generic environment + // somehow. + // + // Once we check for that and handle it properly, the lookupConformance() + // can become a forAbstract(). + return swift::lookupConformance( conformingReplacementType, conformedProtocol); + } return LookUpConformanceInSubstitutionMap(subs)( dependentType, conformingReplacementType, conformedProtocol); diff --git a/lib/Sema/OpenedExistentials.cpp b/lib/Sema/OpenedExistentials.cpp index 21c8bff0b158c..00d978bc6b88e 100644 --- a/lib/Sema/OpenedExistentials.cpp +++ b/lib/Sema/OpenedExistentials.cpp @@ -823,10 +823,11 @@ Type swift::typeEraseOpenedExistentialReference( auto applyOuterSubstitutions = [&](Type t) -> Type { if (t->hasTypeParameter()) { - auto outerSubs = existentialSig.Generalization; - unsigned depth = existentialSig.OpenedSig->getMaxDepth(); - OuterSubstitutions replacer{outerSubs, depth}; - return t.subst(replacer, replacer); + if (auto outerSubs = existentialSig.Generalization) { + unsigned depth = existentialSig.OpenedSig->getMaxDepth(); + OuterSubstitutions replacer{outerSubs, depth}; + return t.subst(replacer, replacer); + } } return t; diff --git a/test/SILGen/existential_erasure_length_2.swift b/test/SILGen/existential_erasure_length_2.swift new file mode 100644 index 0000000000000..9c1e981caf1f0 --- /dev/null +++ b/test/SILGen/existential_erasure_length_2.swift @@ -0,0 +1,25 @@ +// RUN: %target-swift-emit-silgen %s + +protocol N { + associatedtype A: N +} + +protocol P { + associatedtype A: N + associatedtype B + + func f0(_: A) -> B + func f1(_: A.A) -> B + func f2(_: A.A.A) -> B +} + +struct G: N { + typealias A = G> +} + +func call(x: any P>) -> (Any, Any, Any) { + let y0 = x.f0(G()) + let y1 = x.f1(G>()) + let y2 = x.f2(G>>()) + return (y0, y1, y2) +}