Skip to content

Commit f6835ec

Browse files
committed
[Protocol conformance] Simplify/unify checking for @objc/non-@objc conflicts.
When a non-@objc witness matches an @objc requirement except for @objc-ness, treat it the same way whether it's an optional requirement or not, except that it's a warning for the optional case. Should finish off rdar://problem/25159872.
1 parent acbcf04 commit f6835ec

File tree

7 files changed

+84
-141
lines changed

7 files changed

+84
-141
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,27 +1269,22 @@ NOTE(protocol_conformance_implied_here,none,
12691269
"implied protocol conformance %0 here can be made explicit", (Identifier))
12701270

12711271
// "Near matches"
1272-
WARNING(optional_req_nonobjc_near_match,none,
1273-
"non-@objc %select{initializer %1|method %1|property %1|subscript}0 "
1274-
"does not satisfy optional requirement of @objc protocol %2",
1275-
(RequirementKind, DeclName, DeclName))
1276-
NOTE(optional_req_nonobjc_near_match_add_objc,none,
1277-
"add '@objc' to provide an Objective-C entrypoint", ())
1278-
NOTE(optional_req_nonobjc_near_match_silence,none,
1279-
"add '@nonobjc' to silence this warning", ())
1280-
12811272
WARNING(optional_req_near_match,none,
12821273
"%0 %1 nearly matches optional requirement %2 of protocol %3",
12831274
(DescriptiveDeclKind, DeclName, DeclName, DeclName))
12841275
NOTE(optional_req_near_match_rename,none,
12851276
"rename to %0 to satisfy this requirement",
12861277
(DeclName))
1278+
NOTE(optional_req_nonobjc_near_match_add_objc,none,
1279+
"add '@objc' to provide an Objective-C entrypoint", ())
1280+
NOTE(optional_req_nonobjc_to_objc,none,
1281+
"rename to %0 to satisfy this requirement",
1282+
(DeclName))
12871283
NOTE(optional_req_near_match_move,none,
12881284
"move %0 to %select{an|another}1 extension to silence this warning",
12891285
(DeclName, unsigned))
12901286
NOTE(optional_req_near_match_nonobjc,none,
1291-
"move %0 to %select{an|another}1 extension or add '@nonobjc' to "
1292-
"silence this warning", (DeclName))
1287+
"add '@nonobjc' to silence this %select{warning|error}0", (bool))
12931288
NOTE(optional_req_near_match_accessibility,none,
12941289
"make %0 %select{ERROR|private|private or internal}1 to silence this "
12951290
"warning", (DeclName, Accessibility))
@@ -2700,12 +2695,24 @@ NOTE(objc_optional_requirement_swift_rename,none,
27002695
"requirement %1", (bool, DeclName))
27012696

27022697
ERROR(witness_non_objc,none,
2703-
"non-'@objc' " OBJC_DIAG_SELECT " does not satisfy '@objc' requirement in "
2704-
"protocol %2", (unsigned, DeclName, DeclName))
2698+
"non-'@objc' " OBJC_DIAG_SELECT " does not satisfy requirement "
2699+
"of '@objc' protocol %2",
2700+
(unsigned, DeclName, DeclName))
2701+
2702+
WARNING(witness_non_objc_optional,none,
2703+
"non-'@objc' " OBJC_DIAG_SELECT " does not satisfy optional "
2704+
"requirement of '@objc' protocol %2",
2705+
(unsigned, DeclName, DeclName))
27052706

27062707
ERROR(witness_non_objc_storage,none,
2707-
"non-'@objc' %select{property %1|subscript}0 does not satisfy '@objc' "
2708-
"requirement in protocol %2", (bool, DeclName, DeclName))
2708+
"non-'@objc' %select{property %1|subscript}0 does not satisfy "
2709+
"requirement of '@objc' protocol %2",
2710+
(bool, DeclName, DeclName))
2711+
2712+
WARNING(witness_non_objc_storage_optional,none,
2713+
"non-'@objc' %select{property %1|subscript}0 does not satisfy "
2714+
"optional requirement of '@objc' protocol %2",
2715+
(bool, DeclName, DeclName))
27092716

27102717
ERROR(nonobjc_not_allowed,none,
27112718
"declaration is %" OBJC_ATTR_SELECT "0, and cannot be marked @nonobjc",

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2487,7 +2487,7 @@ bool ASTContext::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
24872487
if (auto objcAttr = conflicts[0]->getAttrs().getAttribute<ObjCAttr>())
24882488
hasExplicitObjCAttribute = !objcAttr->isImplicit();
24892489
if (!hasExplicitObjCAttribute)
2490-
Diags.diagnose(conflicts[0], diag::optional_req_nonobjc_near_match_silence)
2490+
Diags.diagnose(conflicts[0], diag::optional_req_near_match_nonobjc, true)
24912491
.fixItInsert(
24922492
conflicts[0]->getAttributeInsertionLoc(/*forModifier=*/false),
24932493
"@nonobjc ");

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 32 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,15 +1395,6 @@ namespace {
13951395
/// Record that the given optional requirement has no witness.
13961396
void recordOptionalWitness(ValueDecl *requirement);
13971397

1398-
/// Check whether there is a potential non-@objc witness for an
1399-
/// @objc optional requirement.
1400-
void checkOptionalWitnessNonObjCMatch(ValueDecl *requirement);
1401-
1402-
/// Check whether there is a potential non-@objc witness for an
1403-
/// @objc optional requirement.
1404-
bool diagnoseOptionalWitnessNonObjCMatch(ValueDecl *requirement,
1405-
ValueDecl *witness);
1406-
14071398
/// Record a type witness.
14081399
///
14091400
/// \param assocType The associated type whose witness is being recorded.
@@ -1870,62 +1861,6 @@ void ConformanceChecker::recordOptionalWitness(ValueDecl *requirement) {
18701861

18711862
// Record that there is no witness.
18721863
Conformance->setWitness(requirement, ConcreteDeclRef());
1873-
1874-
// If the requirement is @objc, note that we have an unsatisfied
1875-
// optional @objc requirement.
1876-
if (requirement->isObjC() &&
1877-
!requirement->getAttrs().isUnavailable(TC.Context)) {
1878-
// If we're not suppressing diagnostics, look for a non-@objc near
1879-
// match.
1880-
if (!SuppressDiagnostics)
1881-
checkOptionalWitnessNonObjCMatch(requirement);
1882-
}
1883-
}
1884-
1885-
void ConformanceChecker::checkOptionalWitnessNonObjCMatch(
1886-
ValueDecl *requirement) {
1887-
for (auto witness : lookupValueWitnesses(requirement, nullptr)) {
1888-
// If the witness is in a different DeclContext, ignore it.
1889-
if (witness->getDeclContext() != DC)
1890-
continue;
1891-
1892-
// If the witness is @objc, selector-collision diagnostics will
1893-
// handle this if there actually is a collision.
1894-
if (witness->isObjC())
1895-
continue;
1896-
1897-
// Diagnose if needed.
1898-
if (diagnoseOptionalWitnessNonObjCMatch(requirement, witness))
1899-
break;
1900-
}
1901-
}
1902-
1903-
bool ConformanceChecker::diagnoseOptionalWitnessNonObjCMatch(
1904-
ValueDecl *requirement, ValueDecl *witness) {
1905-
// This is silenced by writing @nonobjc.
1906-
if (witness->getAttrs().hasAttribute<NonObjCAttr>())
1907-
return false;;
1908-
1909-
// Diagnose the non-@objc declaration.
1910-
TC.diagnose(witness, diag::optional_req_nonobjc_near_match,
1911-
getRequirementKind(requirement), requirement->getFullName(),
1912-
Proto->getFullName());
1913-
1914-
// Add the appropriate "@objc" attribute to make the witness match.
1915-
{
1916-
auto diag = TC.diagnose(witness,
1917-
diag::optional_req_nonobjc_near_match_add_objc);
1918-
fixDeclarationObjCName(diag, witness, requirement->getObjCRuntimeName());
1919-
}
1920-
1921-
// Add the "@nonobjc" attribute to suppress the warning.
1922-
TC.diagnose(witness, diag::optional_req_nonobjc_near_match_silence)
1923-
.fixItInsert(witness->getAttributeInsertionLoc(/*forModifier=*/false),
1924-
"@nonobjc ");
1925-
1926-
TC.diagnose(requirement, diag::protocol_requirement_here,
1927-
requirement->getFullName());
1928-
return true;
19291864
}
19301865

19311866
void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
@@ -3695,35 +3630,29 @@ void ConformanceChecker::checkConformance() {
36953630
auto finalizeWitness = [&] {
36963631
// Find the witness.
36973632
auto witness = Conformance->getWitness(requirement, nullptr).getDecl();
3698-
if (!witness) {
3699-
// If this is an unsatisfied @objc optional requirement,
3700-
// diagnose it.
3701-
if (requirement->isObjC())
3702-
checkOptionalWitnessNonObjCMatch(requirement);
3703-
3704-
return;
3705-
}
3633+
if (!witness) return;
37063634

37073635
// Objective-C checking for @objc requirements.
37083636
if (requirement->isObjC()) {
37093637
// The witness must also be @objc.
37103638
if (!witness->isObjC()) {
3711-
// If the requirement is optional, warn.
3712-
if (requirement->getAttrs().hasAttribute<OptionalAttr>()) {
3713-
diagnoseOptionalWitnessNonObjCMatch(requirement, witness);
3714-
return;
3715-
}
3716-
3639+
bool isOptional =
3640+
requirement->getAttrs().hasAttribute<OptionalAttr>();
37173641
if (auto witnessFunc = dyn_cast<AbstractFunctionDecl>(witness)) {
37183642
auto diagInfo = getObjCMethodDiagInfo(witnessFunc);
3719-
auto diag = TC.diagnose(witness, diag::witness_non_objc,
3643+
auto diag = TC.diagnose(witness,
3644+
isOptional ? diag::witness_non_objc_optional
3645+
: diag::witness_non_objc,
37203646
diagInfo.first, diagInfo.second,
37213647
Proto->getFullName());
37223648
fixDeclarationObjCName(
37233649
diag, witness,
37243650
cast<AbstractFunctionDecl>(requirement)->getObjCSelector());
37253651
} else if (isa<VarDecl>(witness)) {
3726-
auto diag = TC.diagnose(witness, diag::witness_non_objc_storage,
3652+
auto diag = TC.diagnose(witness,
3653+
isOptional
3654+
? diag::witness_non_objc_storage_optional
3655+
: diag::witness_non_objc_storage,
37273656
/*isSubscript=*/false,
37283657
witness->getFullName(),
37293658
Proto->getFullName());
@@ -3732,10 +3661,23 @@ void ConformanceChecker::checkConformance() {
37323661
ObjCSelector(requirement->getASTContext(), 0,
37333662
cast<VarDecl>(requirement)->getObjCPropertyName()));
37343663
} else if (isa<SubscriptDecl>(witness)) {
3735-
TC.diagnose(witness, diag::witness_non_objc_storage,
3664+
TC.diagnose(witness,
3665+
isOptional
3666+
? diag::witness_non_objc_storage_optional
3667+
: diag::witness_non_objc_storage,
37363668
/*isSubscript=*/true,
37373669
witness->getFullName(),
3738-
Proto->getFullName());
3670+
Proto->getFullName())
3671+
.fixItInsert(witness->getAttributeInsertionLoc(false),
3672+
"@objc ");
3673+
}
3674+
3675+
// If the requirement is optional, @nonobjc suppresses the
3676+
// diagnostic.
3677+
if (isOptional) {
3678+
TC.diagnose(witness, diag::optional_req_near_match_nonobjc, false)
3679+
.fixItInsert(witness->getAttributeInsertionLoc(false),
3680+
"@nonobjc ");
37393681
}
37403682

37413683
TC.diagnose(requirement, diag::protocol_requirement_here,
@@ -4354,15 +4296,20 @@ static void diagnosePotentialWitness(TypeChecker &tc, ValueDecl *req,
43544296
req->getFullName(),
43554297
proto->getFullName());
43564298

4357-
// Warning to fix the names.
43584299
if (req->getFullName() != witness->getFullName()) {
4300+
// Note to fix the names.
43594301
auto diag = tc.diagnose(witness, diag::optional_req_near_match_rename,
43604302
req->getFullName());
43614303
fixDeclarationName(diag, witness, req->getFullName());
43624304

43634305
// Also fix the Objective-C name, if needed.
43644306
if (req->isObjC())
43654307
fixDeclarationObjCName(diag, witness, req->getObjCRuntimeName());
4308+
} else if (req->isObjC() && !witness->isObjC()) {
4309+
// Note to add @objc.
4310+
auto diag = tc.diagnose(witness,
4311+
diag::optional_req_nonobjc_near_match_add_objc);
4312+
fixDeclarationObjCName(diag, witness, req->getObjCRuntimeName());
43664313
}
43674314

43684315
// If moving the declaration can help, suggest that.
@@ -4383,7 +4330,7 @@ static void diagnosePotentialWitness(TypeChecker &tc, ValueDecl *req,
43834330

43844331
// If adding @nonobjc can help, suggest that.
43854332
if (canSuppressPotentialWitnessWarningWithNonObjC(req, witness)) {
4386-
tc.diagnose(witness, diag::optional_req_nonobjc_near_match_silence)
4333+
tc.diagnose(witness, diag::optional_req_near_match_nonobjc, false)
43874334
.fixItInsert(witness->getAttributeInsertionLoc(false), "@nonobjc ");
43884335
}
43894336

@@ -4527,11 +4474,6 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
45274474
bestOptionalReqs.push_back(req);
45284475
}
45294476

4530-
// If we found an exactly-matching name, don't diagnose anything here.
4531-
// FIXME: We want to diagnose here with specific detail.
4532-
if (bestScore == 0)
4533-
bestOptionalReqs.clear();
4534-
45354477
// If we found some requirements with nearly-matching names, diagnose
45364478
// the first one.
45374479
if (bestScore < UINT_MAX) {

test/ClangModules/objc_parse.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ func classAnyObject(_ obj: NSObject) {
264264
class Wobbler : NSWobbling { // expected-note{{candidate has non-matching type '()'}}
265265
@objc func wobble() { }
266266

267-
func returnMyself() -> Self { return self } // expected-error{{non-'@objc' method 'returnMyself()' does not satisfy '@objc' requirement in protocol 'NSWobbling'}}{{3-3=@objc }}
267+
func returnMyself() -> Self { return self } // expected-error{{non-'@objc' method 'returnMyself()' does not satisfy requirement of '@objc' protocol 'NSWobbling'}}{{3-3=@objc }}
268268
}
269269

270270
extension Wobbler : NSMaybeInitWobble { // expected-error{{type 'Wobbler' does not conform to protocol 'NSMaybeInitWobble'}}

test/attr/attr_objc.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,8 +2028,11 @@ class ConformsToProtocolThrowsObjCName1 : ProtocolThrowsObjCName {
20282028
@objc func doThing(_ x: String) throws -> String { return x } // okay
20292029
}
20302030

2031-
class ConformsToProtocolThrowsObjCName2 : ProtocolThrowsObjCName { // expected-note{{class 'ConformsToProtocolThrowsObjCName2' declares conformance to protocol 'ProtocolThrowsObjCName' here}}
2032-
@objc func doThing(_ x: Int) throws -> String { return "" } // expected-error{{Objective-C method 'doThing:error:' provided by method 'doThing' conflicts with optional requirement method 'doThing' in protocol 'ProtocolThrowsObjCName'}}
2031+
class ConformsToProtocolThrowsObjCName2 : ProtocolThrowsObjCName {
2032+
@objc func doThing(_ x: Int) throws -> String { return "" }
2033+
// expected-warning@-1{{instance method 'doThing' nearly matches optional requirement 'doThing' of protocol 'ProtocolThrowsObjCName'}}
2034+
// expected-note@-2{{move 'doThing' to an extension to silence this warning}}
2035+
// expected-note@-3{{make 'doThing' private to silence this warning}}{{9-9=private }}
20332036
}
20342037

20352038
@objc class DictionaryTest {

test/decl/protocol/objc.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
}
3030

3131
class C1b : P1 {
32-
func method1() { } // expected-error{{non-'@objc' method 'method1()' does not satisfy '@objc' requirement in protocol 'P1'}}{{3-3=@objc }}
33-
var property1: ObjCClass = ObjCClass() // expected-error{{non-'@objc' property 'property1' does not satisfy '@objc' requirement in protocol 'P1'}}{{3-3=@objc }}
34-
var property2: ObjCClass = ObjCClass() // expected-error{{non-'@objc' property 'property2' does not satisfy '@objc' requirement in protocol 'P1'}}{{3-3=@objc }}
32+
func method1() { } // expected-error{{non-'@objc' method 'method1()' does not satisfy requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
33+
var property1: ObjCClass = ObjCClass() // expected-error{{non-'@objc' property 'property1' does not satisfy requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
34+
var property2: ObjCClass = ObjCClass() // expected-error{{non-'@objc' property 'property2' does not satisfy requirement of '@objc' protocol 'P1'}}{{3-3=@objc }}
3535
}
3636

3737
@objc protocol P2 {
@@ -42,9 +42,9 @@ class C1b : P1 {
4242
}
4343

4444
class C2a : P2 {
45-
func method(_: Int, class: ObjCClass) { } // expected-error{{non-'@objc' method 'method(_:class:)' does not satisfy '@objc' requirement in protocol 'P2'}}{{3-3=@objc(methodWithInt:withClass:) }}
45+
func method(_: Int, class: ObjCClass) { } // expected-error{{non-'@objc' method 'method(_:class:)' does not satisfy requirement of '@objc' protocol 'P2'}}{{3-3=@objc(methodWithInt:withClass:) }}
4646

47-
var empty: Bool { // expected-error{{non-'@objc' property 'empty' does not satisfy '@objc' requirement in protocol 'P2'}}{{3-3=@objc }}
47+
var empty: Bool { // expected-error{{non-'@objc' property 'empty' does not satisfy requirement of '@objc' protocol 'P2'}}{{3-3=@objc }}
4848
get { }
4949
}
5050
}
@@ -103,5 +103,5 @@ class C3a : P3 {
103103
}
104104

105105
class Bar: Foo {
106-
required init() {} // expected-error{{non-'@objc' initializer 'init()' does not satisfy '@objc' requirement in protocol 'Foo'}}{{3-3=@objc }}
106+
required init() {} // expected-error{{non-'@objc' initializer 'init()' does not satisfy requirement of '@objc' protocol 'Foo'}}{{3-3=@objc }}
107107
}

0 commit comments

Comments
 (0)