Skip to content

Commit 5281426

Browse files
committed
RequirementMachine: Fix minimization when protocol is constrained to a class that conforms to the protocol
Consider this example: protocol P : C {} class C : P {} <T where T : P> The GenericSignatureBuilder thinks the minimized signature is <T where T : P>. The RequirementMachine would minimize it down to <T where T : C>. The latter is more correct, since the conformance here is concrete and no witness table needs to be passed in at runtime, however for strict binary compatibility we need to produce the same signature as the GenericSignatureBuilder. Accomplish this by changing the minimal conformances algorithm to detect "circular concrete conformance rules", which take the form [P].[concrete: C : Q] Where Q : P. These rules are given special handling. Ordinarily a protocol conformance rule is eliminated before a concrete conformance rule; however concrete conformances derived from circular conformances are considered to be redundant from the get-go, preventing protocol conformances that can be written in terms of such concrete conformances from themselves becoming redundant. Fixes rdar://problem/89633532.
1 parent 4c33aab commit 5281426

File tree

7 files changed

+223
-54
lines changed

7 files changed

+223
-54
lines changed

lib/AST/RequirementMachine/ConcreteContraction.cpp

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ class ConcreteContraction {
159159

160160
llvm::SmallDenseMap<GenericParamKey, Type> ConcreteTypes;
161161
llvm::SmallDenseMap<GenericParamKey, Type> Superclasses;
162+
llvm::SmallDenseMap<GenericParamKey,
163+
llvm::SmallVector<ProtocolDecl *, 1>> Conformances;
162164

163165
Optional<Type> substTypeParameter(GenericTypeParamType *rootParam,
164166
DependentMemberType *memberType,
@@ -436,8 +438,8 @@ bool ConcreteContraction::performConcreteContraction(
436438
if (Debug) {
437439
llvm::dbgs() << "@ Concrete contraction cannot proceed: "
438440
<< "duplicate concrete type requirements\n";
439-
return false;
440441
}
442+
return false;
441443
}
442444

443445
break;
@@ -454,18 +456,54 @@ bool ConcreteContraction::performConcreteContraction(
454456
if (Debug) {
455457
llvm::dbgs() << "@ Concrete contraction cannot proceed: "
456458
<< "duplicate superclass requirements\n";
457-
return false;
458459
}
460+
return false;
459461
}
460462

461463
break;
462464
}
463-
case RequirementKind::Conformance:
465+
case RequirementKind::Conformance: {
466+
auto *protoDecl = req.req.getProtocolDecl();
467+
Conformances[GenericParamKey(genericParam)].push_back(protoDecl);
468+
469+
break;
470+
}
464471
case RequirementKind::Layout:
465472
break;
466473
}
467474
}
468475

476+
// Block concrete contraction if a generic parameter conforms to a protocol P
477+
// which has a superclass bound C which again conforms to P. This is a really
478+
// silly edge case, but we go to great pains to produce the same minimized
479+
// signature as the GenericSignatureBuilder in this case, <T : P>, and not the
480+
// more logical <T : C>.
481+
for (const auto &pair : Conformances) {
482+
auto subjectType = pair.first;
483+
auto found = Superclasses.find(subjectType);
484+
if (found == Superclasses.end())
485+
continue;
486+
487+
auto superclassTy = found->second;
488+
489+
for (const auto *proto : pair.second) {
490+
if (auto otherSuperclassTy = proto->getSuperclass()) {
491+
if (Debug) {
492+
llvm::dbgs() << "@ Subject type of superclass requirement "
493+
<< "τ_" << subjectType.Depth << "_" << subjectType.Index
494+
<< " : " << superclassTy << " conforms to "
495+
<< proto->getName() << " which has a superclass bound "
496+
<< otherSuperclassTy << "\n";
497+
}
498+
499+
if (superclassTy->isEqual(otherSuperclassTy)) {
500+
Superclasses.erase(subjectType);
501+
break;
502+
}
503+
}
504+
}
505+
}
506+
469507
// If there's nothing to do just return.
470508
if (ConcreteTypes.empty() && Superclasses.empty())
471509
return false;

lib/AST/RequirementMachine/MinimalConformances.cpp

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ class MinimalConformances {
195195
MutableTerm term, unsigned ruleID,
196196
SmallVectorImpl<unsigned> &result) const;
197197

198+
bool isConformanceRuleRecoverable(
199+
llvm::SmallDenseSet<unsigned, 4> &visited,
200+
unsigned ruleID) const;
201+
202+
bool isDerivedViaCircularConformanceRule(
203+
const std::vector<SmallVector<unsigned, 2>> &paths) const;
204+
198205
bool isValidConformancePath(
199206
llvm::SmallDenseSet<unsigned, 4> &visited,
200207
const llvm::SmallVectorImpl<unsigned> &path) const;
@@ -601,57 +608,71 @@ void MinimalConformances::computeCandidateConformancePaths() {
601608
}
602609
}
603610

604-
/// Determines if \p path can be expressed without any of the conformance
605-
/// rules appearing in \p redundantConformances, by possibly substituting
606-
/// any occurrences of the redundant rules with alternate definitions
607-
/// appearing in \p conformancePaths.
611+
/// If \p ruleID is redundant, determines if it can be expressed without
612+
/// without any of the conformance rules determined to be redundant so far.
608613
///
609-
/// The \p conformancePaths map sends conformance rules to a list of
610-
/// disjunctions, where each disjunction is a product of other conformance
611-
/// rules.
612-
bool MinimalConformances::isValidConformancePath(
614+
/// If the rule is not redundant, determines if its parent path can
615+
/// also be recovered.
616+
bool MinimalConformances::isConformanceRuleRecoverable(
613617
llvm::SmallDenseSet<unsigned, 4> &visited,
614-
const llvm::SmallVectorImpl<unsigned> &path) const {
618+
unsigned ruleID) const {
619+
if (RedundantConformances.count(ruleID)) {
620+
SWIFT_DEFER {
621+
visited.erase(ruleID);
622+
};
623+
visited.insert(ruleID);
615624

616-
for (unsigned ruleID : path) {
617-
if (visited.count(ruleID) > 0)
625+
auto found = ConformancePaths.find(ruleID);
626+
if (found == ConformancePaths.end())
618627
return false;
619628

620-
if (RedundantConformances.count(ruleID)) {
629+
bool foundValidConformancePath = false;
630+
for (const auto &otherPath : found->second) {
631+
if (isValidConformancePath(visited, otherPath)) {
632+
foundValidConformancePath = true;
633+
break;
634+
}
635+
}
636+
637+
if (!foundValidConformancePath)
638+
return false;
639+
} else {
640+
auto found = ParentPaths.find(ruleID);
641+
if (found != ParentPaths.end()) {
621642
SWIFT_DEFER {
622643
visited.erase(ruleID);
623644
};
624645
visited.insert(ruleID);
625646

626-
auto found = ConformancePaths.find(ruleID);
627-
if (found == ConformancePaths.end())
647+
// If 'req' is based on some other conformance requirement
648+
// `T.[P.]A : Q', we want to make sure that we have a
649+
// non-redundant derivation for 'T : P'.
650+
if (!isValidConformancePath(visited, found->second))
628651
return false;
652+
}
653+
}
629654

630-
bool foundValidConformancePath = false;
631-
for (const auto &otherPath : found->second) {
632-
if (isValidConformancePath(visited, otherPath)) {
633-
foundValidConformancePath = true;
634-
break;
635-
}
636-
}
655+
return true;
656+
}
637657

638-
if (!foundValidConformancePath)
639-
return false;
640-
} else {
641-
auto found = ParentPaths.find(ruleID);
642-
if (found != ParentPaths.end()) {
643-
SWIFT_DEFER {
644-
visited.erase(ruleID);
645-
};
646-
visited.insert(ruleID);
647-
648-
// If 'req' is based on some other conformance requirement
649-
// `T.[P.]A : Q', we want to make sure that we have a
650-
// non-redundant derivation for 'T : P'.
651-
if (!isValidConformancePath(visited, found->second))
652-
return false;
653-
}
654-
}
658+
/// Determines if \p path can be expressed without any of the conformance
659+
/// rules determined to be redundant so far, by possibly substituting
660+
/// any occurrences of the redundant rules with alternate definitions
661+
/// appearing in the set of known conformancePaths.
662+
///
663+
/// The conformance path map sends conformance rules to a list of
664+
/// disjunctions, where each disjunction is a product of other conformance
665+
/// rules.
666+
bool MinimalConformances::isValidConformancePath(
667+
llvm::SmallDenseSet<unsigned, 4> &visited,
668+
const llvm::SmallVectorImpl<unsigned> &path) const {
669+
670+
for (unsigned ruleID : path) {
671+
if (visited.count(ruleID) > 0)
672+
return false;
673+
674+
if (!isConformanceRuleRecoverable(visited, ruleID))
675+
return false;
655676
}
656677

657678
return true;
@@ -786,12 +807,45 @@ void MinimalConformances::verifyMinimalConformanceEquations() const {
786807
#endif
787808
}
788809

810+
bool MinimalConformances::isDerivedViaCircularConformanceRule(
811+
const std::vector<SmallVector<unsigned, 2>> &paths) const {
812+
for (const auto &path : paths) {
813+
if (!path.empty() &&
814+
System.getRule(path.back()).isCircularConformanceRule())
815+
return true;
816+
}
817+
818+
return false;
819+
}
820+
789821
/// Find a set of minimal conformances by marking all non-minimal
790822
/// conformances redundant.
791823
///
792824
/// In the first pass, we only consider conformance requirements that are
793825
/// made redundant by concrete conformances.
794826
void MinimalConformances::computeMinimalConformances() {
827+
// First, mark any concrete conformances derived via a circular
828+
// conformance as redundant upfront. See the comment at the top of
829+
// Rule::isCircularConformanceRule() for an explanation of this.
830+
for (unsigned ruleID : ConformanceRules) {
831+
auto found = ConformancePaths.find(ruleID);
832+
if (found == ConformancePaths.end())
833+
continue;
834+
835+
const auto &paths = found->second;
836+
837+
if (System.getRule(ruleID).isProtocolConformanceRule())
838+
continue;
839+
840+
if (isDerivedViaCircularConformanceRule(paths))
841+
RedundantConformances.insert(ruleID);
842+
}
843+
844+
// Now, visit each conformance rule, trying to make it redundant by
845+
// deriving a path in terms of other non-redundant conformance rules.
846+
//
847+
// Note that the ConformanceRules vector is sorted in descending
848+
// canonical term order, so less canonical rules are eliminated first.
795849
for (unsigned ruleID : ConformanceRules) {
796850
auto found = ConformancePaths.find(ruleID);
797851
if (found == ConformancePaths.end())
@@ -840,10 +894,7 @@ void MinimalConformances::verifyMinimalConformances() const {
840894
// minimal conformances.
841895
llvm::SmallDenseSet<unsigned, 4> visited;
842896

843-
llvm::SmallVector<unsigned, 1> path;
844-
path.push_back(ruleID);
845-
846-
if (!isValidConformancePath(visited, path)) {
897+
if (!isConformanceRuleRecoverable(visited, ruleID)) {
847898
llvm::errs() << "Redundant conformance is not recoverable:\n";
848899
llvm::errs() << rule << "\n\n";
849900
dumpMinimalConformanceEquations(llvm::errs());

lib/AST/RequirementMachine/RewriteSystem.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,32 @@ bool Rule::isProtocolRefinementRule() const {
120120
return false;
121121
}
122122

123+
/// If this is a rule of the form [P].[concrete: C : Q] => [P] where
124+
/// [P] is a protocol symbol, return true.
125+
///
126+
/// This means that P constrains 'Self' to a concrete type that conforms
127+
/// to some Q with P : Q. We don't consider this to be a valid conformance
128+
/// path element, to ensure compatibility with the GSB in an odd edge
129+
/// case:
130+
///
131+
/// protocol P : C {}
132+
/// class C : P {}
133+
///
134+
/// The GSB minimizes the signature <T where T : P> to <T where T : P>,
135+
/// whereas the minimal conformances algorithm would otherwise minimize
136+
/// it down to <T where T : C> on account of the (T.[P] => T) conformance
137+
/// rule being redundantly expressed via [P].[concrete: C : P].
138+
bool Rule::isCircularConformanceRule() const {
139+
if (LHS.size() != 2 || RHS.size() != 1 || LHS[0] != RHS[0])
140+
return false;
141+
142+
if (LHS[0].getKind() != Symbol::Kind::Protocol ||
143+
LHS[1].getKind() != Symbol::Kind::ConcreteConformance)
144+
return false;
145+
146+
return true;
147+
}
148+
123149
/// A protocol typealias rule takes one of the following two forms,
124150
/// where T is a name symbol:
125151
///

lib/AST/RequirementMachine/RewriteSystem.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class Rule final {
106106

107107
bool isProtocolRefinementRule() const;
108108

109+
bool isCircularConformanceRule() const;
110+
109111
/// See above for an explanation of these predicates.
110112
bool isPermanent() const {
111113
return Permanent;

test/Generics/derived_via_concrete.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
// RUN: %target-typecheck-verify-swift -requirement-machine-inferred-signatures=verify
2-
// RUN: %target-swift-frontend -typecheck %s -debug-generic-signatures -requirement-machine-inferred-signatures=verify 2>&1 | %FileCheck %s
1+
// RUN: %target-typecheck-verify-swift -requirement-machine-inferred-signatures=off
2+
// RUN: not %target-swift-frontend -typecheck %s -debug-generic-signatures -requirement-machine-inferred-signatures=on 2>&1 | %FileCheck %s
3+
4+
// FIXME: Both RUN lines should pass 'on' once diagnostics are implemented.
35

46
protocol P {}
57
class C {}
@@ -38,7 +40,11 @@ func derivedViaConcreteY1<A, B>(_: A, _: B)
3840
// expected-warning@-1 {{redundant conformance constraint 'A' : 'V'}}
3941
// expected-note@-2 {{conformance constraint 'A' : 'V' implied here}}
4042

41-
// CHECK: Generic signature: <A, B where A : Y<B>, B : C>
43+
// CHECK: Generic signature: <A, B where A : Y<B>>
44+
//
45+
// FIXME: Should be <A, B where A : Y<B>, B : C>, but redundant
46+
// superclass requirements currently block concrete contraction.
47+
4248
func derivedViaConcreteY2<A, B>(_: A, _: B)
4349
where A : V, B : C, A : Y<B> {}
4450
// expected-warning@-1 {{redundant conformance constraint 'A' : 'V'}}

test/Generics/protocol_superclass_cycle.swift

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,54 @@
1-
// RUN: %target-typecheck-verify-swift
2-
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
1+
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s -requirement-machine-protocol-signatures=verify -requirement-machine-inferred-signatures=verify 2>&1 | %FileCheck %s
2+
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s -requirement-machine-protocol-signatures=verify -requirement-machine-inferred-signatures=verify -disable-requirement-machine-concrete-contraction 2>&1 | %FileCheck %s
33

44
protocol P : C {}
5-
final class C : P {}
5+
class C : P {}
6+
7+
// CHECK-LABEL: .SubP@
8+
// CHECK-NEXT: Requirement signature: <Self where Self : P>
9+
protocol SubP : P {}
10+
11+
protocol Q {
12+
associatedtype T
13+
}
14+
15+
// CHECK-LABEL: .foo1@
16+
// CHECK-NEXT: Generic signature: <T where T : P>
17+
func foo1<T : P>(_: T) {}
18+
19+
// CHECK-LABEL: .foo1a@
20+
// CHECK-NEXT: Generic signature: <T where T : P>
21+
func foo1a<T>(_: T) where T : P, T : C {}
22+
23+
// CHECK-LABEL: .foo1b@
24+
// CHECK-NEXT: Generic signature: <T where T : P>
25+
func foo1b<T>(_: T) where T : C, T : P {}
26+
27+
// CHECK-LABEL: .foo2@
28+
// CHECK-NEXT: Generic signature: <T where T : Q, T.[Q]T : P>
29+
func foo2<T : Q>(_: T) where T.T : P {}
30+
31+
// CHECK-LABEL: .foo3@
32+
// CHECK-NEXT: Generic signature: <T where T : SubP>
33+
func foo3<T : SubP>(_: T) {}
34+
35+
// CHECK-LABEL: .foo4@
36+
// CHECK-NEXT: Generic signature: <T where T : Q, T.[Q]T : SubP>
37+
func foo4<T : Q>(_: T) where T.T : SubP {}
38+
39+
protocol SuperP {}
40+
41+
// CHECK-LABEL: .SubSuperP@
42+
// CHECK-NEXT: Requirement signature: <Self where Self : SuperC>
43+
protocol SubSuperP : SuperP, SuperC {}
44+
45+
class SuperC : SubSuperP {}
46+
47+
// CHECK-LABEL: .foo5@
48+
// CHECK-NEXT: Generic signature: <T where T : SubSuperP>
49+
func foo5<T : SubSuperP>(_: T) {}
50+
51+
// CHECK-LABEL: .foo6@
52+
// CHECK-NEXT: Generic signature: <T where T : Q, T.[Q]T : SubSuperP>
53+
func foo6<T : Q>(_: T) where T.T : SubSuperP {}
654

7-
// CHECK: Generic signature: <T where T : P>
8-
func foo<T : P>(_: T) {}

test/SILGen/protocol_superclass_cycle.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -emit-silgen %s
1+
// RUN: %target-swift-frontend -emit-silgen -requirement-machine-protocol-signatures=on -requirement-machine-inferred-signatures=on %s
22

33
// When a protocol has a superclass requirement on 'Self' and the class
44
// itself conforms concretely, the forwarding substitution map will have

0 commit comments

Comments
 (0)