Skip to content

[5.9] Fix covariant erasure for constrained existential #66017

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/swift/AST/GenericSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ class alignas(1 << TypeAlignInBits) GenericSignatureImpl final
/// Determine whether the given dependent type is required to be a class.
bool requiresClass(Type type) const;

Type getUpperBound(Type type, bool wantDependentUpperBound = false) const;

/// Determine the superclass bound on the given dependent type.
Type getSuperclassBound(Type type) const;

Expand Down
84 changes: 30 additions & 54 deletions lib/AST/GenericSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,59 +626,36 @@ unsigned GenericSignatureImpl::getGenericParamOrdinal(
}

Type GenericSignatureImpl::getNonDependentUpperBounds(Type type) const {
return getUpperBound(type);
}

Type GenericSignatureImpl::getDependentUpperBounds(Type type) const {
return getUpperBound(type, /*wantDependentBound=*/true);
}

Type GenericSignatureImpl::getUpperBound(Type type,
bool wantDependentBound) const {
assert(type->isTypeParameter());

bool hasExplicitAnyObject = requiresClass(type);

llvm::SmallVector<Type, 2> types;

if (Type superclass = getSuperclassBound(type)) {
// If the class contains a type parameter, try looking for a non-dependent
// superclass.
while (superclass && superclass->hasTypeParameter()) {
superclass = superclass->getSuperclass();
}
do {
superclass = getReducedType(superclass);
if (wantDependentBound || !superclass->hasTypeParameter()) {
break;
}
} while ((superclass = superclass->getSuperclass()));

if (superclass) {
types.push_back(superclass);
hasExplicitAnyObject = false;
}
}
for (auto *proto : getRequiredProtocols(type)) {
if (proto->requiresClass())
hasExplicitAnyObject = false;

types.push_back(proto->getDeclaredInterfaceType());
}

auto constraint = ProtocolCompositionType::get(
getASTContext(), types,
hasExplicitAnyObject);

if (!constraint->isConstraintType()) {
assert(constraint->getClassOrBoundGenericClass());
return constraint;
}

return ExistentialType::get(constraint);
}

Type GenericSignatureImpl::getDependentUpperBounds(Type type) const {
assert(type->isTypeParameter());

llvm::SmallVector<Type, 2> types;

auto &ctx = type->getASTContext();

bool hasExplicitAnyObject = requiresClass(type);

// FIXME: If the superclass bound is implied by one of our protocols, we
// shouldn't add it to the constraint type.
if (Type superclass = getSuperclassBound(type)) {
types.push_back(superclass);
hasExplicitAnyObject = false;
}

for (auto proto : getRequiredProtocols(type)) {
for (auto *proto : getRequiredProtocols(type)) {
if (proto->requiresClass())
hasExplicitAnyObject = false;

Expand Down Expand Up @@ -724,26 +701,25 @@ Type GenericSignatureImpl::getDependentUpperBounds(Type type) const {
argTypes.push_back(reducedType);
}

// We should have either constrained all primary associated types,
// or none of them.
if (!argTypes.empty()) {
if (argTypes.size() != primaryAssocTypes.size()) {
llvm::errs() << "Not all primary associated types constrained?\n";
llvm::errs() << "Interface type: " << type << "\n";
llvm::errs() << GenericSignature(this) << "\n";
abort();
}

types.push_back(ParameterizedProtocolType::get(ctx, baseType, argTypes));
// If we have constrained all primary associated types, create a
// parameterized protocol type. During code completion, we might call
// `getExistentialType` (which calls this method) on a generic parameter
// that doesn't have all parameters specified, e.g. to get a consise
// description of the parameter type to the following function.
//
// func foo<P: Publisher>(p: P) where P.Failure == Never
//
// In that case just add the base type in the default branch below.
if (argTypes.size() == primaryAssocTypes.size()) {
types.push_back(ParameterizedProtocolType::get(getASTContext(), baseType, argTypes));
continue;
}
}

types.push_back(baseType);
}

auto constraint = ProtocolCompositionType::get(
ctx, types, hasExplicitAnyObject);
auto constraint = ProtocolCompositionType::get(getASTContext(), types,
hasExplicitAnyObject);

if (!constraint->isConstraintType()) {
assert(constraint->getClassOrBoundGenericClass());
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8807,6 +8807,10 @@ bool MissingExplicitExistentialCoercion::fixItRequiresParens() const {

void MissingExplicitExistentialCoercion::fixIt(
InFlightDiagnostic &diagnostic) const {

if (ErasedResultType->hasTypeParameter())
return;

bool requiresParens = fixItRequiresParens();

auto callRange = getSourceRange();
Expand Down
6 changes: 6 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2507,6 +2507,12 @@ bool AddExplicitExistentialCoercion::isRequired(
return Action::Stop;
}

if (erasedMemberTy->isExistentialType() &&
erasedMemberTy->hasTypeParameter()) {
RequiresCoercion = true;
return Action::Stop;
}

return Action::SkipChildren;
}

Expand Down
1 change: 1 addition & 0 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12914,6 +12914,7 @@ ConstraintSystem::simplifyApplicableFnConstraint(
// `as` coercion.
if (AddExplicitExistentialCoercion::isRequired(
*this, func2->getResult(), openedExistentials, locator)) {

if (!shouldAttemptFixes())
return SolutionKind::Error;

Expand Down
18 changes: 12 additions & 6 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2053,10 +2053,8 @@ static bool isMainDispatchQueueMember(ConstraintLocator *locator) {
///
/// \note If a 'Self'-rooted type parameter is bound to a concrete type, this
/// routine will recurse into the concrete type.
static Type
typeEraseExistentialSelfReferences(
Type refTy, Type baseTy,
TypePosition outermostPosition) {
static Type typeEraseExistentialSelfReferences(Type refTy, Type baseTy,
TypePosition outermostPosition) {
assert(baseTy->isExistentialType());
if (!refTy->hasTypeParameter()) {
return refTy;
Expand Down Expand Up @@ -2170,7 +2168,6 @@ typeEraseExistentialSelfReferences(
return erasedTy;
});
};

return transformFn(refTy, outermostPosition);
}

Expand Down Expand Up @@ -2299,16 +2296,25 @@ Type ConstraintSystem::getMemberReferenceTypeFromOpenedType(
const auto selfGP = cast<GenericTypeParamType>(
outerDC->getSelfInterfaceType()->getCanonicalType());
auto openedTypeVar = replacements.lookup(selfGP);

type = typeEraseOpenedExistentialReference(type, baseObjTy, openedTypeVar,
TypePosition::Covariant);

Type contextualTy;

if (auto *anchor = getAsExpr(simplifyLocatorToAnchor(locator))) {
contextualTy =
getContextualType(getParentExpr(anchor), /*forConstraint=*/false);
}

if (!hasFixFor(locator) &&
AddExplicitExistentialCoercion::isRequired(
*this, nonErasedResultTy,
[&](TypeVariableType *typeVar) {
return openedTypeVar == typeVar ? baseObjTy : Optional<Type>();
},
locator)) {
locator) &&
!contextualTy) {
recordFix(AddExplicitExistentialCoercion::create(
*this, getResultType(type), locator));
}
Expand Down
70 changes: 67 additions & 3 deletions test/Constraints/opened_existentials.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ protocol P {
associatedtype A: Q
}

protocol P1<A> {
associatedtype A
}

extension Int: P {
typealias A = Double
}
Expand Down Expand Up @@ -228,6 +232,7 @@ func testTakeValueAndClosure(p: any P) {
protocol B {
associatedtype C: P where C.A == Double
associatedtype D: P
associatedtype E: P1 where E.A == Double
}

protocol D {
Expand All @@ -242,6 +247,7 @@ extension B {

func testExplicitCoercionRequirement(v: any B, otherV: any B & D) {
func getC<T: B>(_: T) -> T.C { fatalError() }
func getE<T: B>(_: T) -> T.E { fatalError() }
func getTuple<T: B>(_: T) -> (T, T.C) { fatalError() }
func getNoError<T: B>(_: T) -> T.C.A { fatalError() }
func getComplex<T: B>(_: T) -> ([(x: (a: T.C, b: Int), y: Int)], [Int: T.C]) { fatalError() }
Expand All @@ -252,11 +258,14 @@ func testExplicitCoercionRequirement(v: any B, otherV: any B & D) {

_ = getC(v) // expected-error {{inferred result type 'any P' requires explicit coercion due to loss of generic requirements}} {{14-14=as any P}}
_ = getC(v) as any P // Ok


_ = getE(v) // expected-error {{inferred result type 'any P1<Double>' requires explicit coercion due to loss of generic requirements}} {{14-14=as any P1<Double>}}
_ = getE(v) as any P1<Double> // Ok

_ = getTuple(v) // expected-error {{inferred result type '(any B, any P)' requires explicit coercion due to loss of generic requirements}} {{18-18=as (any B, any P)}}
_ = getTuple(v) as (any B, any P) // Ok

_ = getNoError(v) // Ok because T.C.A == Double
// Ok because T.C.A == Double
_ = getNoError(v)

_ = getComplex(v) // expected-error {{inferred result type '([(x: (a: any P, b: Int), y: Int)], [Int : any P])' requires explicit coercion due to loss of generic requirements}} {{20-20=as ([(x: (a: any P, b: Int), y: Int)], [Int : any P])}}
_ = getComplex(v) as ([(x: (a: any P, b: Int), y: Int)], [Int : any P]) // Ok
Expand Down Expand Up @@ -305,3 +314,58 @@ func testExplicitCoercionRequirement(v: any B, otherV: any B & D) {
getP((getC(v) as any P)) // Ok - parens avoid opening suppression
getP((v.getC() as any P)) // Ok - parens avoid opening suppression
}

class C1 {}
class C2<T>: C1 {}

// Test Associated Types
protocol P2 {
associatedtype A
associatedtype B: C2<A>

func returnAssocTypeB() -> B
}

func testAssocReturn(p: any P2) {
let _ = p.returnAssocTypeB() // returns C1
}

protocol Q2 : P2 where A == Int {}

do {
let q: any Q2
let _ = q.returnAssocTypeB() // returns C1
}

// Test Primary Associated Types
protocol P3<A> {
associatedtype A
associatedtype B: C2<A>

func returnAssocTypeB() -> B
}

func testAssocReturn(p: any P3<Int>) {
let _ = p.returnAssocTypeB() // returns C2<A>
}

func testAssocReturn(p: any P3<any P3<String>>) {
let _ = p.returnAssocTypeB()
}

protocol P4<A> {
associatedtype A
associatedtype B: C2<A>

func returnPrimaryAssocTypeA() -> A
func returnAssocTypeCollection() -> any Collection<A>
}

//Confirm there is no way to access Primary Associated Type directly
func testPrimaryAssocReturn(p: any P4<Int>) {
let _ = p.returnPrimaryAssocTypeA()
}

func testPrimaryAssocCollection(p: any P4<Float>) {
let _: any Collection<Float> = p.returnAssocTypeCollection()
}
41 changes: 41 additions & 0 deletions test/SILGen/existential_member_accesses_self_assoctype.swift
Original file line number Diff line number Diff line change
Expand Up @@ -745,10 +745,51 @@ func testContravariantAssocMethod1Concrete(p3: any P3) {
// CHECK: [[WITNESS:%[0-9]+]] = witness_method $@opened([[OPENED_ID]], any P3) Self, #P3.invariantAssocMethod1 : <Self where Self : P3> (Self) -> () -> GenericStruct<Self.A>
// CHECK: [[RESULT:%[0-9]+]] = apply [[WITNESS]]<@opened([[OPENED_ID]], any P3) Self>([[OPENED]]) : $@convention(witness_method: P3) <τ_0_0 where τ_0_0 : P3> (@in_guaranteed τ_0_0) -> GenericStruct<Bool>
// CHECK: debug_value [[RESULT]] : $GenericStruct<Bool>, let, name "x"
// CHECK: } // end sil function '$s42existential_member_accesses_self_assoctype33testInvariantAssocMethod1Concrete2p3yAA2P3_p_tF'
func testInvariantAssocMethod1Concrete(p3: any P3) {
let x = p3.invariantAssocMethod1()
}

// --------------------------------------------------------------------------------------------------------
// Covariant dependent member type erasure in concrete dependent member type as primary associated type
// --------------------------------------------------------------------------------------------------------

class C {}
class GenericClass<T> {}
class GenericSubClass<T> : C {}

protocol P5<A>{
associatedtype A
associatedtype B : GenericClass<A>
associatedtype C : GenericSubClass<A>

func returnAssocTypeB() -> B

func returnAssocTypeC() -> C
}

// CHECK-LABEL: sil hidden [ossa] @$s42existential_member_accesses_self_assoctype30testCovariantAssocGenericClass2p5AA0iJ0CySiGAA2P5_pSi1AAaGPRts_XP_tF
// CHECK: [[OPENED:%[0-9]+]] = open_existential_addr immutable_access %0 : $*any P5<Int> to $*@opened([[OPENED_ID:"[0-9A-F-]+"]], any P5<Int>) Self
// CHECK: [[APPLY:%[0-9]+]] = apply [[WITNESS]]<@opened([[OPENED_ID]], any P5<Int>) Self>([[OPENED]]) : $@convention(witness_method: P5) <τ_0_0 where τ_0_0 : P5> (@in_guaranteed τ_0_0) -> @owned τ_0_0.B
// CHECK: [[UPCAST:%[0-9]+]] = upcast [[APPLY]] : $@opened([[OPENED_ID]], any P5<Int>) Self.B to $GenericClass<Int>
// CHECK: return %{{[0-9]+}} : $GenericClass<Int>
// CHECK: } // end sil function '$s42existential_member_accesses_self_assoctype30testCovariantAssocGenericClass2p5AA0iJ0CySiGAA2P5_pSi1AAaGPRts_XP_tF'
func testCovariantAssocGenericClass(p5: any P5<Int>) -> GenericClass<Int> {
let x = p5.returnAssocTypeB()
return x
}

// CHECK-LABEL: sil hidden [ossa] @$s42existential_member_accesses_self_assoctype33testCovariantAssocGenericSubClass2p5AA0ijK0CySbGAA2P5_pSb1AAaGPRts_XP_tF
// CHECK: [[OPENED:%[0-9]+]] = open_existential_addr immutable_access %0 : $*any P5<Bool> to $*@opened([[OPENED_ID:"[0-9A-F-]+"]], any P5<Bool>) Self
// CHECK: apply %3<@opened([[OPENED_ID]], any P5<Bool>) Self>([[OPENED]]) : $@convention(witness_method: P5) <τ_0_0 where τ_0_0 : P5> (@in_guaranteed τ_0_0) -> @owned τ_0_0.C
// CHECK: [[UPCAST:%[0-9]+]] = upcast [[APPLY:%[0-9]+]] : $@opened([[OPENED_ID]], any P5<Bool>) Self.C to $GenericSubClass<Bool>
// CHECK: return %{{[0-9]+}} : $GenericSubClass<Bool>
// CHECK: } // end sil function '$s42existential_member_accesses_self_assoctype33testCovariantAssocGenericSubClass2p5AA0ijK0CySbGAA2P5_pSb1AAaGPRts_XP_tF'
func testCovariantAssocGenericSubClass(p5: any P5<Bool>) -> GenericSubClass<Bool> {
let y = p5.returnAssocTypeC()
return y
}

// -----------------------------------------------------------------------------
// Covariant dependent member type erasure in concrete dependent member type
// -----------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -918,12 +918,24 @@ do {
let _: Class2Base = exist.method5()
let _: any Class2Base & CovariantAssocTypeErasure = exist.method6()
let _: any Class2Base & CovariantAssocTypeErasure = exist.method7()

let _: Any? = exist.method8()
let _: (AnyObject, Bool) = exist.method9()
let _: any CovariantAssocTypeErasure.Type = exist.method10()
let _: Array<Class2Base> = exist.method11()
let _: Dictionary<String, Class2Base> = exist.method12()

let _ = exist.method1()
let _ = exist.method2()
let _ = exist.method3()
let _ = exist.method4()
let _ = exist.method5()
let _ = exist.method6()
let _ = exist.method7()
let _ = exist.method8()
let _ = exist.method9()
let _ = exist.method10()
let _ = exist.method11()
let _ = exist.method12()
}
do {
let exist: any CovariantAssocTypeErasureDerived
Expand Down
Loading