Skip to content

Commit 2687e93

Browse files
committed
RequirementMachine: Proper handling of identity conformances when computing generating conformances
Consider this example: protocol P { associatedtype X : P where X == Self } Clearly the conformance requirement 'X : P' is redundant, but previously nothing in our formulation would make it so. Here is the rewrite system: (1) [P:X].[P] => [P:X] (2) [P:X] => [P] These two terms overlap on [P:X].[P]; resolving the critical pair introduces the 'identity conformance' [P].[P] => [P]. Homotopy reduction would delete this conformance, but at this point, [P:X].[P] => [P:X] would no longer be redundant, since nothing else proves that [P].[P] => [P]. Now that [P].[P] => [P] is a permanent rule, we can handle this properly; any conformance that appears in a rewrite loop together with an identity conformance without context is completely redundant; it is equivalent to the empty generating conformance path.
1 parent 745acea commit 2687e93

File tree

4 files changed

+86
-33
lines changed

4 files changed

+86
-33
lines changed

lib/AST/RequirementMachine/GeneratingConformances.cpp

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
// they are added by completion, or they are redundant rules written by the
3131
// user.
3232
//
33-
// Using the 3-cells that generate the homotopy relation on rewrite paths,
33+
// Using the rewrite loops that generate the homotopy relation on rewrite paths,
3434
// decompositions can be found for all "derived" conformance rules, producing
3535
// a minimal set of generating conformances.
3636
//
@@ -69,19 +69,17 @@
6969
using namespace swift;
7070
using namespace rewriting;
7171

72-
/// Finds all protocol conformance rules appearing in a 3-cell, both without
73-
/// context, and with a non-empty left context. Applications of rules with a
74-
/// non-empty right context are ignored.
72+
/// Finds all protocol conformance rules appearing in a rewrite loop, both
73+
/// in empty context, and with a non-empty left context. Applications of rules
74+
/// with a non-empty right context are ignored.
7575
///
7676
/// The rules are organized by protocol. For each protocol, the first element
7777
/// of the pair stores conformance rules that appear without context. The
7878
/// second element of the pair stores rules that appear with non-empty left
7979
/// context. For each such rule, the left prefix is also stored alongside.
8080
void HomotopyGenerator::findProtocolConformanceRules(
8181
llvm::SmallDenseMap<const ProtocolDecl *,
82-
std::pair<SmallVector<unsigned, 2>,
83-
SmallVector<std::pair<MutableTerm, unsigned>, 2>>>
84-
&result,
82+
ProtocolConformanceRules, 2> &result,
8583
const RewriteSystem &system) const {
8684

8785
auto redundantRules = findRulesAppearingOnceInEmptyContext(system);
@@ -90,11 +88,13 @@ void HomotopyGenerator::findProtocolConformanceRules(
9088
for (unsigned ruleID : redundantRules) {
9189
const auto &rule = system.getRule(ruleID);
9290

93-
if (rule.isIdentityConformanceRule())
94-
continue;
95-
9691
if (auto *proto = rule.isProtocolConformanceRule()) {
97-
result[proto].first.push_back(ruleID);
92+
if (rule.isIdentityConformanceRule()) {
93+
result[proto].SawIdentityConformance = true;
94+
continue;
95+
}
96+
97+
result[proto].RulesInEmptyContext.push_back(ruleID);
9898
foundAny = true;
9999
}
100100
}
@@ -105,7 +105,7 @@ void HomotopyGenerator::findProtocolConformanceRules(
105105
RewritePathEvaluator evaluator(Basepoint);
106106

107107
// Now look for rewrite steps with conformance rules in empty right context,
108-
// that is something like X.(Y.[P] => Z) (or it's inverse, X.(Z => Y.[P])).
108+
// that is something like X.(Y.[P] => Y) (or it's inverse, X.(Y => Y.[P])).
109109
for (const auto &step : Path) {
110110
if (!evaluator.isInContext()) {
111111
switch (step.Kind) {
@@ -124,7 +124,7 @@ void HomotopyGenerator::findProtocolConformanceRules(
124124
// the prefix term is 'X'.
125125
const auto &term = evaluator.getCurrentTerm();
126126
MutableTerm prefix(term.begin(), term.begin() + step.StartOffset);
127-
result[proto].second.emplace_back(prefix, step.RuleID);
127+
result[proto].RulesInContext.emplace_back(prefix, step.RuleID);
128128
}
129129
}
130130

@@ -279,9 +279,7 @@ void RewriteSystem::computeCandidateConformancePaths(
279279
continue;
280280

281281
llvm::SmallDenseMap<const ProtocolDecl *,
282-
std::pair<SmallVector<unsigned, 2>,
283-
SmallVector<std::pair<MutableTerm, unsigned>, 2>>>
284-
result;
282+
ProtocolConformanceRules, 2> result;
285283

286284
loop.findProtocolConformanceRules(result, *this);
287285

@@ -296,21 +294,18 @@ void RewriteSystem::computeCandidateConformancePaths(
296294

297295
for (const auto &pair : result) {
298296
const auto *proto = pair.first;
299-
const auto &notInContext = pair.second.first;
300-
const auto &inContext = pair.second.second;
297+
const auto &inEmptyContext = pair.second.RulesInEmptyContext;
298+
const auto &inContext = pair.second.RulesInContext;
299+
bool sawIdentityConformance = pair.second.SawIdentityConformance;
301300

302301
// No rules appear without context.
303-
if (notInContext.empty())
304-
continue;
305-
306-
// No replacement rules.
307-
if (notInContext.size() == 1 && inContext.empty())
302+
if (inEmptyContext.empty())
308303
continue;
309304

310305
if (Debug.contains(DebugFlags::GeneratingConformances)) {
311306
llvm::dbgs() << "* Protocol " << proto->getName() << ":\n";
312307
llvm::dbgs() << "** Conformance rules not in context:\n";
313-
for (unsigned ruleID : notInContext) {
308+
for (unsigned ruleID : inEmptyContext) {
314309
llvm::dbgs() << "-- (#" << ruleID << ") " << getRule(ruleID) << "\n";
315310
}
316311

@@ -321,6 +316,10 @@ void RewriteSystem::computeCandidateConformancePaths(
321316
llvm::dbgs() << " (#" << ruleID << ") " << getRule(ruleID) << "\n";
322317
}
323318

319+
if (sawIdentityConformance) {
320+
llvm::dbgs() << "** Equivalent to identity conformance\n";
321+
}
322+
324323
llvm::dbgs() << "\n";
325324
}
326325

@@ -331,8 +330,8 @@ void RewriteSystem::computeCandidateConformancePaths(
331330
//
332331
// (T.[P] => T) := (T'.[P])
333332
// (T'.[P] => T') := (T.[P])
334-
for (unsigned candidateRuleID : notInContext) {
335-
for (unsigned otherRuleID : notInContext) {
333+
for (unsigned candidateRuleID : inEmptyContext) {
334+
for (unsigned otherRuleID : inEmptyContext) {
336335
if (otherRuleID == candidateRuleID)
337336
continue;
338337

@@ -342,11 +341,22 @@ void RewriteSystem::computeCandidateConformancePaths(
342341
}
343342
}
344343

345-
// Suppose a 3-cell contains a conformance rule (T.[P] => T) in an empty
346-
// context, and a conformance rule (V.[P] => V) with a non-empty left
344+
// If a rewrite loop contains a conformance rule (T.[P] => T) together
345+
// with the identity conformance ([P].[P] => [P]), both in empty context,
346+
// the conformance rule (T.[P] => T) is equivalent to the *empty product*
347+
// of conformance rules; that is, it is trivially redundant.
348+
if (sawIdentityConformance) {
349+
for (unsigned candidateRuleID : inEmptyContext) {
350+
SmallVector<unsigned, 2> emptyPath;
351+
conformancePaths[candidateRuleID].push_back(emptyPath);
352+
}
353+
}
354+
355+
// Suppose a rewrite loop contains a conformance rule (T.[P] => T) in
356+
// empty context, and a conformance rule (V.[P] => V) in non-empty left
347357
// context U.
348358
//
349-
// The 3-cell looks something like this:
359+
// The rewrite loop looks something like this:
350360
//
351361
// ... ⊗ (T.[P] => T) ⊗ ... ⊗ U.(V => V.[P]) ⊗ ...
352362
// ^ ^
@@ -381,7 +391,7 @@ void RewriteSystem::computeCandidateConformancePaths(
381391

382392
// This decomposition defines a conformance access path for each
383393
// conformance rule we saw in empty context.
384-
for (unsigned otherRuleID : notInContext)
394+
for (unsigned otherRuleID : inEmptyContext)
385395
conformancePaths[otherRuleID].push_back(conformancePath);
386396
}
387397
}
@@ -470,6 +480,11 @@ bool RewriteSystem::isValidRefinementPath(
470480
void RewriteSystem::dumpConformancePath(
471481
llvm::raw_ostream &out,
472482
const SmallVectorImpl<unsigned> &path) const {
483+
if (path.empty()) {
484+
out << "1";
485+
return;
486+
}
487+
473488
for (unsigned ruleID : path)
474489
out << "(" << getRule(ruleID).getLHS() << ")";
475490
}
@@ -504,6 +519,9 @@ void RewriteSystem::verifyGeneratingConformanceEquations(
504519
(void) simplify(baseTerm);
505520

506521
for (const auto &path : pair.second) {
522+
if (path.empty())
523+
continue;
524+
507525
const auto &otherRule = getRule(path.back());
508526
auto *otherProto = otherRule.getLHS().back().getProtocol();
509527

lib/AST/RequirementMachine/RewriteSystem.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,15 @@ class RewritePath {
356356
const RewriteSystem &system) const;
357357
};
358358

359+
/// Information about protocol conformance rules appearing in a rewrite loop.
360+
///
361+
/// This is the return value of HomotopyGenerator::findProtocolConformanceRules().
362+
struct ProtocolConformanceRules {
363+
SmallVector<unsigned, 2> RulesInEmptyContext;
364+
SmallVector<std::pair<MutableTerm, unsigned>, 2> RulesInContext;
365+
bool SawIdentityConformance = false;
366+
};
367+
359368
/// A loop (3-cell) that rewrites the basepoint back to the basepoint.
360369
class HomotopyGenerator {
361370
public:
@@ -387,9 +396,7 @@ class HomotopyGenerator {
387396

388397
void findProtocolConformanceRules(
389398
llvm::SmallDenseMap<const ProtocolDecl *,
390-
std::pair<SmallVector<unsigned, 2>,
391-
SmallVector<std::pair<MutableTerm, unsigned>, 2>>>
392-
&result,
399+
ProtocolConformanceRules, 2> &result,
393400
const RewriteSystem &system) const;
394401

395402
void dump(llvm::raw_ostream &out, const RewriteSystem &system) const;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %target-swift-frontend -typecheck %s -debug-generic-signatures -requirement-machine-protocol-signatures=on 2>&1 | %FileCheck %s
2+
3+
// CHECK-LABEL: identity_conformance.(file).P1@
4+
// CHECK: Requirement signature: <Self where Self == Self.X>
5+
protocol P1 {
6+
associatedtype X : P1 where X == Self
7+
}
8+
9+
// CHECK-LABEL: identity_conformance.(file).P2@
10+
// CHECK: Requirement signature: <Self where Self == Self.X>
11+
protocol P2 {
12+
associatedtype X where X == Self
13+
}

test/Generics/trivial_reduction.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %target-swift-frontend -typecheck %s -debug-generic-signatures -requirement-machine-protocol-signatures=on 2>&1 | %FileCheck %s
2+
3+
// CHECK-LABEL: trivial_reduction.(file).P1@
4+
// CHECK-LABEL: Requirement signature: <Self where Self == Self.C.C, Self.C : P1, Self.C == Self.R>
5+
protocol P1 {
6+
associatedtype R : P1 where R.R == Self
7+
associatedtype C : P1 where C.C == Self, C.R == Self, R.C.R.C == Self
8+
}
9+
10+
// CHECK-LABEL: trivial_reduction.(file).P2@
11+
// CHECK-LABEL: Requirement signature: <Self where Self == Self.C, Self.C == Self.R>
12+
protocol P2 {
13+
associatedtype R : P2 where R.R == Self
14+
associatedtype C : P2 where C.C.C == Self, C.C.R.C.C.R == Self, R.C.R.C.R.C == Self
15+
}

0 commit comments

Comments
 (0)