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.
This commit is contained in:
Jordan Rose
2017-01-10 13:36:45 -08:00
committed by GitHub
parent f2fb1e9d93
commit 1f2121377e
3 changed files with 171 additions and 41 deletions

View File

@@ -5357,10 +5357,18 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
if (isa<TypeDecl>(witness)) return result; if (isa<TypeDecl>(witness)) return result;
auto dc = witness->getDeclContext(); auto dc = witness->getDeclContext();
auto name = witness->getFullName();
auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext(); auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext();
if (!nominal) return result; if (!nominal) return result;
DeclName name = witness->getFullName();
auto accessorKind = AccessorKind::NotAccessor;
if (auto *fn = dyn_cast<FuncDecl>(witness)) {
accessorKind = fn->getAccessorKind();
if (accessorKind != AccessorKind::NotAccessor) {
name = fn->getAccessorStorageDecl()->getFullName();
}
}
for (auto proto : nominal->getAllProtocols()) { for (auto proto : nominal->getAllProtocols()) {
// We only care about Objective-C protocols. // We only care about Objective-C protocols.
if (!proto->isObjC()) continue; if (!proto->isObjC()) continue;
@@ -5385,38 +5393,70 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
} }
if (!*conformance) continue; if (!*conformance) continue;
// Determine whether the witness for this conformance is in fact const Decl *found = (*conformance)->getWitness(req, this).getDecl();
// our witness.
if ((*conformance)->getWitness(req, this).getDecl() == witness) { if (!found) {
// If we have an optional requirement in an inherited conformance,
// check whether the potential witness matches the requirement.
// FIXME: for now, don't even try this with generics involved. We
// should be tracking how subclasses implement optional requirements,
// in which case the getWitness() check above would suffice.
if (!req->getAttrs().hasAttribute<OptionalAttr>() ||
!isa<InheritedProtocolConformance>(*conformance)) {
continue;
}
auto normal = (*conformance)->getRootNormalConformance();
auto dc = (*conformance)->getDeclContext();
if (dc->getGenericSignatureOfContext() ||
normal->getDeclContext()->getGenericSignatureOfContext()) {
continue;
}
const ValueDecl *witnessToMatch = witness;
if (accessorKind != AccessorKind::NotAccessor)
witnessToMatch = cast<FuncDecl>(witness)->getAccessorStorageDecl();
RequirementEnvironment reqEnvironment(*this, dc, req, *conformance);
if (matchWitness(*this, proto, *conformance,
witnessToMatch->getDeclContext(), req,
const_cast<ValueDecl *>(witnessToMatch),
reqEnvironment).Kind == MatchKind::ExactMatch) {
if (accessorKind != AccessorKind::NotAccessor) {
auto *storageReq = dyn_cast<AbstractStorageDecl>(req);
if (!storageReq)
continue;
req = storageReq->getAccessorFunction(accessorKind);
}
result.push_back(req); result.push_back(req);
if (anySingleRequirement) return result; if (anySingleRequirement) return result;
continue; continue;
} }
// If we have an inherited conformance, check whether the potential continue;
// witness matches the requirement. }
// FIXME: for now, don't even try this with generics involved. We
// should be tracking how subclasses implement optional requirements, // Dig out the appropriate accessor, if necessary.
// in which case the getWitness() check above would suffice. if (accessorKind != AccessorKind::NotAccessor) {
if (req->getAttrs().hasAttribute<OptionalAttr>() && auto *storageReq = dyn_cast<AbstractStorageDecl>(req);
isa<InheritedProtocolConformance>(*conformance)) { auto *storageFound = dyn_cast_or_null<AbstractStorageDecl>(found);
auto normal = (*conformance)->getRootNormalConformance(); if (!storageReq || !storageFound)
if (!(*conformance)->getDeclContext()->getGenericSignatureOfContext() && continue;
!normal->getDeclContext()->getGenericSignatureOfContext()) { req = storageReq->getAccessorFunction(accessorKind);
auto dc = (*conformance)->getDeclContext(); if (!req)
RequirementEnvironment reqEnvironment(*this, dc, req, *conformance); continue;
if (matchWitness(*this, proto, *conformance, witness->getDeclContext(), found = storageFound->getAccessorFunction(accessorKind);
req, const_cast<ValueDecl *>(witness), }
reqEnvironment)
.Kind == MatchKind::ExactMatch) { // Determine whether the witness for this conformance is in fact
// our witness.
if (found == witness) {
result.push_back(req); result.push_back(req);
if (anySingleRequirement) return result; if (anySingleRequirement) return result;
continue; continue;
} }
} }
} }
}
}
// Sort the results. // Sort the results.
if (result.size() > 2) { if (result.size() > 2) {

View File

@@ -3205,7 +3205,8 @@ bool TypeChecker::isRepresentableInObjC(
// Global computed properties may however @_cdecl their accessors. // Global computed properties may however @_cdecl their accessors.
auto storage = FD->getAccessorStorageDecl(); auto storage = FD->getAccessorStorageDecl();
validateDecl(storage); validateDecl(storage);
if (!storage->isObjC() && Reason != ObjCReason::ExplicitlyCDecl) { if (!storage->isObjC() && Reason != ObjCReason::ExplicitlyCDecl &&
Reason != ObjCReason::WitnessToObjC) {
if (Diagnose) { if (Diagnose) {
auto error = FD->isGetter() auto error = FD->isGetter()
? (isa<VarDecl>(storage) ? (isa<VarDecl>(storage)
@@ -3220,7 +3221,20 @@ bool TypeChecker::isRepresentableInObjC(
} }
return false; return false;
} }
} else {
// willSet/didSet implementations are never exposed to objc, they are
// always directly dispatched from the synthesized setter.
if (FD->isObservingAccessor()) {
if (Diagnose) {
diagnose(AFD->getLoc(), diag::objc_observing_accessor);
describeObjCReason(*this, AFD, Reason);
}
return false;
}
assert(FD->isGetterOrSetter() && "missing diags for other accessors");
return true;
}
unsigned ExpectedParamPatterns = 1; unsigned ExpectedParamPatterns = 1;
if (FD->getImplicitSelfDecl()) if (FD->getImplicitSelfDecl())
ExpectedParamPatterns++; ExpectedParamPatterns++;
@@ -3234,17 +3248,6 @@ bool TypeChecker::isRepresentableInObjC(
} }
} }
// willSet/didSet implementations are never exposed to objc, they are always
// directly dispatched from the synthesized setter.
if (FD->isObservingAccessor()) {
if (Diagnose) {
diagnose(AFD->getLoc(), diag::objc_observing_accessor);
describeObjCReason(*this, AFD, Reason);
}
return false;
}
}
// As a special case, an initializer with a single, named parameter of type // As a special case, an initializer with a single, named parameter of type
// '()' is always representable in Objective-C. This allows us to cope with // '()' is always representable in Objective-C. This allows us to cope with
// zero-parameter methods with selectors that are longer than "init". For // zero-parameter methods with selectors that are longer than "init". For

View File

@@ -45,7 +45,7 @@ class C2a : P2 {
func method(_: Int, class: ObjCClass) { } func method(_: Int, class: ObjCClass) { }
var empty: Bool { var empty: Bool {
get { } // expected-error{{Objective-C method 'empty' provided by getter for 'empty' does not match the requirement's selector ('checkIfEmpty')}} get { }
} }
} }
@@ -53,7 +53,7 @@ class C2b : P2 {
@objc func method(_: Int, class: ObjCClass) { } @objc func method(_: Int, class: ObjCClass) { }
@objc var empty: Bool { @objc var empty: Bool {
@objc get { } // expected-error{{Objective-C method 'empty' provided by getter for 'empty' does not match the requirement's selector ('checkIfEmpty')}}{{10-10=(checkIfEmpty)}} @objc get { }
} }
} }
@@ -146,3 +146,90 @@ class C4_5a : P4, P5 {
class C6a : P6 { class C6a : P6 {
func doSomething(sender: AnyObject?) { } // expected-error{{method 'doSomething(sender:)' has different argument names from those required by protocol 'P6' ('doSomething')}}{{20-20=_ }}{{none}} func doSomething(sender: AnyObject?) { } // expected-error{{method 'doSomething(sender:)' has different argument names from those required by protocol 'P6' ('doSomething')}}{{20-20=_ }}{{none}}
} }
@objc protocol P7 {
var prop: Int {
@objc(getTheProp) get
@objc(setTheProp:) set
}
}
class C7 : P7 {
var prop: Int {
get { return 0 }
set {}
}
}
class C7a : P7 {
@objc var prop: Int {
get { return 0 }
set {}
}
}
class C7b : P7 {
@objc var prop: Int {
@objc(getTheProp) get { return 0 }
@objc(setTheProp:) set {}
}
}
class C7c : P7 {
var prop: Int = 0
}
class C7d : P7 {
@objc var prop: Int = 0
}
class C7e : P7 {
// FIXME: This should probably still complain.
@objc(notProp) var prop: Int {
get { return 0 }
set {}
}
}
class C7f : P7 {
var prop: Int {
@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}}
set {}
}
}
class C7g : P7 {
var prop: Int {
get { return 0 }
@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:}}
}
}
@objc protocol P8 {
@objc optional var prop: Int {
@objc(getTheProp) get
}
}
class C8Base: P8 {}
class C8Sub: C8Base {
var prop: Int { return 0 } // expected-note {{getter for 'prop' declared here}}
@objc(getTheProp) func collision() {} // expected-error {{method 'collision()' with Objective-C selector 'getTheProp' conflicts with getter for 'prop' with the same Objective-C selector}}
}
class C8SubA: C8Base {
var prop: Int {
@objc get { return 0 } // expected-note {{getter for 'prop' declared here}}
}
@objc(getTheProp) func collision() {} // expected-error {{method 'collision()' with Objective-C selector 'getTheProp' conflicts with getter for 'prop' with the same Objective-C selector}}
}
class C8SubB: C8Base {
var prop: Int {
@objc(getProp) get { return 0 }
}
@objc(getTheProp) func collision() {} // okay
}