Skip to content

Commit eb9f121

Browse files
committed
RequirementMachine: Conditional requirement inference
If a type parameter is subject to both a conformance requirement and a concrete type requirement, the concrete type might conform conditionally. In this case, introduce new requirements to satisfy the conditional conformance. Since this can add new hitherto-unseen protocols to the rewrite system, restrict this feature to top-level generic signatures, and not protocol requirement signatures. Allowing this to occur in protocol requirement signatures would change the connectivity of the protocol dependency graph (and hence the connected components) during completion, which would be a major complication in the design. The GSB already enforces this restriction. I changed the existing conditional_requirement_inference.swift test to run with -requirement-machine-inferred-signatures=verify. Since one of the test cases there triggers an unrelated bug in the Requirement Machine, I split it off into a new file named conditional_requirement_inference_2.swift which still runs with the GSB. Once the bug is fixed I'll merge the files again.
1 parent 75dfab0 commit eb9f121

File tree

7 files changed

+176
-57
lines changed

7 files changed

+176
-57
lines changed

lib/AST/RequirementMachine/Debug.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,21 @@ enum class DebugFlags : unsigned {
3838
/// Print debug output when concretizing nested types in the property map.
3939
ConcretizeNestedTypes = (1<<5),
4040

41+
/// Print debug output when inferring conditional requirements in the
42+
/// property map.
43+
ConditionalRequirements = (1<<6),
44+
4145
/// Print debug output from the homotopy reduction algorithm.
42-
HomotopyReduction = (1<<6),
46+
HomotopyReduction = (1<<7),
4347

4448
/// Print debug output from the minimal conformances algorithm.
45-
MinimalConformances = (1<<7),
49+
MinimalConformances = (1<<8),
4650

4751
/// Print debug output from the protocol dependency graph.
48-
ProtocolDependencies = (1<<8),
52+
ProtocolDependencies = (1<<9),
4953

5054
/// Print debug output from generic signature minimization.
51-
Minimization = (1<<9),
55+
Minimization = (1<<10),
5256
};
5357

5458
using DebugOptions = OptionSet<DebugFlags>;

lib/AST/RequirementMachine/PropertyMap.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ class PropertyMap {
264264
ProtocolConformance *concrete,
265265
AssociatedTypeDecl *assocType) const;
266266

267+
void inferConditionalRequirements(
268+
ProtocolConformance *concrete,
269+
ArrayRef<Term> substitutions) const;
270+
267271
MutableTerm computeConstraintTermForTypeWitness(
268272
Term key, RequirementKind requirementKind,
269273
CanType concreteType, CanType typeWitness,
@@ -284,4 +288,4 @@ class PropertyMap {
284288

285289
} // end namespace swift
286290

287-
#endif
291+
#endif

lib/AST/RequirementMachine/PropertyUnification.cpp

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <vector>
3434

3535
#include "PropertyMap.h"
36+
#include "RequirementLowering.h"
3637

3738
using namespace swift;
3839
using namespace rewriting;
@@ -703,15 +704,16 @@ void PropertyMap::concretizeNestedTypesFromConcreteParent(
703704
recordConcreteConformanceRule(concreteRuleID, conformanceRuleID,
704705
requirementKind, concreteConformanceSymbol);
705706

706-
auto assocTypes = proto->getAssociatedTypeMembers();
707-
if (assocTypes.empty())
708-
continue;
709-
710-
for (auto *assocType : assocTypes) {
707+
for (auto *assocType : proto->getAssociatedTypeMembers()) {
711708
concretizeTypeWitnessInConformance(key, requirementKind,
712709
concreteConformanceSymbol,
713710
concrete, assocType);
714711
}
712+
713+
// We only infer conditional requirements in top-level generic signatures,
714+
// not in protocol requirement signatures.
715+
if (key.getRootProtocols().empty())
716+
inferConditionalRequirements(concrete, substitutions);
715717
}
716718
}
717719

@@ -1002,3 +1004,80 @@ void PropertyMap::recordConcreteConformanceRule(
10021004

10031005
(void) System.addRule(std::move(lhs), std::move(rhs), &path);
10041006
}
1007+
1008+
/// If \p key is fixed to a concrete type and is also subject to a conformance
1009+
/// requirement, the concrete type might conform conditionally. In this case,
1010+
/// introduce rules for any conditional requirements in the given conformance.
1011+
void PropertyMap::inferConditionalRequirements(
1012+
ProtocolConformance *concrete, ArrayRef<Term> substitutions) const {
1013+
1014+
auto conditionalRequirements = concrete->getConditionalRequirements();
1015+
1016+
if (Debug.contains(DebugFlags::ConditionalRequirements)) {
1017+
if (conditionalRequirements.empty())
1018+
llvm::dbgs() << "@@ No conditional requirements from ";
1019+
else
1020+
llvm::dbgs() << "@@ Inferring conditional requirements from ";
1021+
1022+
llvm::dbgs() << concrete->getType() << " : ";
1023+
llvm::dbgs() << concrete->getProtocol()->getName() << "\n";
1024+
}
1025+
1026+
if (conditionalRequirements.empty())
1027+
return;
1028+
1029+
SmallVector<Requirement, 2> desugaredRequirements;
1030+
1031+
// First, desugar all conditional requirements.
1032+
for (auto req : conditionalRequirements) {
1033+
if (Debug.contains(DebugFlags::ConditionalRequirements)) {
1034+
llvm::dbgs() << "@@@ Original requirement: ";
1035+
req.dump(llvm::dbgs());
1036+
llvm::dbgs() << "\n";
1037+
}
1038+
1039+
desugarRequirement(req, desugaredRequirements);
1040+
}
1041+
1042+
// Now, convert desugared conditional requirements to rules.
1043+
for (auto req : desugaredRequirements) {
1044+
if (Debug.contains(DebugFlags::ConditionalRequirements)) {
1045+
llvm::dbgs() << "@@@ Desugared requirement: ";
1046+
req.dump(llvm::dbgs());
1047+
llvm::dbgs() << "\n";
1048+
}
1049+
1050+
if (req.getKind() == RequirementKind::Conformance) {
1051+
auto *proto = req.getProtocolDecl();
1052+
1053+
// If we haven't seen this protocol before, add rules for its
1054+
// requirements.
1055+
if (!System.isKnownProtocol(proto)) {
1056+
if (Debug.contains(DebugFlags::ConditionalRequirements)) {
1057+
llvm::dbgs() << "@@@ Unknown protocol: "<< proto->getName() << "\n";
1058+
}
1059+
1060+
RuleBuilder builder(Context, System.getProtocolMap());
1061+
builder.addProtocol(proto, /*initialComponent=*/false);
1062+
builder.collectRulesFromReferencedProtocols();
1063+
1064+
for (const auto &rule : builder.PermanentRules)
1065+
System.addPermanentRule(rule.first, rule.second);
1066+
1067+
for (const auto &rule : builder.RequirementRules)
1068+
System.addExplicitRule(rule.first, rule.second);
1069+
}
1070+
}
1071+
1072+
auto pair = getRuleForRequirement(req.getCanonical(), /*proto=*/nullptr,
1073+
substitutions, Context);
1074+
1075+
if (Debug.contains(DebugFlags::ConditionalRequirements)) {
1076+
llvm::dbgs() << "@@@ Induced rule from conditional requirement: "
1077+
<< pair.first << " => " << pair.second << "\n";
1078+
}
1079+
1080+
// FIXME: Do we need a rewrite path here?
1081+
(void) System.addRule(pair.first, pair.second);
1082+
}
1083+
}

lib/AST/RequirementMachine/RewriteContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ static DebugOptions parseDebugFlags(StringRef debugFlags) {
3333
.Case("completion", DebugFlags::Completion)
3434
.Case("concrete-unification", DebugFlags::ConcreteUnification)
3535
.Case("concretize-nested-types", DebugFlags::ConcretizeNestedTypes)
36+
.Case("conditional-requirements", DebugFlags::ConditionalRequirements)
3637
.Case("homotopy-reduction", DebugFlags::HomotopyReduction)
3738
.Case("minimal-conformances", DebugFlags::MinimalConformances)
3839
.Case("protocol-dependencies", DebugFlags::ProtocolDependencies)
Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,25 @@
11
// RUN: %target-typecheck-verify-swift
2-
// RUN: not %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
2+
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures -requirement-machine-inferred-signatures=verify %s 2>&1 | %FileCheck %s
33

4+
protocol Equatable {}
5+
6+
struct Array<Element> {}
7+
8+
extension Array : Equatable where Element : Equatable {}
49

5-
// Valid example
610
struct EquatableBox<T : Equatable> {
711
// CHECK: Generic signature: <T, U where T == Array<U>, U : Equatable>
812
func withArray<U>(_: U) where T == Array<U> {}
913
}
1014

11-
struct EquatableSequenceBox<T : Sequence> where T.Element : Equatable {
12-
// CHECK: Generic signature: <T, U where T == Array<Array<U>>, U : Equatable>
13-
func withArrayArray<U>(_: U) where T == Array<Array<U>> {}
14-
}
15-
16-
17-
// A very elaborate invalid example (see comment in mergeP1AndP2())
18-
struct G<T> {}
15+
// A conditional requirement with a protocol we haven't seen before.
16+
protocol First {}
1917

20-
protocol P {}
21-
extension G : P where T : P {}
18+
protocol Second {}
2219

23-
protocol P1 {
24-
associatedtype T
25-
associatedtype U where U == G<T>
26-
associatedtype R : P1
27-
}
28-
29-
protocol P2 {
30-
associatedtype U : P
31-
associatedtype R : P2
32-
}
20+
extension Array : First where Element : Second {}
3321

34-
func takesP<T : P>(_: T.Type) {}
35-
// expected-note@-1 {{where 'T' = 'T.T'}}
36-
// expected-note@-2 {{where 'T' = 'T.R.T'}}
37-
// expected-note@-3 {{where 'T' = 'T.R.R.T'}}
38-
// expected-note@-4 {{where 'T' = 'T.R.R.R.T'}}
39-
40-
// CHECK: Generic signature: <T where T : P1, T : P2>
41-
func mergeP1AndP2<T : P1 & P2>(_: T) {
42-
// P1 implies that T.(R)*.U == G<T.(R)*.T>, and P2 implies that T.(R)*.U : P.
43-
//
44-
// These together would seem to imply that G<T.(R)*.T> : P, therefore
45-
// the conditional conformance G : P should imply that T.(R)*.T : P.
46-
//
47-
// However, this would require us to infer an infinite number of
48-
// conformance requirements in the signature of mergeP1AndP2() of the
49-
// form T.(R)*.T : P.
50-
//
51-
// Since we're unable to represent that, make sure that a) we don't crash,
52-
// b) we reject the conformance T.(R)*.T : P.
53-
54-
takesP(T.T.self) // expected-error {{global function 'takesP' requires that 'T.T' conform to 'P'}}
55-
takesP(T.R.T.self) // expected-error {{global function 'takesP' requires that 'T.R.T' conform to 'P'}}
56-
takesP(T.R.R.T.self) // expected-error {{global function 'takesP' requires that 'T.R.R.T' conform to 'P'}}
57-
takesP(T.R.R.R.T.self) // expected-error {{global function 'takesP' requires that 'T.R.R.R.T' conform to 'P'}}
58-
}
22+
struct SillyBox<T : First> {
23+
// CHECK: Generic signature: <T, U where T == Array<U>, U : Second>
24+
func withArray<U>(_: U) where T == Array<U> {}
25+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %target-typecheck-verify-swift
2+
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
3+
4+
// A more complicated example.
5+
protocol Equatable {}
6+
7+
struct Array<Element> {}
8+
9+
extension Array : Equatable where Element : Equatable {}
10+
11+
protocol Sequence {
12+
associatedtype Element
13+
}
14+
15+
extension Array : Sequence {}
16+
17+
struct EquatableSequenceBox<T : Sequence> where T.Element : Equatable {
18+
// CHECK: Generic signature: <T, U where T == Array<Array<U>>, U : Equatable>
19+
func withArrayArray<U>(_: U) where T == Array<Array<U>> {}
20+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// RUN: %target-typecheck-verify-swift
2+
// RUN: not %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
3+
4+
// A very elaborate invalid example (see comment in mergeP1AndP2())
5+
struct G<T> {}
6+
7+
protocol P {}
8+
extension G : P where T : P {}
9+
10+
protocol P1 {
11+
associatedtype T
12+
associatedtype U where U == G<T>
13+
associatedtype R : P1
14+
}
15+
16+
protocol P2 {
17+
associatedtype U : P
18+
associatedtype R : P2
19+
}
20+
21+
func takesP<T : P>(_: T.Type) {}
22+
// expected-note@-1 {{where 'T' = 'T.R.T'}}
23+
// expected-note@-2 {{where 'T' = 'T.R.R.T'}}
24+
// expected-note@-3 {{where 'T' = 'T.R.R.R.T'}}
25+
26+
// CHECK: Generic signature: <T where T : P1, T : P2>
27+
func mergeP1AndP2<T : P1 & P2>(_: T) {
28+
// P1 implies that T.(R)*.U == G<T.(R)*.T>, and P2 implies that T.(R)*.U : P.
29+
//
30+
// These together would seem to imply that G<T.(R)*.T> : P, therefore
31+
// the conditional conformance G : P should imply that T.(R)*.T : P.
32+
//
33+
// However, this would require us to infer an infinite number of
34+
// conformance requirements in the signature of mergeP1AndP2() of the
35+
// form T.(R)*.T : P.
36+
//
37+
// Since we're unable to represent that, make sure that a) we don't crash,
38+
// b) we reject the conformance T.(R)*.T : P.
39+
40+
takesP(T.T.self)
41+
takesP(T.R.T.self) // expected-error {{global function 'takesP' requires that 'T.R.T' conform to 'P'}}
42+
takesP(T.R.R.T.self) // expected-error {{global function 'takesP' requires that 'T.R.R.T' conform to 'P'}}
43+
takesP(T.R.R.R.T.self) // expected-error {{global function 'takesP' requires that 'T.R.R.R.T' conform to 'P'}}
44+
}

0 commit comments

Comments
 (0)