Skip to content

Commit 6d89b42

Browse files
committed
RequirementMachine: Simplify concrete substitutions when adding a new rule
Suppose we have these rules: (1) [P].[P] => [P] (2) [P].[P:X] => [P:X] (3) [P].[P:Y] => [P:Y] (4) [P:X].[concrete: G<τ_0_0> with <[P:Y]>] Rule (2) and (4) overlap on the following term, which has had the concrete type adjustment applied: [P].[P:X].[concrete: G<τ_0_0> with <[P].[P:Y]>] The critical pair is obtained by applying rule (2) to both sides of the branching is [P:X].[concrete: G<τ_0_0> with <[P].[P:Y]>] => [P:X] Note that this is a distinct rule from (4), and again this new rule overlaps with (2), producing another rule [P:X].[concrete: G<τ_0_0> with <[P].[P].[P:Y]>] => [P:X] This process doesn't terminate. The root cause of this problem is the existence of rule (1), which appears when there are multiple non-trivial conformance paths witnessing the conformance Self : P. This occurs when a same-type requirement is defined between Self and some other type conforming to P. To make this work, we need to simplify concrete substitutions when adding a new rule in completion. Now that rewrite paths can represent this form of simplification, this is easy to add.
1 parent 97ed28a commit 6d89b42

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

lib/AST/RequirementMachine/RewriteSystem.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,77 @@ void RewriteSystem::initialize(
136136
addRule(rule.first, rule.second);
137137
}
138138

139+
/// Simplify terms appearing in the substitutions of the last symbol of \p term,
140+
/// which must be a superclass or concrete type symbol.
141+
void RewriteSystem::simplifySubstitutions(MutableTerm &term,
142+
RewritePath &path) const {
143+
auto symbol = term.back();
144+
assert(symbol.isSuperclassOrConcreteType());
145+
146+
auto substitutions = symbol.getSubstitutions();
147+
if (substitutions.empty())
148+
return;
149+
150+
// Save the original rewrite path length so that we can reset if if we don't
151+
// find anything to simplify.
152+
unsigned oldSize = path.size();
153+
154+
// The term is on the A stack. Push all substitutions onto the A stack.
155+
path.add(RewriteStep::forDecompose(substitutions.size(), /*inverse=*/false));
156+
157+
// Move all substitutions but the first one to the B stack.
158+
for (unsigned i = 1; i < substitutions.size(); ++i)
159+
path.add(RewriteStep::forShift(/*inverse=*/false));
160+
161+
// Simplify and collect substitutions.
162+
SmallVector<Term, 2> newSubstitutions;
163+
newSubstitutions.reserve(substitutions.size());
164+
165+
bool first = true;
166+
bool anyChanged = false;
167+
for (auto substitution : substitutions) {
168+
// Move the next substitution from the B stack to the A stack.
169+
if (!first)
170+
path.add(RewriteStep::forShift(/*inverse=*/true));
171+
first = false;
172+
173+
// The current substitution is at the top of the A stack; simplify it.
174+
MutableTerm mutTerm(substitution);
175+
anyChanged |= simplify(mutTerm, &path);
176+
177+
// Record the new substitution.
178+
newSubstitutions.push_back(Term::get(mutTerm, Context));
179+
}
180+
181+
// All simplified substitutions are now on the A stack. Collect them to
182+
// produce the new term.
183+
path.add(RewriteStep::forDecompose(substitutions.size(), /*inverse=*/true));
184+
185+
// If nothing changed, we don't have to rebuild the symbol.
186+
if (!anyChanged) {
187+
// The rewrite path should consist of a Decompose, followed by a number
188+
// of Shifts, followed by a Compose.
189+
#ifndef NDEBUG
190+
for (auto iter = path.begin() + oldSize; iter < path.end(); ++iter) {
191+
assert(iter->Kind == RewriteStep::Shift ||
192+
iter->Kind == RewriteStep::Decompose);
193+
}
194+
#endif
195+
196+
path.resize(oldSize);
197+
return;
198+
}
199+
200+
// Build the new symbol with simplified substitutions.
201+
auto newSymbol = (symbol.getKind() == Symbol::Kind::Superclass
202+
? Symbol::forSuperclass(symbol.getSuperclass(),
203+
newSubstitutions, Context)
204+
: Symbol::forConcreteType(symbol.getConcreteType(),
205+
newSubstitutions, Context));
206+
207+
term.back() = newSymbol;
208+
}
209+
139210
/// Adds a rewrite rule, returning true if the new rule was non-trivial.
140211
///
141212
/// If both sides simplify to the same term, the rule is trivial and discarded,
@@ -163,6 +234,12 @@ bool RewriteSystem::addRule(MutableTerm lhs, MutableTerm rhs,
163234
RewritePath lhsPath;
164235
RewritePath rhsPath;
165236

237+
if (lhs.back().isSuperclassOrConcreteType())
238+
simplifySubstitutions(lhs, lhsPath);
239+
240+
if (rhs.back().isSuperclassOrConcreteType())
241+
simplifySubstitutions(rhs, rhsPath);
242+
166243
simplify(lhs, &lhsPath);
167244
simplify(rhs, &rhsPath);
168245

lib/AST/RequirementMachine/RewriteSystem.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ class RewritePath {
323323
Steps.append(other.begin(), other.end());
324324
}
325325

326+
void resize(unsigned newSize) {
327+
assert(newSize <= size());
328+
Steps.erase(Steps.begin() + newSize, Steps.end());
329+
}
330+
326331
decltype(Steps)::const_iterator begin() const {
327332
return Steps.begin();
328333
}
@@ -495,6 +500,8 @@ class RewriteSystem final {
495500

496501
bool simplify(MutableTerm &term, RewritePath *path=nullptr) const;
497502

503+
void simplifySubstitutions(MutableTerm &term, RewritePath &path) const;
504+
498505
//////////////////////////////////////////////////////////////////////////////
499506
///
500507
/// Completion
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %target-swift-frontend -typecheck %s -debug-generic-signatures -requirement-machine-protocol-signatures=on 2>&1 | %FileCheck %s
2+
3+
// The rule
4+
//
5+
// [P:Y].[concrete: G<τ_0_0, τ_0_1> with <[P:Z1], [P:Z2]>] => [P:Y]
6+
//
7+
// overlaps with [P].[P:Y] => [P], which applies the adjustment prepending
8+
// [P] to [P:Z1] and [P:Z2], respectively.
9+
//
10+
// This produces the rule
11+
//
12+
// [P:Y].[concrete: G<τ_0_0, τ_0_1> with <[P].[P:Z1], [P].[P:Z2]>] => [P:Y]
13+
//
14+
// When adding the rule, we have to simplify the concrete substitutions to
15+
// reduce [P].[P:Z1] to [P:Z1] and [P].[P:Z2] to [P:Z2], respectively.
16+
17+
// CHECK-LABEL: simplify_concrete_substitutions.(file).P@
18+
// CHECK-LABEL: Requirement signature: <Self where Self == Self.X.X, Self.X : P, Self.Y == G<Self.Z1, Self.Z2>>
19+
20+
protocol P {
21+
associatedtype X : P where X.X == Self
22+
associatedtype Y where Y == G<Z1, Z2>
23+
associatedtype Z1
24+
associatedtype Z2
25+
}
26+
27+
struct G<T, U> {}

0 commit comments

Comments
 (0)