Skip to content

Commit 0c785c0

Browse files
jrose-applevarungandhi-apple
authored andcommitted
[ClangImporter] Don't use local scope decls to find a function's home (swiftlang#27440)
In C, a function can be declared at local scope: void aFunctionInBase(void) { void theFunctionInQuestion(int); } and then again in a different header at top-level scope: void theFunctionInQuestion(int); If the first one appears first, it becomes what Clang considers the "canonical" declaration, which (up until now) Swift has been using to decide what module to import a function into. (Since a C function can be redeclared arbitrarily many times, we have to pick one.) This is important for diagnostics and anything else that might ask "where did this Swift declaration come from". Instead of the very first redeclaration, use the first non-local one to determine the "home" module. (The standard library wants a guarantee that forward declarations they put in SwiftShims won't interfere with declarations found elsewhere. I don't think Clang can /ever/ provide that, so if there's ever a mismatch between the standard library's forward declarations and the "real" declarations the standard library's might win out at the LLVM level---say, in terms of attributes. But this at least removes a place where that could be visible to users even when it isn't otherwise a problem.)
1 parent 910661f commit 0c785c0

File tree

6 files changed

+72
-1
lines changed

6 files changed

+72
-1
lines changed

lib/ClangImporter/ClangAdapter.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ importer::getDefinitionForClangTypeDecl(const clang::Decl *D) {
7777
return None;
7878
}
7979

80+
const clang::Decl *
81+
importer::getFirstNonLocalDecl(const clang::Decl *D) {
82+
D = D->getCanonicalDecl();
83+
auto iter = llvm::find_if(D->redecls(), [](const clang::Decl *next) -> bool {
84+
return !next->isLexicallyWithinFunctionOrMethod();
85+
});
86+
if (iter == D->redecls_end())
87+
return nullptr;
88+
return *iter;
89+
}
90+
8091
Optional<clang::Module *>
8192
importer::getClangSubmoduleForDecl(const clang::Decl *D,
8293
bool allowForwardDeclaration) {
@@ -91,7 +102,7 @@ importer::getClangSubmoduleForDecl(const clang::Decl *D,
91102
}
92103

93104
if (!actual)
94-
actual = D->getCanonicalDecl();
105+
actual = getFirstNonLocalDecl(D);
95106

96107
return actual->getImportedOwningModule();
97108
}

lib/ClangImporter/ClangAdapter.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,34 @@ struct PlatformAvailability;
6060
Optional<const clang::Decl *>
6161
getDefinitionForClangTypeDecl(const clang::Decl *D);
6262

63+
/// Returns the first redeclaration of \p D outside of a function.
64+
///
65+
/// C allows redeclaring most declarations in function bodies, as so:
66+
///
67+
/// void usefulPublicFunction(void) {
68+
/// extern void importantInternalFunction(int code);
69+
/// importantInternalFunction(42);
70+
/// }
71+
///
72+
/// This should allow clients to call \c usefulPublicFunction without exposing
73+
/// \c importantInternalFunction . However, if there is another declaration of
74+
/// \c importantInternalFunction later, Clang still needs to treat them as the
75+
/// same function. This is normally fine...except that if the local declaration
76+
/// is the \e first declaration, it'll also get used as the "canonical"
77+
/// declaration that Clang (and Swift) use for uniquing purposes.
78+
///
79+
/// Every imported declaration gets assigned to a module in Swift, and for
80+
/// declarations without definitions that choice is somewhat arbitrary. But it
81+
/// would be better not to pick a local declaration like the one above, and
82+
/// therefore this method should be used instead of
83+
/// clang::Decl::getCanonicalDecl when the containing module is important.
84+
///
85+
/// If there are no non-local redeclarations, returns null.
86+
/// If \p D is not a kind of declaration that supports being redeclared, just
87+
/// returns \p D itself.
88+
const clang::Decl *
89+
getFirstNonLocalDecl(const clang::Decl *D);
90+
6391
/// Returns the module \p D comes from, or \c None if \p D does not have
6492
/// a valid associated module.
6593
///
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "LocalVsFileScopeBase.h"
2+
3+
void theFunctionInQuestion(int);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
void aFunctionInBase(void) {
2+
void theFunctionInQuestion(int);
3+
}

test/ClangImporter/Inputs/custom-modules/module.map

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,11 @@ module ConditionallyFoo {
233233
module ForwardDeclarationsHelper {
234234
header "ForwardDeclarationsHelper.h"
235235
}
236+
237+
module LocalVsFileScopeBase {
238+
header "LocalVsFileScopeBase.h"
239+
}
240+
module LocalVsFileScope {
241+
header "LocalVsFileScope.h"
242+
export *
243+
}

test/ClangImporter/cfuncs_scope.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: not %target-swift-frontend -typecheck %s -I %S/Inputs/custom-modules 2>&1 | %FileCheck %s
2+
3+
import LocalVsFileScope
4+
5+
func testLocalVsFileScope() {
6+
LocalVsFileScopeBase.theFunctionInQuestion()
7+
// CHECK: :[[@LINE-1]]:3: error: module 'LocalVsFileScopeBase' has no member named 'theFunctionInQuestion'
8+
9+
theFunctionInQuestion()
10+
// CHECK: :[[@LINE-1]]:25: error: missing argument
11+
// CHECK: LocalVsFileScope.theFunctionInQuestion:1:{{[0-9]+}}: note:
12+
// This is not a wonderful test because it's relying on the diagnostic
13+
// engine's synthesis of fake declarations to figure out what module the
14+
// importer assigned the function to. But, well, that's what we get.
15+
16+
aFunctionInBase() // just make sure it's imported
17+
// CHECK-NOT: :[[@LINE-1]]:{{[0-9]+}}:
18+
}

0 commit comments

Comments
 (0)