Skip to content

Commit 32aef82

Browse files
committed
[Sema] Infer @objc and Objective-C name from conformance to a protocol.
When a particular method/initializer/property/subscript is used to satisfied a requirement in an @objc protocol, infer both the presence of @objc and the @objc name so that it matches the requirement. This eliminates the need to explicitly specify @objc and @objc(foo:bar:) in most cases. Note that we already did this for overrides, so it's a generalization of that behavior. Note that we keep this inference somewhat local, checking only those protocols that the enclosing context conforms to, to limit spooky-action-at-a-distance inference. It's possible that we could lift this restriction later. Fixes rdar://problem/24049773.
1 parent 7070cbe commit 32aef82

File tree

10 files changed

+265
-100
lines changed

10 files changed

+265
-100
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2550,7 +2550,7 @@ ERROR(objc_enum_case_multi,none,
25502550
"'@objc' enum case declaration defines multiple enum cases with the same Objective-C name", ())
25512551

25522552
// If you change this, also change enum ObjCReason
2553-
#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @IBOutlet|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override}"
2553+
#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @IBOutlet|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override|an implementation of an @objc requirement}"
25542554

25552555
ERROR(objc_invalid_on_var,none,
25562556
"property cannot be %" OBJC_ATTR_SELECT "0 "
@@ -2595,6 +2595,9 @@ NOTE(objc_inferring_on_objc_protocol_member,none,
25952595
NOTE(objc_overriding_objc_decl,none,
25962596
"overriding '@objc' %select{property|subscript|initializer|method}0 %1 "
25972597
"here", (unsigned, DeclName))
2598+
NOTE(objc_witness_objc_requirement,none,
2599+
"satisfying requirement for %0 %1 in protocol %2",
2600+
(DescriptiveDeclKind, DeclName, DeclName))
25982601

25992602
ERROR(objc_invalid_on_func_curried,none,
26002603
"method cannot be %" OBJC_ATTR_SELECT "0 because curried functions "

lib/Sema/CodeSynthesis.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,12 +741,14 @@ void swift::addTrivialAccessorsToStorage(AbstractStorageDecl *storage,
741741

742742
// Create the getter.
743743
auto *getter = createGetterPrototype(storage, TC);
744+
if (storage->hasAccessorFunctions()) return;
744745

745746
// Create the setter.
746747
FuncDecl *setter = nullptr;
747748
ParamDecl *setterValueParam = nullptr;
748749
if (doesStorageNeedSetter(storage)) {
749750
setter = createSetterPrototype(storage, setterValueParam, TC);
751+
if (storage->hasAccessorFunctions()) return;
750752
}
751753

752754
// Okay, we have both the getter and setter. Set them in VD.
@@ -964,10 +966,12 @@ static void convertNSManagedStoredVarToComputed(VarDecl *VD, TypeChecker &TC) {
964966

965967
// Create the getter.
966968
auto *Get = createGetterPrototype(VD, TC);
969+
if (VD->hasAccessorFunctions()) return;
967970

968971
// Create the setter.
969972
ParamDecl *SetValueDecl = nullptr;
970973
auto *Set = createSetterPrototype(VD, SetValueDecl, TC);
974+
if (VD->hasAccessorFunctions()) return;
971975

972976
// Okay, we have both the getter and setter. Set them in VD.
973977
VD->makeComputed(VD->getLoc(), Get, Set, nullptr, VD->getLoc());

lib/Sema/TypeCheckDecl.cpp

Lines changed: 148 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,9 @@ static bool isNeverDefaultInitializable(Pattern *p) {
940940
bool result = false;
941941

942942
p->forEachVariable([&](VarDecl *var) {
943-
assert(!var->getAttrs().hasAttribute<NSManagedAttr>());
943+
if (var->getAttrs().hasAttribute<NSManagedAttr>())
944+
return;
945+
944946
if (var->isDebuggerVar() ||
945947
var->isLet())
946948
result = true;
@@ -2078,6 +2080,11 @@ static Optional<ObjCReason> shouldMarkAsObjC(TypeChecker &TC,
20782080
// An override of an @objc declaration is implicitly @objc.
20792081
else if (VD->getOverriddenDecl() && VD->getOverriddenDecl()->isObjC())
20802082
return ObjCReason::OverridesObjC;
2083+
// A witness to an @objc protocol requirement is implicitly @objc.
2084+
else if (!TC.findWitnessedObjCRequirements(
2085+
VD,
2086+
/*onlyFirstRequirement=*/true).empty())
2087+
return ObjCReason::WitnessToObjC;
20812088
else if (VD->isInvalid())
20822089
return None;
20832090
// Implicitly generated declarations are not @objc, except for constructors.
@@ -2185,6 +2192,135 @@ static void checkBridgedFunctions(TypeChecker &TC) {
21852192
}
21862193
}
21872194

2195+
/// Infer the Objective-C name for a given declaration.
2196+
static void inferObjCName(TypeChecker &tc, ValueDecl *decl) {
2197+
// If this declaration overrides an @objc declaration, use its name.
2198+
if (auto overridden = decl->getOverriddenDecl()) {
2199+
if (overridden->isObjC()) {
2200+
// Handle methods first.
2201+
if (auto overriddenFunc = dyn_cast<AbstractFunctionDecl>(overridden)) {
2202+
// Determine the selector of the overridden method.
2203+
ObjCSelector overriddenSelector = overriddenFunc->getObjCSelector(&tc);
2204+
2205+
// Dig out the @objc attribute on the method, if it exists.
2206+
auto attr = decl->getAttrs().getAttribute<ObjCAttr>();
2207+
if (!attr) {
2208+
// There was no @objc attribute; add one with the
2209+
// appropriate name.
2210+
decl->getAttrs().add(ObjCAttr::create(tc.Context,
2211+
overriddenSelector,
2212+
true));
2213+
return;
2214+
}
2215+
2216+
// Determine whether there is a name conflict.
2217+
bool shouldFixName = !attr->hasName();
2218+
if (attr->hasName() && *attr->getName() != overriddenSelector) {
2219+
// If the user explicitly wrote the incorrect name, complain.
2220+
if (!attr->isNameImplicit()) {
2221+
{
2222+
auto diag = tc.diagnose(
2223+
attr->AtLoc,
2224+
diag::objc_override_method_selector_mismatch,
2225+
*attr->getName(), overriddenSelector);
2226+
fixDeclarationObjCName(diag, decl, overriddenSelector);
2227+
}
2228+
2229+
tc.diagnose(overriddenFunc, diag::overridden_here);
2230+
}
2231+
2232+
shouldFixName = true;
2233+
}
2234+
2235+
// If we have to set the name, do so.
2236+
if (shouldFixName) {
2237+
// Override the name on the attribute.
2238+
const_cast<ObjCAttr *>(attr)->setName(overriddenSelector,
2239+
/*implicit=*/true);
2240+
}
2241+
return;
2242+
}
2243+
2244+
// Handle properties.
2245+
if (auto overriddenProp = dyn_cast<VarDecl>(overridden)) {
2246+
Identifier overriddenName = overriddenProp->getObjCPropertyName();
2247+
ObjCSelector overriddenNameAsSel(tc.Context, 0, overriddenName);
2248+
2249+
// Dig out the @objc attribute, if specified.
2250+
auto attr = decl->getAttrs().getAttribute<ObjCAttr>();
2251+
if (!attr) {
2252+
// There was no @objc attribute; add one with the
2253+
// appropriate name.
2254+
decl->getAttrs().add(
2255+
ObjCAttr::createNullary(tc.Context,
2256+
overriddenName,
2257+
/*isNameImplicit=*/true));
2258+
return;
2259+
}
2260+
2261+
// Determine whether there is a name conflict.
2262+
bool shouldFixName = !attr->hasName();
2263+
if (attr->hasName() && *attr->getName() != overriddenNameAsSel) {
2264+
// If the user explicitly wrote the wrong name, complain.
2265+
if (!attr->isNameImplicit()) {
2266+
tc.diagnose(attr->AtLoc,
2267+
diag::objc_override_property_name_mismatch,
2268+
attr->getName()->getSelectorPieces()[0],
2269+
overriddenName)
2270+
.fixItReplaceChars(attr->getNameLocs().front(),
2271+
attr->getRParenLoc(),
2272+
overriddenName.str());
2273+
tc.diagnose(overridden, diag::overridden_here);
2274+
}
2275+
2276+
shouldFixName = true;
2277+
}
2278+
2279+
// Fix the name, if needed.
2280+
if (shouldFixName) {
2281+
const_cast<ObjCAttr *>(attr)->setName(overriddenNameAsSel,
2282+
/*implicit=*/true);
2283+
}
2284+
return;
2285+
}
2286+
}
2287+
}
2288+
2289+
// Dig out the @objc attribute. If it already has a name, do
2290+
// nothing; the protocol conformance checker will handle any
2291+
// mismatches.
2292+
auto attr = decl->getAttrs().getAttribute<ObjCAttr>();
2293+
if (attr && attr->hasName()) return;
2294+
2295+
// When no overridde determined the Objective-C name, look for
2296+
// requirements for which this declaration is a witness.
2297+
Optional<ObjCSelector> requirementObjCName;
2298+
for (auto req : tc.findWitnessedObjCRequirements(decl,
2299+
/*onlyFirst=*/false)) {
2300+
// If this is the first requirement, take its name.
2301+
if (!requirementObjCName) {
2302+
requirementObjCName = req->getObjCRuntimeName();
2303+
continue;
2304+
}
2305+
2306+
// If this requirement has a different name from one we've seen,
2307+
// bail out and let protocol-conformance diagnostics handle this.
2308+
if (*requirementObjCName != *req->getObjCRuntimeName())
2309+
return;
2310+
}
2311+
2312+
// If we have a name, install it via an @objc attribute.
2313+
if (requirementObjCName) {
2314+
if (attr)
2315+
const_cast<ObjCAttr *>(attr)->setName(*requirementObjCName,
2316+
/*implicit=*/true);
2317+
else
2318+
decl->getAttrs().add(
2319+
ObjCAttr::create(tc.Context, *requirementObjCName,
2320+
/*isNameImplicit=*/true));
2321+
}
2322+
}
2323+
21882324
/// Mark the given declaration as being Objective-C compatible (or
21892325
/// not) as appropriate.
21902326
///
@@ -2224,13 +2360,12 @@ void swift::markAsObjC(TypeChecker &TC, ValueDecl *D,
22242360
if (auto classDecl
22252361
= D->getDeclContext()->getAsClassOrClassExtensionContext()) {
22262362
if (auto method = dyn_cast<AbstractFunctionDecl>(D)) {
2227-
// If we are overriding another method, make sure the
2228-
// selectors line up.
2363+
// Determine the foreign error convention.
22292364
if (auto baseMethod = method->getOverriddenDecl()) {
22302365
// If the overridden method has a foreign error convention,
2231-
// adopt it. Set the foreign error convention for a
2232-
// throwing method. Note that the foreign error convention
2233-
// affects the selector, so we perform this first.
2366+
// adopt it. Set the foreign error convention for a throwing
2367+
// method. Note that the foreign error convention affects the
2368+
// selector, so we perform this before inferring a selector.
22342369
if (method->isBodyThrowing()) {
22352370
if (auto baseErrorConvention
22362371
= baseMethod->getForeignErrorConvention()) {
@@ -2240,38 +2375,16 @@ void swift::markAsObjC(TypeChecker &TC, ValueDecl *D,
22402375
assert(errorConvention && "Missing error convention");
22412376
method->setForeignErrorConvention(*errorConvention);
22422377
}
2243-
2244-
ObjCSelector baseSelector = baseMethod->getObjCSelector(&TC);
2245-
if (baseSelector != method->getObjCSelector(&TC)) {
2246-
// The selectors differ. If the method's selector was
2247-
// explicitly specified, this is an error. Otherwise, we
2248-
// inherit the selector.
2249-
if (auto attr = method->getAttrs().getAttribute<ObjCAttr>()) {
2250-
if (attr->hasName() && !attr->isNameImplicit()) {
2251-
{
2252-
auto diag = TC.diagnose(attr->AtLoc,
2253-
diag::objc_override_method_selector_mismatch,
2254-
*attr->getName(), baseSelector);
2255-
fixDeclarationObjCName(diag, method, baseSelector);
2256-
}
2257-
TC.diagnose(baseMethod, diag::overridden_here);
2258-
}
2259-
2260-
// Override the name on the attribute.
2261-
const_cast<ObjCAttr *>(attr)->setName(baseSelector,
2262-
/*implicit=*/true);
2263-
} else {
2264-
method->getAttrs().add(ObjCAttr::create(TC.Context,
2265-
baseSelector,
2266-
true));
2267-
}
2268-
}
22692378
} else if (method->isBodyThrowing()) {
22702379
// Attach the foreign error convention.
22712380
assert(errorConvention && "Missing error convention");
22722381
method->setForeignErrorConvention(*errorConvention);
22732382
}
22742383

2384+
// Infer the Objective-C name for this method.
2385+
inferObjCName(TC, method);
2386+
2387+
// ... then record it.
22752388
classDecl->recordObjCMethod(method);
22762389

22772390
// Swift does not permit class methods with Objective-C selectors 'load',
@@ -2295,37 +2408,9 @@ void swift::markAsObjC(TypeChecker &TC, ValueDecl *D,
22952408
diagInfo.first, diagInfo.second, sel);
22962409
}
22972410
}
2298-
} else if (auto var = dyn_cast<VarDecl>(D)) {
2299-
// If we are overriding a property, make sure that the
2300-
// Objective-C names of the properties match.
2301-
if (auto baseVar = var->getOverriddenDecl()) {
2302-
if (var->getObjCPropertyName() != baseVar->getObjCPropertyName()) {
2303-
Identifier baseName = baseVar->getObjCPropertyName();
2304-
ObjCSelector baseSelector(TC.Context, 0, baseName);
2305-
2306-
// If not, see whether we can implicitly adjust.
2307-
if (auto attr = var->getAttrs().getAttribute<ObjCAttr>()) {
2308-
if (attr->hasName() && !attr->isNameImplicit()) {
2309-
TC.diagnose(attr->AtLoc,
2310-
diag::objc_override_property_name_mismatch,
2311-
attr->getName()->getSelectorPieces()[0],
2312-
baseName)
2313-
.fixItReplaceChars(attr->getNameLocs().front(),
2314-
attr->getRParenLoc(),
2315-
baseName.str());
2316-
TC.diagnose(baseVar, diag::overridden_here);
2317-
}
2318-
2319-
// Override the name on the attribute.
2320-
const_cast<ObjCAttr *>(attr)->setName(baseSelector,
2321-
/*implicit=*/true);
2322-
} else {
2323-
var->getAttrs().add(ObjCAttr::create(TC.Context,
2324-
baseSelector,
2325-
true));
2326-
}
2327-
}
2328-
}
2411+
} else if (isa<VarDecl>(D)) {
2412+
// Infer the Objective-C name for this property.
2413+
inferObjCName(TC, D);
23292414
}
23302415
} else if (auto method = dyn_cast<AbstractFunctionDecl>(D)) {
23312416
if (method->isBodyThrowing()) {

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4607,6 +4607,41 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
46074607
}
46084608
}
46094609

4610+
llvm::TinyPtrVector<ValueDecl *>
4611+
TypeChecker::findWitnessedObjCRequirements(const ValueDecl *witness,
4612+
bool onlyFirstRequirement) {
4613+
llvm::TinyPtrVector<ValueDecl *> result;
4614+
4615+
// Types don't infer @objc this way.
4616+
if (isa<TypeDecl>(witness)) return result;
4617+
4618+
auto dc = witness->getDeclContext();
4619+
auto name = witness->getFullName();
4620+
for (auto conformance : dc->getLocalConformances(ConformanceLookupKind::All,
4621+
nullptr, /*sorted=*/true)) {
4622+
// We only care about Objective-C protocols.
4623+
auto proto = conformance->getProtocol();
4624+
if (!proto->isObjC()) continue;
4625+
4626+
for (auto req : proto->lookupDirect(name, true)) {
4627+
// Skip anything in a protocol extension.
4628+
if (req->getDeclContext() != proto) continue;
4629+
4630+
// Skip types.
4631+
if (isa<TypeDecl>(req)) continue;
4632+
4633+
// Determine whether the witness for this conformance is in fact
4634+
// our witness.
4635+
if (conformance->getWitness(req, this).getDecl() == witness) {
4636+
result.push_back(req);
4637+
if (onlyFirstRequirement) return result;
4638+
}
4639+
}
4640+
}
4641+
4642+
return result;
4643+
}
4644+
46104645
void TypeChecker::resolveTypeWitness(
46114646
const NormalProtocolConformance *conformance,
46124647
AssociatedTypeDecl *assocType) {

lib/Sema/TypeCheckType.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2389,10 +2389,15 @@ static void describeObjCReason(TypeChecker &TC, const ValueDecl *VD,
23892389
: 3;
23902390

23912391
auto overridden = VD->getOverriddenDecl();
2392-
if (overridden->getLoc().isValid()) {
2393-
TC.diagnose(overridden->getLoc(), diag::objc_overriding_objc_decl,
2394-
kind, VD->getOverriddenDecl()->getFullName());
2395-
}
2392+
TC.diagnose(overridden, diag::objc_overriding_objc_decl,
2393+
kind, VD->getOverriddenDecl()->getFullName());
2394+
} else if (Reason == ObjCReason::WitnessToObjC) {
2395+
auto requirement =
2396+
TC.findWitnessedObjCRequirements(VD, /*onlyFirst=*/true).front();
2397+
TC.diagnose(requirement, diag::objc_witness_objc_requirement,
2398+
VD->getDescriptiveKind(), requirement->getFullName(),
2399+
cast<ProtocolDecl>(requirement->getDeclContext())
2400+
->getFullName());
23962401
}
23972402
}
23982403

0 commit comments

Comments
 (0)