Skip to content

[Type checker] Infer @objc for protocol conformances in other extensions #4640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2069,7 +2069,7 @@ static Optional<ObjCReason> shouldMarkAsObjC(TypeChecker &TC,
// A witness to an @objc protocol requirement is implicitly @objc.
else if (!TC.findWitnessedObjCRequirements(
VD,
/*onlyFirstRequirement=*/true).empty())
/*anySingleRequirement=*/true).empty())
return ObjCReason::WitnessToObjC;
else if (VD->isInvalid())
return None;
Expand Down Expand Up @@ -2280,8 +2280,7 @@ static void inferObjCName(TypeChecker &tc, ValueDecl *decl) {
// requirements for which this declaration is a witness.
Optional<ObjCSelector> requirementObjCName;
ValueDecl *firstReq = nullptr;
for (auto req : tc.findWitnessedObjCRequirements(decl,
/*onlyFirst=*/false)) {
for (auto req : tc.findWitnessedObjCRequirements(decl)) {
// If this is the first requirement, take its name.
if (!requirementObjCName) {
requirementObjCName = req->getObjCRuntimeName();
Expand Down
38 changes: 32 additions & 6 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4875,18 +4875,19 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,

llvm::TinyPtrVector<ValueDecl *>
TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
bool onlyFirstRequirement) {
bool anySingleRequirement) {
llvm::TinyPtrVector<ValueDecl *> result;

// Types don't infer @objc this way.
if (isa<TypeDecl>(witness)) return result;

auto dc = witness->getDeclContext();
auto name = witness->getFullName();
for (auto conformance : dc->getLocalConformances(ConformanceLookupKind::All,
nullptr, /*sorted=*/true)) {
auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext();
if (!nominal) return result;

for (auto proto : nominal->getAllProtocols()) {
// We only care about Objective-C protocols.
auto proto = conformance->getProtocol();
if (!proto->isObjC()) continue;

for (auto req : proto->lookupDirect(name, true)) {
Expand All @@ -4895,16 +4896,41 @@ TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,

// Skip types.
if (isa<TypeDecl>(req)) continue;

// Dig out the conformance.
Optional<ProtocolConformance *> conformance;
if (!conformance.hasValue()) {
SmallVector<ProtocolConformance *, 2> conformances;
nominal->lookupConformance(dc->getParentModule(), proto,
conformances);
if (conformances.size() == 1)
conformance = conformances.front();
else
conformance = nullptr;
}
if (!*conformance) continue;

// Determine whether the witness for this conformance is in fact
// our witness.
if (conformance->getWitness(req, this).getDecl() == witness) {
if ((*conformance)->getWitness(req, this).getDecl() == witness) {
result.push_back(req);
if (onlyFirstRequirement) return result;
if (anySingleRequirement) return result;
}
}
}

// Sort the results.
if (result.size() > 2) {
std::stable_sort(result.begin(), result.end(),
[&](ValueDecl *lhs, ValueDecl *rhs) {
ProtocolDecl *lhsProto
= cast<ProtocolDecl>(lhs->getDeclContext());
ProtocolDecl *rhsProto
= cast<ProtocolDecl>(rhs->getDeclContext());
return ProtocolType::compareProtocols(&lhsProto,
&rhsProto) < 0;
});
}
return result;
}

Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2712,8 +2712,7 @@ static void describeObjCReason(TypeChecker &TC, const ValueDecl *VD,
TC.diagnose(overridden, diag::objc_overriding_objc_decl,
kind, VD->getOverriddenDecl()->getFullName());
} else if (Reason == ObjCReason::WitnessToObjC) {
auto requirement =
TC.findWitnessedObjCRequirements(VD, /*onlyFirst=*/true).front();
auto requirement = TC.findWitnessedObjCRequirements(VD).front();
TC.diagnose(requirement, diag::objc_witness_objc_requirement,
VD->getDescriptiveKind(), requirement->getFullName(),
cast<ProtocolDecl>(requirement->getDeclContext())
Expand Down
6 changes: 3 additions & 3 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -1592,14 +1592,14 @@ class TypeChecker final : public LazyResolver {
/// Find the @objc requirement that are witnessed by the given
/// declaration.
///
/// \param onlyFirstRequirement If true, only returns the first such
/// requirement, rather than all of them.
/// \param anySingleRequirement If true, returns at most a single requirement,
/// which might be any of the requirements that match.
///
/// \returns the set of requirements to which the given witness is a
/// witness.
llvm::TinyPtrVector<ValueDecl *> findWitnessedObjCRequirements(
const ValueDecl *witness,
bool onlyFirstRequirement);
bool anySingleRequirement = false);

/// Mark any _ObjectiveCBridgeable conformances in the given type as "used".
void useObjectiveCBridgeableConformances(DeclContext *dc, Type type);
Expand Down
5 changes: 5 additions & 0 deletions test/SourceKit/DocSupport/doc_clang_module.swift.response
Original file line number Diff line number Diff line change
Expand Up @@ -5073,6 +5073,11 @@ var FooSubUnnamedEnumeratorA1: Int { get }
key.name: "init(rawValue:)",
key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x"
},
{
key.kind: source.lang.swift.ref.function.constructor,
key.name: "init(rawValue:)",
key.usr: "s:FPs9OptionSetcFT8rawValuewx8RawValue_x"
},
{
key.kind: source.lang.swift.ref.function.constructor,
key.name: "init(rawValue:)",
Expand Down
60 changes: 60 additions & 0 deletions test/attr/attr_objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2117,3 +2117,63 @@ func ==(lhs: ObjC_Class1, rhs: ObjC_Class1) -> Bool {
@objc protocol OperatorInProtocol {
static func +(lhs: Self, rhs: Self) -> Self // expected-error {{@objc protocols may not have operator requirements}}
}

//===--- @objc inference for witnesses

@objc protocol InferFromProtocol {
@objc(inferFromProtoMethod1:)
optional func method1(value: Int)
}

// Infer when in the same declaration context.
// CHECK-LABEL: ClassInfersFromProtocol1
class ClassInfersFromProtocol1 : InferFromProtocol{
// CHECK: {{^}} @objc func method1(value: Int)
func method1(value: Int) { }
}

// Infer when in a different declaration context of the same class.
// CHECK-LABEL: ClassInfersFromProtocol2a
class ClassInfersFromProtocol2a {
// CHECK: {{^}} @objc func method1(value: Int)
func method1(value: Int) { }
}

extension ClassInfersFromProtocol2a : InferFromProtocol { }

// Infer when in a different declaration context of the same class.
class ClassInfersFromProtocol2b : InferFromProtocol { }

// CHECK-LABEL: ClassInfersFromProtocol2b
extension ClassInfersFromProtocol2b {
// CHECK: {{^}} @objc dynamic func method1(value: Int)
func method1(value: Int) { }
}

// Don't infer when there is a signature mismatch.
// CHECK-LABEL: ClassInfersFromProtocol3
class ClassInfersFromProtocol3 : InferFromProtocol {
}

extension ClassInfersFromProtocol3 {
// CHECK: {{^}} func method1(value: String)
func method1(value: String) { }
}

class SuperclassImplementsProtocol : InferFromProtocol { }

// Note: no inference for subclasses
class SubclassInfersFromProtocol1 : SuperclassImplementsProtocol {
// CHECK: {{^}} func method1(value: Int)
func method1(value: Int) { }
}

// Note: no inference for subclasses
class SubclassInfersFromProtocol2 : SuperclassImplementsProtocol {
}

extension SubclassInfersFromProtocol2 {
// CHECK: {{^}} func method1(value: Int)
func method1(value: Int) { }
}

26 changes: 12 additions & 14 deletions test/decl/protocol/req/optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
@objc class ObjCClass { }

@objc protocol P1 {
@objc optional func method(_ x: Int) // expected-note 2{{requirement 'method' declared here}}
@objc optional func method(_ x: Int) // expected-note {{requirement 'method' declared here}}

@objc optional var prop: Int { get } // expected-note{{requirement 'prop' declared here}}
@objc optional var prop: Int { get }

@objc optional subscript (i: Int) -> ObjCClass? { get } // expected-note{{requirement 'subscript' declared here}}
@objc optional subscript (i: Int) -> ObjCClass? { get }
}

@objc protocol P2 {
Expand Down Expand Up @@ -39,10 +39,6 @@ class C2 : P1 {
}
}

// -----------------------------------------------------------------------
// "Near" matches.
// -----------------------------------------------------------------------

class C3 : P1 {
func method(_ x: Int) { }

Expand Down Expand Up @@ -71,28 +67,26 @@ extension C4 : P1 {
}
}

// -----------------------------------------------------------------------
// Okay to match via extensions.
// -----------------------------------------------------------------------

class C5 : P1 { }

extension C5 {
func method(_ x: Int) { }
// expected-warning@-1{{non-'@objc' method 'method' does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
// expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }}

var prop: Int { return 5 }
// expected-warning@-1{{non-'@objc' property 'prop' does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
// expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }}

subscript (i: Int) -> ObjCClass? {
// expected-warning@-1{{non-'@objc' subscript does not satisfy optional requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
// expected-note@-2{{add '@nonobjc' to silence this warning}}{{3-3=@nonobjc }}
get {
return nil
}
set {}
}
}

// Note: @nonobjc suppresses warnings
// Note: @nonobjc suppresses witness match.
class C6 { }

extension C6 : P1 {
Expand All @@ -108,6 +102,10 @@ extension C6 : P1 {
}
}

// -----------------------------------------------------------------------
// "Near" matches.
// -----------------------------------------------------------------------

// Note: warn about selector matches where the Swift names didn't match.
@objc class C7 : P1 { // expected-note{{class 'C7' declares conformance to protocol 'P1' here}}
@objc(method:) func otherMethod(x: Int) { } // expected-error{{Objective-C method 'method:' provided by method 'otherMethod(x:)' conflicts with optional requirement method 'method' in protocol 'P1'}}
Expand Down