Skip to content

Commit 6ef5fb5

Browse files
authored
Merge pull request #23702 from rjmccall/implementation-only-diagnostics
[WIP] implementation-only import checking
2 parents 93632f0 + ae6561c commit 6ef5fb5

11 files changed

+417
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4099,6 +4099,11 @@ WARNING(resilience_decl_unavailable_warn,
40994099
"should not be referenced from " FRAGILE_FUNC_KIND "3",
41004100
(DescriptiveDeclKind, DeclName, AccessLevel, unsigned, bool))
41014101

4102+
ERROR(inlinable_decl_ref_implementation_only,
4103+
none, "%0 %1 cannot be used in an inlinable "
4104+
"function because its module was imported implementation-only",
4105+
(DescriptiveDeclKind, DeclName))
4106+
41024107
#undef FRAGILE_FUNC_KIND
41034108

41044109
NOTE(resilience_decl_declared_here_public,

include/swift/AST/Module.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,10 @@ class SourceFile final : public FileUnit {
959959
/// May be -1, to indicate no association with a buffer.
960960
int BufferID;
961961

962+
/// Does this source file have any implementation-only imports?
963+
/// If not, we can fast-path module checks.
964+
bool HasImplementationOnlyImports = false;
965+
962966
/// The list of protocol conformances that were "used" within this
963967
/// source file.
964968
llvm::SetVector<NormalProtocolConformance *> UsedConformances;
@@ -1047,6 +1051,12 @@ class SourceFile final : public FileUnit {
10471051
hasTestableOrPrivateImport(AccessLevel accessLevel, const ValueDecl *ofDecl,
10481052
ImportQueryKind kind = TestableAndPrivate) const;
10491053

1054+
bool hasImplementationOnlyImports() const {
1055+
return HasImplementationOnlyImports;
1056+
}
1057+
1058+
bool isImportedImplementationOnly(const ModuleDecl *module) const;
1059+
10501060
void clearLookupCache();
10511061

10521062
void cacheVisibleDecls(SmallVectorImpl<ValueDecl *> &&globals) const;

lib/AST/Module.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,14 @@ void SourceFile::addImports(ArrayRef<ImportedModuleDesc> IM) {
14211421
assert(iter == newBuf.end());
14221422

14231423
Imports = newBuf;
1424+
1425+
// Update the HasImplementationOnlyImports flag.
1426+
if (!HasImplementationOnlyImports) {
1427+
for (auto &desc : IM) {
1428+
if (desc.importOptions.contains(ImportFlags::ImplementationOnly))
1429+
HasImplementationOnlyImports = true;
1430+
}
1431+
}
14241432
}
14251433

14261434
bool SourceFile::hasTestableOrPrivateImport(
@@ -1485,6 +1493,43 @@ bool SourceFile::hasTestableOrPrivateImport(
14851493
});
14861494
}
14871495

1496+
bool SourceFile::isImportedImplementationOnly(const ModuleDecl *module) const {
1497+
// Implementation-only imports are (currently) always source-file-specific,
1498+
// so if we don't have any, we know the search is complete.
1499+
if (!hasImplementationOnlyImports())
1500+
return false;
1501+
1502+
auto isImportedBy = [](const ModuleDecl *dest, const ModuleDecl *src) {
1503+
// Fast path.
1504+
if (dest == src) return true;
1505+
1506+
// Walk the transitive imports, respecting visibility.
1507+
// This return true if the search *didn't* short-circuit, and it short
1508+
// circuits if we found `dest`, so we need to invert the sense before
1509+
// returning.
1510+
return !const_cast<ModuleDecl*>(src)
1511+
->forAllVisibleModules({}, [dest](ModuleDecl::ImportedModule im) {
1512+
// Continue searching as long as we haven't found `dest` yet.
1513+
return im.second != dest;
1514+
});
1515+
};
1516+
1517+
// Look at the imports of this source file.
1518+
for (auto &desc : Imports) {
1519+
// Ignore implementation-only imports.
1520+
if (desc.importOptions.contains(ImportFlags::ImplementationOnly))
1521+
continue;
1522+
1523+
// If the module is imported this way, it's not imported
1524+
// implementation-only.
1525+
if (isImportedBy(module, desc.module.second))
1526+
return false;
1527+
}
1528+
1529+
// Now check this file's enclosing module in case there are re-exports.
1530+
return !isImportedBy(module, getParentModule());
1531+
}
1532+
14881533
void SourceFile::clearLookupCache() {
14891534
if (!Cache)
14901535
return;

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ bool TypeChecker::diagnoseInlinableDeclRef(SourceLoc loc,
102102
const DeclContext *DC,
103103
FragileFunctionKind Kind,
104104
bool TreatUsableFromInlineAsPublic) {
105+
// Do some important fast-path checks that apply to all cases.
106+
105107
// Local declarations are OK.
106108
if (D->getDeclContext()->isLocalContext())
107109
return false;
@@ -110,6 +112,23 @@ bool TypeChecker::diagnoseInlinableDeclRef(SourceLoc loc,
110112
if (isa<AbstractTypeParamDecl>(D))
111113
return false;
112114

115+
// Check whether the declaration is accessible.
116+
if (diagnoseInlinableDeclRefAccess(loc, D, DC, Kind,
117+
TreatUsableFromInlineAsPublic))
118+
return true;
119+
120+
// Check whether the declaration comes from a publically-imported module.
121+
if (diagnoseDeclRefExportability(loc, D, DC))
122+
return true;
123+
124+
return false;
125+
}
126+
127+
bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
128+
const ValueDecl *D,
129+
const DeclContext *DC,
130+
FragileFunctionKind Kind,
131+
bool TreatUsableFromInlineAsPublic) {
113132
// Public declarations are OK.
114133
if (D->getFormalAccessScope(/*useDC=*/nullptr,
115134
TreatUsableFromInlineAsPublic).isPublic())
@@ -178,3 +197,38 @@ bool TypeChecker::diagnoseInlinableDeclRef(SourceLoc loc,
178197
return (downgradeToWarning == DowngradeToWarning::No);
179198
}
180199

200+
bool TypeChecker::diagnoseDeclRefExportability(SourceLoc loc,
201+
const ValueDecl *D,
202+
const DeclContext *DC) {
203+
// We're only interested in diagnosing uses from source files.
204+
auto userSF = DC->getParentSourceFile();
205+
if (!userSF)
206+
return false;
207+
208+
// If the source file doesn't have any implementation-only imports,
209+
// we can fast-path this. In the current language design, we never
210+
// need to consider the possibility of implementation-only imports
211+
// from other source files in the module (or indirectly in other modules).
212+
// TODO: maybe check whether D is from a bridging header?
213+
if (!userSF->hasImplementationOnlyImports())
214+
return false;
215+
216+
auto userModule = userSF->getParentModule();
217+
auto definingModule = D->getModuleContext();
218+
219+
// Nothing to diagnose in the very common case of the same module.
220+
if (userModule == definingModule)
221+
return false;
222+
223+
// Nothing to diagnose in the very common case that the module is
224+
// imported for use in signatures.
225+
if (!userSF->isImportedImplementationOnly(definingModule))
226+
return false;
227+
228+
// TODO: different diagnostics
229+
diagnose(loc, diag::inlinable_decl_ref_implementation_only,
230+
D->getDescriptiveKind(), D->getFullName());
231+
232+
// TODO: notes explaining why
233+
return true;
234+
}

lib/Sema/TypeChecker.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,6 +1933,19 @@ class TypeChecker final : public LazyResolver {
19331933
FragileFunctionKind Kind,
19341934
bool TreatUsableFromInlineAsPublic);
19351935

1936+
private:
1937+
bool diagnoseInlinableDeclRefAccess(SourceLoc loc, const ValueDecl *D,
1938+
const DeclContext *DC,
1939+
FragileFunctionKind Kind,
1940+
bool TreatUsableFromInlineAsPublic);
1941+
1942+
public:
1943+
/// Given that a declaration is used from a particular context which
1944+
/// exposes it in the interface of the current module, diagnose if it cannot
1945+
/// reasonably be shared.
1946+
bool diagnoseDeclRefExportability(SourceLoc loc, const ValueDecl *D,
1947+
const DeclContext *DC);
1948+
19361949
/// Given that \p DC is within a fragile context for some reason, describe
19371950
/// why.
19381951
///
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@_exported import indirects
2+
3+
public struct StructFromDirect {
4+
public func method() {}
5+
public var property: Int {
6+
get { return 0 }
7+
set {}
8+
}
9+
public subscript(index: Int) -> Int {
10+
get { return 0 }
11+
set {}
12+
}
13+
}
14+
public typealias AliasFromDirect = StructFromDirect
15+
public typealias GenericAliasFromDirect<T> = (StructFromDirect, T)
16+
17+
public func globalFunctionFromDirect() {}
18+
public var globalVariableFromDirect = 0
19+
20+
extension StructFromIndirect {
21+
public func extensionMethodFromDirect() {}
22+
public var extensionPropertyFromDirect: Int {
23+
get { return 0 }
24+
set {}
25+
}
26+
public subscript(extensionSubscript index: Int) -> Int {
27+
get { return 0 }
28+
set {}
29+
}
30+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public struct StructFromIndirect {
2+
public init() {}
3+
}
4+
public typealias AliasFromIndirect = StructFromIndirect
5+
public typealias GenericAliasFromIndirect<T> = (StructFromIndirect, T)
6+
7+
public func globalFunctionFromIndirect() {}
8+
public var globalVariableFromIndirect = 0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@_exported import indirects
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t/indirects.swiftmodule %S/Inputs/implementation-only-imports/indirects.swift
3+
// RUN: %target-swift-frontend -emit-module -o %t/directs.swiftmodule -I %t %S/Inputs/implementation-only-imports/directs.swift
4+
5+
// RUN: %target-swift-frontend -typecheck -verify %s -I %t
6+
7+
@_implementationOnly import directs
8+
9+
// Types
10+
11+
@inlinable
12+
public func testStructFromIndirect() {
13+
_ = StructFromIndirect() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
14+
}
15+
16+
@inlinable
17+
public func testAliasFromIndirect() {
18+
_ = AliasFromIndirect() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
19+
}
20+
21+
@inlinable
22+
public func testGenericAliasFromIndirect() {
23+
_ = GenericAliasFromIndirect<Int>() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
24+
}
25+
26+
// Functions
27+
28+
@inlinable
29+
public func testFunctionFromIndirect() {
30+
globalFunctionFromIndirect() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
31+
}
32+
33+
// Variables
34+
35+
@inlinable
36+
public func testVariableFromIndirect_get() {
37+
_ = globalVariableFromIndirect // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
38+
}
39+
40+
@inlinable
41+
public func testVariableFromIndirect_set() {
42+
globalVariableFromIndirect = 5 // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
43+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t/indirects.swiftmodule %S/Inputs/implementation-only-imports/indirects.swift
3+
// RUN: %target-swift-frontend -emit-module -o %t/directs.swiftmodule -I %t %S/Inputs/implementation-only-imports/directs.swift
4+
5+
// RUN: %target-swift-frontend -typecheck -verify -primary-file %s %S/Inputs/implementation-only-imports/secondary_file.swift -I %t
6+
7+
@_implementationOnly import directs
8+
// 'indirects' is imported for re-export in a secondary file
9+
10+
// Types
11+
12+
@inlinable
13+
public func testStructFromDirect() {
14+
_ = StructFromDirect() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
15+
}
16+
17+
@inlinable
18+
public func testStructFromIndirect() {
19+
_ = StructFromIndirect()
20+
}
21+
22+
@inlinable
23+
public func testAliasFromDirect() {
24+
_ = AliasFromDirect() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
25+
}
26+
27+
@inlinable
28+
public func testAliasFromIndirect() {
29+
_ = AliasFromIndirect()
30+
}
31+
32+
@inlinable
33+
public func testGenericAliasFromDirect() {
34+
_ = GenericAliasFromDirect<Int>() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
35+
}
36+
37+
@inlinable
38+
public func testGenericAliasFromIndirect() {
39+
let tmp: GenericAliasFromIndirect<Int>?
40+
_ = tmp
41+
}
42+
43+
// Functions
44+
45+
@inlinable
46+
public func testFunctionFromDirect() {
47+
globalFunctionFromDirect() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
48+
}
49+
50+
@inlinable
51+
public func testFunctionFromIndirect() {
52+
globalFunctionFromIndirect()
53+
}
54+
55+
// Variables
56+
57+
@inlinable
58+
public func testVariableFromDirect_get() {
59+
_ = globalVariableFromDirect // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
60+
}
61+
62+
@inlinable
63+
public func testVariableFromIndirect_get() {
64+
_ = globalVariableFromIndirect
65+
}
66+
67+
@inlinable
68+
public func testVariableFromDirect_set() {
69+
globalVariableFromDirect = 5 // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
70+
}
71+
72+
@inlinable
73+
public func testVariableFromIndirect_set() {
74+
globalVariableFromIndirect = 5
75+
}
76+
77+
// Extensions
78+
79+
@inlinable
80+
public func testExtensionMethod(s: inout StructFromIndirect) {
81+
s.extensionMethodFromDirect() // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
82+
}
83+
84+
@inlinable
85+
public func testExtensionProperty_get(s: inout StructFromIndirect) {
86+
_ = s.extensionPropertyFromDirect // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
87+
}
88+
89+
@inlinable
90+
public func testExtensionProperty_set(s: inout StructFromIndirect) {
91+
s.extensionPropertyFromDirect = 5 // expected-error {{cannot be used in an inlinable function because its module was imported implementation-only}}
92+
}
93+
94+
@inlinable
95+
public func testExtensionSubscript_get(s: inout StructFromIndirect) {
96+
// FIXME: why is this error being double-emitted?
97+
_ = s[extensionSubscript: 0] // expected-error 2 {{cannot be used in an inlinable function because its module was imported implementation-only}}
98+
}
99+
100+
@inlinable
101+
public func testExtensionSubscript_set(s: inout StructFromIndirect) {
102+
// FIXME: why is this error being double-emitted?
103+
s[extensionSubscript: 0] = 5 // expected-error 2 {{cannot be used in an inlinable function because its module was imported implementation-only}}
104+
}

0 commit comments

Comments
 (0)