Skip to content

Commit 94b02a0

Browse files
committed
Sema: Fix requirement/witness disambiguation for subclass existentials
When performing a lookup into a class C, we might find a member of C that witnesses a requirement in a protocol Q that C conforms to. In this case, AST name lookup returns both results. There are two further levels of fitering which can eliminate the ambiguity: - Sema name lookup maps requirements to witnesses if the base type of the lookup is a nominal type or a class-constrained archetype. Imported conformances don't have this mapping recorded, but there's another hack in this code path where if a requirement maps to itself inside a conformance, it is dropped from the results altogether. - If the duplicate results were not filtered out in Sema name lookup, there was another hack in CSRanking which would a witness higher than it's requirement when comparing solutions. This doesn't work for imported conformances, but usually name lookup filters out the duplicate results sooner, which also eliminates the exponential behavior from having multiple constraint system solutions. However, if we have a subclass existential C & P where C conforms to Q and we find a member of C that witnesses a requirement of Q, then (C & P) does not conform to Q. So if the conformance was imported, *both* of the above checks would fail to disambiguate the witness and requirement, and the member access would fail to type check. To make this work with imported conformances, teach Sema name lookup to extract the superclass from an existential before performing the conformance check. If the conformance check fails, it means we found the protocol member via the 'existential' part of the subclass existential (eg, in our case, a member of P), and we proceed as before. Fixes <rdar://problem/33291112>.
1 parent 1d33945 commit 94b02a0

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

lib/Sema/TypeCheckNameLookup.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//
1717
//===----------------------------------------------------------------------===//
1818
#include "TypeChecker.h"
19+
#include "swift/AST/ExistentialLayout.h"
1920
#include "swift/AST/Initializer.h"
2021
#include "swift/AST/NameLookup.h"
2122
#include "swift/AST/ProtocolConformance.h"
@@ -120,27 +121,48 @@ namespace {
120121
if (!Options.contains(NameLookupFlags::ProtocolMembers) ||
121122
!isa<ProtocolDecl>(foundDC) ||
122123
isa<GenericTypeParamDecl>(found) ||
123-
(isa<FuncDecl>(found) && cast<FuncDecl>(found)->isOperator()) ||
124-
foundInType->isAnyExistentialType()) {
124+
(isa<FuncDecl>(found) && cast<FuncDecl>(found)->isOperator())) {
125125
addResult(found);
126126
return;
127127
}
128128

129129
assert(isa<ProtocolDecl>(foundDC));
130130

131+
auto conformingType = foundInType;
132+
133+
// When performing a lookup on a subclass existential, we might
134+
// find a member of the class that witnesses a requirement on a
135+
// protocol that the class conforms to.
136+
//
137+
// Since subclass existentials don't normally conform to protocols,
138+
// pull out the superclass instead, and use that below.
139+
if (foundInType->isExistentialType()) {
140+
auto layout = foundInType->getExistentialLayout();
141+
if (layout.superclass)
142+
conformingType = layout.superclass;
143+
}
144+
131145
// If we found something within the protocol itself, and our
132146
// search began somewhere that is not in a protocol or extension
133147
// thereof, remap this declaration to the witness.
134148
if (foundInType->is<ArchetypeType>() ||
149+
foundInType->isExistentialType() ||
135150
Options.contains(NameLookupFlags::PerformConformanceCheck)) {
136151
// Dig out the protocol conformance.
137-
auto conformance = TC.conformsToProtocol(foundInType, foundProto, DC,
152+
auto conformance = TC.conformsToProtocol(conformingType, foundProto, DC,
138153
conformanceOptions);
139-
if (!conformance)
154+
if (!conformance) {
155+
// If there's no conformance, we have an existential
156+
// and we found a member from one of the protocols, and
157+
// not a class constraint if any.
158+
assert(foundInType->isExistentialType());
159+
addResult(found);
140160
return;
161+
}
141162

142163
if (conformance->isAbstract()) {
143-
assert(foundInType->is<ArchetypeType>());
164+
assert(foundInType->is<ArchetypeType>() ||
165+
foundInType->isExistentialType());
144166
addResult(found);
145167
return;
146168
}
@@ -161,6 +183,10 @@ namespace {
161183

162184
// FIXME: the "isa<ProtocolDecl>()" check will be wrong for
163185
// default implementations in protocols.
186+
//
187+
// If we have an imported conformance or the witness could
188+
// not be deserialized, getWitnessDecl() will just return
189+
// the requirement, so just drop the lookup result here.
164190
if (witness && !isa<ProtocolDecl>(witness->getDeclContext()))
165191
addResult(witness);
166192

test/ClangImporter/subclass_existentials.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ class OldSwiftLaundryService : NSLaundry {
3535
return g!
3636
}
3737
}
38+
39+
// Make sure the method lookup is not ambiguous
40+
41+
_ = Coat.fashionStatement.wear()

test/Inputs/clang-importer-sdk/usr/include/Foundation.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,13 +1086,21 @@ typedef enum __attribute__((ns_error_domain(FictionalServerErrorDomain))) Fictio
10861086
FictionalServerErrorMeltedDown = 1
10871087
} FictionalServerErrorCode;
10881088

1089+
@protocol Wearable
1090+
- (void)wear;
1091+
@end
1092+
10891093
@protocol Garment
10901094
@end
10911095

10921096
@protocol Cotton
10931097
@end
10941098

1095-
@interface Coat
1099+
@interface Coat : NSObject<Wearable>
1100+
1101+
- (void)wear;
1102+
@property (class) Coat <Wearable> *fashionStatement;
1103+
10961104
@end
10971105

10981106
@protocol NSLaundry

0 commit comments

Comments
 (0)