Skip to content

Commit c15a576

Browse files
committed
[Sema] Introduce remarks about the source of each decl in API
Using `-Rmodule-api-import` the compiler prints a remark about the import bringing in every decl used in public function signatures or inlinable code. It also remarks on the source of conformances where they are used and the source of typealias underlying types.
1 parent a0b848e commit c15a576

File tree

7 files changed

+215
-0
lines changed

7 files changed

+215
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,20 @@ REMARK(module_loaded,none,
11261126
"; source: '%2', loaded: '%3'",
11271127
(Identifier, unsigned, StringRef, StringRef))
11281128

1129+
REMARK(module_api_import,none,
1130+
"%select{|implicitly used }4"
1131+
"%kind0 is imported via %1"
1132+
"%select{, which reexports definition from %2|}3",
1133+
(const Decl *, ModuleDecl *, ModuleDecl *, bool, bool))
1134+
REMARK(module_api_import_conformance,none,
1135+
"conformance of %0 to %kind1 used here is imported via %2"
1136+
"%select{ which reexports the definition from %3|}4",
1137+
(const Type, const Decl *, ModuleDecl *, ModuleDecl *, bool))
1138+
REMARK(module_api_import_aliases,none,
1139+
"typealias underlying type %kind0 is imported via %1"
1140+
"%select{, which reexports definition from %2|}3",
1141+
(const Decl *, ModuleDecl *, ModuleDecl *, bool))
1142+
11291143
REMARK(macro_loaded,none,
11301144
"loaded macro implementation module %0 from "
11311145
"%select{shared library '%2'|executable '%2'|"

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@ namespace swift {
246246
/// Emit remarks about contextual inconsistencies in loaded modules.
247247
bool EnableModuleRecoveryRemarks = false;
248248

249+
/// Emit remarks about the source of each element exposed by the module API.
250+
bool EnableModuleApiImportRemarks = false;
251+
249252
/// Emit a remark after loading a macro implementation.
250253
bool EnableMacroLoadingRemarks = false;
251254

include/swift/Option/Options.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ def remark_loading_module : Flag<["-"], "Rmodule-loading">,
397397
def remark_module_recovery : Flag<["-"], "Rmodule-recovery">,
398398
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
399399
HelpText<"Emit remarks about contextual inconsistencies in loaded modules">;
400+
def remark_module_api_import : Flag<["-"], "Rmodule-api-import">,
401+
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
402+
HelpText<"Emit remarks about the import briging in each element composing the API">;
400403

401404
def remark_macro_loading : Flag<["-"], "Rmacro-loading">,
402405
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,

lib/Frontend/CompilerInvocation.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
10211021

10221022
Opts.EnableModuleLoadingRemarks = Args.hasArg(OPT_remark_loading_module);
10231023
Opts.EnableModuleRecoveryRemarks = Args.hasArg(OPT_remark_module_recovery);
1024+
Opts.EnableModuleApiImportRemarks = Args.hasArg(OPT_remark_module_api_import);
10241025
Opts.EnableMacroLoadingRemarks = Args.hasArg(OPT_remark_macro_loading);
10251026
Opts.EnableIndexingSystemModuleRemarks = Args.hasArg(OPT_remark_indexing_system_module);
10261027

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
6969
if (SF)
7070
SF->registerAccessLevelUsingImport(problematicImport.value(),
7171
AccessLevel::Public);
72+
73+
if (Context.LangOpts.EnableModuleApiImportRemarks) {
74+
ModuleDecl *importedVia = problematicImport->module.importedModule,
75+
*sourceModule = D->getModuleContext();
76+
Context.Diags.diagnose(loc, diag::module_api_import,
77+
D, importedVia, sourceModule,
78+
importedVia == sourceModule,
79+
/*isImplicit*/false);
80+
}
7281
}
7382

7483
// General check on access-level of the decl.
@@ -150,6 +159,14 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc,
150159
if (SF)
151160
SF->registerAccessLevelUsingImport(problematicImport.value(),
152161
AccessLevel::Public);
162+
163+
if (ctx.LangOpts.EnableModuleApiImportRemarks) {
164+
ModuleDecl *importedVia = problematicImport->module.importedModule,
165+
*sourceModule = D->getModuleContext();
166+
ctx.Diags.diagnose(loc, diag::module_api_import_aliases,
167+
D, importedVia, sourceModule,
168+
importedVia == sourceModule);
169+
}
153170
}
154171

155172
auto ignoredDowngradeToWarning = DowngradeToWarning::No;
@@ -313,6 +330,15 @@ TypeChecker::diagnoseConformanceExportability(SourceLoc loc,
313330
if (SF)
314331
SF->registerAccessLevelUsingImport(problematicImport.value(),
315332
AccessLevel::Public);
333+
334+
if (ctx.LangOpts.EnableModuleApiImportRemarks) {
335+
ModuleDecl *importedVia = problematicImport->module.importedModule,
336+
*sourceModule = ext->getModuleContext();
337+
ctx.Diags.diagnose(loc, diag::module_api_import_conformance,
338+
rootConf->getType(), rootConf->getProtocol(),
339+
importedVia, sourceModule,
340+
importedVia == sourceModule);
341+
}
316342
}
317343

318344
auto originKind = getDisallowedOriginKind(ext, where);

lib/Sema/TypeCheckAccess.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,17 @@ void AccessControlCheckerBase::checkTypeAccessImpl(
203203
: AccessLevel::Package;
204204
SF->registerAccessLevelUsingImport(import.value(), useLevel);
205205
}
206+
207+
if (Context.LangOpts.EnableModuleApiImportRemarks) {
208+
SourceLoc diagLoc = typeRepr? typeRepr->getLoc()
209+
: extractNearestSourceLoc(useDC);
210+
ModuleDecl *importedVia = import->module.importedModule,
211+
*sourceModule = VD->getModuleContext();
212+
Context.Diags.diagnose(diagLoc, diag::module_api_import,
213+
VD, importedVia, sourceModule,
214+
importedVia == sourceModule,
215+
/*isImplicit*/!typeRepr);
216+
}
206217
}
207218
};
208219

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file --leading-lines %s %t
3+
// REQUIRES: asserts
4+
5+
/// Build the libraries.
6+
// RUN: %target-swift-frontend -emit-module %t/DepUsedFromInlinableCode.swift -o %t
7+
// RUN: %target-swift-frontend -emit-module %t/DepUsedInSignature.swift -o %t
8+
// RUN: %target-swift-frontend -emit-module %t/Exportee.swift -o %t
9+
// RUN: %target-swift-frontend -emit-module %t/Exporter.swift -o %t -I %t
10+
// RUN: %target-swift-frontend -emit-module %t/ConformanceBaseTypes.swift -o %t
11+
// RUN: %target-swift-frontend -emit-module %t/ConformanceDefinition.swift -o %t -I %t
12+
// RUN: %target-swift-frontend -emit-module %t/AliasesBase.swift -o %t
13+
// RUN: %target-swift-frontend -emit-module %t/Aliases.swift -o %t -I %t
14+
// RUN: %target-swift-frontend -emit-module %t/UnusedImport.swift -o %t -I %t
15+
// RUN: %target-swift-frontend -emit-module %t/UnusedPackageImport.swift -o %t -I %t
16+
// RUN: %target-swift-frontend -emit-module %t/ImportNotUseFromAPI.swift -o %t -I %t
17+
// RUN: %target-swift-frontend -emit-module %t/ImportUsedInPackage.swift -o %t -I %t
18+
19+
/// Check diagnostics.
20+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
21+
// RUN: -enable-experimental-feature AccessLevelOnImport -verify \
22+
// RUN: -package-name pkg -Rmodule-api-import -swift-version 6
23+
// RUN: %target-swift-frontend -typecheck %t/Client_Swift5.swift -I %t \
24+
// RUN: -enable-experimental-feature AccessLevelOnImport -verify \
25+
// RUN: -swift-version 5
26+
27+
//--- DepUsedFromInlinableCode.swift
28+
public struct TypeUsedFromInlinableCode {}
29+
public func funcUsedFromInlinableCode() {}
30+
31+
public func funcUsedFromDefaultValue() -> Int { 42 }
32+
33+
//--- DepUsedInSignature.swift
34+
public struct TypeUsedInSignature {}
35+
public protocol ComposedProtoA {}
36+
public protocol ComposedProtoB {}
37+
38+
//--- Exportee.swift
39+
public struct ExportedType {}
40+
41+
//--- Exporter.swift
42+
@_exported import Exportee
43+
44+
//--- ConformanceBaseTypes.swift
45+
public protocol Proto {}
46+
public struct ConformingType {
47+
public init () {}
48+
}
49+
50+
//--- ConformanceDefinition.swift
51+
import ConformanceBaseTypes
52+
extension ConformingType : Proto {}
53+
54+
//--- AliasesBase.swift
55+
open class Clazz {}
56+
57+
//--- Aliases.swift
58+
import AliasesBase
59+
public typealias ClazzAlias = Clazz
60+
61+
//--- UnusedImport.swift
62+
63+
//--- UnusedPackageImport.swift
64+
65+
//--- ImportNotUseFromAPI.swift
66+
public struct NotAnAPIType {}
67+
public func notAnAPIFunc() -> NotAnAPIType { return NotAnAPIType() }
68+
69+
//--- ImportUsedInPackage.swift
70+
public struct PackageType {}
71+
public func packageFunc() -> PackageType { return PackageType() }
72+
73+
//--- Client_Swift5.swift
74+
/// No diagnostics should be raised on the implicit access level.
75+
import UnusedImport // expected-error {{ambiguous implicit access level for import of 'UnusedImport'; it is imported as 'public' elsewhere}}
76+
public import UnusedImport // expected-warning {{public import of 'UnusedImport' was not used in public declarations or inlinable code}} {{1-7=internal}}
77+
// expected-note @-1 {{imported 'public' here}}
78+
79+
//--- Client.swift
80+
public import DepUsedFromInlinableCode
81+
public import DepUsedInSignature
82+
public import Exporter
83+
public import ConformanceBaseTypes
84+
public import ConformanceDefinition
85+
public import AliasesBase
86+
public import Aliases
87+
88+
public import UnusedImport // expected-warning {{public import of 'UnusedImport' was not used in public declarations or inlinable code}} {{1-8=}}
89+
public import UnusedImport // expected-warning {{public import of 'UnusedImport' was not used in public declarations or inlinable code}} {{1-8=}}
90+
package import UnusedImport // expected-warning {{package import of 'UnusedImport' was not used in package declarations}} {{1-9=}}
91+
92+
package import UnusedPackageImport // expected-warning {{package import of 'UnusedPackageImport' was not used in package declarations}} {{1-9=}}
93+
public import ImportNotUseFromAPI // expected-warning {{public import of 'ImportNotUseFromAPI' was not used in public declarations or inlinable code}} {{1-8=}}
94+
public import ImportUsedInPackage // expected-warning {{public import of 'ImportUsedInPackage' was not used in public declarations or inlinable code}} {{1-7=package}}
95+
96+
public func useInSignature(_ a: TypeUsedInSignature) {} // expected-remark {{struct 'TypeUsedInSignature' is imported via 'DepUsedInSignature'}}
97+
public func exportedTypeUseInSignature(_ a: ExportedType) {} // expected-remark {{struct 'ExportedType' is imported via 'Exporter', which reexports definition from 'Exportee'}}
98+
99+
public func useInDefaultValue(_ a: Int = funcUsedFromDefaultValue()) {}
100+
// expected-remark @-1 {{struct 'Int' is imported via 'Swift'}}
101+
// expected-remark @-2 {{global function 'funcUsedFromDefaultValue()' is imported via 'DepUsedFromInlinableCode'}}
102+
103+
public func genericType(_ a: Array<TypeUsedInSignature>) {}
104+
// expected-remark @-1 {{generic struct 'Array' is imported via 'Swift'}}
105+
// expected-remark @-2 {{struct 'TypeUsedInSignature' is imported via 'DepUsedInSignature'}}
106+
107+
public func protocolComposition(_ a: any ComposedProtoA & ComposedProtoB) {}
108+
// expected-remark @-1 {{protocol 'ComposedProtoA' is imported via 'DepUsedInSignature'}}
109+
// expected-remark @-2 {{protocol 'ComposedProtoB' is imported via 'DepUsedInSignature'}}
110+
111+
public func useConformance(_ a: any Proto = ConformingType()) {}
112+
// expected-remark @-1 {{protocol 'Proto' is imported via 'ConformanceBaseTypes'}}
113+
// expected-remark @-2 {{conformance of 'ConformingType' to protocol 'Proto' used here is imported via 'ConformanceDefinition'}}
114+
// expected-remark @-3 {{struct 'ConformingType' is imported via 'ConformanceBaseTypes'}}
115+
// expected-remark @-4 {{initializer 'init()' is imported via 'ConformanceBaseTypes'}}
116+
117+
@usableFromInline internal func useInDefaultValue(_ a: TypeUsedInSignature) {} // expected-remark {{struct 'TypeUsedInSignature' is imported via 'DepUsedInSignature'}}
118+
119+
@inlinable
120+
public func publicFuncUsesPrivate() {
121+
let _: TypeUsedFromInlinableCode // expected-remark {{struct 'TypeUsedFromInlinableCode' is imported via 'DepUsedFromInlinableCode'}}
122+
let _: ExportedType // expected-remark {{struct 'ExportedType' is imported via 'Exporter', which reexports definition from 'Exportee'}}
123+
funcUsedFromInlinableCode() // expected-remark {{global function 'funcUsedFromInlinableCode()' is imported via 'DepUsedFromInlinableCode'}}
124+
125+
let _: Array<TypeUsedInSignature>
126+
// expected-remark @-1 {{generic struct 'Array' is imported via 'Swift'}}
127+
// expected-remark @-2 {{struct 'TypeUsedInSignature' is imported via 'DepUsedInSignature'}}
128+
129+
let _: any ComposedProtoA & ComposedProtoB
130+
// expected-remark @-1 {{protocol 'ComposedProtoA' is imported via 'DepUsedInSignature'}}
131+
// expected-remark @-2 {{protocol 'ComposedProtoB' is imported via 'DepUsedInSignature'}}
132+
133+
let _: any Proto = ConformingType()
134+
// expected-remark @-1 {{protocol 'Proto' is imported via 'ConformanceBaseTypes'}}
135+
// expected-remark @-2 {{conformance of 'ConformingType' to protocol 'Proto' used here is imported via 'ConformanceDefinition'}}
136+
// expected-remark @-3 {{struct 'ConformingType' is imported via 'ConformanceBaseTypes'}}
137+
// expected-remark @-4 {{initializer 'init()' is imported via 'ConformanceBaseTypes'}}
138+
139+
let _: ClazzAlias
140+
// expected-remark @-1 {{type alias 'ClazzAlias' is imported via 'Aliases'}}
141+
// expected-remark @-2 2 {{typealias underlying type class 'Clazz' is imported via 'AliasesBase'}}
142+
}
143+
144+
public struct Struct { // expected-remark {{implicitly used struct 'Int' is imported via 'Swift'}}
145+
public var propWithInferredIntType = 42
146+
public var propWithExplicitType: String = "Text" // expected-remark {{struct 'String' is imported via 'Swift'}}
147+
}
148+
149+
public func publicFunction() {
150+
let _: NotAnAPIType = notAnAPIFunc()
151+
}
152+
153+
internal func internalFunc(a: NotAnAPIType = notAnAPIFunc()) {}
154+
func implicitlyInternalFunc(a: NotAnAPIType = notAnAPIFunc()) {}
155+
156+
// For package decls we only remark on types used in signatures, not for inlinable code.
157+
package func packageFunc(a: PackageType = packageFunc()) {} // expected-remark {{struct 'PackageType' is imported via 'ImportUsedInPackage'}}

0 commit comments

Comments
 (0)