Skip to content

Commit afa08f0

Browse files
committed
GSB: Formalize the old hack where we rebuild a signature that had redundant conformance requirements
When constructing a generic signature, any redundant explicit requirements are dropped from the final signature. We would assume this operation is idempotent, that is, building a new GenericSignatureBuilder from the resulting minimized signature produces an equivalent GenericSignatureBuilder to the original one. Unfortunately, this is not true in the case of conformance requirements. Namely, if a conformance requirement is made redundant by a superclass or concrete same-type requirement, then dropping the conformance requirement changes the canonical type computation. For example, consider the following: public protocol P { associatedtype Element } public class C<O: P>: P { public typealias Element = O.Element } public func toe<T, O, E>(_: T, _: O, _: E, _: T.Element) where T : P, O : P, O.Element == T.Element, T : C<E> {} In the generic signature of toe(), the superclass requirement 'T : C<E>' implies the conformance requirement 'T : P' because C conforms to P. However, the presence of the conformance requirement makes it so that T.Element is the canonical representative, so previously this signature was minimized down to: <T : C<E>, O : P, T.Element == O.Element> If we build the signature again from the above requirements, then we see that T.Element is no longer the canonical representative; instead, T.Element canonicalizes as E.Element. For this reason, we must rebuild the signature to get the correct canonical type computation. I realized that this is not an artifact of incorrect design in the current GSB; my new rewrite system formalism would produce the same result. Rather, it is a subtle consequence of the specification of our minimization algorithm, and therefore it must be formalized in this manner. We used to sort-of do this with the HadAnyRedundantRequirements hack, but it was both overly broad (we only need to rebuild if a conformance requirement was implied by a superclass or concrete same-type requirement) and not sufficient (when rebuilding, we need to strip any bound associated types from our requirements to ensure the canonical type anchors are re-computed). Fixes rdar://problem/65263302, rdar://problem/75010156, rdar://problem/75171977.
1 parent 60c1755 commit afa08f0

File tree

5 files changed

+141
-5
lines changed

5 files changed

+141
-5
lines changed

include/swift/AST/GenericSignatureBuilder.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,8 @@ class GenericSignatureBuilder {
620620
/// generic signature builder no longer has valid state.
621621
GenericSignature computeGenericSignature(
622622
bool allowConcreteGenericParams = false,
623-
bool allowBuilderToMove = true) &&;
623+
bool buildingRequirementSignature = false,
624+
bool rebuildingWithoutRedundantConformances = false) &&;
624625

625626
/// Compute the requirement signature for the given protocol.
626627
static GenericSignature computeRequirementSignature(ProtocolDecl *proto);
@@ -645,6 +646,8 @@ class GenericSignatureBuilder {
645646
private:
646647
void computeRedundantRequirements();
647648

649+
bool hasExplicitConformancesImpliedByConcrete() const;
650+
648651
/// Describes the relationship between a given constraint and
649652
/// the canonical constraint of the equivalence class.
650653
enum class ConstraintRelation {

lib/AST/GenericSignatureBuilder.cpp

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ STATISTIC(NumRewriteRhsSimplifiedToLhs,
124124
"# of rewrite rule right-hand sides simplified to lhs (and removed)");
125125
STATISTIC(NumRewriteRulesRedundant,
126126
"# of rewrite rules that are redundant (and removed)");
127+
STATISTIC(NumSignaturesRebuiltWithoutRedundantRequirements,
128+
"# of generic signatures which had a concretized conformance requirement");
127129

128130
namespace {
129131

@@ -4645,7 +4647,7 @@ ConstraintResult GenericSignatureBuilder::addTypeRequirement(
46454647
unresolvedHandling);
46464648
}
46474649

4648-
// If the resolved subject is a type, there may be things we can infer (if it
4650+
// If the resolved subject is concrete, there may be things we can infer (if it
46494651
// conditionally conforms to the protocol), and we can probably perform
46504652
// diagnostics here.
46514653
if (auto subjectType = resolvedSubject.getAsConcreteType()) {
@@ -8172,9 +8174,60 @@ static void checkGenericSignature(CanGenericSignature canSig,
81728174
}
81738175
#endif
81748176

8177+
bool GenericSignatureBuilder::hasExplicitConformancesImpliedByConcrete() const {
8178+
for (auto pair : Impl->RedundantRequirements) {
8179+
if (pair.first.getKind() != RequirementKind::Conformance)
8180+
continue;
8181+
8182+
for (auto impliedByReq : pair.second) {
8183+
if (impliedByReq.getKind() == RequirementKind::Superclass)
8184+
return true;
8185+
8186+
if (impliedByReq.getKind() == RequirementKind::SameType)
8187+
return true;
8188+
}
8189+
}
8190+
8191+
return false;
8192+
}
8193+
8194+
static Type stripBoundDependentMemberTypes(Type t) {
8195+
if (auto *depMemTy = t->getAs<DependentMemberType>()) {
8196+
return DependentMemberType::get(
8197+
stripBoundDependentMemberTypes(depMemTy->getBase()),
8198+
depMemTy->getName());
8199+
}
8200+
8201+
return t;
8202+
}
8203+
8204+
static Requirement stripBoundDependentMemberTypes(Requirement req) {
8205+
auto subjectType = stripBoundDependentMemberTypes(req.getFirstType());
8206+
8207+
switch (req.getKind()) {
8208+
case RequirementKind::Conformance:
8209+
return Requirement(RequirementKind::Conformance, subjectType,
8210+
req.getSecondType());
8211+
8212+
case RequirementKind::Superclass:
8213+
case RequirementKind::SameType:
8214+
return Requirement(req.getKind(), subjectType,
8215+
req.getSecondType().transform([](Type t) {
8216+
return stripBoundDependentMemberTypes(t);
8217+
}));
8218+
8219+
case RequirementKind::Layout:
8220+
return Requirement(RequirementKind::Conformance, subjectType,
8221+
req.getLayoutConstraint());
8222+
}
8223+
8224+
llvm_unreachable("Bad requirement kind");
8225+
}
8226+
81758227
GenericSignature GenericSignatureBuilder::computeGenericSignature(
81768228
bool allowConcreteGenericParams,
8177-
bool allowBuilderToMove) && {
8229+
bool buildingRequirementSignature,
8230+
bool rebuildingWithoutRedundantConformances) && {
81788231
// Finalize the builder, producing any necessary diagnostics.
81798232
finalize(getGenericParams(), allowConcreteGenericParams);
81808233

@@ -8185,6 +8238,43 @@ GenericSignature GenericSignatureBuilder::computeGenericSignature(
81858238
// Form the generic signature.
81868239
auto sig = GenericSignature::get(getGenericParams(), requirements);
81878240

8241+
// If any of our explicit conformance requirements were implied by
8242+
// superclass or concrete same-type requirements, we have to build the
8243+
// signature again, since dropping the redundant conformance requirements
8244+
// changes the canonical type computation.
8245+
//
8246+
// However, if we already diagnosed an error, don't do this, because
8247+
// we might end up emitting duplicate diagnostics.
8248+
//
8249+
// Also, don't do this when building a requirement signature.
8250+
if (!buildingRequirementSignature &&
8251+
!Impl->HadAnyError &&
8252+
hasExplicitConformancesImpliedByConcrete()) {
8253+
NumSignaturesRebuiltWithoutRedundantRequirements++;
8254+
8255+
if (rebuildingWithoutRedundantConformances) {
8256+
llvm::errs() << "Rebuilt signature still has "
8257+
<< "redundant conformance requirements: ";
8258+
llvm::errs() << sig << "\n";
8259+
abort();
8260+
}
8261+
8262+
GenericSignatureBuilder newBuilder(Context);
8263+
8264+
for (auto param : sig->getGenericParams())
8265+
newBuilder.addGenericParameter(param);
8266+
8267+
for (auto &req : sig->getRequirements()) {
8268+
newBuilder.addRequirement(stripBoundDependentMemberTypes(req),
8269+
FloatingRequirementSource::forAbstract(), nullptr);
8270+
}
8271+
8272+
return std::move(newBuilder).computeGenericSignature(
8273+
allowConcreteGenericParams,
8274+
buildingRequirementSignature,
8275+
/*rebuildingWithoutRedundantConformances=*/true);
8276+
}
8277+
81888278
#ifndef NDEBUG
81898279
if (!Impl->HadAnyError) {
81908280
checkGenericSignature(sig.getCanonicalSignature(), *this);
@@ -8196,7 +8286,9 @@ GenericSignature GenericSignatureBuilder::computeGenericSignature(
81968286
// will produce the same thing.
81978287
//
81988288
// We cannot do this when there were errors.
8199-
if (allowBuilderToMove && !Impl->HadAnyError) {
8289+
//
8290+
// Also, we cannot do this when building a requirement signature.
8291+
if (!buildingRequirementSignature && !Impl->HadAnyError) {
82008292
// Register this generic signature builder as the canonical builder for the
82018293
// given signature.
82028294
Context.registerGenericSignatureBuilder(sig, std::move(*this));

lib/Sema/TypeCheckDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ RequirementSignatureRequest::evaluate(Evaluator &evaluator,
939939

940940
auto reqSignature = std::move(builder).computeGenericSignature(
941941
/*allowConcreteGenericParams=*/false,
942-
/*allowBuilderToMove=*/false);
942+
/*buildingRequirementSignature=*/true);
943943
return reqSignature->getRequirements();
944944
}
945945

test/Generics/rdar65263302.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
2+
// RUN: %target-swift-frontend -verify -emit-ir %s
3+
4+
public protocol P {
5+
associatedtype Element
6+
}
7+
8+
public class C<O: P>: P {
9+
public typealias Element = O.Element
10+
}
11+
12+
// CHECK: Generic signature: <T, O, E where T : C<E>, O : P, E : P, O.Element == E.Element>
13+
public func toe1<T, O, E>(_: T, _: O, _: E, _: T.Element)
14+
where T : P, // expected-warning {{redundant conformance constraint 'T': 'P'}}
15+
O : P,
16+
O.Element == T.Element,
17+
T : C<E> {} // expected-note {{conformance constraint 'T': 'P' implied here}}
18+
19+
// CHECK: Generic signature: <T, O, E where T : C<E>, O : P, E : P, O.Element == E.Element>
20+
public func toe2<T, O, E>(_: T, _: O, _: E, _: T.Element)
21+
where O : P,
22+
O.Element == T.Element,
23+
T : C<E> {}
24+

test/Generics/rdar75171977.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: %target-swift-frontend -verify -emit-ir %s
2+
3+
public protocol P1 {}
4+
5+
public protocol P2 {
6+
associatedtype A : P1
7+
associatedtype B : P2
8+
func f()
9+
}
10+
11+
public extension P2 where B.A == A {
12+
func f() {}
13+
}
14+
15+
public class C<B: P2>: P2 {
16+
public typealias A = B.A
17+
}

0 commit comments

Comments
 (0)