From 627b8f9915545a48d38dcd85ef5d4f3a3c006bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Thu, 13 Nov 2025 08:48:32 -0800 Subject: [PATCH 1/8] Sema: Test @frozen struct in hidden-memory-layout.swift --- test/Sema/hidden-memory-layout.swift | 95 +++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/test/Sema/hidden-memory-layout.swift b/test/Sema/hidden-memory-layout.swift index 1262af7943214..373aa4cc0198d 100644 --- a/test/Sema/hidden-memory-layout.swift +++ b/test/Sema/hidden-memory-layout.swift @@ -72,12 +72,12 @@ public struct ExposedLayoutPublic { } internal struct ExposedLayoutInternal { -// expected-note @-1 {{type declared here}} +// expected-note @-1 2 {{type declared here}} } private struct ExposedLayoutPrivate { // expected-note @-1 2 {{struct 'ExposedLayoutPrivate' is not '@usableFromInline' or public}} -// expected-note @-2 2 {{type declared here}} +// expected-note @-2 3 {{type declared here}} init() { fatalError() } // expected-note {{initializer 'init()' is not '@usableFromInline' or public}} } @@ -86,12 +86,12 @@ public class ExposedClassPublic { } internal class ExposedClassInternal { -// expected-note @-1 {{type declared here}} +// expected-note @-1 2 {{type declared here}} } private class ExposedClassPrivate { // expected-note @-1 2 {{class 'ExposedClassPrivate' is not '@usableFromInline' or public}} -// expected-note @-2 2 {{type declared here}} +// expected-note @-2 3 {{type declared here}} init() { fatalError() } // expected-note {{initializer 'init()' is not '@usableFromInline' or public}} } @@ -100,14 +100,14 @@ private class ExposedClassPrivate { private class HiddenClass { // expected-opt-in-note @-1 2 {{class 'HiddenClass' is not '@usableFromInline' or public}} // expected-opt-in-note @-2 1 {{initializer 'init()' is not '@usableFromInline' or public}} -// expected-opt-in-note @-3 6 {{class declared here}} -// expected-opt-in-note @-4 2 {{type declared here}} +// expected-opt-in-note @-3 7 {{class declared here}} +// expected-opt-in-note @-4 3 {{type declared here}} } #else private class HiddenClass { // expected-not-opt-in-note @-1 2 {{class 'HiddenClass' is not '@usableFromInline' or public}} // expected-not-opt-in-note @-2 1 {{initializer 'init()' is not '@usableFromInline' or public}} -// expected-not-opt-in-note @-3 2 {{type declared here}} +// expected-not-opt-in-note @-3 3 {{type declared here}} } #endif @@ -116,14 +116,14 @@ private class HiddenClass { private struct HiddenLayout { // expected-opt-in-note @-1 2 {{struct 'HiddenLayout' is not '@usableFromInline' or public}} // expected-opt-in-note @-2 1 {{initializer 'init()' is not '@usableFromInline' or public}} -// expected-opt-in-note @-3 9 {{struct declared here}} -// expected-opt-in-note @-4 2 {{type declared here}} +// expected-opt-in-note @-3 10 {{struct declared here}} +// expected-opt-in-note @-4 3 {{type declared here}} } #else private struct HiddenLayout { // expected-not-opt-in-note @-1 2 {{struct 'HiddenLayout' is not '@usableFromInline' or public}} // expected-not-opt-in-note @-2 1 {{initializer 'init()' is not '@usableFromInline' or public}} -// expected-not-opt-in-note @-3 2 {{type declared here}} +// expected-not-opt-in-note @-3 3 {{type declared here}} } #endif @@ -134,6 +134,7 @@ public enum ExposedEnumPublic { private enum ExposedEnumPrivate { // expected-note @-1 2 {{enum 'ExposedEnumPrivate' is not '@usableFromInline' or public}} +// expected-note @-2 {{type declared here}} case A // expected-note @-1 1 {{enum case 'A' is not '@usableFromInline' or public}} case B @@ -142,8 +143,9 @@ private enum ExposedEnumPrivate { #if UseImplementationOnly @_implementationOnly private enum HiddenEnum { -// expected-opt-in-note @-1 6 {{enum declared here}} +// expected-opt-in-note @-1 7 {{enum declared here}} // expected-opt-in-note @-2 2 {{enum 'HiddenEnum' is not '@usableFromInline' or public}} +// expected-opt-in-note @-3 {{type declared here}} case A // expected-opt-in-note @-1 {{enum case 'A' is not '@usableFromInline' or public}} case B @@ -151,6 +153,7 @@ private enum HiddenEnum { #else private enum HiddenEnum { // expected-not-opt-in-note @-1 2 {{enum 'HiddenEnum' is not '@usableFromInline' or public}} +// expected-not-opt-in-note @-2 {{type declared here}} case A // expected-not-opt-in-note @-1 {{enum case 'A' is not '@usableFromInline' or public}} case B @@ -162,28 +165,31 @@ public protocol ExposedProtocolPublic { internal protocol ExposedProtocolInternal { // expected-note @-1 {{protocol 'ExposedProtocolInternal' is not '@usableFromInline' or public}} -// expected-note @-2 {{type declared here}} +// expected-note @-2 2 {{type declared here}} } private protocol ExposedProtocolPrivate { // expected-note @-1 {{protocol 'ExposedProtocolPrivate' is not '@usableFromInline' or public}} -// expected-note @-2 2 {{type declared here}} +// expected-note @-2 3 {{type declared here}} } #if UseImplementationOnly @_implementationOnly private protocol HiddenProtocol { // expected-opt-in-note @-1 {{protocol 'HiddenProtocol' is not '@usableFromInline' or public}} -// expected-opt-in-note @-2 9 {{protocol declared here}} -// expected-opt-in-note @-3 2 {{type declared here}} +// expected-opt-in-note @-2 10 {{protocol declared here}} +// expected-opt-in-note @-3 3 {{type declared here}} } #else private protocol HiddenProtocol { // expected-not-opt-in-note @-1 1 {{protocol 'HiddenProtocol' is not '@usableFromInline' or public}} -// expected-not-opt-in-note @-2 2 {{type declared here}} +// expected-not-opt-in-note @-2 3 {{type declared here}} } #endif +@_spi(S) public struct SPIStruct {} +// expected-note @-1 {{struct declared here}} + /// Function use sites @inlinable @@ -311,6 +317,63 @@ internal func explicitNonInliableInternal() { /// Struct use sites +@frozen +public struct ExposedLayoutFrozenUser: ProtocolFromDirect { +// expected-error @-1 {{cannot use protocol 'ProtocolFromDirect' in a public or '@usableFromInline' conformance; 'directs' has been imported as implementation-only}} + + public var publicField: StructFromDirect + // expected-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + + private var privateField: StructFromDirect + // expected-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + + private var a: ExposedLayoutPublic + private var aa: ExposedLayoutInternal + // expected-error @-1 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + private var b: ExposedLayoutPrivate + // expected-error @-1 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + private var c: HiddenLayout + // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenLayout' is marked '@_implementationOnly'}} + // expected-error @-2 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + + private var ca: ExposedClassPublic + private var cb: ExposedClassInternal + // expected-error @-1 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + private var cc: ExposedClassPrivate + // expected-error @-1 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + private var cd: HiddenClass + // expected-opt-in-error @-1 {{cannot use class 'HiddenClass' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenClass' is marked '@_implementationOnly'}} + // expected-error @-2 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + + private var d: ExposedEnumPublic + private var e: ExposedEnumPrivate + // expected-error @-1 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + private var f: HiddenEnum + // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenEnum' is marked '@_implementationOnly'}} + // expected-error @-2 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + + private var pp: ProtocolFromDirect + // expected-error @-1 {{cannot use protocol 'ProtocolFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + + private var g: ExposedProtocolPublic + private var h: ExposedProtocolInternal + // expected-error @-1 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + private var i: ExposedProtocolPrivate + // expected-error @-1 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + private var j: HiddenProtocol + // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenProtocol' is marked '@_implementationOnly'}} + // expected-error @-2 {{type referenced from a stored property in a '@frozen' struct must be '@usableFromInline' or public}} + + private func privateFunc(h: HiddenLayout) {} + // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} + private func privateFuncClass(h: HiddenClass) {} + // expected-embedded-opt-in-error @-1 {{class 'HiddenClass' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenClass' is marked '@_implementationOnly'}} + + @_spi(S) public var s: SPIStruct + // expected-error @-1 {{stored property 's' cannot be declared '@_spi' in a '@frozen' struct}} + // expected-error @-2 {{cannot use struct 'SPIStruct' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; it is SPI}} +} + public struct ExposedLayoutPublicUser: ProtocolFromDirect { // expected-error @-1 {{cannot use protocol 'ProtocolFromDirect' in a public or '@usableFromInline' conformance; 'directs' has been imported as implementation-only}} From 834bb63d5ff1f77eccf43a282c1d5dc83a7527c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 11 Nov 2025 15:25:30 -0800 Subject: [PATCH 2/8] Sema: Refactor source of truth for exceptions to exportability check --- lib/Sema/ResilienceDiagnostics.cpp | 9 ++++----- lib/Sema/TypeCheckAvailability.cpp | 27 +++++++++++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index cc927feef9a3e..2408d386ab582 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -294,6 +294,9 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D, } }); + if (where.canReferenceOrigin(originKind)) + return false; + auto fragileKind = where.getFragileFunctionKind(); switch (originKind) { case DisallowedOriginKind::None: @@ -326,14 +329,10 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D, if (reason && reason == ExportabilityReason::AvailableAttribute && ctx.LangOpts.LibraryLevel == LibraryLevel::API) return false; - LLVM_FALLTHROUGH; + break; case DisallowedOriginKind::SPIImported: case DisallowedOriginKind::SPILocal: - if (fragileKind.kind == FragileFunctionKind::EmbeddedAlwaysEmitIntoClient) - return false; - break; - case DisallowedOriginKind::ImplementationOnly: case DisallowedOriginKind::FragileCxxAPI: case DisallowedOriginKind::ImplementationOnlyMemoryLayout: diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index c41dc9ed14a38..25ce988d0bee1 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -239,12 +239,27 @@ 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; + // Implicitly always emit into client code in embedded mode can still + // access many restricted origins as more dependencies are loaded + // in non-library-evolution mode and there isn't an enforced separation of + // public vs SPI. + if (getFragileFunctionKind().kind == + FragileFunctionKind::EmbeddedAlwaysEmitIntoClient) { + switch (originKind) { + case DisallowedOriginKind::None: + case DisallowedOriginKind::NonPublicImport: + case DisallowedOriginKind::SPIOnly: + case DisallowedOriginKind::SPIImported: + case DisallowedOriginKind::SPILocal: + return true; + case DisallowedOriginKind::MissingImport: + case DisallowedOriginKind::InternalBridgingHeaderImport: + case DisallowedOriginKind::ImplementationOnly: + case DisallowedOriginKind::FragileCxxAPI: + case DisallowedOriginKind::ImplementationOnlyMemoryLayout: + break; + } + } return false; } From bf951b15919bd022ef5965241bb1a2d25124c614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Wed, 12 Nov 2025 13:55:32 -0800 Subject: [PATCH 3/8] Sema: Intro `ExportedLevel` and use it for `isExported` --- include/swift/AST/DeclExportabilityVisitor.h | 19 ++++++-- lib/AST/Availability.cpp | 49 ++++++++++---------- lib/AST/AvailabilityScopeBuilder.cpp | 2 +- lib/Sema/TypeCheckAccess.cpp | 2 +- lib/Sema/TypeCheckAvailability.cpp | 2 +- lib/Sema/TypeCheckStorage.cpp | 2 +- 6 files changed, 44 insertions(+), 32 deletions(-) diff --git a/include/swift/AST/DeclExportabilityVisitor.h b/include/swift/AST/DeclExportabilityVisitor.h index 732113d55a056..912d4d5e527ec 100644 --- a/include/swift/AST/DeclExportabilityVisitor.h +++ b/include/swift/AST/DeclExportabilityVisitor.h @@ -24,6 +24,19 @@ namespace swift { +/// How a decl is exported. +enum class ExportedLevel { + /// Not exported. + None, + + /// Exported implicitly for types in non-library-evolution mode not marked + /// `@_implementationOnly`. + ImplicitlyExported, + + /// Explicitly marked as exported with public or `@frozen`. + Exported +}; + /// This visitor determines whether a declaration is "exportable", meaning whether /// it can be referenced by other modules. For example, a function with a public /// access level or with the `@usableFromInline` attribute is exportable. @@ -183,13 +196,13 @@ class DeclExportabilityVisitor /// Check if a declaration is exported as part of a module's external interface. /// This includes public and @usableFromInline decls. /// FIXME: This is legacy that should be subsumed by `DeclExportabilityVisitor` -bool isExported(const Decl *D); +ExportedLevel isExported(const Decl *D); /// A specialization of `isExported` for `ValueDecl`. -bool isExported(const ValueDecl *VD); +ExportedLevel isExported(const ValueDecl *VD); /// A specialization of `isExported` for `ExtensionDecl`. -bool isExported(const ExtensionDecl *ED); +ExportedLevel isExported(const ExtensionDecl *ED); /// Returns true if the extension declares any protocol conformances that /// require the extension to be exported. diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 88514a0a0d0ef..5f33fee66c84b 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -972,7 +972,7 @@ bool ASTContext::supportsVersionedAvailability() const { return minimumAvailableOSVersionForTriple(LangOpts.Target).has_value(); } -bool swift::isExported(const Decl *D) { +ExportedLevel swift::isExported(const Decl *D) { if (auto *VD = dyn_cast(D)) { return isExported(VD); } @@ -982,32 +982,36 @@ bool swift::isExported(const Decl *D) { return isExported(VD); } - return false; + return ExportedLevel::None; } if (auto *ED = dyn_cast(D)) { return isExported(ED); } - return true; + return ExportedLevel::Exported; } -bool swift::isExported(const ValueDecl *VD) { +ExportedLevel swift::isExported(const ValueDecl *VD) { if (VD->getAttrs().hasAttribute()) - return false; + return ExportedLevel::None; if (VD->isObjCMemberImplementation()) - return false; + return ExportedLevel::None; // Is this part of the module's API or ABI? AccessScope accessScope = VD->getFormalAccessScope(nullptr, /*treatUsableFromInlineAsPublic*/ true); if (accessScope.isPublic()) - return true; + return ExportedLevel::Exported; // Is this a stored property in a @frozen struct or class? if (auto *property = dyn_cast(VD)) if (property->isLayoutExposedToClients(/*applyImplicit=*/true)) - return true; + return ExportedLevel::Exported; + + // Case of an enum not marked @_implementationOnly in a non-resilient module? + if (auto *EED = dyn_cast(VD)) + return isExported(EED->getParentEnum()); // Is this a type exposed by default in a non-resilient module? if (isa(VD) && @@ -1016,13 +1020,9 @@ bool swift::isExported(const ValueDecl *VD) { VD->getDeclContext()->getParentModule()->getResilienceStrategy() != ResilienceStrategy::Resilient && !VD->getAttrs().hasAttribute()) - return true; - - // Case of an enum not marked @_implementationOnly in a non-resilient module? - if (auto *EED = dyn_cast(VD)) - return isExported(EED->getParentEnum()); + return ExportedLevel::Exported; - return false; + return ExportedLevel::None; } bool swift::hasConformancesToPublicProtocols(const ExtensionDecl *ED) { @@ -1046,23 +1046,22 @@ bool swift::hasConformancesToPublicProtocols(const ExtensionDecl *ED) { return false; } -bool swift::isExported(const ExtensionDecl *ED) { +ExportedLevel swift::isExported(const ExtensionDecl *ED) { // An extension can only be exported if it extends an exported type. if (auto *NTD = ED->getExtendedNominal()) { - if (!isExported(NTD)) - return false; - } - - // If there are any exported members then the extension is exported. - for (const Decl *D : ED->getMembers()) { - if (isExported(D)) - return true; + if (isExported(NTD) == ExportedLevel::None) + return ExportedLevel::None; } // If the extension declares a conformance to a public protocol then the // extension is exported. if (hasConformancesToPublicProtocols(ED)) - return true; + return ExportedLevel::Exported; - return false; + // If there are any exported members then the extension is exported. + ExportedLevel exported = ExportedLevel::None; + for (const Decl *D : ED->getMembers()) + exported = std::max(exported, isExported(D)); + + return exported; } diff --git a/lib/AST/AvailabilityScopeBuilder.cpp b/lib/AST/AvailabilityScopeBuilder.cpp index b08853dd62d0a..1834bba75c8c1 100644 --- a/lib/AST/AvailabilityScopeBuilder.cpp +++ b/lib/AST/AvailabilityScopeBuilder.cpp @@ -479,7 +479,7 @@ class AvailabilityScopeBuilder : private ASTWalker { if (decl->isSPI()) return true; - return !isExported(decl); + return isExported(decl) == ExportedLevel::None; } /// Returns the source range which should be refined by declaration. This diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index 72ead5268aeb1..b87a62e1ca493 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -2603,7 +2603,7 @@ class DeclAvailabilityChecker : public DeclVisitor { auto *valueMember = dyn_cast(member); if (!valueMember) return false; - return isExported(valueMember); + return isExported(valueMember) == ExportedLevel::Exported; }); Where = wasWhere.withExported(hasExportedMembers); diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 25ce988d0bee1..b38b267fea3ec 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -178,7 +178,7 @@ ExportContext ExportContext::forDeclSignature(Decl *D) { computeExportContextBits(Ctx, D, &spi, &implicit); }); - bool exported = ::isExported(D); + bool exported = ::isExported(D) != ExportedLevel::None; return ExportContext(DC, availabilityContext, fragileKind, nullptr, spi, exported, implicit); diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 48a90c449a717..c6c2179985c18 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -2755,7 +2755,7 @@ static bool requiresCorrespondingUnderscoredCoroutineAccessorImpl( return false; // Non-exported storage has no ABI to keep stable. - if (!isExported(storage)) + if (isExported(storage) == ExportedLevel::None) return false; // The non-underscored accessor is not present, the underscored accessor From 60731d957c7fcc2f55c49b97546005fd654141d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Fri, 14 Nov 2025 16:13:27 -0800 Subject: [PATCH 4/8] Sema: Use `ExportedLevel` for `isLayoutExposedToClients` --- include/swift/AST/Decl.h | 6 +-- include/swift/AST/DeclExportabilityVisitor.h | 2 +- lib/AST/Availability.cpp | 5 +- lib/AST/Decl.cpp | 54 ++++++++++---------- lib/Sema/TypeCheckAccess.cpp | 2 +- lib/Sema/TypeCheckAttr.cpp | 3 +- 6 files changed, 35 insertions(+), 37 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index ac2a1a21e7b41..ece65f3c0759e 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -81,6 +81,7 @@ namespace swift { class DiagnosticEngine; class DynamicSelfType; class Type; + enum class ExportedLevel; class Expr; struct ExternalSourceLocs; class CaptureListExpr; @@ -6749,10 +6750,7 @@ class VarDecl : public AbstractStorageDecl { /// /// From the standpoint of access control and exportability checking, this /// var will behave as if it was public, even if it is internal or private. - /// - /// If \p applyImplicit, consider implicitly exposed layouts as well. - /// This applies to non-resilient modules. - bool isLayoutExposedToClients(bool applyImplicit = false) const; + ExportedLevel isLayoutExposedToClients() const; /// Is this a special debugger variable? bool isDebuggerVar() const { return Bits.VarDecl.IsDebuggerVar; } diff --git a/include/swift/AST/DeclExportabilityVisitor.h b/include/swift/AST/DeclExportabilityVisitor.h index 912d4d5e527ec..27361076913f9 100644 --- a/include/swift/AST/DeclExportabilityVisitor.h +++ b/include/swift/AST/DeclExportabilityVisitor.h @@ -115,7 +115,7 @@ class DeclExportabilityVisitor } bool visitVarDecl(const VarDecl *var) { - if (var->isLayoutExposedToClients()) + if (var->isLayoutExposedToClients() == ExportedLevel::Exported) return true; // Consider all lazy var storage as exportable. diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 5f33fee66c84b..dd87e2c17ba5d 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -1006,8 +1006,7 @@ ExportedLevel swift::isExported(const ValueDecl *VD) { // Is this a stored property in a @frozen struct or class? if (auto *property = dyn_cast(VD)) - if (property->isLayoutExposedToClients(/*applyImplicit=*/true)) - return ExportedLevel::Exported; + return property->isLayoutExposedToClients(); // Case of an enum not marked @_implementationOnly in a non-resilient module? if (auto *EED = dyn_cast(VD)) @@ -1020,7 +1019,7 @@ ExportedLevel swift::isExported(const ValueDecl *VD) { VD->getDeclContext()->getParentModule()->getResilienceStrategy() != ResilienceStrategy::Resilient && !VD->getAttrs().hasAttribute()) - return ExportedLevel::Exported; + return ExportedLevel::ImplicitlyExported; return ExportedLevel::None; } diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 61dcdcf8d92d0..f8dcb01d3b310 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2774,44 +2774,44 @@ bool VarDecl::isInitExposedToClients() const { if (getAttrs().hasAttribute()) return false; - return hasInitialValue() && isLayoutExposedToClients(); + return hasInitialValue() && + isLayoutExposedToClients() == ExportedLevel::Exported; } -bool VarDecl::isLayoutExposedToClients(bool applyImplicit) const { +ExportedLevel VarDecl::isLayoutExposedToClients() const { auto parent = dyn_cast(getDeclContext()); - if (!parent) return false; - if (isStatic()) return false; + if (!parent) return ExportedLevel::None; + if (isStatic()) return ExportedLevel::None; - auto M = getDeclContext()->getParentModule(); + // Does it contribute to the layout? + if (!hasStorage() && + !getAttrs().hasAttribute() && + !hasAttachedPropertyWrapper()) { + return ExportedLevel::None; + } + + // Is it a member of a frozen type? auto nominalAccess = parent->getFormalAccessScope(/*useDC=*/nullptr, /*treatUsableFromInlineAsPublic=*/true); + if (nominalAccess.isPublic() && + (parent->getAttrs().hasAttribute() || + parent->getAttrs().hasAttribute())) + return ExportedLevel::Exported; - // Resilient modules and classes hide layouts by default. - bool layoutIsHiddenByDefault = !applyImplicit || - !getASTContext().LangOpts.hasFeature(Feature::CheckImplementationOnly) || - M->getResilienceStrategy() == ResilienceStrategy::Resilient; - if (layoutIsHiddenByDefault) { - if (!nominalAccess.isPublic()) - return false; + // Is it a member of an `@_implementationOnly` type? + if (parent->getAttrs().hasAttribute()) + return ExportedLevel::None; - if (!parent->getAttrs().hasAttribute() && - !parent->getAttrs().hasAttribute()) - return false; + auto M = getDeclContext()->getParentModule(); + if (getASTContext().LangOpts.hasFeature(Feature::CheckImplementationOnly) && + M->getResilienceStrategy() != ResilienceStrategy::Resilient) { + // Non-resilient module expose layouts by default. + return ExportedLevel::ImplicitlyExported; } else { - // Non-resilient module: layouts are exposed by default unless marked - // otherwise. - if (parent->getAttrs().hasAttribute()) - return false; + // Resilient modules hide layouts by default. + return ExportedLevel::None; } - - if (!hasStorage() && - !getAttrs().hasAttribute() && - !hasAttachedPropertyWrapper()) { - return false; - } - - return true; } /// Check whether the given type representation will be diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index b87a62e1ca493..d8591bd58901e 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -1498,7 +1498,7 @@ class UsableFromInlineChecker : public AccessControlCheckerBase, /// the struct instead of the VarDecl, and customize the diagnostics. static const ValueDecl * getFixedLayoutStructContext(const VarDecl *VD) { - if (VD->isLayoutExposedToClients()) + if (VD->isLayoutExposedToClients() == ExportedLevel::Exported) return dyn_cast(VD->getDeclContext()); return nullptr; diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index c16e0bb558eb7..02f1ee606fda8 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -30,6 +30,7 @@ #include "swift/AST/ClangModuleLoader.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/Decl.h" +#include "swift/AST/DeclExportabilityVisitor.h" #include "swift/AST/DiagnosticsParse.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/Effects.h" @@ -1408,7 +1409,7 @@ void AttributeChecker::visitSPIAccessControlAttr(SPIAccessControlAttr *attr) { // Forbid stored properties marked SPI in frozen types. if (auto property = dyn_cast(VD)) { if (auto NTD = dyn_cast(D->getDeclContext())) { - if (property->isLayoutExposedToClients() && !NTD->isSPI()) { + if (property->isLayoutExposedToClients() == ExportedLevel::Exported && !NTD->isSPI()) { diagnoseAndRemoveAttr(attr, diag::spi_attribute_on_frozen_stored_properties, VD); From e04f4eadf3c6b3e3012eaa313ea8dbbc7a09ddec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Wed, 12 Nov 2025 16:23:06 -0800 Subject: [PATCH 5/8] Sema: Use `ExportedLevel` for `ExportContext` services --- lib/Sema/TypeCheckAvailability.cpp | 15 ++++++++------- lib/Sema/TypeCheckAvailability.h | 14 +++++++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index b38b267fea3ec..752cc98775e94 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -86,12 +86,12 @@ ExportContext::ExportContext(DeclContext *DC, AvailabilityContext availability, FragileFunctionKind kind, llvm::SmallVectorImpl *unsafeUses, - bool spi, bool exported, + bool spi, ExportedLevel exported, bool implicit) : DC(DC), Availability(availability), FragileKind(kind), UnsafeUses(unsafeUses) { SPI = spi; - Exported = exported; + Exported = unsigned(exported); Implicit = implicit; Reason = unsigned(ExportabilityReason::General); } @@ -178,7 +178,7 @@ ExportContext ExportContext::forDeclSignature(Decl *D) { computeExportContextBits(Ctx, D, &spi, &implicit); }); - bool exported = ::isExported(D) != ExportedLevel::None; + ExportedLevel exported = ::isExported(D); return ExportContext(DC, availabilityContext, fragileKind, nullptr, spi, exported, implicit); @@ -194,7 +194,7 @@ ExportContext ExportContext::forFunctionBody(DeclContext *DC, SourceLoc loc) { forEachOuterDecl( DC, [&](Decl *D) { computeExportContextBits(Ctx, D, &spi, &implicit); }); - bool exported = false; + ExportedLevel exported = ExportedLevel::None; return ExportContext(DC, availabilityContext, fragileKind, nullptr, spi, exported, implicit); @@ -205,8 +205,9 @@ ExportContext ExportContext::forConformance(DeclContext *DC, assert(isa(DC) || isa(DC)); auto where = forDeclSignature(DC->getInnermostDeclarationDeclContext()); - where.Exported &= proto->getFormalAccessScope( - DC, /*usableFromInlineAsPublic*/true).isPublic(); + if (!proto->getFormalAccessScope( + DC, /*usableFromInlineAsPublic*/true).isPublic()) + where.Exported = unsigned(ExportedLevel::None); return where; } @@ -219,7 +220,7 @@ ExportContext ExportContext::withReason(ExportabilityReason reason) const { ExportContext ExportContext::withExported(bool exported) const { auto copy = *this; - copy.Exported = isExported() && exported; + copy.Exported = exported ? Exported : unsigned(ExportedLevel::None); return copy; } diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index 87ed2555754c0..b33ec076ac24d 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -17,6 +17,7 @@ #include "swift/AST/AvailabilityConstraint.h" #include "swift/AST/AvailabilityContext.h" #include "swift/AST/DeclContext.h" +#include "swift/AST/DeclExportabilityVisitor.h" #include "swift/AST/Identifier.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/OptionSet.h" @@ -115,14 +116,14 @@ class ExportContext { FragileFunctionKind FragileKind; llvm::SmallVectorImpl *UnsafeUses; unsigned SPI : 1; - unsigned Exported : 1; + unsigned Exported : 2; unsigned Implicit : 1; unsigned Reason : 3; ExportContext(DeclContext *DC, AvailabilityContext availability, FragileFunctionKind kind, llvm::SmallVectorImpl *unsafeUses, - bool spi, bool exported, bool implicit); + bool spi, ExportedLevel exported, bool implicit); public: @@ -184,9 +185,12 @@ class ExportContext { /// If true, the context is SPI and can reference SPI declarations. bool isSPI() const { return SPI; } - /// If true, the context is exported and cannot reference SPI declarations - /// or declarations from `@_implementationOnly` imports. - bool isExported() const { return Exported; } + /// If true, the context is exported explicitly and cannot reference + /// restricted decls. + bool isExported() const { return Exported != unsigned(ExportedLevel::None); } + + /// Get the export level of the context. + ExportedLevel getExportedLevel() const { return ExportedLevel(Exported); } /// If true, the context can only reference exported declarations, either /// because it is the signature context of an exported declaration, or From e6976e6291a6db0915e014a4d8bb21eb4d0adec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Wed, 3 Dec 2025 14:58:27 -0800 Subject: [PATCH 6/8] Sema: Centralize exportability reason spelling --- include/swift/AST/DiagnosticsSema.def | 33 ++++++++++----------------- lib/Sema/TypeCheckAvailability.h | 10 ++++---- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 116e11f825d1e..198c434bd3fab 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3840,14 +3840,17 @@ NOTE(enum_raw_value_incrementing_from_zero,none, NOTE(construct_raw_representable_from_unwrapped_value,none, "construct %0 from unwrapped %1 value", (Type, Type)) +#define EXPORTABILITY_REASON_SELECT "select{" \ + "here|as property wrapper here|" \ + "as result builder here|" \ + "in an extension with public or '@usableFromInline' members|" \ + "in an extension with conditional conformances|" \ + "in a public or '@usableFromInline' conformance|" \ + "in an '@available' attribute here|" \ + "in a property declaration marked public or in a '@frozen' or '@usableFromInline' context}" + ERROR(decl_from_hidden_module,none, - "cannot use %kind0 %select{here|as property wrapper here|" - "as result builder here|" - "in an extension with public or '@usableFromInline' members|" - "in an extension with conditional conformances|" - "in a public or '@usableFromInline' conformance|" - "in an '@available' attribute here|" - "in a property declaration marked public or in a '@frozen' or '@usableFromInline' context}1; " + "cannot use %kind0 %" EXPORTABILITY_REASON_SELECT "1; " "%select{%2 has been imported as implementation-only|" "it is an SPI imported from %2|" "it is SPI|" @@ -3859,14 +3862,7 @@ ERROR(decl_from_hidden_module,none, "%0 is marked '@_implementationOnly'}3", (const Decl *, unsigned, Identifier, unsigned)) ERROR(typealias_desugars_to_type_from_hidden_module,none, - "%0 aliases '%1.%2' and cannot be used %select{here|" - "as property wrapper here|" - "as result builder here|" - "in an extension with public or '@usableFromInline' members|" - "in an extension with conditional conformance|" - "in a public or '@usableFromInline' conformance|" - "<>|" - "in a property declaration marked public or in a '@frozen' or '@usableFromInline' context}3 " + "%0 aliases '%1.%2' and cannot be used %" EXPORTABILITY_REASON_SELECT "3 " "because %select{%4 has been imported as implementation-only|" "it is an SPI imported from %4|" "<>|" @@ -3878,12 +3874,7 @@ ERROR(typealias_desugars_to_type_from_hidden_module,none, "%0 is marked '@_implementationOnly'}5", (const TypeAliasDecl *, StringRef, StringRef, unsigned, Identifier, unsigned)) ERROR(conformance_from_implementation_only_module,none, - "cannot use conformance of %0 to %1 %select{here|as property wrapper here|" - "as result builder here|" - "in an extension with public or '@usableFromInline' members|" - "in an extension with conditional conformances|" - "<>|<>|" - "in a property declaration marked public or in a '@frozen' or '@usableFromInline' context}2; " + "cannot use conformance of %0 to %1 %" EXPORTABILITY_REASON_SELECT "2; " "%select{%3 has been imported as implementation-only|" "the conformance is declared as SPI in %3|" "the conformance is declared as SPI|" diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index b33ec076ac24d..67b9cca7607ba 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -70,10 +70,11 @@ enum class DeclAvailabilityFlag : uint8_t { }; using DeclAvailabilityFlags = OptionSet; -// This enum must be kept in sync with -// diag::decl_from_hidden_module, -// diag::typealias_desugars_to_type_from_hidden_module, and -// diag::conformance_from_implementation_only_module. +// Classification of the kind of declaration visible to clients that is +// restricting references to some decls. +// +// This enum must be kept in sync with diag's `EXPORTABILITY_REASON_SELECT`, +// and fit in the size of `ExportContext.Reason`. enum class ExportabilityReason : unsigned { General, PropertyWrapper, @@ -83,6 +84,7 @@ enum class ExportabilityReason : unsigned { Inheritance, AvailableAttribute, PublicVarDecl, + ImplicitlyPublicVarDecl, }; /// A description of the restrictions on what declarations can be referenced From 8e0443049840868b26c4ac0b1fbae550c6fb8066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Thu, 4 Dec 2025 13:53:41 -0800 Subject: [PATCH 7/8] Sema: Differentiate the reason for implictly public memory layouts --- include/swift/AST/DiagnosticsSema.def | 3 +- lib/Sema/ResilienceDiagnostics.cpp | 1 + lib/Sema/TypeCheckAccess.cpp | 12 +++++- lib/Sema/TypeCheckAvailability.h | 2 +- test/Sema/hidden-memory-layout.swift | 60 +++++++++++++-------------- 5 files changed, 44 insertions(+), 34 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 198c434bd3fab..eb12c5309a743 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3847,7 +3847,8 @@ NOTE(construct_raw_representable_from_unwrapped_value,none, "in an extension with conditional conformances|" \ "in a public or '@usableFromInline' conformance|" \ "in an '@available' attribute here|" \ - "in a property declaration marked public or in a '@frozen' or '@usableFromInline' context}" + "in a property declaration marked public or in a '@frozen' or '@usableFromInline' context|" \ + "in a property declaration member of a type not marked '@_implementationOnly'}" ERROR(decl_from_hidden_module,none, "cannot use %kind0 %" EXPORTABILITY_REASON_SELECT "1; " diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index 2408d386ab582..b395d3e1e65cf 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -261,6 +261,7 @@ static bool shouldDiagnoseDeclAccess(const ValueDecl *D, case ExportabilityReason::ResultBuilder: case ExportabilityReason::PropertyWrapper: case ExportabilityReason::PublicVarDecl: + case ExportabilityReason::ImplicitlyPublicVarDecl: return false; } } diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index d8591bd58901e..7a76c25d7b2ae 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -2383,8 +2383,12 @@ class DeclAvailabilityChecker : public DeclVisitor { if (seenVars.count(theVar)) return; + ExportabilityReason reason = + Where.getExportedLevel() == ExportedLevel::ImplicitlyExported ? + ExportabilityReason::ImplicitlyPublicVarDecl : + ExportabilityReason::PublicVarDecl; checkType(theVar->getValueInterfaceType(), /*typeRepr*/nullptr, theVar, - ExportabilityReason::PublicVarDecl); + reason); for (auto attr : theVar->getAttachedPropertyWrappers()) { checkType(attr->getType(), attr->getTypeRepr(), theVar, @@ -2405,9 +2409,13 @@ class DeclAvailabilityChecker : public DeclVisitor { anyVar = V; }); + ExportabilityReason reason = + Where.getExportedLevel() == ExportedLevel::ImplicitlyExported ? + ExportabilityReason::ImplicitlyPublicVarDecl : + ExportabilityReason::PublicVarDecl; checkType(TP->hasType() ? TP->getType() : Type(), TP->getTypeRepr(), anyVar ? (Decl *)anyVar : (Decl *)PBD, - ExportabilityReason::PublicVarDecl); + reason); // Check the property wrapper types. if (anyVar) { diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index 67b9cca7607ba..d4d615336693a 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -120,7 +120,7 @@ class ExportContext { unsigned SPI : 1; unsigned Exported : 2; unsigned Implicit : 1; - unsigned Reason : 3; + unsigned Reason : 4; ExportContext(DeclContext *DC, AvailabilityContext availability, FragileFunctionKind kind, diff --git a/test/Sema/hidden-memory-layout.swift b/test/Sema/hidden-memory-layout.swift index 373aa4cc0198d..8925871f1a999 100644 --- a/test/Sema/hidden-memory-layout.swift +++ b/test/Sema/hidden-memory-layout.swift @@ -381,33 +381,33 @@ public struct ExposedLayoutPublicUser: ProtocolFromDirect { // expected-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} private var privateField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var a: ExposedLayoutPublic private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate private var c: HiddenLayout - // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenLayout' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenLayout' is marked '@_implementationOnly'}} private var ca: ExposedClassPublic private var cb: ExposedClassInternal private var cc: ExposedClassPrivate private var cd: HiddenClass - // expected-opt-in-error @-1 {{cannot use class 'HiddenClass' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenClass' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use class 'HiddenClass' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenClass' is marked '@_implementationOnly'}} private var d: ExposedEnumPublic private var e: ExposedEnumPrivate private var f: HiddenEnum - // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenEnum' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenEnum' is marked '@_implementationOnly'}} private var pp: ProtocolFromDirect - // expected-opt-in-error @-1 {{cannot use protocol 'ProtocolFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use protocol 'ProtocolFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var g: ExposedProtocolPublic private var h: ExposedProtocolInternal private var i: ExposedProtocolPrivate private var j: HiddenProtocol - // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenProtocol' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenProtocol' is marked '@_implementationOnly'}} private func privateFunc(h: HiddenLayout) {} // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} @@ -419,30 +419,30 @@ internal struct ExposedLayoutInternalUser: ProtocolFromDirect { // expected-opt-in-error @-1 {{cannot use protocol 'ProtocolFromDirect' in a public or '@usableFromInline' conformance; 'directs' has been imported as implementation-only}} private var privateField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var a: ExposedLayoutPublic private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate private var c: HiddenLayout - // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenLayout' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenLayout' is marked '@_implementationOnly'}} private var ca: ExposedClassPublic private var cb: ExposedClassInternal private var cc: ExposedClassPrivate private var cd: HiddenClass - // expected-opt-in-error @-1 {{cannot use class 'HiddenClass' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenClass' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use class 'HiddenClass' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenClass' is marked '@_implementationOnly'}} private var d: ExposedEnumPublic private var e: ExposedEnumPrivate private var f: HiddenEnum - // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenEnum' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenEnum' is marked '@_implementationOnly'}} private var g: ExposedProtocolPublic private var h: ExposedProtocolInternal private var i: ExposedProtocolPrivate private var j: HiddenProtocol - // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenProtocol' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenProtocol' is marked '@_implementationOnly'}} private func privateFunc(h: HiddenLayout) {} // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} @@ -454,30 +454,30 @@ private struct ExposedLayoutPrivateUser: ProtocolFromDirect { // expected-opt-in-error @-1 {{cannot use protocol 'ProtocolFromDirect' in a public or '@usableFromInline' conformance; 'directs' has been imported as implementation-only}} private var privateField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var a: ExposedLayoutPublic private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate private var c: HiddenLayout - // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenLayout' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenLayout' is marked '@_implementationOnly'}} private var ca: ExposedClassPublic private var cb: ExposedClassInternal private var cc: ExposedClassPrivate private var cd: HiddenClass - // expected-opt-in-error @-1 {{cannot use class 'HiddenClass' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenClass' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use class 'HiddenClass' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenClass' is marked '@_implementationOnly'}} private var d: ExposedEnumPublic private var e: ExposedEnumPrivate private var f: HiddenEnum - // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenEnum' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenEnum' is marked '@_implementationOnly'}} private var g: ExposedProtocolPublic private var h: ExposedProtocolInternal private var i: ExposedProtocolPrivate private var j: HiddenProtocol - // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenProtocol' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenProtocol' is marked '@_implementationOnly'}} private func privateFunc(h: HiddenLayout) {} // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} @@ -637,23 +637,23 @@ public class PublicClassUser: ProtocolFromDirect { // expected-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} private var privateField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var a: ExposedLayoutPublic private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate private var c: HiddenLayout - // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenLayout' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenLayout' is marked '@_implementationOnly'}} private var d: ExposedEnumPublic private var e: ExposedEnumPrivate private var f: HiddenEnum - // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenEnum' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenEnum' is marked '@_implementationOnly'}} private var g: ExposedProtocolPublic private var h: ExposedProtocolInternal private var i: ExposedProtocolPrivate private var j: HiddenProtocol - // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenProtocol' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenProtocol' is marked '@_implementationOnly'}} @export(interface) private func privateFunc(h: HiddenLayout) {} @@ -665,26 +665,26 @@ internal class InternalClassUser: ProtocolFromDirect { public init() { fatalError() } public var publicField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var privateField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var a: ExposedLayoutPublic private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate private var c: HiddenLayout - // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenLayout' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenLayout' is marked '@_implementationOnly'}} private var d: ExposedEnumPublic private var e: ExposedEnumPrivate private var f: HiddenEnum - // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenEnum' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenEnum' is marked '@_implementationOnly'}} private var g: ExposedProtocolPublic private var h: ExposedProtocolInternal private var i: ExposedProtocolPrivate private var j: HiddenProtocol - // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenProtocol' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenProtocol' is marked '@_implementationOnly'}} private func privateFunc(h: HiddenLayout) {} // expected-embedded-opt-in-error {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} } @@ -695,26 +695,26 @@ private class PrivateClassUser: ProtocolFromDirect { public init() { fatalError() } public var publicField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var privateField: StructFromDirect - // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'directs' has been imported as implementation-only}} + // expected-opt-in-error @-1 {{cannot use struct 'StructFromDirect' in a property declaration member of a type not marked '@_implementationOnly'; 'directs' has been imported as implementation-only}} private var a: ExposedLayoutPublic private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate private var c: HiddenLayout - // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenLayout' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use struct 'HiddenLayout' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenLayout' is marked '@_implementationOnly'}} private var d: ExposedEnumPublic private var e: ExposedEnumPrivate private var f: HiddenEnum - // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenEnum' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use enum 'HiddenEnum' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenEnum' is marked '@_implementationOnly'}} private var g: ExposedProtocolPublic private var h: ExposedProtocolInternal private var i: ExposedProtocolPrivate private var j: HiddenProtocol - // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration marked public or in a '@frozen' or '@usableFromInline' context; 'HiddenProtocol' is marked '@_implementationOnly'}} + // expected-opt-in-error @-1 {{cannot use protocol 'HiddenProtocol' in a property declaration member of a type not marked '@_implementationOnly'; 'HiddenProtocol' is marked '@_implementationOnly'}} private func privateFunc(h: HiddenLayout) {} // expected-embedded-opt-in-error {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} } From e3571eb8337220c71701637196c33fd10f438e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Wed, 12 Nov 2025 14:42:13 -0800 Subject: [PATCH 8/8] Sema: Allow SPI references from implicitly public layouts --- lib/Sema/TypeCheckAvailability.cpp | 12 +++++++----- test/Sema/hidden-memory-layout.swift | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 752cc98775e94..593a6b7c65478 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -240,12 +240,14 @@ bool ExportContext::canReferenceOrigin(DisallowedOriginKind originKind) const { if (originKind == DisallowedOriginKind::None) return true; - // Implicitly always emit into client code in embedded mode can still - // access many restricted origins as more dependencies are loaded - // in non-library-evolution mode and there isn't an enforced separation of - // public vs SPI. + // Exportability checks for non-library-evolution mode have less restrictions + // than the library-evolution ones. Implicitly always emit into client code + // in embedded mode and implicitly exported layouts in non-library-evolution + // mode can reference SPIs and non-public dependencies. + auto reason = getExportabilityReason(); if (getFragileFunctionKind().kind == - FragileFunctionKind::EmbeddedAlwaysEmitIntoClient) { + FragileFunctionKind::EmbeddedAlwaysEmitIntoClient || + (reason && *reason == ExportabilityReason::ImplicitlyPublicVarDecl)) { switch (originKind) { case DisallowedOriginKind::None: case DisallowedOriginKind::NonPublicImport: diff --git a/test/Sema/hidden-memory-layout.swift b/test/Sema/hidden-memory-layout.swift index 8925871f1a999..e5a7b528a9c65 100644 --- a/test/Sema/hidden-memory-layout.swift +++ b/test/Sema/hidden-memory-layout.swift @@ -413,6 +413,8 @@ public struct ExposedLayoutPublicUser: ProtocolFromDirect { // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} private func privateFuncClass(h: HiddenClass) {} // expected-embedded-opt-in-error @-1 {{class 'HiddenClass' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenClass' is marked '@_implementationOnly'}} + + @_spi(S) public var s: SPIStruct } internal struct ExposedLayoutInternalUser: ProtocolFromDirect { @@ -448,6 +450,8 @@ internal struct ExposedLayoutInternalUser: ProtocolFromDirect { // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} private func privateFuncClass(h: HiddenClass) {} // expected-embedded-opt-in-error @-1 {{class 'HiddenClass' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenClass' is marked '@_implementationOnly'}} + + @_spi(S) public var s: SPIStruct } private struct ExposedLayoutPrivateUser: ProtocolFromDirect { @@ -483,6 +487,8 @@ private struct ExposedLayoutPrivateUser: ProtocolFromDirect { // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenLayout' is marked '@_implementationOnly'}} private func privateFuncClass(h: HiddenClass) {} // expected-embedded-opt-in-error @-1 {{class 'HiddenClass' cannot be used in an embedded function not marked '@export(interface)' because 'HiddenClass' is marked '@_implementationOnly'}} + + @_spi(S) public var s: SPIStruct } #if UseImplementationOnly