Skip to content
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

[Sema] Some ObjCSelector diagnostic cleanups #31962

Merged
merged 6 commits into from May 22, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/swift/AST/Decl.h
Expand Up @@ -4117,8 +4117,8 @@ class ClassDecl final : public NominalTypeDecl {
///
/// \param isInstance Whether we are looking for an instance method
/// (vs. a class method).
MutableArrayRef<AbstractFunctionDecl *> lookupDirect(ObjCSelector selector,
bool isInstance);
TinyPtrVector<AbstractFunctionDecl *> lookupDirect(ObjCSelector selector,
bool isInstance);

/// Record the presence of an @objc method with the given selector.
void recordObjCMethod(AbstractFunctionDecl *method, ObjCSelector selector);
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/NameLookup.cpp
Expand Up @@ -1309,7 +1309,7 @@ void ClassDecl::createObjCMethodLookup() {
});
}

MutableArrayRef<AbstractFunctionDecl *>
TinyPtrVector<AbstractFunctionDecl *>
ClassDecl::lookupDirect(ObjCSelector selector, bool isInstance) {
if (!ObjCMethodLookup) {
createObjCMethodLookup();
Expand All @@ -1325,7 +1325,7 @@ ClassDecl::lookupDirect(ObjCSelector selector, bool isInstance) {
stored.Generation = ctx.getCurrentGeneration();
}

return { stored.Methods.begin(), stored.Methods.end() };
return stored.Methods;
}

void ClassDecl::recordObjCMethod(AbstractFunctionDecl *method,
Expand Down
166 changes: 62 additions & 104 deletions lib/Sema/TypeCheckDeclObjC.cpp
Expand Up @@ -1886,12 +1886,7 @@ bool swift::fixDeclarationObjCName(InFlightDiagnostic &diag, const ValueDecl *de

namespace {
/// Produce a deterministic ordering of the given declarations.
class OrderDeclarations {
SourceManager &SrcMgr;

public:
OrderDeclarations(SourceManager &srcMgr) : SrcMgr(srcMgr) { }

struct OrderDeclarations {
bool operator()(ValueDecl *lhs, ValueDecl *rhs) const {
// If the declarations come from different modules, order based on the
// module.
Expand All @@ -1912,6 +1907,7 @@ namespace {
}

// Prefer the declaration that comes first in the source file.
const auto &SrcMgr = lhsSF->getASTContext().SourceMgr;
return SrcMgr.isBeforeInBuffer(lhs->getLoc(), rhs->getLoc());
}

Expand All @@ -1924,57 +1920,43 @@ namespace {
} // end anonymous namespace

/// Lookup for an Objective-C method with the given selector in the
/// given class or any of its superclasses.
static AbstractFunctionDecl *lookupObjCMethodInClass(
ClassDecl *classDecl,
ObjCSelector selector,
bool isInstanceMethod,
bool isInitializer,
SourceManager &srcMgr,
bool inheritingInits = true) {
if (!classDecl)
return nullptr;
/// given class or any of its superclasses. We intentionally don't respect
/// access control, since everything is visible to the Objective-C runtime.
static AbstractFunctionDecl *
lookupOverridenObjCMethod(ClassDecl *classDecl, AbstractFunctionDecl *method,
bool inheritingInits = true) {
assert(classDecl);

// Look for an Objective-C method in this class.
auto methods = classDecl->lookupDirect(selector, isInstanceMethod);
auto methods = classDecl->lookupDirect(method->getObjCSelector(),
method->isObjCInstanceMethod());
if (!methods.empty()) {
// If we aren't inheriting initializers, remove any initializers from the
// list.
if (!inheritingInits &&
std::find_if(methods.begin(), methods.end(),
[](AbstractFunctionDecl *func) {
return isa<ConstructorDecl>(func);
}) != methods.end()) {
SmallVector<AbstractFunctionDecl *, 4> nonInitMethods;
std::copy_if(methods.begin(), methods.end(),
std::back_inserter(nonInitMethods),
[&](AbstractFunctionDecl *func) {
return !isa<ConstructorDecl>(func);
});
if (nonInitMethods.empty())
if (!inheritingInits) {
llvm::erase_if(methods, [](AbstractFunctionDecl *afd) {
return isa<ConstructorDecl>(afd);
});
if (methods.empty())
return nullptr;

return *std::min_element(nonInitMethods.begin(), nonInitMethods.end(),
OrderDeclarations(srcMgr));
}

return *std::min_element(methods.begin(), methods.end(),
OrderDeclarations(srcMgr));
OrderDeclarations());
}

// Recurse into the superclass.
// If we've reached the bottom of the inheritance heirarchy, we're done.
if (!classDecl->hasSuperclass())
return nullptr;

// Determine whether we are (still) inheriting initializers.
inheritingInits = inheritingInits &&
classDecl->inheritsSuperclassInitializers();
if (isInitializer && !inheritingInits)
if (!classDecl->inheritsSuperclassInitializers())
inheritingInits = false;

if (isa<ConstructorDecl>(method) && !inheritingInits)
return nullptr;

return lookupObjCMethodInClass(classDecl->getSuperclassDecl(), selector,
isInstanceMethod, isInitializer, srcMgr,
inheritingInits);
return lookupOverridenObjCMethod(classDecl->getSuperclassDecl(), method,
inheritingInits);
}

bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
Expand All @@ -1986,7 +1968,7 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
return false;

// Sort the methods by declaration order.
std::sort(methods.begin(), methods.end(), OrderDeclarations(Ctx.SourceMgr));
std::sort(methods.begin(), methods.end(), OrderDeclarations());

// For each Objective-C method declared in this file, check whether
// it overrides something in one of its superclasses. We
Expand Down Expand Up @@ -2022,18 +2004,13 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
if (!classDecl->hasSuperclass())
continue;

// Look for a method that we have overridden in one of our
// superclasses.
// Look for a method that we have overridden in one of our superclasses by
// virtue of having the same selector.
// Note: This should be treated as a lookup for intra-module dependency
// purposes, but a subclass already depends on its superclasses and any
// extensions for many other reasons.
auto selector = method->getObjCSelector();
AbstractFunctionDecl *overriddenMethod
= lookupObjCMethodInClass(classDecl->getSuperclassDecl(),
selector,
method->isObjCInstanceMethod(),
isa<ConstructorDecl>(method),
Ctx.SourceMgr);
auto *overriddenMethod =
lookupOverridenObjCMethod(classDecl->getSuperclassDecl(), method);
if (!overriddenMethod)
continue;

Expand All @@ -2051,18 +2028,18 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
// Diagnose the override.
auto methodDiagInfo = getObjCMethodDiagInfo(method);
auto overriddenDiagInfo = getObjCMethodDiagInfo(overriddenMethod);
Ctx.Diags.diagnose(method, diag::objc_override_other,
methodDiagInfo.first,
methodDiagInfo.second,
overriddenDiagInfo.first,
overriddenDiagInfo.second,
selector,
overriddenMethod->getDeclContext()
->getDeclaredInterfaceType());

Ctx.Diags.diagnose(
method, diag::objc_override_other, methodDiagInfo.first,
methodDiagInfo.second, overriddenDiagInfo.first,
overriddenDiagInfo.second, method->getObjCSelector(),
overriddenMethod->getDeclContext()->getDeclaredInterfaceType());

const ValueDecl *overriddenDecl = overriddenMethod;
if (overriddenMethod->isImplicit())
if (auto accessor = dyn_cast<AccessorDecl>(overriddenMethod))
overriddenDecl = accessor->getStorage();

Ctx.Diags.diagnose(overriddenDecl, diag::objc_declared_here,
overriddenDiagInfo.first, overriddenDiagInfo.second);

Expand All @@ -2073,7 +2050,7 @@ bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
}

/// Retrieve the source file for the given Objective-C member conflict.
static MutableArrayRef<AbstractFunctionDecl *>
static TinyPtrVector<AbstractFunctionDecl *>
getObjCMethodConflictDecls(const SourceFile::ObjCMethodConflict &conflict) {
ClassDecl *classDecl = std::get<0>(conflict);
ObjCSelector selector = std::get<1>(conflict);
Expand All @@ -2082,45 +2059,13 @@ getObjCMethodConflictDecls(const SourceFile::ObjCMethodConflict &conflict) {
return classDecl->lookupDirect(selector, isInstanceMethod);
}

/// Given a set of conflicting Objective-C methods, remove any methods
/// that are legitimately overridden in Objective-C, i.e., because
/// they occur in different modules, one is defined in the class, and
/// the other is defined in an extension (category) thereof.
static void removeValidObjCConflictingMethods(
MutableArrayRef<AbstractFunctionDecl *> &methods) {
// Erase any invalid or stub declarations. We don't want to complain about
// them, because we might already have complained about
// redeclarations based on Swift matching.
auto newEnd = std::remove_if(methods.begin(), methods.end(),
[&](AbstractFunctionDecl *method) {
if (method->isInvalid())
return true;

if (auto ad = dyn_cast<AccessorDecl>(method)) {
return ad->getStorage()->isInvalid();
}

if (auto ctor
= dyn_cast<ConstructorDecl>(method)) {
if (ctor->hasStubImplementation())
return true;

return false;
}

return false;
});
methods = methods.slice(0, newEnd - methods.begin());
}

bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
// If there were no conflicts, we're done.
if (sf.ObjCMethodConflicts.empty())
return false;

auto &Ctx = sf.getASTContext();

OrderDeclarations ordering(Ctx.SourceMgr);
OrderDeclarations ordering;

// Sort the set of conflicts so we get a deterministic order for
// diagnostics. We use the first conflicting declaration in each set to
Expand All @@ -2140,8 +2085,23 @@ bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {

auto methods = getObjCMethodConflictDecls(conflict);

// Prune out cases where it is acceptable to have a conflict.
removeValidObjCConflictingMethods(methods);
// Erase any invalid or stub declarations. We don't want to complain about
// them, because we might already have complained about redeclarations
// based on Swift matching.
llvm::erase_if(methods, [](AbstractFunctionDecl *afd) -> bool {
if (afd->isInvalid())
return true;

if (auto ad = dyn_cast<AccessorDecl>(afd))
return ad->getStorage()->isInvalid();

if (auto *ctor = dyn_cast<ConstructorDecl>(afd)) {
if (ctor->hasStubImplementation())
return true;
}
return false;
});

if (methods.size() < 2)
continue;

Expand All @@ -2150,22 +2110,23 @@ bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {

// If the first method is in an extension and the second is not, swap them
// so the primary diagnostic is on the extension method.
MutableArrayRef<AbstractFunctionDecl *> methodsRef(methods);
if (isa<ExtensionDecl>(methods[0]->getDeclContext()) &&
!isa<ExtensionDecl>(methods[1]->getDeclContext())) {
std::swap(methods[0], methods[1]);
std::swap(methodsRef[0], methodsRef[1]);

// Within a source file, use our canonical ordering.
} else if (methods[0]->getParentSourceFile() ==
methods[1]->getParentSourceFile() &&
!ordering(methods[0], methods[1])) {
std::swap(methods[0], methods[1]);
std::swap(methodsRef[0], methodsRef[1]);
}

// Otherwise, fall back to the order in which the declarations were type
// checked.

auto originalMethod = methods.front();
auto conflictingMethods = methods.slice(1);
auto conflictingMethods = methodsRef.slice(1);

auto origDiagInfo = getObjCMethodDiagInfo(originalMethod);
for (auto conflictingDecl : conflictingMethods) {
Expand All @@ -2182,12 +2143,9 @@ bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
Ctx.Diags.diagnose(originalDecl, diag::invalid_redecl_prev,
originalDecl->getBaseName());
} else {
Ctx.Diags.diagnose(conflictingDecl, diag::objc_redecl,
diagInfo.first,
diagInfo.second,
origDiagInfo.first,
origDiagInfo.second,
selector);
Ctx.Diags.diagnose(conflictingDecl, diag::objc_redecl, diagInfo.first,
diagInfo.second, origDiagInfo.first,
origDiagInfo.second, selector);
Ctx.Diags.diagnose(originalDecl, diag::objc_declared_here,
origDiagInfo.first, origDiagInfo.second);
}
Expand Down