Skip to content

Commit bbaa7f7

Browse files
committed
Sema: Discard associated type inference candidates that are obviously tautological or divergent.
When considering an implementation from a protocol extension as a possible witness to a protocol requirement, and using that witness to try to infer the protocol's associated types, we would sometimes consider `AssocType == ConformingType.AssocType` as a potential solution, even though this is a tautology; we would subsequently mark the potential solution as failed (because ConformingType.AssocType doesn't exist yet; it doesn't conform to any protocols) even when the witness is necessary to infer other associated types. Worse, this would introduce an order dependency when certain potential witnesses were visited before the right associated types were inferred. Fix this by filtering out useless tautological inferred witnesses before trying to validate them for conforming to the necessary requirements. Fixes rdar://problem/29954938.
1 parent db32af7 commit bbaa7f7

5 files changed

+124
-0
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2950,6 +2950,23 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
29502950
return ResolveWitnessResult::ExplicitFailed;
29512951
}
29522952

2953+
static bool associatedTypesAreSameEquivalenceClass(AssociatedTypeDecl *a,
2954+
AssociatedTypeDecl *b) {
2955+
if (a == b)
2956+
return true;
2957+
2958+
// TODO: Do a proper equivalence check here by looking for some relationship
2959+
// between a and b's protocols. In practice today, it's unlikely that
2960+
// two same-named associated types can currently be independent, since we
2961+
// don't have anything like `@implements(P.foo)` to rename witnesses (and
2962+
// we still fall back to name lookup for witnesses in more cases than we
2963+
// should).
2964+
if (a->getName() == b->getName())
2965+
return true;
2966+
2967+
return false;
2968+
}
2969+
29532970
InferredAssociatedTypesByWitnesses
29542971
ConformanceChecker::inferTypeWitnessesViaValueWitnesses(ValueDecl *req) {
29552972
InferredAssociatedTypesByWitnesses result;
@@ -2974,6 +2991,27 @@ ConformanceChecker::inferTypeWitnessesViaValueWitnesses(ValueDecl *req) {
29742991
result.second->getCanonicalType()})
29752992
.second)
29762993
return true;
2994+
2995+
// Filter out circular possibilities, e.g. that
2996+
// AssocType == S.AssocType or
2997+
// AssocType == Foo<S.AssocType>.
2998+
bool containsTautologicalType =
2999+
result.second.findIf([&](Type t) -> bool {
3000+
auto dmt = t->getAs<DependentMemberType>();
3001+
if (!dmt)
3002+
return false;
3003+
if (!associatedTypesAreSameEquivalenceClass(
3004+
dmt->getAssocType(), result.first))
3005+
return false;
3006+
if (!dmt->getBase()->isEqual(Conformance->getType()))
3007+
return false;
3008+
3009+
return true;
3010+
});
3011+
3012+
if (containsTautologicalType) {
3013+
return true;
3014+
}
29773015

29783016
// Check that the type witness meets the
29793017
// requirements on the associated type.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s
2+
3+
// rdar://problem/29954938 -- A bug in associated type inference exposed an
4+
// order dependency where, if a type conformed to Collection in one extension
5+
// then conformed to MutableCollection in a later extension, it would fail
6+
// to type-check.
7+
8+
struct Butz { }
9+
10+
extension Butz: Collection {
11+
public var startIndex: Int { return 0 }
12+
public var endIndex: Int { return 0 }
13+
}
14+
15+
extension Butz: MutableCollection {
16+
public subscript (_ position: Int) -> Int {
17+
get { return 0 }
18+
set { }
19+
}
20+
public func index(after i: Int) -> Int { return 0 }
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s
2+
3+
// rdar://problem/29954938 -- A bug in associated type inference exposed an
4+
// order dependency where, if a type conformed to Collection in one extension
5+
// then conformed to MutableCollection in a later extension, it would fail
6+
// to type-check.
7+
8+
struct Butz<Flubz: Comparable> { }
9+
10+
extension Butz: Collection {
11+
public var startIndex: Flubz { fatalError() }
12+
public var endIndex: Flubz { fatalError() }
13+
}
14+
15+
extension Butz: MutableCollection {
16+
public subscript (_ position: Flubz) -> Flubz {
17+
get { fatalError() }
18+
set { }
19+
}
20+
public func index(after i: Flubz) -> Flubz { fatalError() }
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s
2+
3+
// rdar://problem/29954938 -- A bug in associated type inference exposed an
4+
// order dependency where, if a type conformed to Collection in one extension
5+
// then conformed to MutableCollection in a later extension, it would fail
6+
// to type-check. This regression test ensures that a "working" order,
7+
// where MutableCollection comes first, remains working.
8+
9+
struct Butz { }
10+
11+
extension Butz: MutableCollection {
12+
public var startIndex: Int { return 0 }
13+
public var endIndex: Int { return 0 }
14+
}
15+
16+
extension Butz: Collection {
17+
public subscript (_ position: Int) -> Int {
18+
get { return 0 }
19+
set { }
20+
}
21+
public func index(after i: Int) -> Int { return 0 }
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s
2+
3+
// rdar://problem/29954938 -- A bug in associated type inference exposed an
4+
// order dependency where, if a type conformed to Collection in one extension
5+
// then conformed to MutableCollection in a later extension, it would fail
6+
// to type-check. This regression test ensures that a "working" order,
7+
// where MutableCollection is the only explicit conformance, still works.
8+
9+
struct Butz { }
10+
11+
extension Butz: MutableCollection {
12+
public var startIndex: Int { return 0 }
13+
public var endIndex: Int { return 0 }
14+
}
15+
16+
extension Butz {
17+
public subscript (_ position: Int) -> Int {
18+
get { return 0 }
19+
set { }
20+
}
21+
public func index(after i: Int) -> Int { return 0 }
22+
}

0 commit comments

Comments
 (0)