Skip to content

Commit 1f21213

Browse files
authored
Infer selectors from protocols for property accessors too. (#6634)
Most property accessors have selectors matching their protocols, but not all. Don't force the user to write '@objc' explicitly on an accessor, which isn't even possible for stored properties. More groundwork for rdar://problem/28543037.
1 parent f2fb1e9 commit 1f21213

File tree

3 files changed

+169
-39
lines changed

3 files changed

+169
-39
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5357,10 +5357,18 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
53575357
if (isa<TypeDecl>(witness)) return result;
53585358

53595359
auto dc = witness->getDeclContext();
5360-
auto name = witness->getFullName();
53615360
auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext();
53625361
if (!nominal) return result;
53635362

5363+
DeclName name = witness->getFullName();
5364+
auto accessorKind = AccessorKind::NotAccessor;
5365+
if (auto *fn = dyn_cast<FuncDecl>(witness)) {
5366+
accessorKind = fn->getAccessorKind();
5367+
if (accessorKind != AccessorKind::NotAccessor) {
5368+
name = fn->getAccessorStorageDecl()->getFullName();
5369+
}
5370+
}
5371+
53645372
for (auto proto : nominal->getAllProtocols()) {
53655373
// We only care about Objective-C protocols.
53665374
if (!proto->isObjC()) continue;
@@ -5385,36 +5393,68 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
53855393
}
53865394
if (!*conformance) continue;
53875395

5396+
const Decl *found = (*conformance)->getWitness(req, this).getDecl();
5397+
5398+
if (!found) {
5399+
// If we have an optional requirement in an inherited conformance,
5400+
// check whether the potential witness matches the requirement.
5401+
// FIXME: for now, don't even try this with generics involved. We
5402+
// should be tracking how subclasses implement optional requirements,
5403+
// in which case the getWitness() check above would suffice.
5404+
if (!req->getAttrs().hasAttribute<OptionalAttr>() ||
5405+
!isa<InheritedProtocolConformance>(*conformance)) {
5406+
continue;
5407+
}
5408+
5409+
auto normal = (*conformance)->getRootNormalConformance();
5410+
auto dc = (*conformance)->getDeclContext();
5411+
if (dc->getGenericSignatureOfContext() ||
5412+
normal->getDeclContext()->getGenericSignatureOfContext()) {
5413+
continue;
5414+
}
5415+
5416+
const ValueDecl *witnessToMatch = witness;
5417+
if (accessorKind != AccessorKind::NotAccessor)
5418+
witnessToMatch = cast<FuncDecl>(witness)->getAccessorStorageDecl();
5419+
5420+
RequirementEnvironment reqEnvironment(*this, dc, req, *conformance);
5421+
if (matchWitness(*this, proto, *conformance,
5422+
witnessToMatch->getDeclContext(), req,
5423+
const_cast<ValueDecl *>(witnessToMatch),
5424+
reqEnvironment).Kind == MatchKind::ExactMatch) {
5425+
if (accessorKind != AccessorKind::NotAccessor) {
5426+
auto *storageReq = dyn_cast<AbstractStorageDecl>(req);
5427+
if (!storageReq)
5428+
continue;
5429+
req = storageReq->getAccessorFunction(accessorKind);
5430+
}
5431+
result.push_back(req);
5432+
if (anySingleRequirement) return result;
5433+
continue;
5434+
}
5435+
5436+
continue;
5437+
}
5438+
5439+
// Dig out the appropriate accessor, if necessary.
5440+
if (accessorKind != AccessorKind::NotAccessor) {
5441+
auto *storageReq = dyn_cast<AbstractStorageDecl>(req);
5442+
auto *storageFound = dyn_cast_or_null<AbstractStorageDecl>(found);
5443+
if (!storageReq || !storageFound)
5444+
continue;
5445+
req = storageReq->getAccessorFunction(accessorKind);
5446+
if (!req)
5447+
continue;
5448+
found = storageFound->getAccessorFunction(accessorKind);
5449+
}
5450+
53885451
// Determine whether the witness for this conformance is in fact
53895452
// our witness.
5390-
if ((*conformance)->getWitness(req, this).getDecl() == witness) {
5453+
if (found == witness) {
53915454
result.push_back(req);
53925455
if (anySingleRequirement) return result;
53935456
continue;
53945457
}
5395-
5396-
// If we have an inherited conformance, check whether the potential
5397-
// witness matches the requirement.
5398-
// FIXME: for now, don't even try this with generics involved. We
5399-
// should be tracking how subclasses implement optional requirements,
5400-
// in which case the getWitness() check above would suffice.
5401-
if (req->getAttrs().hasAttribute<OptionalAttr>() &&
5402-
isa<InheritedProtocolConformance>(*conformance)) {
5403-
auto normal = (*conformance)->getRootNormalConformance();
5404-
if (!(*conformance)->getDeclContext()->getGenericSignatureOfContext() &&
5405-
!normal->getDeclContext()->getGenericSignatureOfContext()) {
5406-
auto dc = (*conformance)->getDeclContext();
5407-
RequirementEnvironment reqEnvironment(*this, dc, req, *conformance);
5408-
if (matchWitness(*this, proto, *conformance, witness->getDeclContext(),
5409-
req, const_cast<ValueDecl *>(witness),
5410-
reqEnvironment)
5411-
.Kind == MatchKind::ExactMatch) {
5412-
result.push_back(req);
5413-
if (anySingleRequirement) return result;
5414-
continue;
5415-
}
5416-
}
5417-
}
54185458
}
54195459
}
54205460

lib/Sema/TypeCheckType.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3205,7 +3205,8 @@ bool TypeChecker::isRepresentableInObjC(
32053205
// Global computed properties may however @_cdecl their accessors.
32063206
auto storage = FD->getAccessorStorageDecl();
32073207
validateDecl(storage);
3208-
if (!storage->isObjC() && Reason != ObjCReason::ExplicitlyCDecl) {
3208+
if (!storage->isObjC() && Reason != ObjCReason::ExplicitlyCDecl &&
3209+
Reason != ObjCReason::WitnessToObjC) {
32093210
if (Diagnose) {
32103211
auto error = FD->isGetter()
32113212
? (isa<VarDecl>(storage)
@@ -3220,25 +3221,27 @@ bool TypeChecker::isRepresentableInObjC(
32203221
}
32213222
return false;
32223223
}
3223-
} else {
3224-
unsigned ExpectedParamPatterns = 1;
3225-
if (FD->getImplicitSelfDecl())
3226-
ExpectedParamPatterns++;
3227-
if (FD->getParameterLists().size() != ExpectedParamPatterns) {
3224+
3225+
// willSet/didSet implementations are never exposed to objc, they are
3226+
// always directly dispatched from the synthesized setter.
3227+
if (FD->isObservingAccessor()) {
32283228
if (Diagnose) {
3229-
diagnose(AFD->getLoc(), diag::objc_invalid_on_func_curried,
3230-
getObjCDiagnosticAttrKind(Reason));
3229+
diagnose(AFD->getLoc(), diag::objc_observing_accessor);
32313230
describeObjCReason(*this, AFD, Reason);
32323231
}
32333232
return false;
32343233
}
3234+
assert(FD->isGetterOrSetter() && "missing diags for other accessors");
3235+
return true;
32353236
}
32363237

3237-
// willSet/didSet implementations are never exposed to objc, they are always
3238-
// directly dispatched from the synthesized setter.
3239-
if (FD->isObservingAccessor()) {
3238+
unsigned ExpectedParamPatterns = 1;
3239+
if (FD->getImplicitSelfDecl())
3240+
ExpectedParamPatterns++;
3241+
if (FD->getParameterLists().size() != ExpectedParamPatterns) {
32403242
if (Diagnose) {
3241-
diagnose(AFD->getLoc(), diag::objc_observing_accessor);
3243+
diagnose(AFD->getLoc(), diag::objc_invalid_on_func_curried,
3244+
getObjCDiagnosticAttrKind(Reason));
32423245
describeObjCReason(*this, AFD, Reason);
32433246
}
32443247
return false;

test/decl/protocol/objc.swift

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ class C2a : P2 {
4545
func method(_: Int, class: ObjCClass) { }
4646

4747
var empty: Bool {
48-
get { } // expected-error{{Objective-C method 'empty' provided by getter for 'empty' does not match the requirement's selector ('checkIfEmpty')}}
48+
get { }
4949
}
5050
}
5151

5252
class C2b : P2 {
5353
@objc func method(_: Int, class: ObjCClass) { }
5454

5555
@objc var empty: Bool {
56-
@objc get { } // expected-error{{Objective-C method 'empty' provided by getter for 'empty' does not match the requirement's selector ('checkIfEmpty')}}{{10-10=(checkIfEmpty)}}
56+
@objc get { }
5757
}
5858
}
5959

@@ -146,3 +146,90 @@ class C4_5a : P4, P5 {
146146
class C6a : P6 {
147147
func doSomething(sender: AnyObject?) { } // expected-error{{method 'doSomething(sender:)' has different argument names from those required by protocol 'P6' ('doSomething')}}{{20-20=_ }}{{none}}
148148
}
149+
150+
151+
152+
@objc protocol P7 {
153+
var prop: Int {
154+
@objc(getTheProp) get
155+
@objc(setTheProp:) set
156+
}
157+
}
158+
159+
class C7 : P7 {
160+
var prop: Int {
161+
get { return 0 }
162+
set {}
163+
}
164+
}
165+
166+
class C7a : P7 {
167+
@objc var prop: Int {
168+
get { return 0 }
169+
set {}
170+
}
171+
}
172+
173+
class C7b : P7 {
174+
@objc var prop: Int {
175+
@objc(getTheProp) get { return 0 }
176+
@objc(setTheProp:) set {}
177+
}
178+
}
179+
180+
class C7c : P7 {
181+
var prop: Int = 0
182+
}
183+
184+
class C7d : P7 {
185+
@objc var prop: Int = 0
186+
}
187+
188+
class C7e : P7 {
189+
// FIXME: This should probably still complain.
190+
@objc(notProp) var prop: Int {
191+
get { return 0 }
192+
set {}
193+
}
194+
}
195+
196+
class C7f : P7 {
197+
var prop: Int {
198+
@objc(getProp) get { return 0 } // expected-error {{Objective-C method 'getProp' provided by getter for 'prop' does not match the requirement's selector ('getTheProp')}} {{11-18=getTheProp}}
199+
set {}
200+
}
201+
}
202+
203+
class C7g : P7 {
204+
var prop: Int {
205+
get { return 0 }
206+
@objc(prop:) set {} // expected-error {{Objective-C method 'prop:' provided by setter for 'prop' does not match the requirement's selector ('setTheProp:')}} {{11-16=setTheProp:}}
207+
}
208+
}
209+
210+
@objc protocol P8 {
211+
@objc optional var prop: Int {
212+
@objc(getTheProp) get
213+
}
214+
}
215+
216+
class C8Base: P8 {}
217+
class C8Sub: C8Base {
218+
var prop: Int { return 0 } // expected-note {{getter for 'prop' declared here}}
219+
220+
@objc(getTheProp) func collision() {} // expected-error {{method 'collision()' with Objective-C selector 'getTheProp' conflicts with getter for 'prop' with the same Objective-C selector}}
221+
}
222+
class C8SubA: C8Base {
223+
var prop: Int {
224+
@objc get { return 0 } // expected-note {{getter for 'prop' declared here}}
225+
}
226+
227+
@objc(getTheProp) func collision() {} // expected-error {{method 'collision()' with Objective-C selector 'getTheProp' conflicts with getter for 'prop' with the same Objective-C selector}}
228+
}
229+
class C8SubB: C8Base {
230+
var prop: Int {
231+
@objc(getProp) get { return 0 }
232+
}
233+
234+
@objc(getTheProp) func collision() {} // okay
235+
}

0 commit comments

Comments
 (0)