Skip to content

Commit ac8e037

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 c28f0f0 commit ac8e037

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"
@@ -121,27 +122,48 @@ namespace {
121122
if (!Options.contains(NameLookupFlags::ProtocolMembers) ||
122123
!isa<ProtocolDecl>(foundDC) ||
123124
isa<GenericTypeParamDecl>(found) ||
124-
(isa<FuncDecl>(found) && cast<FuncDecl>(found)->isOperator()) ||
125-
foundInType->isAnyExistentialType()) {
125+
(isa<FuncDecl>(found) && cast<FuncDecl>(found)->isOperator())) {
126126
addResult(found);
127127
return;
128128
}
129129

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

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

143164
if (conformance->isAbstract()) {
144-
assert(foundInType->is<ArchetypeType>());
165+
assert(foundInType->is<ArchetypeType>() ||
166+
foundInType->isExistentialType());
145167
addResult(found);
146168
return;
147169
}
@@ -158,6 +180,10 @@ namespace {
158180

159181
// FIXME: the "isa<ProtocolDecl>()" check will be wrong for
160182
// default implementations in protocols.
183+
//
184+
// If we have an imported conformance or the witness could
185+
// not be deserialized, getWitnessDecl() will just return
186+
// the requirement, so just drop the lookup result here.
161187
if (witness && !isa<ProtocolDecl>(witness->getDeclContext()))
162188
addResult(witness);
163189

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
@@ -1098,13 +1098,21 @@ typedef enum __attribute__((ns_error_domain(FictionalServerErrorDomain))) Fictio
10981098
FictionalServerErrorMeltedDown = 1
10991099
} FictionalServerErrorCode;
11001100

1101+
@protocol Wearable
1102+
- (void)wear;
1103+
@end
1104+
11011105
@protocol Garment
11021106
@end
11031107

11041108
@protocol Cotton
11051109
@end
11061110

1107-
@interface Coat
1111+
@interface Coat : NSObject<Wearable>
1112+
1113+
- (void)wear;
1114+
@property (class) Coat <Wearable> *fashionStatement;
1115+
11081116
@end
11091117

11101118
@protocol NSLaundry

0 commit comments

Comments
 (0)