Skip to content

Small fixes for new abstract conformance representation #80327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 1, 2025
10 changes: 5 additions & 5 deletions include/swift/AST/TypeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ case TypeKind::Id:
return *result;

auto subMap = opaque->getSubstitutions();
auto newSubMap = asDerived().transformSubMap(subMap);
auto newSubMap = asDerived().transformSubstitutionMap(subMap);
if (newSubMap == subMap)
return t;
if (!newSubMap)
Expand All @@ -177,7 +177,7 @@ case TypeKind::Id:
auto subMap = env->getOuterSubstitutions();
auto uuid = env->getOpenedExistentialUUID();

auto newSubMap = asDerived().transformSubMap(subMap);
auto newSubMap = asDerived().transformSubstitutionMap(subMap);
if (newSubMap == subMap)
return t;
if (!newSubMap)
Expand Down Expand Up @@ -259,7 +259,7 @@ case TypeKind::Id:
}

auto oldSubMap = boxTy->getSubstitutions();
auto newSubMap = asDerived().transformSubMap(oldSubMap);
auto newSubMap = asDerived().transformSubstitutionMap(oldSubMap);
if (oldSubMap && !newSubMap)
return Type();
changed |= (oldSubMap != newSubMap);
Expand All @@ -281,7 +281,7 @@ case TypeKind::Id:
return fnTy;

auto updateSubs = [&](SubstitutionMap &subs) -> bool {
auto newSubs = asDerived().transformSubMap(subs);
auto newSubs = asDerived().transformSubstitutionMap(subs);
if (subs && !newSubs)
return false;
if (subs == newSubs)
Expand Down Expand Up @@ -1052,7 +1052,7 @@ case TypeKind::Id:

// If original was non-empty and transformed is empty, we're
// signaling failure, that is, a Type() return from doIt().
SubstitutionMap transformSubMap(SubstitutionMap subs) {
SubstitutionMap transformSubstitutionMap(SubstitutionMap subs) {
if (subs.empty())
return subs;

Expand Down
6 changes: 6 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5521,6 +5521,12 @@ class PrintConformance : public PrintBase {
assert(conformance.isAbstract());

printHead("abstract_conformance", ASTNodeColor, label);

PrintOptions PO;
PO.OpaqueReturnTypePrinting =
PrintOptions::OpaqueReturnTypePrintingMode::StableReference;

printTypeField(conformance.getType(), Label::always("type"), PO);
printReferencedDeclField(conformance.getProtocol(),
Label::always("protocol"));
printFoot();
Expand Down
59 changes: 27 additions & 32 deletions lib/AST/ProtocolConformanceRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,24 @@ ProtocolConformanceRef::subst(Type origType, InFlightSubstitution &IFS) const {
if (isPack())
return getPack()->subst(IFS);

// Handle abstract conformances below:
ASSERT(isAbstract());
auto *proto = getProtocol();

// If the type is an opaque archetype, the conformance will remain abstract,
// unless we're specifically substituting opaque types.
if (auto origArchetype = origType->getAs<ArchetypeType>()) {
if (!IFS.shouldSubstituteOpaqueArchetypes()
&& isa<OpaqueTypeArchetypeType>(origArchetype)) {
return *this;
if (auto origArchetype = origType->getAs<OpaqueTypeArchetypeType>()) {
if (!IFS.shouldSubstituteOpaqueArchetypes()) {
return forAbstract(origType.subst(IFS), proto);
}
}

// FIXME: Handle local archetypes as above!

// Otherwise, compute the substituted type.
auto substType = origType.subst(IFS);

auto *proto = getProtocol();

// If the type is an existential, it must be self-conforming.
// FIXME: This feels like it's in the wrong place.
if (substType->isExistentialType()) {
auto optConformance =
lookupConformance(substType, proto, /*allowMissing=*/true);
Expand All @@ -119,7 +120,7 @@ ProtocolConformanceRef::subst(Type origType, InFlightSubstitution &IFS) const {
return ProtocolConformanceRef::forInvalid();
}

// Check the conformance map.
// Local conformance lookup into the substitution map.
// FIXME: Pack element level?
return IFS.lookupConformance(origType->getCanonicalType(), substType, proto,
/*level=*/0);
Expand All @@ -128,24 +129,20 @@ ProtocolConformanceRef::subst(Type origType, InFlightSubstitution &IFS) const {
ProtocolConformanceRef ProtocolConformanceRef::mapConformanceOutOfContext() const {
if (isConcrete()) {
return getConcrete()->subst(
[](SubstitutableType *type) -> Type {
if (auto *archetypeType = type->getAs<ArchetypeType>())
return archetypeType->getInterfaceType();
return type;
},
MapTypeOutOfContext(),
MakeAbstractConformanceForGenericType(),
SubstFlags::PreservePackExpansionLevel |
SubstFlags::SubstitutePrimaryArchetypes);
} else if (isPack()) {
return getPack()->subst(
[](SubstitutableType *type) -> Type {
if (auto *archetypeType = type->getAs<ArchetypeType>())
return archetypeType->getInterfaceType();
return type;
},
MapTypeOutOfContext(),
MakeAbstractConformanceForGenericType(),
SubstFlags::PreservePackExpansionLevel |
SubstFlags::SubstitutePrimaryArchetypes);
} else if (isAbstract()) {
auto *abstract = getAbstract();
return forAbstract(abstract->getType()->mapTypeOutOfContext(),
abstract->getProtocol());
}

return *this;
Expand Down Expand Up @@ -266,6 +263,15 @@ ProtocolConformanceRef::getAssociatedConformance(Type conformingType,
return conformance->getAssociatedConformance(assocType, protocol);
}

auto computeSubjectType = [&](Type conformingType) -> Type {
return assocType.transformRec(
[&](TypeBase *t) -> std::optional<Type> {
if (isa<GenericTypeParamType>(t))
return conformingType;
return std::nullopt;
});
};

// An associated conformance of an archetype might be known to be
// a concrete conformance, if the subject type is fixed to a concrete
// type in the archetype's generic signature. We don't actually have
Expand All @@ -276,15 +282,8 @@ ProtocolConformanceRef::getAssociatedConformance(Type conformingType,
// conformances where they store their subject types, we can also
// cache the lookups inside the abstract conformance instance too.
if (auto archetypeType = conformingType->getAs<ArchetypeType>()) {
conformingType = archetypeType->getInterfaceType();
auto *genericEnv = archetypeType->getGenericEnvironment();

auto subjectType = assocType.transformRec(
[&](TypeBase *t) -> std::optional<Type> {
if (isa<GenericTypeParamType>(t))
return conformingType;
return std::nullopt;
});
auto subjectType = computeSubjectType(archetypeType->getInterfaceType());

return lookupConformance(
genericEnv->mapTypeIntoContext(subjectType),
Expand All @@ -296,12 +295,8 @@ ProtocolConformanceRef::getAssociatedConformance(Type conformingType,
// signature of the substitution (or in the case of type variables,
// we have no visibility into constraints). See the parallel hack
// to handle this in SubstitutionMap::lookupConformance().
CONDITIONAL_ASSERT(conformingType->isTypeParameter() ||
conformingType->isTypeVariableOrMember() ||
conformingType->is<UnresolvedType>() ||
conformingType->is<PlaceholderType>());

return ProtocolConformanceRef::forAbstract(conformingType, protocol);
auto subjectType = computeSubjectType(conformingType);
return ProtocolConformanceRef::forAbstract(subjectType, protocol);
}

/// Check of all types used by the conformance are canonical.
Expand Down
2 changes: 1 addition & 1 deletion lib/AST/SubstitutionMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ SubstitutionMap SubstitutionMap::subst(InFlightSubstitution &IFS) const {
ProtocolConformanceRef(conformance.getConcrete()->subst(IFS)));
} else {
auto origType = req.getFirstType();
auto substType = origType.subst(*this, IFS.getOptions());
auto substType = origType.subst(*this);

newConformances.push_back(conformance.subst(substType, IFS));
}
Expand Down
31 changes: 9 additions & 22 deletions lib/AST/TypeSubstitution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1108,37 +1108,23 @@ ProtocolConformanceRef swift::substOpaqueTypesWithUnderlyingTypes(
ProtocolConformanceRef ReplaceOpaqueTypesWithUnderlyingTypes::
operator()(CanType maybeOpaqueType, Type replacementType,
ProtocolDecl *protocol) const {
auto abstractRef = ProtocolConformanceRef::forAbstract(maybeOpaqueType, protocol);

auto archetype = dyn_cast<OpaqueTypeArchetypeType>(maybeOpaqueType);
if (!archetype) {
if (maybeOpaqueType->isTypeParameter() ||
maybeOpaqueType->is<ArchetypeType>())
return abstractRef;

// SIL type lowering may have already substituted away the opaque type, in
// which case we'll end up "substituting" the same type.
if (maybeOpaqueType->isEqual(replacementType)) {
return lookupConformance(replacementType, protocol);
}

llvm_unreachable("origType should have been an opaque type or type parameter");
}
if (!archetype)
return lookupConformance(replacementType, protocol);

auto *genericEnv = archetype->getGenericEnvironment();
auto *decl = genericEnv->getOpaqueTypeDecl();
auto outerSubs = genericEnv->getOuterSubstitutions();

auto substitutionKind = shouldPerformSubstitution(decl);
if (substitutionKind == OpaqueSubstitutionKind::DontSubstitute) {
return abstractRef;
}
if (substitutionKind == OpaqueSubstitutionKind::DontSubstitute)
return lookupConformance(replacementType, protocol);

auto subs = decl->getUniqueUnderlyingTypeSubstitutions();
// If the body of the opaque decl providing decl has not been type checked we
// don't have a underlying substitution.
if (!subs.has_value())
return abstractRef;
return lookupConformance(replacementType, protocol);

// Apply the underlying type substitutions to the interface type of the
// archetype in question. This will map the inner generic signature of the
Expand All @@ -1159,7 +1145,7 @@ operator()(CanType maybeOpaqueType, Type replacementType,
return true;
return false;
}))
return abstractRef;
return lookupConformance(replacementType, protocol);

// Then apply the substitutions from the root opaque archetype, to specialize
// for its type arguments. We perform this substitution after checking for
Expand All @@ -1168,7 +1154,8 @@ operator()(CanType maybeOpaqueType, Type replacementType,
auto substTy = partialSubstTy.subst(outerSubs);

auto partialSubstRef =
abstractRef.subst(archetype->getInterfaceType(), *subs);
subs->lookupConformance(archetype->getInterfaceType()->getCanonicalType(),
protocol);
auto substRef =
partialSubstRef.subst(partialSubstTy, outerSubs);

Expand All @@ -1181,7 +1168,7 @@ operator()(CanType maybeOpaqueType, Type replacementType,
// type back to the caller. This substitution will fail at runtime
// instead.
if (!alreadySeen->insert(seenKey).second) {
return abstractRef;
return lookupConformance(replacementType, protocol);
}

auto res = ::substOpaqueTypesWithUnderlyingTypesRec(
Expand Down
12 changes: 6 additions & 6 deletions test/Frontend/debug-generic-signatures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ struct Generic<T> {}
// CHECK-NEXT: (assoc_conformance type="Self" proto="Escapable"
// CHECK-NEXT: (builtin_conformance type="Generic<T>" protocol="Escapable"))
// CHECK-NEXT: (assoc_conformance type="Self.A" proto="Copyable"
// CHECK-NEXT: (abstract_conformance protocol="Copyable"))
// CHECK-NEXT: (abstract_conformance type="T" protocol="Copyable"))
// CHECK-NEXT: (assoc_conformance type="Self.A" proto="Escapable"
// CHECK-NEXT: (abstract_conformance protocol="Escapable"))
// CHECK-NEXT: (abstract_conformance type="T" protocol="Escapable"))
// CHECK-NEXT: (requirement "T" conforms_to "P1"))
extension Generic: P1 where T: P1 {
typealias A = T
Expand All @@ -123,9 +123,9 @@ class Super<T, U> {}
// CHECK-NEXT: (assoc_conformance type="Self" proto="Escapable"
// CHECK-NEXT: (builtin_conformance type="Super<T, U>" protocol="Escapable"))
// CHECK-NEXT: (assoc_conformance type="Self.A" proto="P2"
// CHECK-NEXT: (abstract_conformance protocol="P2"))
// CHECK-NEXT: (abstract_conformance type="T" protocol="P2"))
// CHECK-NEXT: (assoc_conformance type="Self.B" proto="P2"
// CHECK-NEXT: (abstract_conformance protocol="P2"))
// CHECK-NEXT: (abstract_conformance type="T" protocol="P2"))
// CHECK-NEXT: (requirement "T" conforms_to "P2")
// CHECK-NEXT: (requirement "U" conforms_to "P2"))
extension Super: P2 where T: P2, U: P2 {
Expand Down Expand Up @@ -177,9 +177,9 @@ extension Super: P2 where T: P2, U: P2 {
// CHECK-NEXT: (assoc_conformance type="Self" proto="Escapable"
// CHECK-NEXT: (builtin_conformance type="Super<T, U>" protocol="Escapable"))
// CHECK-NEXT: (assoc_conformance type="Self.A" proto="P2"
// CHECK-NEXT: (abstract_conformance protocol="P2"))
// CHECK-NEXT: (abstract_conformance type="T" protocol="P2"))
// CHECK-NEXT: (assoc_conformance type="Self.B" proto="P2"
// CHECK-NEXT: (abstract_conformance protocol="P2"))
// CHECK-NEXT: (abstract_conformance type="T" protocol="P2"))
// CHECK-NEXT: (requirement "T" conforms_to "P2")
// CHECK-NEXT: (requirement "U" conforms_to "P2"))))
class Sub: Super<NonRecur, Recur> {}
Expand Down
27 changes: 27 additions & 0 deletions validation-test/SILGen/SwiftUI/opaque_result_type_erasure.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %target-swift-frontend -emit-silgen %s -target %target-cpu-apple-macosx11 -swift-version 5

// REQUIRES: objc_interop
// REQUIRES: OS=macosx

import SwiftUI

struct MyView: View {
@State private var isPresented = false

var body: some View {
NavigationView {
Form {
subsection
}
.listStyle(DefaultListStyle())
.navigationTitle(Text(""))
.sheet(isPresented: $isPresented,
onDismiss: { },
content: { Text("") })
}
}

private var subsection: some View {
Text("")
}
}