From 3fedebfe6c91c76ee149aff6e70a7e36bd5227ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 4 Nov 2025 11:18:54 -0800 Subject: [PATCH 1/2] Sema: Embedded can reference non-publicly imported conformances rdar://163965839 --- lib/Sema/ResilienceDiagnostics.cpp | 2 +- lib/Sema/TypeCheckAvailability.cpp | 15 ++ lib/Sema/TypeCheckAvailability.h | 5 + ...port-embedded-inlinable-conformances.swift | 237 ++++++++++++++++++ 4 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 test/Sema/restricted-import-embedded-inlinable-conformances.swift diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index d4e1c45a35f36..de3e2da5911b1 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -442,7 +442,7 @@ TypeChecker::diagnoseConformanceExportability(SourceLoc loc, }); auto originKind = getDisallowedOriginKind(ext, where); - if (originKind == DisallowedOriginKind::None) + if (where.canReferenceOrigin(originKind)) return false; auto reason = where.getExportabilityReason(); diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index bb799e6c65390..e1d496a26d28f 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -16,6 +16,7 @@ #include "TypeCheckAvailability.h" #include "MiscDiagnostics.h" +#include "TypeCheckAccess.h" #include "TypeCheckConcurrency.h" #include "TypeCheckObjC.h" #include "TypeCheckType.h" @@ -234,6 +235,20 @@ bool ExportContext::mustOnlyReferenceExportedDecls() const { return Exported || FragileKind.kind != FragileFunctionKind::None; } +bool ExportContext::canReferenceOrigin(DisallowedOriginKind originKind) const { + if (originKind == DisallowedOriginKind::None) + return true; + + // Non public imports aren't hidden dependencies in embedded mode, + // don't enforce them on implicitly always emit into client code. + if (originKind == DisallowedOriginKind::NonPublicImport && + getFragileFunctionKind().kind == + FragileFunctionKind::EmbeddedAlwaysEmitIntoClient) + return true; + + return false; +} + std::optional ExportContext::getExportabilityReason() const { if (Exported) diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index ce74c185e80ee..87ed2555754c0 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -38,6 +38,7 @@ namespace swift { class TypeRepr; class UnsafeUse; class ValueDecl; + enum class DisallowedOriginKind : uint8_t; enum class DeclAvailabilityFlag : uint8_t { /// Do not diagnose uses of protocols in versions before they were introduced. @@ -192,6 +193,10 @@ class ExportContext { /// because it is the function body context of an inlinable function. bool mustOnlyReferenceExportedDecls() const; + /// If true, the context reference a dependency of \p originKind without + /// restriction. + bool canReferenceOrigin(DisallowedOriginKind originKind) const; + /// Get the ExportabilityReason for diagnostics. If this is 'None', there /// are no restrictions on referencing unexported declarations. std::optional getExportabilityReason() const; diff --git a/test/Sema/restricted-import-embedded-inlinable-conformances.swift b/test/Sema/restricted-import-embedded-inlinable-conformances.swift new file mode 100644 index 0000000000000..cf955055c68d4 --- /dev/null +++ b/test/Sema/restricted-import-embedded-inlinable-conformances.swift @@ -0,0 +1,237 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -o %t/NormalLibrary.swiftmodule \ +// RUN: %S/Inputs/implementation-only-import-in-decls-public-helper.swift \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded +// RUN: %target-swift-frontend -emit-module -o %t/BADLibrary.swiftmodule \ +// RUN: %S/Inputs/implementation-only-import-in-decls-helper.swift -I %t \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded + +/// Access-level on imports allow references from implicitly inlinable code. +// RUN: %target-typecheck-verify-swift -I %t \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded \ +// RUN: -verify-additional-prefix access-level- + +/// @_implementationOnly rejects references from implicitly inlinable code. +// RUN: %target-typecheck-verify-swift -I %t \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded \ +// RUN: -D IOI -verify-additional-prefix ioi- + +// REQUIRES: swift_feature_Embedded +// REQUIRES: embedded_stdlib_cross_compiling + +#if IOI +@_implementationOnly import BADLibrary // expected-ioi-warning {{using '@_implementationOnly' without enabling library evolution for 'main' may lead to instability during execution}} +#else +internal import BADLibrary // expected-access-level-note 35 {{imported as 'internal' from 'BADLibrary' here}} +#endif +import NormalLibrary + +public typealias NormalProtoAssoc = T.Assoc +@inlinable func testConformanceInTypealias() { + let x: NormalProtoAssoc? = nil // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + _ = x + _ = NormalProtoAssoc() // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} +} + +func internalConformanceInTypealias() { + let x: NormalProtoAssoc? = nil // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}}} + _ = x + _ = NormalProtoAssoc() // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}}} +} + +@_neverEmitIntoClient +func internalConformanceInTypealiasNEIC() { + let x: NormalProtoAssoc? = nil // okay + _ = x + _ = NormalProtoAssoc() // okay +} + +public struct NormalProtoAssocHolder { + public var value: T.Assoc? + public init() {} + public init(_ value: T?) {} +} +@inlinable func testConformanceInBoundGeneric() { + let x: NormalProtoAssocHolder? = nil // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + _ = x + // FIXME: We get this error twice: once for the TypeExpr and once for the implicit init. + _ = NormalProtoAssocHolder() // expected-error 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + _ = NormalProtoAssocHolder(nil as NormalStruct?) // expected-error 2{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} +} + +func internalConformanceInBoundGeneric() { + let x: NormalProtoAssocHolder? = nil // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}}} + _ = x + _ = NormalProtoAssocHolder() // expected-ioi-error 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}}} + _ = NormalProtoAssocHolder(nil as NormalStruct?) // expected-ioi-error 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}}} +} + +@_neverEmitIntoClient +func internalConformanceInBoundGenericNEIC() { + let x: NormalProtoAssocHolder? = nil // okay + _ = x + _ = NormalProtoAssocHolder() // okay + _ = NormalProtoAssocHolder(nil as NormalStruct?) // okay +} + +@inlinable func testDowncast(_ x: Any) -> Bool { + let normal = x is NormalProtoAssocHolder // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + let alias = x is NormalProtoAssoc // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + return normal || alias +} + +func internalDowncast(_ x: Any) -> Bool { + let normal = x is NormalProtoAssocHolder // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary' has been imported as implementation-only}} + let alias = x is NormalProtoAssoc // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary' has been imported as implementation-only}} + return normal || alias +} + +@_neverEmitIntoClient +func internalDowncastNEIC(_ x: Any) -> Bool { + let normal = x is NormalProtoAssocHolder // okay + let alias = x is NormalProtoAssoc // okay + return normal || alias +} + +@inlinable func testSwitch(_ x: Any) { + switch x { + case let holder as NormalProtoAssocHolder: // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + _ = holder + break + case is NormalProtoAssoc: // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + break + default: + break + } +} + +func internalSwitch(_ x: Any) { + switch x { + case let holder as NormalProtoAssocHolder: // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary' has been imported as implementation-only}} + _ = holder + break + case is NormalProtoAssoc: // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary' has been imported as implementation-only}} + break + default: + break + } +} + +@_neverEmitIntoClient +func internalSwitchNEIC(_ x: Any) { + switch x { + case let holder as NormalProtoAssocHolder: // okay + _ = holder + break + case is NormalProtoAssoc: // okay + break + default: + break + } +} + +public enum NormalProtoEnumUser { + case a +} + +@inlinable func testEnum() { + // FIXME: We get this error twice: once for the pattern and once for the implicit TypeExpr. + let x: NormalProtoEnumUser = .a // expected-error 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + _ = x + // FIXME: We get this error twice: once for the TypeExpr and once for the case. + _ = NormalProtoEnumUser.a // expected-error 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} +} + +func internalEnum() { + let x: NormalProtoEnumUser = .a // expected-ioi-error 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary' has been imported as implementation-only}} + _ = x + _ = NormalProtoEnumUser.a // expected-ioi-error 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary' has been imported as implementation-only}} +} + +@_neverEmitIntoClient +func internalEnumNEIC() { + let x: NormalProtoEnumUser = .a // okay + _ = x + _ = NormalProtoEnumUser.a // okay +} + +@usableFromInline func testFuncImpl(_: T.Type) {} + +@inlinable func testFunc() { + testFuncImpl(NormalStruct.self) // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} +} + +func internalFunc() { + testFuncImpl(NormalStruct.self) // expected-ioi-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary' has been imported as implementation-only}} +} + +@_neverEmitIntoClient +func internalFuncNEIC() { + testFuncImpl(NormalStruct.self) // okay +} + +public struct ForTestingMembers { + public init() {} + public init(_: T.Type) {} + + public subscript(_: T.Type) -> Int { + get { return 0 } + set {} + } + + public func method(_: T.Type) {} +} + +@inlinable func testMembers() { + _ = ForTestingMembers(NormalStruct.self) // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + _ = ForTestingMembers.init(NormalStruct.self) // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + + _ = ForTestingMembers()[NormalStruct.self] // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + var instance = ForTestingMembers() + instance[NormalStruct.self] = 1 // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + + ForTestingMembers().method(NormalStruct.self) // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} +} + +extension NormalProtoAssocHolder { + public static func testAnotherConformance(_: U.Type) {} +} + +@inlinable func testMultipleConformances() { + NormalProtoAssocHolder.testAnotherConformance(NormalClass.self) + // expected-error@-1 2 {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + // expected-error@-2 {{cannot use conformance of 'NormalClass' to 'NormalProto' here; 'BADLibrary'}} +} + +@inlinable func localTypeAlias() { + typealias LocalUser = NormalProtoAssocHolder // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + typealias LocalGenericUser = (T, NormalProtoAssocHolder) // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + + typealias LocalProtoAssoc = T.Assoc + _ = LocalProtoAssoc() // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} +} + +@inlinable func localFunctions() { + func local(_: NormalProtoAssocHolder) {} // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + func localReturn() -> NormalProtoAssocHolder { fatalError() } // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + let _ = { (_: NormalProtoAssocHolder) in return } // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + let _ = { () -> NormalProtoAssocHolder in fatalError() } // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} +} + +@inlinable public func signatureOfInlinable(_: NormalProtoAssocHolder) {} // expected-error{{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + +public func testDefaultArgument(_: Int = NormalProtoAssoc()) {} // expected-error {{cannot use conformance of 'NormalStruct' to 'NormalProto' here; 'BADLibrary'}} + + +public class SubclassOfNormalClass: NormalClass {} + +@inlinable public func testInheritedConformance() { + _ = NormalProtoAssocHolder.self // expected-error {{cannot use conformance of 'NormalClass' to 'NormalProto' here; 'BADLibrary'}} +} +@inlinable public func testSpecializedConformance() { + _ = NormalProtoAssocHolder>.self // expected-error {{cannot use conformance of 'GenericStruct' to 'NormalProto' here; 'BADLibrary'}} +} From e04dac552374d99cae9d85eca4ad5daf72bb09dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 4 Nov 2025 11:19:53 -0800 Subject: [PATCH 2/2] Sema: Embedded can reference typealiases to non-publicly imported types --- lib/Sema/ResilienceDiagnostics.cpp | 2 +- test/Sema/access-level-import-embedded.swift | 4 ++++ test/Sema/implementation-only-import-embedded.swift | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index de3e2da5911b1..3ecb5deae2050 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -174,7 +174,7 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc, auto ignoredDowngradeToWarning = DowngradeToWarning::No; auto originKind = getDisallowedOriginKind(D, where, ignoredDowngradeToWarning); - if (originKind == DisallowedOriginKind::None) + if (where.canReferenceOrigin(originKind)) return false; // As an exception, if the import of the module that defines the desugared diff --git a/test/Sema/access-level-import-embedded.swift b/test/Sema/access-level-import-embedded.swift index dc78227b2d056..394dcc4f40be4 100644 --- a/test/Sema/access-level-import-embedded.swift +++ b/test/Sema/access-level-import-embedded.swift @@ -24,6 +24,8 @@ import indirects internal func localInternalFunc() {} // expected-note {{global function 'localInternalFunc()' is not '@usableFromInline' or public}} +typealias AliasToDirect = StructFromDirect + @inlinable public func explicitlyInlinable(arg: StructFromDirect = StructFromDirect()) { // expected-error @-1 {{initializer 'init()' is internal and cannot be referenced from a default argument value}} @@ -75,6 +77,8 @@ public func implicitlyInlinablePublic(arg: StructFromDirect = StructFromDirect() implicitlyInlinablePublic() implicitlyInlinablePrivate() explicitNonInliable() + + let _: AliasToDirect } internal func implicitlyInlinablePrivate(arg: StructFromDirect = StructFromDirect()) { diff --git a/test/Sema/implementation-only-import-embedded.swift b/test/Sema/implementation-only-import-embedded.swift index 38ee43a9b5f04..cc2d1ce30653c 100644 --- a/test/Sema/implementation-only-import-embedded.swift +++ b/test/Sema/implementation-only-import-embedded.swift @@ -26,6 +26,8 @@ internal func localInternalFunc() {} // expected-note {{global function 'localIn @_spi(S) public func localSPI() {} +typealias AliasToDirect = StructFromDirect + @inlinable public func explicitlyInlinable(arg: StructFromDirect = StructFromDirect()) { // expected-error @-1 {{initializer 'init()' cannot be used in a default argument value because 'directs' was imported implementation-only}} @@ -82,6 +84,8 @@ public func implicitlyInlinablePublic(arg: StructFromDirect = StructFromDirect() localSPI() spiFunctionFromDirect() + + let _: AliasToDirect // expected-error {{AliasToDirect' aliases 'directs.StructFromDirect' and cannot be used in an embedded function not marked '@_neverEmitIntoClient' because 'directs' has been imported as implementation-only}} } private func implicitlyInlinablePrivate(arg: StructFromDirect = StructFromDirect()) {