Skip to content

Commit 941438d

Browse files
committed
RequirementMachine: New linear order on associated type symbols
Previously we said that if P1 inherits from P2, then [P1:T] < [P2:T]. However, this didn't generalize to merged associated type symbols; we always had [P1&P2:T] < [P3:T] even if P3 inherited from both P1 and P2. This meant that the 'merge' operation did not preserve order, which fired an assertion in the completion logic. Fix this by generalizing the linear order to compare the 'support' of protocols rather than the 'depth', where the 'support' is defined as the size of the transitive closure of the set under protocol inheritance. Then, if you have something like protocol P1 {} protocol P2 {} protocol P3 : P1, P2 {} The support of 'P1 & P2' is 2, and the support of 'P3' is 3, therefore [P3:T] < [P1&P2:T] in this example. Fixes <rdar://problem/83768458>.
1 parent 8eea642 commit 941438d

File tree

5 files changed

+111
-18
lines changed

5 files changed

+111
-18
lines changed

lib/AST/RequirementMachine/ProtocolGraph.cpp

+53-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,50 @@ const ProtocolInfo &ProtocolGraph::getProtocolInfo(
4949
return found->second;
5050
}
5151

52+
/// The "support" of a protocol P is the size of the transitive closure of
53+
/// the singleton set {P} under protocol inheritance.
54+
unsigned ProtocolGraph::getProtocolSupport(
55+
const ProtocolDecl *proto) const {
56+
return getProtocolInfo(proto).AllInherited.size() + 1;
57+
}
58+
59+
/// The "support" of a set S of protocols is the size of the transitive
60+
/// closure of S under protocol inheritance. For example, if you start
61+
/// with
62+
///
63+
/// protocol P1 : P3 {}
64+
/// protocol P2 : P3 {}
65+
/// protocol P3 {}
66+
///
67+
/// Then the "support" of P1 & P2 is 3 because |P1 & P2 & P3| = 3.
68+
///
69+
/// The \p protos array must be sorted in canonical order and
70+
/// permanently-allocated; one safe choice is to use the return value of
71+
/// Symbol::getProtocols().
72+
unsigned ProtocolGraph::getProtocolSupport(
73+
ArrayRef<const ProtocolDecl *> protos) const {
74+
auto found = Support.find(protos);
75+
if (found != Support.end())
76+
return found->second;
77+
78+
unsigned result;
79+
if (protos.size() == 1) {
80+
result = getProtocolSupport(protos[0]);
81+
} else {
82+
llvm::DenseSet<const ProtocolDecl *> visited;
83+
for (const auto *proto : protos) {
84+
visited.insert(proto);
85+
for (const auto *inheritedProto : getProtocolInfo(proto).AllInherited)
86+
visited.insert(inheritedProto);
87+
}
88+
89+
result = visited.size();
90+
}
91+
92+
const_cast<ProtocolGraph *>(this)->Support[protos] = result;
93+
return result;
94+
}
95+
5296
/// Record information about a protocol if we have no seen it yet.
5397
void ProtocolGraph::addProtocol(const ProtocolDecl *proto,
5498
bool initialComponent) {
@@ -199,17 +243,20 @@ void ProtocolGraph::compute() {
199243
/// from another protocol Q, then P < Q. (The converse cannot be true, since
200244
/// this is a linear order.)
201245
///
202-
/// We first compare the 'depth' of a protocol, which is defined in
203-
/// ProtocolGraph::computeProtocolDepth() above.
246+
/// We first compare the 'support' of a protocol, which is defined in
247+
/// ProtocolGraph::getProtocolSupport() above.
204248
///
205-
/// If two protocols have the same depth, the tie is broken by the standard
249+
/// If two protocols have the same support, the tie is broken by the standard
206250
/// TypeDecl::compare().
207251
int ProtocolGraph::compareProtocols(const ProtocolDecl *lhs,
208252
const ProtocolDecl *rhs) const {
209-
const auto &infoLHS = getProtocolInfo(lhs);
210-
const auto &infoRHS = getProtocolInfo(rhs);
253+
unsigned lhsSupport = getProtocolSupport(lhs);
254+
unsigned rhsSupport = getProtocolSupport(rhs);
255+
256+
if (lhsSupport != rhsSupport)
257+
return rhsSupport - lhsSupport;
211258

212-
return infoLHS.Index - infoRHS.Index;
259+
return TypeDecl::compare(lhs, rhs);
213260
}
214261

215262
/// Returns if \p thisProto transitively inherits from \p otherProto.

lib/AST/RequirementMachine/ProtocolGraph.h

+7
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ struct ProtocolInfo {
8888
/// Out-of-line methods are documented in ProtocolGraph.cpp.
8989
class ProtocolGraph {
9090
llvm::DenseMap<const ProtocolDecl *, ProtocolInfo> Info;
91+
llvm::DenseMap<ArrayRef<const ProtocolDecl *>, unsigned> Support;
9192
std::vector<const ProtocolDecl *> Protocols;
9293
bool Debug = false;
9394

@@ -107,6 +108,12 @@ class ProtocolGraph {
107108
const ProtocolInfo &getProtocolInfo(
108109
const ProtocolDecl *proto) const;
109110

111+
unsigned getProtocolSupport(
112+
const ProtocolDecl *proto) const;
113+
114+
unsigned getProtocolSupport(
115+
ArrayRef<const ProtocolDecl *> protos) const;
116+
110117
private:
111118
void addProtocol(const ProtocolDecl *proto,
112119
bool initialComponent);

lib/AST/RequirementMachine/RewriteSystemCompletion.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Symbol RewriteContext::mergeAssociatedTypes(Symbol lhs, Symbol rhs,
9595
auto otherProtos = rhs.getProtocols();
9696

9797
// This must follow from lhs > rhs.
98-
assert(protos.size() <= otherProtos.size());
98+
assert(graph.getProtocolSupport(protos) <= graph.getProtocolSupport(otherProtos));
9999

100100
// Compute sorted and merged list of protocols, with duplicates.
101101
llvm::TinyPtrVector<const ProtocolDecl *> newProtos;

lib/AST/RequirementMachine/Symbol.cpp

+29-11
Original file line numberDiff line numberDiff line change
@@ -460,24 +460,35 @@ ArrayRef<const ProtocolDecl *> Symbol::getRootProtocols() const {
460460
///
461461
/// Then we break ties when both symbols have the same kind as follows:
462462
///
463-
/// * For associated type symbols, we first order the number of protocols,
464-
/// with symbols containing more protocols coming first. This ensures
465-
/// that the following holds:
463+
/// * For associated type symbols, symbols with larger support are smaller
464+
/// than those with smaller support.
465+
///
466+
/// This ensures that if P inherits from Q, then [P:T] < [Q:T].
467+
///
468+
/// Furthermore, if P1...Pm and Q1...Qn are two minimal sets of protocols,
469+
/// this ensures that the following holds, where merge() is the
470+
/// RewriteContext::mergeAssociatedTypes() operation:
471+
///
472+
/// [merge(P1&...&Pm, Q1&...&Qn):T] < [P1&...&Pm:T]
473+
/// [merge(P1&...&Pm, Q1&...&Qn):T] < [Q1&...&Qn:T]
474+
///
475+
/// For example, if P1 and P2 are unrelated protocols, and P3 inherits
476+
/// both P1 and P2, then
466477
///
467478
/// [P1&P2:T] < [P1:T]
468479
/// [P1&P2:T] < [P2:T]
480+
/// [P3:T] < [P1&P2:T]
469481
///
470-
/// If both symbols have the same number of protocols, we perform a
471-
/// lexicographic comparison on the protocols pair-wise, using the
472-
/// protocol order defined by \p graph (see
473-
/// ProtocolGraph::compareProtocols()).
482+
/// If two different lists of protocols have the same support, the tie is
483+
/// broken by a lexshort comparison on the lists.
474484
///
475485
/// * For generic parameter symbols, we first order by depth, then index.
476486
///
477487
/// * For unbound name symbols, we compare identifiers lexicographically.
478488
///
479-
/// * For protocol symbols, we compare the protocols using the protocol
480-
/// linear order on \p graph.
489+
/// * For protocol symbols, protocols with larger support are ordered before
490+
/// those with smaller support. The type order defined in TypeDecl::compare()
491+
/// is used to break ties; based on the protocol name and parent module.
481492
///
482493
/// * For layout symbols, we use LayoutConstraint::compare().
483494
int Symbol::compare(Symbol other, const ProtocolGraph &graph) const {
@@ -509,9 +520,16 @@ int Symbol::compare(Symbol other, const ProtocolGraph &graph) const {
509520
if (getName() != other.getName())
510521
return getName().compare(other.getName());
511522

512-
// Symbols with more protocols are 'smaller' than those with fewer.
523+
// Symbols with larger support are *smaller* than those with
524+
// smaller support.
525+
unsigned support = graph.getProtocolSupport(protos);
526+
unsigned otherSupport = graph.getProtocolSupport(otherProtos);
527+
if (support != otherSupport)
528+
return support > otherSupport ? -1 : 1;
529+
530+
// Otherwise, perform a shortlex comparison in the protocols.
513531
if (protos.size() != otherProtos.size())
514-
return protos.size() > otherProtos.size() ? -1 : 1;
532+
return protos.size() < otherProtos.size() ? -1 : 1;
515533

516534
for (unsigned i : indices(protos)) {
517535
int result = graph.compareProtocols(protos[i], otherProtos[i]);

test/Generics/rdar83768458.swift

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
public protocol Collection {
4+
associatedtype Index
5+
}
6+
7+
public protocol BidirectionalCollection: Collection {}
8+
9+
public protocol RandomAccessCollection: BidirectionalCollection {}
10+
11+
public protocol Numeric {
12+
associatedtype Magnitude
13+
}
14+
15+
public protocol SignedNumeric: Numeric {}
16+
17+
public protocol BinaryInteger: Numeric {}
18+
19+
public protocol SignedInteger: BinaryInteger, SignedNumeric {}
20+
21+
struct S<X> where X : RandomAccessCollection, X.Index : SignedInteger {}

0 commit comments

Comments
 (0)