Skip to content

Commit 5ad504e

Browse files
authored
Merge pull request #68954 from xymus/report-non-public-extensions
Sema: Report public extensions using non-publicly imported types
2 parents 58f73d5 + 82ccf64 commit 5ad504e

File tree

4 files changed

+310
-12
lines changed

4 files changed

+310
-12
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3475,7 +3475,7 @@ ERROR(decl_from_hidden_module,none,
34753475
"%2 was imported for SPI only|"
34763476
"%2 was not imported by this file|"
34773477
"C++ types from imported module %2 do not support library evolution|"
3478-
"<<ERROR>>}3",
3478+
"%2 was not imported publicly}3",
34793479
(const Decl *, unsigned, Identifier, unsigned))
34803480
ERROR(typealias_desugars_to_type_from_hidden_module,none,
34813481
"%0 aliases '%1.%2' and cannot be used %select{here|"
@@ -6615,7 +6615,7 @@ ERROR(inlinable_decl_ref_from_hidden_module,
66156615
"%2 was imported for SPI only|"
66166616
"%2 was not imported by this file|"
66176617
"C++ APIs from imported module %2 do not support library evolution|"
6618-
"<<ERROR>>}3",
6618+
"%2 was not imported publicly}3",
66196619
(const ValueDecl *, unsigned, Identifier, unsigned))
66206620

66216621
WARNING(inlinable_decl_ref_from_hidden_module_warn,

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,41 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
233233
auto definingModule = D->getModuleContext();
234234
auto downgradeToWarning = DowngradeToWarning::No;
235235

236-
auto originKind = getDisallowedOriginKind(
237-
D, where, downgradeToWarning);
238-
if (originKind == DisallowedOriginKind::None)
239-
return false;
236+
auto reason = where.getExportabilityReason();
237+
auto DC = where.getDeclContext();
238+
ASTContext &ctx = DC->getASTContext();
239+
auto originKind = getDisallowedOriginKind(D, where, downgradeToWarning);
240+
241+
// If we got here it was used in API, we can record the use of the import.
242+
ImportAccessLevel import = D->getImportAccessFrom(DC);
243+
if (import.has_value() && reason.has_value()) {
244+
auto SF = DC->getParentSourceFile();
245+
if (SF)
246+
SF->registerAccessLevelUsingImport(import.value(),
247+
AccessLevel::Public);
248+
}
240249

241250
// Access levels from imports are reported with the others access levels.
242-
if (originKind == DisallowedOriginKind::NonPublicImport)
251+
// Except for extensions, we report them here.
252+
if (originKind == DisallowedOriginKind::NonPublicImport &&
253+
reason != ExportabilityReason::ExtensionWithPublicMembers &&
254+
reason != ExportabilityReason::ExtensionWithConditionalConformances)
255+
return false;
256+
257+
if (ctx.LangOpts.EnableModuleApiImportRemarks &&
258+
import.has_value() && where.isExported() &&
259+
reason != ExportabilityReason::General &&
260+
originKind != DisallowedOriginKind::NonPublicImport) {
261+
// These may be reported twice, for the Type and for the TypeRepr.
262+
ModuleDecl *importedVia = import->module.importedModule,
263+
*sourceModule = D->getModuleContext();
264+
ctx.Diags.diagnose(loc, diag::module_api_import,
265+
D, importedVia, sourceModule,
266+
importedVia == sourceModule,
267+
/*isImplicit*/false);
268+
}
269+
270+
if (originKind == DisallowedOriginKind::None)
243271
return false;
244272

245273
auto diagName = D->getName();
@@ -254,11 +282,7 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
254282
diagName = accessor->getStorage()->getName();
255283
}
256284

257-
ASTContext &ctx = where.getDeclContext()->getASTContext();
258-
259285
auto fragileKind = where.getFragileFunctionKind();
260-
auto reason = where.getExportabilityReason();
261-
262286
if (fragileKind.kind == FragileFunctionKind::None) {
263287
DiagnosticBehavior limit = downgradeToWarning == DowngradeToWarning::Yes
264288
? DiagnosticBehavior::Warning
@@ -288,6 +312,17 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
288312
addMissingImport(loc, D, where);
289313
}
290314

315+
// If limited by an import, note which one.
316+
if (originKind == DisallowedOriginKind::NonPublicImport) {
317+
assert(import.has_value() &&
318+
import->accessLevel < AccessLevel::Public &&
319+
"The import should still be non-public");
320+
ctx.Diags.diagnose(import->importLoc,
321+
diag::decl_import_via_here, D,
322+
import->accessLevel,
323+
import->module.importedModule);
324+
}
325+
291326
return true;
292327
}
293328

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file --leading-lines %s %t
3+
4+
/// Build the libraries.
5+
// RUN: %target-swift-frontend -emit-module %t/PublicLib.swift -o %t \
6+
// RUN: -enable-library-evolution
7+
// RUN: %target-swift-frontend -emit-module %t/PackageLib.swift -o %t \
8+
// RUN: -enable-library-evolution
9+
// RUN: %target-swift-frontend -emit-module %t/InternalLib.swift -o %t \
10+
// RUN: -enable-library-evolution
11+
// RUN: %target-swift-frontend -emit-module %t/FileprivateLib.swift -o %t \
12+
// RUN: -enable-library-evolution
13+
// RUN: %target-swift-frontend -emit-module %t/PrivateLib.swift -o %t \
14+
// RUN: -enable-library-evolution
15+
16+
/// Check diagnostics.
17+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
18+
// RUN: -package-name TestPackage -swift-version 5 \
19+
// RUN: -enable-experimental-feature AccessLevelOnImport -verify
20+
21+
/// Check diagnostics with library-evolution.
22+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
23+
// RUN: -package-name TestPackage -swift-version 5 \
24+
// RUN: -enable-library-evolution \
25+
// RUN: -enable-experimental-feature AccessLevelOnImport -verify
26+
27+
//--- PublicLib.swift
28+
public struct PublicImportType {
29+
public init() {}
30+
}
31+
32+
//--- PackageLib.swift
33+
public struct PackageImportType {
34+
public init() {}
35+
}
36+
37+
//--- InternalLib.swift
38+
public struct InternalImportType {
39+
public init() {}
40+
}
41+
42+
//--- FileprivateLib.swift
43+
public struct FileprivateImportType {
44+
public init() {}
45+
}
46+
47+
//--- PrivateLib.swift
48+
public struct PrivateImportType {
49+
public init() {}
50+
}
51+
52+
//--- Client.swift
53+
public import PublicLib
54+
package import PackageLib // expected-note 2 {{struct 'PackageImportType' imported as 'package' from 'PackageLib' here}}
55+
internal import InternalLib // expected-note 2 {{struct 'InternalImportType' imported as 'internal' from 'InternalLib' here}}
56+
fileprivate import FileprivateLib // expected-note 2 {{struct 'FileprivateImportType' imported as 'fileprivate' from 'FileprivateLib' here}}
57+
private import PrivateLib // expected-note 2 {{struct 'PrivateImportType' imported as 'private' from 'PrivateLib' here}}
58+
59+
public protocol PublicConstrainedExtensionProto {}
60+
extension Array: PublicConstrainedExtensionProto where Element == PublicImportType {}
61+
extension PublicImportType {
62+
public func publicMethod() {}
63+
}
64+
65+
package protocol PublicConstrainedExtensionProtoInPackage {}
66+
extension Array: PublicConstrainedExtensionProtoInPackage where Element == PackageImportType {}
67+
extension PublicImportType {
68+
package func packageMethod() {}
69+
}
70+
71+
internal protocol PublicConstrainedExtensionProtoInInternal {}
72+
extension Array: PublicConstrainedExtensionProtoInInternal where Element == PublicImportType {}
73+
extension PublicImportType {
74+
internal func internalMethod() {}
75+
}
76+
77+
fileprivate protocol PublicConstrainedExtensionProtoInFileprivate {}
78+
extension Array: PublicConstrainedExtensionProtoInFileprivate where Element == PublicImportType {}
79+
extension PublicImportType {
80+
fileprivate func fileprivateMethod() {}
81+
}
82+
83+
private protocol PublicConstrainedExtensionProtoInPrivate {}
84+
extension Array: PublicConstrainedExtensionProtoInPrivate where Element == PublicImportType {}
85+
extension PublicImportType {
86+
private func privateMethod() {}
87+
}
88+
89+
public protocol PackageConstrainedExtensionProto {}
90+
extension Array: PackageConstrainedExtensionProto where Element == PackageImportType {} // expected-error {{cannot use struct 'PackageImportType' in an extension with conditional conformances; 'PackageLib' was not imported publicly}}
91+
extension PackageImportType { // expected-error {{cannot use struct 'PackageImportType' in an extension with public or '@usableFromInline' members; 'PackageLib' was not imported publicly}}
92+
public func publicMethod() {}
93+
}
94+
95+
package protocol PackageConstrainedExtensionProtoInPackage {}
96+
extension Array: PackageConstrainedExtensionProtoInPackage where Element == PackageImportType {}
97+
extension PackageImportType {
98+
package func packageMethod() {}
99+
}
100+
101+
internal protocol PackageConstrainedExtensionProtoInInternal {}
102+
extension Array: PackageConstrainedExtensionProtoInInternal where Element == PackageImportType {}
103+
extension PackageImportType {
104+
internal func internalMethod() {}
105+
}
106+
107+
fileprivate protocol PackageConstrainedExtensionProtoInFileprivate {}
108+
extension Array: PackageConstrainedExtensionProtoInFileprivate where Element == PackageImportType {}
109+
extension PackageImportType {
110+
fileprivate func fileprivateMethod() {}
111+
}
112+
113+
private protocol PackageConstrainedExtensionProtoInPrivate {}
114+
extension Array: PackageConstrainedExtensionProtoInPrivate where Element == PackageImportType {}
115+
extension PackageImportType {
116+
private func privateMethod() {}
117+
}
118+
119+
public protocol InternalConstrainedExtensionProto {}
120+
extension Array: InternalConstrainedExtensionProto where Element == InternalImportType {} // expected-error {{cannot use struct 'InternalImportType' in an extension with conditional conformances; 'InternalLib' was not imported publicly}}
121+
extension InternalImportType { // expected-error {{cannot use struct 'InternalImportType' in an extension with public or '@usableFromInline' members; 'InternalLib' was not imported publicly}}
122+
public func foo() {}
123+
}
124+
125+
package protocol InternalConstrainedExtensionProtoInPackage {}
126+
extension Array: InternalConstrainedExtensionProtoInPackage where Element == InternalImportType {}
127+
extension InternalImportType {
128+
package func packageMethod() {}
129+
}
130+
131+
internal protocol InternalConstrainedExtensionProtoInInternal {}
132+
extension Array: InternalConstrainedExtensionProtoInInternal where Element == InternalImportType {}
133+
extension InternalImportType {
134+
internal func internalMethod() {}
135+
}
136+
137+
fileprivate protocol InternalConstrainedExtensionProtoInFileprivate {}
138+
extension Array: InternalConstrainedExtensionProtoInFileprivate where Element == InternalImportType {}
139+
extension InternalImportType {
140+
fileprivate func fileprivateMethod() {}
141+
}
142+
143+
private protocol InternalConstrainedExtensionProtoInPrivate {}
144+
extension Array: InternalConstrainedExtensionProtoInPrivate where Element == InternalImportType {}
145+
extension InternalImportType {
146+
private func privateMethod() {}
147+
}
148+
149+
public protocol FileprivateConstrainedExtensionProto {}
150+
extension Array: FileprivateConstrainedExtensionProto where Element == FileprivateImportType {} // expected-error {{cannot use struct 'FileprivateImportType' in an extension with conditional conformances; 'FileprivateLib' was not imported publicly}}
151+
extension FileprivateImportType { // expected-error {{cannot use struct 'FileprivateImportType' in an extension with public or '@usableFromInline' members; 'FileprivateLib' was not imported publicly}}
152+
public func foo() {}
153+
}
154+
155+
package protocol FileprivateConstrainedExtensionProtoInPackage {}
156+
extension Array: FileprivateConstrainedExtensionProtoInPackage where Element == FileprivateImportType {}
157+
extension FileprivateImportType {
158+
package func packageMethod() {}
159+
}
160+
161+
internal protocol FileprivateConstrainedExtensionProtoInInternal {}
162+
extension Array: FileprivateConstrainedExtensionProtoInInternal where Element == FileprivateImportType {}
163+
extension FileprivateImportType {
164+
internal func internalMethod() {}
165+
}
166+
167+
fileprivate protocol FileprivateConstrainedExtensionProtoInFileprivate {}
168+
extension Array: FileprivateConstrainedExtensionProtoInFileprivate where Element == FileprivateImportType {}
169+
extension FileprivateImportType {
170+
fileprivate func fileprivateMethod() {}
171+
}
172+
173+
private protocol FileprivateConstrainedExtensionProtoInPrivate {}
174+
extension Array: FileprivateConstrainedExtensionProtoInPrivate where Element == FileprivateImportType {}
175+
extension FileprivateImportType {
176+
private func privateMethod() {}
177+
}
178+
179+
public protocol PrivateConstrainedExtensionProto {}
180+
extension Array: PrivateConstrainedExtensionProto where Element == PrivateImportType {} // expected-error {{cannot use struct 'PrivateImportType' in an extension with conditional conformances; 'PrivateLib' was not imported publicly}}
181+
extension PrivateImportType { // expected-error {{cannot use struct 'PrivateImportType' in an extension with public or '@usableFromInline' members; 'PrivateLib' was not imported publicly}}
182+
public func foo() {}
183+
}
184+
185+
package protocol PrivateConstrainedExtensionProtoInPackage {}
186+
extension Array: PrivateConstrainedExtensionProtoInPackage where Element == PrivateImportType {}
187+
extension PrivateImportType {
188+
package func packageMethod() {}
189+
}
190+
191+
internal protocol PrivateConstrainedExtensionProtoInInternal {}
192+
extension Array: PrivateConstrainedExtensionProtoInInternal where Element == PrivateImportType {}
193+
extension PrivateImportType {
194+
internal func internalMethod() {}
195+
}
196+
197+
fileprivate protocol PrivateConstrainedExtensionProtoInFileprivate {}
198+
extension Array: PrivateConstrainedExtensionProtoInFileprivate where Element == PrivateImportType {}
199+
extension PrivateImportType {
200+
fileprivate func fileprivateMethod() {}
201+
}
202+
203+
private protocol PrivateConstrainedExtensionProtoInPrivate {}
204+
extension Array: PrivateConstrainedExtensionProtoInPrivate where Element == PrivateImportType {}
205+
extension PrivateImportType {
206+
private func privateMethod() {}
207+
}

test/Sema/superfluously-public-imports.swift

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
// RUN: %target-swift-frontend -emit-module %t/ConformanceDefinition.swift -o %t -I %t
1212
// RUN: %target-swift-frontend -emit-module %t/AliasesBase.swift -o %t
1313
// RUN: %target-swift-frontend -emit-module %t/Aliases.swift -o %t -I %t
14+
// RUN: %target-swift-frontend -emit-module %t/ExtensionA.swift -o %t -I %t
15+
// RUN: %target-swift-frontend -emit-module %t/ExtensionB.swift -o %t -I %t
16+
// RUN: %target-swift-frontend -emit-module %t/PropertyWrapper.swift -o %t -I %t
17+
// RUN: %target-swift-frontend -emit-module %t/ExtendedDefinitionPublic.swift -o %t -I %t
18+
// RUN: %target-swift-frontend -emit-module %t/ExtendedDefinitionNonPublic.swift -o %t -I %t
1419
// RUN: %target-swift-frontend -emit-module %t/UnusedImport.swift -o %t -I %t
1520
// RUN: %target-swift-frontend -emit-module %t/UnusedPackageImport.swift -o %t -I %t
1621
// RUN: %target-swift-frontend -emit-module %t/ImportNotUseFromAPI.swift -o %t -I %t
@@ -58,6 +63,33 @@ open class Clazz {}
5863
import AliasesBase
5964
public typealias ClazzAlias = Clazz
6065

66+
//--- ExtensionA.swift
67+
import ConformanceBaseTypes
68+
extension ConformingType {
69+
public func extFuncA() {}
70+
}
71+
72+
//--- ExtensionB.swift
73+
import ConformanceBaseTypes
74+
extension ConformingType {
75+
public func extFuncB() {}
76+
}
77+
78+
//--- PropertyWrapper.swift
79+
@propertyWrapper
80+
public struct MyPropertyWrapper<T> {
81+
public var wrappedValue: T
82+
83+
public init(wrappedValue value: T) { self.wrappedValue = value }
84+
public init(_ value: T) { self.wrappedValue = value }
85+
}
86+
87+
//--- ExtendedDefinitionPublic.swift
88+
public struct PublicExtendedType {}
89+
90+
//--- ExtendedDefinitionNonPublic.swift
91+
public struct NonPublicExtendedType {}
92+
6193
//--- UnusedImport.swift
6294

6395
//--- UnusedPackageImport.swift
@@ -84,7 +116,13 @@ public import ConformanceBaseTypes
84116
public import ConformanceDefinition
85117
public import AliasesBase
86118
public import Aliases
119+
public import ExtensionA
120+
public import ExtensionB
121+
public import PropertyWrapper
122+
public import ExtendedDefinitionPublic
123+
public import ExtendedDefinitionNonPublic // expected-warning {{public import of 'ExtendedDefinitionNonPublic' was not used in public declarations or inlinable code}} {{1-8=}}
87124

125+
/// Repeat some imports to make sure we report all of them.
88126
public import UnusedImport // expected-warning {{public import of 'UnusedImport' was not used in public declarations or inlinable code}} {{1-8=}}
89127
public import UnusedImport // expected-warning {{public import of 'UnusedImport' was not used in public declarations or inlinable code}} {{1-8=}}
90128
package import UnusedImport // expected-warning {{package import of 'UnusedImport' was not used in package declarations}} {{1-9=}}
@@ -114,7 +152,7 @@ public func useConformance(_ a: any Proto = ConformingType()) {}
114152
// expected-remark @-3 {{struct 'ConformingType' is imported via 'ConformanceBaseTypes'}}
115153
// expected-remark @-4 {{initializer 'init()' is imported via 'ConformanceBaseTypes'}}
116154

117-
@usableFromInline internal func useInDefaultValue(_ a: TypeUsedInSignature) {} // expected-remark {{struct 'TypeUsedInSignature' is imported via 'DepUsedInSignature'}}
155+
@usableFromInline internal func usableFromInlineFunc(_ a: TypeUsedInSignature) {} // expected-remark {{struct 'TypeUsedInSignature' is imported via 'DepUsedInSignature'}}
118156

119157
@inlinable
120158
public func publicFuncUsesPrivate() {
@@ -139,6 +177,24 @@ public func publicFuncUsesPrivate() {
139177
let _: ClazzAlias
140178
// expected-remark @-1 {{type alias 'ClazzAlias' is imported via 'Aliases'}}
141179
// expected-remark @-2 2 {{typealias underlying type class 'Clazz' is imported via 'AliasesBase'}}
180+
181+
let x = ConformingType()
182+
// expected-remark @-1 {{struct 'ConformingType' is imported via 'ConformanceBaseTypes'}}
183+
// expected-remark @-2 {{initializer 'init()' is imported via 'ConformanceBaseTypes'}}
184+
x.extFuncA() // expected-remark {{instance method 'extFuncA()' is imported via 'ExtensionA'}}
185+
x.extFuncB() // expected-remark {{instance method 'extFuncB()' is imported via 'ExtensionB'}}
186+
}
187+
188+
public struct StructUsingPropertyWrapper {
189+
@MyPropertyWrapper(42) public var wrapped: Any // expected-remark 2 {{generic struct 'MyPropertyWrapper' is imported via 'PropertyWrapper'}}
190+
}
191+
192+
extension PublicExtendedType { // expected-remark 2 {{struct 'PublicExtendedType' is imported via 'ExtendedDefinitionPublic'}}
193+
public func foo() {}
194+
}
195+
196+
extension NonPublicExtendedType {
197+
func foo() {}
142198
}
143199

144200
public struct Struct { // expected-remark {{implicitly used struct 'Int' is imported via 'Swift'}}

0 commit comments

Comments
 (0)