Skip to content

Commit 7725e38

Browse files
committed
Don't mirror-import a protocol method if there's another method in
the class's protocol hierarchy that overrides it. rdar://31471034
1 parent bb4d90f commit 7725e38

File tree

4 files changed

+207
-17
lines changed

4 files changed

+207
-17
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 149 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,9 @@ applyPropertyOwnership(VarDecl *prop,
18651865
}
18661866
}
18671867

1868+
using MirroredMethodEntry =
1869+
std::pair<const clang::ObjCMethodDecl*, ProtocolDecl*>;
1870+
18681871
namespace {
18691872
/// Customized llvm::DenseMapInfo for storing borrowed APSInts.
18701873
struct APSIntRefDenseMapInfo {
@@ -3934,6 +3937,10 @@ namespace {
39343937
SmallVectorImpl<Decl *> &members,
39353938
ASTContext &Ctx);
39363939

3940+
void importNonOverriddenMirroredMethods(DeclContext *dc,
3941+
MutableArrayRef<MirroredMethodEntry> entries,
3942+
SmallVectorImpl<Decl *> &newMembers);
3943+
39373944
/// \brief Import constructors from our superclasses (and their
39383945
/// categories/extensions), effectively "inheriting" constructors.
39393946
void importInheritedConstructors(ClassDecl *classDecl,
@@ -6405,6 +6412,15 @@ void SwiftDeclConverter::importMirroredProtocolMembers(
64056412
const ClangModuleUnit *declModule;
64066413
const ClangModuleUnit *interfaceModule;
64076414

6415+
// 'protocols' is, for some reason, the full recursive expansion of
6416+
// the protocol hierarchy, so there's no need to recursively descend
6417+
// into inherited protocols.
6418+
6419+
// Try to import only the most specific methods with a particular name.
6420+
// We use a MapVector to get deterministic iteration order later.
6421+
llvm::MapVector<clang::Selector, std::vector<MirroredMethodEntry>>
6422+
methodsByName;
6423+
64086424
for (auto proto : protocols) {
64096425
auto clangProto =
64106426
cast_or_null<clang::ObjCProtocolDecl>(proto->getClangDecl());
@@ -6493,27 +6509,143 @@ void SwiftDeclConverter::importMirroredProtocolMembers(
64936509
if (!objcMethod)
64946510
continue;
64956511

6496-
// When mirroring an initializer, make it designated and required.
6497-
if (isInitMethod(objcMethod)) {
6498-
// Import the constructor.
6499-
if (auto imported = importConstructor(objcMethod, dc, /*implicit=*/true,
6500-
CtorInitializerKind::Designated,
6501-
/*required=*/true)) {
6502-
members.push_back(imported);
6503-
}
6512+
// For now, just remember that we saw this method.
6513+
methodsByName[objcMethod->getSelector()]
6514+
.push_back(MirroredMethodEntry{objcMethod, proto});
6515+
}
6516+
}
65046517

6505-
continue;
6506-
}
6518+
// Process all the methods, now that we've arranged them by selector.
6519+
for (auto &mapEntry : methodsByName) {
6520+
importNonOverriddenMirroredMethods(dc, mapEntry.second, members);
6521+
}
6522+
}
65076523

6508-
// Import the method.
6509-
if (auto imported =
6510-
Impl.importMirroredDecl(objcMethod, dc, getVersion(), proto)) {
6511-
members.push_back(imported);
6524+
enum MirrorImportComparison {
6525+
// There's no suppression relationship between the methods.
6526+
NoSuppression,
6527+
6528+
// The first method suppresses the second.
6529+
Suppresses,
6530+
6531+
// The second method suppresses the first.
6532+
IsSuppressed,
6533+
};
6534+
6535+
/// Should the mirror import of the first method be suppressed in favor
6536+
/// of the second method? The methods are known to have the same selector
6537+
/// and (because this is mirror-import) to be declared on protocols.
6538+
///
6539+
/// The algorithm that uses this assumes that it is transitive.
6540+
static bool isMirrorImportSuppressedBy(ClangImporter::Implementation &importer,
6541+
const clang::ObjCMethodDecl *first,
6542+
const clang::ObjCMethodDecl *second) {
6543+
auto firstProto = cast<clang::ObjCProtocolDecl>(first->getDeclContext());
6544+
auto secondProto = cast<clang::ObjCProtocolDecl>(second->getDeclContext());
6545+
6546+
// If the first method's protocol is a super-protocol of the second's,
6547+
// then the second method overrides the first and we should suppress.
6548+
// Clang provides a function to check that, phrased in terms of whether
6549+
// a value of one protocol (the RHS) can be assigned to an l-value of
6550+
// the other (the LHS).
6551+
auto &ctx = importer.getClangASTContext();
6552+
return ctx.ProtocolCompatibleWithProtocol(
6553+
const_cast<clang::ObjCProtocolDecl*>(firstProto),
6554+
const_cast<clang::ObjCProtocolDecl*>(secondProto));
6555+
}
6556+
6557+
/// Compare two methods for mirror-import purposes.
6558+
static MirrorImportComparison
6559+
compareMethodsForMirrorImport(ClangImporter::Implementation &importer,
6560+
const clang::ObjCMethodDecl *first,
6561+
const clang::ObjCMethodDecl *second) {
6562+
if (isMirrorImportSuppressedBy(importer, first, second))
6563+
return IsSuppressed;
6564+
if (isMirrorImportSuppressedBy(importer, second, first))
6565+
return Suppresses;
6566+
return NoSuppression;
6567+
}
6568+
6569+
/// Mark any methods in the given array that are overridden by this method
6570+
/// as suppressed by nulling their entries out.
6571+
/// Return true if this method is overridden by any methods in the array.
6572+
static bool suppressOverriddenMethods(ClangImporter::Implementation &importer,
6573+
const clang::ObjCMethodDecl *method,
6574+
MutableArrayRef<MirroredMethodEntry> entries) {
6575+
assert(method && "method was already suppressed");
6576+
6577+
for (auto &entry: entries) {
6578+
auto otherMethod = entry.first;
6579+
if (!otherMethod) continue;
6580+
6581+
assert(method != otherMethod && "found same method twice?");
6582+
switch (compareMethodsForMirrorImport(importer, method, otherMethod)) {
6583+
// If the second method is suppressed, null it out.
6584+
case Suppresses:
6585+
entry.first = nullptr;
6586+
continue;
6587+
6588+
// If the first method is suppressed, return immediately. We should
6589+
// be able to suppress any following methods.
6590+
case IsSuppressed:
6591+
return true;
6592+
6593+
case NoSuppression:
6594+
continue;
6595+
}
6596+
llvm_unreachable("bad comparison result");
6597+
}
6598+
6599+
return false;
6600+
}
6601+
6602+
/// Given a set of methods with the same selector, each taken from a
6603+
/// different protocol in the protocol hierarchy of a class into which
6604+
/// we want to introduce mirror imports, import only the methods which
6605+
/// are not overridden by another method in the set.
6606+
///
6607+
/// It's possible that we'll end up selecting multiple methods to import
6608+
/// here, in the cases where there's no hierarchical relationship between
6609+
/// two methods. The importer already has code to handle this case.
6610+
void SwiftDeclConverter::importNonOverriddenMirroredMethods(DeclContext *dc,
6611+
MutableArrayRef<MirroredMethodEntry> entries,
6612+
SmallVectorImpl<Decl *> &members) {
6613+
for (size_t i = 0, e = entries.size(); i != e; ++i) {
6614+
auto objcMethod = entries[i].first;
6615+
6616+
// If the method was suppressed by a previous method, ignore it.
6617+
if (!objcMethod)
6618+
continue;
6619+
6620+
// Compare this method to all the following methods, suppressing any
6621+
// that it overrides. If it is overridden by any of them, suppress it
6622+
// instead; but there's no need to mark that in the array, just continue
6623+
// on to the next method.
6624+
if (suppressOverriddenMethods(Impl, objcMethod, entries.slice(i + 1)))
6625+
continue;
6626+
6627+
// Okay, the method wasn't suppressed, import it.
65126628

6513-
for (auto alternate : Impl.getAlternateDecls(imported))
6514-
if (imported->getDeclContext() == alternate->getDeclContext())
6515-
members.push_back(alternate);
6629+
// When mirroring an initializer, make it designated and required.
6630+
if (isInitMethod(objcMethod)) {
6631+
// Import the constructor.
6632+
if (auto imported = importConstructor(objcMethod, dc, /*implicit=*/true,
6633+
CtorInitializerKind::Designated,
6634+
/*required=*/true)) {
6635+
members.push_back(imported);
65166636
}
6637+
continue;
6638+
}
6639+
6640+
// Import the method.
6641+
auto proto = entries[i].second;
6642+
if (auto imported =
6643+
Impl.importMirroredDecl(objcMethod, dc, getVersion(), proto)) {
6644+
members.push_back(imported);
6645+
6646+
for (auto alternate : Impl.getAlternateDecls(imported))
6647+
if (imported->getDeclContext() == alternate->getDeclContext())
6648+
members.push_back(alternate);
65176649
}
65186650
}
65196651
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@protocol Context
2+
- (void) operate;
3+
@end
4+
5+
@protocol A
6+
- (void) use: (nonnull void(^)(__nonnull id)) callback;
7+
@end
8+
9+
@protocol B<A>
10+
@end
11+
12+
@protocol C<A>
13+
- (void) use: (nonnull void(^)(__nonnull id<Context>)) callback;
14+
@end
15+
16+
@protocol D<C, B>
17+
@end
18+
19+
@interface NSObject
20+
@end
21+
22+
@interface Widget : NSObject<D>
23+
@end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@protocol Context
2+
- (void) operate;
3+
@end
4+
5+
@protocol A
6+
- (void) use: (nonnull void(^)(__nonnull id)) callback;
7+
@end
8+
9+
@protocol B<A>
10+
@end
11+
12+
@protocol C<A>
13+
- (void) use: (nonnull void(^)(__nonnull id<Context>)) callback;
14+
@end
15+
16+
@protocol D<B, C>
17+
@end
18+
19+
@interface NSObject
20+
@end
21+
22+
@interface Widget : NSObject<D>
23+
@end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -import-objc-header %S/Inputs/mirror_import_overrides_1.h -typecheck -verify %s
2+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -import-objc-header %S/Inputs/mirror_import_overrides_2.h -typecheck -verify %s
3+
4+
// REQUIRES: objc_interop
5+
6+
// rdar://31471034
7+
8+
func foo(widget: Widget) {
9+
widget.use { context in
10+
context.operate()
11+
}
12+
}

0 commit comments

Comments
 (0)