Skip to content

Commit ca736fd

Browse files
committed
[Omit needless words] Ensure Swift name consistency for overriding/overridden decalrations.
When we transform an Objective-C method or property name, we take into account the parameter types, result type, and type of the enclosing context. Doing so means that one can get different Swift names for a particular method (or property) and one that overrides it, which leads to dangerous inconsistencies in the Swift names. Address this limitation by using the original declaration's name (i.e., one that does not override any other). It is possible that there is more than one "original declaration", when the same method/property name comes from different protocols. When this is the case, check for consistency and complain if there are inconsistencies. Amusingly, this changes exactly one method in Cocoa (UINavigationController's showViewController:sender:), with no conflicts detected. Fixes rdar://problem/24558337.
1 parent 35abad2 commit ca736fd

File tree

6 files changed

+206
-3
lines changed

6 files changed

+206
-3
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ WARNING(invalid_swift_name_method,none,
5858
"too %select{few|many}0 parameters in swift_name attribute (expected %1; "
5959
"got %2)", (bool, unsigned, unsigned))
6060

61+
WARNING(inconsistent_swift_name,none,
62+
"inconsistent Swift name for Objective-C %select{method|property}0 "
63+
"'%1' in '%2' (%3 in '%4' vs. %5 in '%6')",
64+
(bool, StringRef, StringRef, DeclName, StringRef, DeclName,
65+
StringRef))
66+
6167
#ifndef DIAG_NO_UNDEF
6268
# if defined(DIAG)
6369
# undef DIAG

lib/ClangImporter/ClangImporter.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,95 @@ Identifier ClangImporter::Implementation::importMacroName(
19031903
return SwiftContext.getIdentifier(name);
19041904
}
19051905

1906+
namespace {
1907+
typedef ClangImporter::Implementation::ImportedName ImportedName;
1908+
}
1909+
1910+
namespace llvm {
1911+
// An Identifier is "pointer like".
1912+
template<typename T> class PointerLikeTypeTraits;
1913+
template<>
1914+
class PointerLikeTypeTraits<swift::DeclName> {
1915+
public:
1916+
static inline void *getAsVoidPointer(swift::DeclName name) {
1917+
return name.getOpaqueValue();
1918+
}
1919+
static inline swift::DeclName getFromVoidPointer(void *ptr) {
1920+
return swift::DeclName::getFromOpaqueValue(ptr);
1921+
}
1922+
enum { NumLowBitsAvailable = 0 };
1923+
};
1924+
}
1925+
1926+
/// Retrieve the name of the given Clang declaration context for
1927+
/// printing.
1928+
static StringRef getClangDeclContextName(const clang::DeclContext *dc) {
1929+
auto type = ClangImporter::Implementation::getClangDeclContextType(dc);
1930+
1931+
// FIXME: Hack to work around getClangDeclContextType() failing for
1932+
// protocols.
1933+
if (type.isNull()) {
1934+
if (auto constProto = dyn_cast<clang::ObjCProtocolDecl>(dc)) {
1935+
auto proto = const_cast<clang::ObjCProtocolDecl *>(constProto);
1936+
clang::ASTContext &ctx = dc->getParentASTContext();
1937+
type = ctx.getObjCObjectType(ctx.ObjCBuiltinIdTy, { }, { proto }, false);
1938+
type = ctx.getObjCObjectPointerType(type);
1939+
}
1940+
}
1941+
1942+
if (type.isNull()) return StringRef();
1943+
1944+
return ClangImporter::Implementation::getClangTypeNameForOmission(
1945+
dc->getParentASTContext(),
1946+
type).Name;
1947+
}
1948+
1949+
namespace {
1950+
/// Merge the a set of imported names produced for the overridden
1951+
/// declarations of a given method or property.
1952+
template<typename DeclType>
1953+
void mergeOverriddenNames(ASTContext &ctx,
1954+
const DeclType *decl,
1955+
SmallVectorImpl<std::pair<const DeclType *,
1956+
ImportedName>>
1957+
&overriddenNames) {
1958+
typedef std::pair<const DeclType *, ImportedName> OverriddenName;
1959+
llvm::SmallPtrSet<DeclName, 4> known;
1960+
(void)known.insert(DeclName());
1961+
overriddenNames.erase(std::remove_if(overriddenNames.begin(),
1962+
overriddenNames.end(),
1963+
[&](OverriddenName overridden) {
1964+
return !known.insert(
1965+
overridden.second.Imported)
1966+
.second;
1967+
}),
1968+
overriddenNames.end());
1969+
1970+
if (overriddenNames.size() < 2)
1971+
return;
1972+
1973+
// Complain about inconsistencies.
1974+
std::string nameStr;
1975+
auto method = dyn_cast<clang::ObjCMethodDecl>(decl);
1976+
if (method)
1977+
nameStr = method->getSelector().getAsString();
1978+
else
1979+
nameStr = cast<clang::ObjCPropertyDecl>(decl)->getName().str();
1980+
for (unsigned i = 1, n = overriddenNames.size(); i != n; ++i) {
1981+
ctx.Diags.diagnose(SourceLoc(), diag::inconsistent_swift_name,
1982+
method == nullptr,
1983+
nameStr,
1984+
getClangDeclContextName(decl->getDeclContext()),
1985+
overriddenNames[0].second,
1986+
getClangDeclContextName(
1987+
overriddenNames[0].first->getDeclContext()),
1988+
overriddenNames[i].second,
1989+
getClangDeclContextName(
1990+
overriddenNames[i].first->getDeclContext()));
1991+
}
1992+
}
1993+
}
1994+
19061995
auto ClangImporter::Implementation::importFullName(
19071996
const clang::NamedDecl *D,
19081997
ImportNameOptions options,
@@ -1951,6 +2040,64 @@ auto ClangImporter::Implementation::importFullName(
19512040
}
19522041
}
19532042

2043+
// When omitting needless words, find the original method/property
2044+
// declaration.
2045+
if (OmitNeedlessWords) {
2046+
if (auto method = dyn_cast<clang::ObjCMethodDecl>(D)) {
2047+
// Inherit the name from the "originating" declarations, if
2048+
// there are any.
2049+
SmallVector<std::pair<const clang::ObjCMethodDecl *, ImportedName>, 4>
2050+
overriddenNames;
2051+
SmallVector<const clang::ObjCMethodDecl *, 4> overriddenMethods;
2052+
method->getOverriddenMethods(overriddenMethods);
2053+
for (auto overridden : overriddenMethods) {
2054+
const auto overriddenName = importFullName(overridden, options,
2055+
nullptr,clangSemaOverride);
2056+
if (overriddenName.Imported)
2057+
overriddenNames.push_back({overridden, overriddenName});
2058+
}
2059+
2060+
// If we found any names of overridden methods, return those names.
2061+
if (!overriddenNames.empty()) {
2062+
if (overriddenNames.size() > 1)
2063+
mergeOverriddenNames(SwiftContext, method, overriddenNames);
2064+
return overriddenNames[0].second;
2065+
}
2066+
} else if (auto property = dyn_cast<clang::ObjCPropertyDecl>(D)) {
2067+
// Inherit the name from the "originating" declarations, if
2068+
// there are any.
2069+
if (auto getter = property->getGetterMethodDecl()) {
2070+
SmallVector<std::pair<const clang::ObjCPropertyDecl *, ImportedName>, 4>
2071+
overriddenNames;
2072+
SmallVector<const clang::ObjCMethodDecl *, 4> overriddenMethods;
2073+
SmallPtrSet<const clang::ObjCPropertyDecl *, 4> knownProperties;
2074+
(void)knownProperties.insert(property);
2075+
2076+
getter->getOverriddenMethods(overriddenMethods);
2077+
for (auto overridden : overriddenMethods) {
2078+
if (!overridden->isPropertyAccessor()) continue;
2079+
auto overriddenProperty = overridden->findPropertyDecl(true);
2080+
if (!overriddenProperty) continue;
2081+
if (!knownProperties.insert(overriddenProperty).second) continue;
2082+
2083+
const auto overriddenName = importFullName(overriddenProperty,
2084+
options,
2085+
nullptr,
2086+
clangSemaOverride);
2087+
if (overriddenName.Imported)
2088+
overriddenNames.push_back({overriddenProperty, overriddenName});
2089+
}
2090+
2091+
// If we found any names of overridden methods, return those names.
2092+
if (!overriddenNames.empty()) {
2093+
if (overriddenNames.size() > 1)
2094+
mergeOverriddenNames(SwiftContext, property, overriddenNames);
2095+
return overriddenNames[0].second;
2096+
}
2097+
}
2098+
}
2099+
}
2100+
19542101
// If we have a swift_name attribute, use that.
19552102
if (auto *nameAttr = D->getAttr<clang::SwiftNameAttr>()) {
19562103
bool skipCustomName = false;

lib/ClangImporter/ImportType.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1857,7 +1857,7 @@ bool ClangImporter::Implementation::omitNeedlessWordsInFunctionName(
18571857
/// Retrieve the instance type of the given Clang declaration context.
18581858
clang::QualType ClangImporter::Implementation::getClangDeclContextType(
18591859
const clang::DeclContext *dc) {
1860-
auto &ctx = getClangASTContext();
1860+
auto &ctx = dc->getParentASTContext();
18611861
if (auto objcClass = dyn_cast<clang::ObjCInterfaceDecl>(dc))
18621862
return ctx.getObjCObjectPointerType(ctx.getObjCInterfaceType(objcClass));
18631863

lib/ClangImporter/ImporterImpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
796796

797797
/// Retrieve the type of an instance of the given Clang declaration context,
798798
/// or a null type if the DeclContext does not have a corresponding type.
799-
clang::QualType getClangDeclContextType(const clang::DeclContext *dc);
799+
static clang::QualType getClangDeclContextType(const clang::DeclContext *dc);
800800

801801
/// Determine whether this typedef is a CF type.
802802
static bool isCFTypeDecl(const clang::TypedefNameDecl *Decl);

test/IDE/Inputs/custom-modules/OmitNeedlessWords.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
@import Foundation;
22

3+
#ifndef NS_SWIFT_NAME(Name)
4+
# define NS_SWIFT_NAME(Name) __attribute__((swift_name(#Name)))
5+
#endif
6+
37
@interface SEGreebieArray : NSObject
48
@end
59

@@ -27,3 +31,26 @@
2731
-(void)drawFilledPolygonWithPoints:(NSPointArray)points count:(NSInteger)count;
2832
-(void)drawGreebies:(nonnull SEGreebieArray*)greebies;
2933
@end
34+
35+
@protocol OMWWiggle
36+
-(void)joinSub;
37+
-(void)conflicting1 NS_SWIFT_NAME(wiggle1());
38+
@property (readonly) NSInteger conflictingProp1 NS_SWIFT_NAME(wiggleProp1);
39+
@end
40+
41+
@protocol OMWWaggle
42+
-(void)conflicting1 NS_SWIFT_NAME(waggle1());
43+
@property (readonly) NSInteger conflictingProp1 NS_SWIFT_NAME(waggleProp1);
44+
@end
45+
46+
@interface OMWSuper : NSObject <OMWWiggle>
47+
-(void)jumpSuper;
48+
@property (readonly) NSInteger conflictingProp1;
49+
@end
50+
51+
@interface OMWSub : OMWSuper <OMWWaggle>
52+
-(void)jumpSuper;
53+
-(void)joinSub;
54+
-(void)conflicting1;
55+
@property (readonly) NSInteger conflictingProp1;
56+
@end

test/IDE/print_omit_needless_words.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
// RUN: %target-swift-ide-test(mock-sdk: -sdk %S/../Inputs/clang-importer-sdk -I %t -I %S/../ClangModules/Inputs/custom-modules) -print-module -source-filename %s -module-to-print=CoreCooling -function-definitions=false -prefer-type-repr=true -enable-omit-needless-words -enable-strip-ns-prefix -skip-parameter-names -enable-infer-default-arguments > %t.CoreCooling.txt
2222
// RUN: FileCheck %s -check-prefix=CHECK-CORECOOLING -strict-whitespace < %t.CoreCooling.txt
2323

24-
// RUN: %target-swift-ide-test(mock-sdk: -sdk %S/../Inputs/clang-importer-sdk -I %t -I %S/Inputs/custom-modules) -print-module -source-filename %s -module-to-print=OmitNeedlessWords -function-definitions=false -prefer-type-repr=true -enable-omit-needless-words -enable-strip-ns-prefix -skip-parameter-names -enable-infer-default-arguments > %t.OmitNeedlessWords.txt
24+
// RUN: %target-swift-ide-test(mock-sdk: -sdk %S/../Inputs/clang-importer-sdk -I %t -I %S/Inputs/custom-modules) -print-module -source-filename %s -module-to-print=OmitNeedlessWords -function-definitions=false -prefer-type-repr=true -enable-omit-needless-words -enable-strip-ns-prefix -skip-parameter-names -enable-infer-default-arguments > %t.OmitNeedlessWords.txt 2> %t.OmitNeedlessWords.diagnostics.txt
2525
// RUN: FileCheck %s -check-prefix=CHECK-OMIT-NEEDLESS-WORDS -strict-whitespace < %t.OmitNeedlessWords.txt
26+
// RUN: FileCheck %s -check-prefix=CHECK-OMIT-NEEDLESS-WORDS-DIAGS -strict-whitespace < %t.OmitNeedlessWords.diagnostics.txt
27+
2628

2729
// RUN: %target-swift-ide-test(mock-sdk: -sdk %S/../Inputs/clang-importer-sdk -I %t) -print-module -source-filename %s -module-to-print=errors -function-definitions=false -prefer-type-repr=true -enable-omit-needless-words -enable-strip-ns-prefix -skip-parameter-names -enable-infer-default-arguments > %t.errors.txt
2830
// RUN: FileCheck %s -check-prefix=CHECK-ERRORS -strict-whitespace < %t.errors.txt
@@ -307,5 +309,26 @@
307309
// Non-parameterized Objective-C class ending in "Array".
308310
// CHECK-OMIT-NEEDLESS-WORDS: func draw(_: SEGreebieArray)
309311

312+
// Verify that we get the Swift name from the original declaration.
313+
// CHECK-OMIT-NEEDLESS-WORDS: protocol OMWWiggle
314+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: func joinSub()
315+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: func wiggle1()
316+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: var wiggleProp1: Int { get }
317+
318+
// CHECK-OMIT-NEEDLESS-WORDS: protocol OMWWaggle
319+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: func waggle1()
320+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: var waggleProp1: Int { get }
321+
322+
// CHECK-OMIT-NEEDLESS-WORDS: class OMWSuper
323+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: func jump()
324+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: var wiggleProp1: Int { get }
325+
326+
// CHECK-OMIT-NEEDLESS-WORDS: class OMWSub
327+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: func jump()
328+
// CHECK-OMIT-NEEDLESS-WORDS-NEXT: func joinSub()
329+
330+
// CHECK-OMIT-NEEDLESS-WORDS-DIAGS: inconsistent Swift name for Objective-C method 'conflicting1' in 'OMWSub' ('waggle1()' in 'OMWWaggle' vs. 'wiggle1()' in 'OMWWiggle')
331+
// CHECK-OMIT-NEEDLESS-WORDS-DIAGS: inconsistent Swift name for Objective-C property 'conflictingProp1' in 'OMWSub' ('waggleProp1' in 'OMWWaggle' vs. 'wiggleProp1' in 'OMWSuper')
332+
310333
// Don't drop the 'error'.
311334
// CHECK-ERRORS: func tryAndReturnError(_: ()) throws

0 commit comments

Comments
 (0)