From 62ee4486d7637971a0e9bfc1b4183aabbae8a0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 4 Nov 2025 13:50:25 -0800 Subject: [PATCH 1/5] Features: Intro CheckImplementationOnly --- include/swift/Basic/Features.def | 3 +++ lib/AST/FeatureSet.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index af0a8c6eb6f6d..608dbc80e1f52 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -558,6 +558,9 @@ EXPERIMENTAL_FEATURE(EmbeddedExistentials, false) /// Allow use of the 'anyAppleOS' availability domain. EXPERIMENTAL_FEATURE(AnyAppleOSAvailability, true) +/// Check @_implementationOnly imports in non-library-evolution mode. +EXPERIMENTAL_FEATURE(CheckImplementationOnly, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 84c2d55645fe6..f561e03d5e8c8 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -144,6 +144,7 @@ UNINTERESTING_FEATURE(ExtractConstantsFromMembers) UNINTERESTING_FEATURE(GroupActorErrors) UNINTERESTING_FEATURE(SameElementRequirements) UNINTERESTING_FEATURE(SendingArgsAndResults) +UNINTERESTING_FEATURE(CheckImplementationOnly) static bool findUnderscoredLifetimeAttr(Decl *decl) { auto hasUnderscoredLifetimeAttr = [](Decl *decl) { From 71035a51de1c0b38ac4e29379fdeb4b8e6e4f373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 21 Oct 2025 15:48:56 -0700 Subject: [PATCH 2/5] Sema: Accept @_implementationOnly on non-public structs Gate it behind CheckImplementationOnly. --- include/swift/AST/DeclAttr.def | 2 +- include/swift/AST/DiagnosticsSema.def | 5 ++++- lib/Sema/TypeCheckAttr.cpp | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index 3517bd718c87f..67eaabe4e88a2 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -474,7 +474,7 @@ SIMPLE_DECL_ATTR(_alwaysEmitIntoClient, AlwaysEmitIntoClient, 83) SIMPLE_DECL_ATTR(_implementationOnly, ImplementationOnly, - OnImport | OnFunc | OnConstructor | OnVar | OnSubscript, + OnImport | OnFunc | OnConstructor | OnVar | OnSubscript | OnStruct, UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | UnreachableInABIAttr, 84) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index e3326cf5a8343..11bc45f1b9d9c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3946,6 +3946,9 @@ ERROR(implementation_only_override_import_without_attr,none, "override of %kindonly0 imported as implementation-only must be declared " "'@_implementationOnly'", (const ValueDecl *)) +ERROR(implementation_only_on_structs_feature,none, + "'@_implementationOnly' on structs requires " + "'-enable-experimental-feature CheckImplementationOnly'", ()) ERROR(import_attr_conflict,none, "%0 inconsistently imported with %1", @@ -4128,7 +4131,7 @@ WARNING(attr_has_no_effect_on_decl_with_access_level,none, (DeclAttribute, AccessLevel)) ERROR(attr_not_on_decl_with_invalid_access_level,none, "'%0' may not be used on " - "%select{private|fileprivate|internal|package|%error|%error}1 declarations", + "%select{private|fileprivate|internal|package|public|%error}1 declarations", (DeclAttribute, AccessLevel)) ERROR(attr_has_no_effect_decl_not_available_before,none, diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index aeb7098690963..556a95eead8cd 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -4995,7 +4995,25 @@ AttributeChecker::visitImplementationOnlyAttr(ImplementationOnlyAttr *attr) { return; } + // @_implementationOnly on structs only applies to non-public types. auto *VD = cast(D); + if (isa(VD)) { + if (!Ctx.LangOpts.hasFeature(Feature::CheckImplementationOnly)) { + diagnoseAndRemoveAttr(attr, + diag::implementation_only_on_structs_feature); + return; + } + + auto access = + VD->getFormalAccessScope(/*useDC=*/nullptr, + /*treatUsableFromInlineAsPublic=*/true); + if (access.isPublicOrPackage()) + diagnoseAndRemoveAttr( + attr, diag::attr_not_on_decl_with_invalid_access_level, + attr, access.accessLevelForDiagnostics()); + return; + } + auto *overridden = VD->getOverriddenDecl(); if (!overridden) { diagnoseAndRemoveAttr(attr, diag::implementation_only_decl_non_override); From 1383fcda800235676dc2e6f610730d0835292144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Fri, 26 Sep 2025 16:29:19 -0700 Subject: [PATCH 3/5] Sema: Report non-LE structs as fragile use sites in CheckImplementationOnly In non-library-evolution mode, gated behind the CheckImplementationOnly feature flag, consider structs to be a fragile use site by default, unless marked `@_implementationOnly`. This prevents them to refer to restricted imports like implementation-only. --- lib/AST/Decl.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 9ad2c3c8f61c4..d169df168455c 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2758,15 +2758,26 @@ bool VarDecl::isLayoutExposedToClients() const { if (!parent) return false; if (isStatic()) return false; - + auto M = getDeclContext()->getParentModule(); auto nominalAccess = parent->getFormalAccessScope(/*useDC=*/nullptr, /*treatUsableFromInlineAsPublic=*/true); - if (!nominalAccess.isPublic()) return false; - if (!parent->getAttrs().hasAttribute() && - !parent->getAttrs().hasAttribute()) + // Resilient modules hide layouts by default. + if (!getASTContext().LangOpts.hasFeature(Feature::CheckImplementationOnly) || + M->getResilienceStrategy() == ResilienceStrategy::Resilient) { + if (!nominalAccess.isPublic()) + return false; + + if (!parent->getAttrs().hasAttribute() && + !parent->getAttrs().hasAttribute()) return false; + } else { + // Non-resilient module: layouts are exposed by default unless marked + // otherwise. + if (parent->getAttrs().hasAttribute()) + return false; + } if (!hasStorage() && !getAttrs().hasAttribute() && From 488321256ece48d905fe8c73e00f52e8460832dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 21 Oct 2025 15:50:00 -0700 Subject: [PATCH 4/5] Sema: Consider non-LE structs to be of a restricted type by default In non-library-evolution mode, gated behind the CheckImplementationOnly feature flag, report references to structs marked with `@_implementationOnly` from a fragile context. Preventing references from inlinable functions and structs not marked `@_implementationOnly`. --- include/swift/AST/DiagnosticsSema.def | 15 +- lib/Sema/ResilienceDiagnostics.cpp | 1 + lib/Sema/TypeCheckAccess.cpp | 8 + lib/Sema/TypeCheckAccess.h | 3 +- test/Sema/hidden-memory-layout.swift | 206 ++++++++++++++++++++++++++ 5 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 test/Sema/hidden-memory-layout.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 11bc45f1b9d9c..ca0028d492e66 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3847,7 +3847,8 @@ ERROR(decl_from_hidden_module,none, "%2 was not imported by this file|" "C++ types from imported module %2 do not support library evolution|" "it was imported via the internal bridging header|" - "%2 was not imported publicly}3", + "%2 was not imported publicly|" + "it is a struct 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|" @@ -3865,7 +3866,8 @@ ERROR(typealias_desugars_to_type_from_hidden_module,none, "%4 was not imported by this file|" "C++ types from imported module %4 do not support library evolution|" "it was imported via the internal bridging header|" - "%4 was not imported publicly}5", + "%4 was not imported publicly|" + "it is a struct 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|" @@ -3881,7 +3883,8 @@ ERROR(conformance_from_implementation_only_module,none, "%3 was not imported by this file|" "C++ types from imported module %3 do not support library evolution|" "it was imported via the internal bridging header|" - "%3 was not imported publicly}4", + "%3 was not imported publicly|" + "it is a struct marked '@_implementationOnly'}4", (Type, Identifier, unsigned, Identifier, unsigned)) NOTE(assoc_conformance_from_implementation_only_module,none, "in associated type %0 (inferred as %1)", (Type, Type)) @@ -7349,7 +7352,8 @@ ERROR(inlinable_decl_ref_from_hidden_module, "%2 was not imported by this file|" "C++ APIs from imported module %2 do not support library evolution|" "it was imported via the internal bridging header|" - "%2 was not imported publicly}3", + "%2 was not imported publicly|" + "it is a struct marked '@_implementationOnly'}3", (const ValueDecl *, unsigned, Identifier, unsigned)) ERROR(inlinable_typealias_desugars_to_type_from_hidden_module, @@ -7361,7 +7365,8 @@ ERROR(inlinable_typealias_desugars_to_type_from_hidden_module, "%4 was not imported by this file|" "C++ types from imported module %4 do not support library evolution|" "it was imported via the internal bridging header|" - "%4 was not imported publicly}5", + "%4 was not imported publicly|" + "it is a struct marked '@_implementationOnly'}5", (const TypeAliasDecl *, StringRef, StringRef, unsigned, Identifier, unsigned)) NOTE(missing_import_inserted, diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index d4e1c45a35f36..40b4ec6bc7f2f 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -338,6 +338,7 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D, case DisallowedOriginKind::ImplementationOnly: case DisallowedOriginKind::FragileCxxAPI: + case DisallowedOriginKind::ImplementationOnlyMemoryLayout: break; } diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index aaf1ebb079ce4..2250437ae0221 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -2163,6 +2163,14 @@ swift::getDisallowedOriginKind(const Decl *decl, Feature::AssumeResilientCxxTypes)) return DisallowedOriginKind::FragileCxxAPI; + if (isa(decl) || isa(decl)) { + if (decl->getASTContext().LangOpts.hasFeature( + Feature::CheckImplementationOnly) && + decl->getAttrs().hasAttribute()) { + return DisallowedOriginKind::ImplementationOnlyMemoryLayout; + } + } + // Report non-public import last as it can be ignored by the caller. // See \c diagnoseValueDeclRefExportability. auto importSource = decl->getImportAccessFrom(where.getDeclContext()); diff --git a/lib/Sema/TypeCheckAccess.h b/lib/Sema/TypeCheckAccess.h index c97312b962370..a42805e693df6 100644 --- a/lib/Sema/TypeCheckAccess.h +++ b/lib/Sema/TypeCheckAccess.h @@ -32,7 +32,7 @@ class SourceFile; /// itself. Related checks may also be performed. void checkAccessControl(Decl *D); -/// Problematic origin of an exported type. +/// Problematic origin of a decl that may restrict its exportability. /// /// This enum must be kept in sync with a number of diagnostics: /// diag::inlinable_decl_ref_from_hidden_module @@ -52,6 +52,7 @@ enum class DisallowedOriginKind : uint8_t { InternalBridgingHeaderImport, NonPublicImport, + ImplementationOnlyMemoryLayout, None }; diff --git a/test/Sema/hidden-memory-layout.swift b/test/Sema/hidden-memory-layout.swift new file mode 100644 index 0000000000000..c273daac49da6 --- /dev/null +++ b/test/Sema/hidden-memory-layout.swift @@ -0,0 +1,206 @@ +/// Test @_implementationOnly import exportability diagnostics in non-library-evolution mode + +/// Standard / non-embedded + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -o %t/indirects.swiftmodule \ +// RUN: %S/Inputs/implementation-only-imports/indirects.swift \ +// RUN: -swift-version 5 +// RUN: %target-swift-frontend -emit-module -o %t/directs.swiftmodule -I %t\ +// RUN: %S/Inputs/implementation-only-imports/directs.swift \ +// RUN: -swift-version 5 + +/// Old diags +// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \ +// RUN: -swift-version 5 \ +// RUN: -verify-additional-prefix not-opt-in- + +/// New diags +// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \ +// RUN: -swift-version 5 \ +// RUN: -verify-additional-prefix opt-in- -DUseImplementationOnly \ +// RUN: -enable-experimental-feature CheckImplementationOnly + +/// Embedded +/// Will also show errors in non-@_neverEmitIntoClient functions. + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -o %t/indirects.swiftmodule \ +// RUN: %S/Inputs/implementation-only-imports/indirects.swift \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded +// RUN: %target-swift-frontend -emit-module -o %t/directs.swiftmodule -I %t\ +// RUN: %S/Inputs/implementation-only-imports/directs.swift \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded + +/// Old diags +// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded \ +// RUN: -verify-additional-prefix not-opt-in- + +/// New diags +// RUN: %target-swift-frontend -emit-module -verify -verify-ignore-unrelated %s -I %t \ +// RUN: -swift-version 5 -target arm64-apple-none-macho \ +// RUN: -enable-experimental-feature Embedded \ +// RUN: -verify-additional-prefix opt-in- -DUseImplementationOnly \ +// RUN: -verify-additional-prefix embedded-opt-in- \ +// RUN: -enable-experimental-feature CheckImplementationOnly + +// REQUIRES: swift_feature_Embedded +// REQUIRES: swift_feature_CheckImplementationOnly +// REQUIRES: embedded_stdlib_cross_compiling + +@_implementationOnly import directs +// expected-warning @-1 {{using '@_implementationOnly' without enabling library evolution for 'main' may lead to instability during execution}} +import indirects + +/// Referenced types + +public struct ExposedLayoutPublic { + public init() { fatalError() } +} + +private struct ExposedLayoutPrivate { +// expected-note @-1 2 {{struct 'ExposedLayoutPrivate' is not '@usableFromInline' or public}} +// expected-opt-in-note @-2 {{type declared here}} + init() { fatalError() } // expected-note {{initializer 'init()' is not '@usableFromInline' or public}} +} + +#if UseImplementationOnly +@_implementationOnly +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 2 {{struct declared here}} +// expected-opt-in-note @-4 {{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}} +} +#endif + +public enum ExposedEnumPublic { + case A + case B +} + +private enum ExposedEnumPrivate { +// expected-note @-1 2 {{enum 'ExposedEnumPrivate' is not '@usableFromInline' or public}} + case A +// expected-note @-1 1 {{enum case 'A' is not '@usableFromInline' or public}} + case B +} + +/// Function use sites + +@inlinable +public func explicitlyInlinable() { + let _: ExposedLayoutPublic = ExposedLayoutPublic() + let _: ExposedLayoutPrivate = ExposedLayoutPrivate() + // expected-error @-1 2 {{struct 'ExposedLayoutPrivate' is private and cannot be referenced from an '@inlinable' function}} + // expected-error @-2 {{initializer 'init()' is private and cannot be referenced from an '@inlinable' function}} + + let _: HiddenLayout = HiddenLayout() + // expected-error @-1 2 {{struct 'HiddenLayout' is private and cannot be referenced from an '@inlinable' function}} + // expected-error @-2 {{initializer 'init()' is private and cannot be referenced from an '@inlinable' function}} + + let _: ExposedEnumPublic = ExposedEnumPublic.A + let _: ExposedEnumPrivate = ExposedEnumPrivate.A + // expected-error @-1 2 {{enum 'ExposedEnumPrivate' is private and cannot be referenced from an '@inlinable' function}} + // expected-error @-2 {{enum case 'A' is private and cannot be referenced from an '@inlinable' function}} +} + +public func implicitlyInlinablePublic() { + let _: ExposedLayoutPublic = ExposedLayoutPublic() + let _: ExposedLayoutPrivate = ExposedLayoutPrivate() + let _: HiddenLayout = HiddenLayout() + // expected-embedded-opt-in-error @-1 2 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}} + + let _: ExposedEnumPublic = ExposedEnumPublic.A + let _: ExposedEnumPrivate = ExposedEnumPrivate.A +} + +private func implicitlyInlinablePrivate() { + let _: ExposedLayoutPublic = ExposedLayoutPublic() + let _: ExposedLayoutPrivate = ExposedLayoutPrivate() + let _: HiddenLayout = HiddenLayout() + // expected-embedded-opt-in-error @-1 2 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}} + + let _: ExposedEnumPublic = ExposedEnumPublic.A + let _: ExposedEnumPrivate = ExposedEnumPrivate.A +} + +@_neverEmitIntoClient +public func explicitNonInliable() { + let _: ExposedLayoutPublic = ExposedLayoutPublic() + let _: ExposedLayoutPrivate = ExposedLayoutPrivate() + let _: HiddenLayout = HiddenLayout() + let _: ExposedEnumPublic = ExposedEnumPublic.A + let _: ExposedEnumPrivate = ExposedEnumPrivate.A +} + +@_neverEmitIntoClient +internal func explicitNonInliableInternal() { + let _: ExposedLayoutPublic = ExposedLayoutPublic() + let _: ExposedLayoutPrivate = ExposedLayoutPrivate() + let _: HiddenLayout = HiddenLayout() + let _: ExposedEnumPublic = ExposedEnumPublic.A + let _: ExposedEnumPrivate = ExposedEnumPrivate.A +} + +/// Struct use sites + +public struct ExposedLayoutPublicUser { + + 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-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}} + + private var a: ExposedLayoutPublic + private var b: ExposedLayoutPrivate + // expected-opt-in-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; it is a struct marked '@_implementationOnly'}} + // expected-opt-in-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 '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}} +} + +private struct ExposedLayoutPrivateUser { + + 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}} + + private var a: ExposedLayoutPublic + 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; it is a struct marked '@_implementationOnly'}} + + private func privateFunc(h: HiddenLayout) {} + // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}} +} + +#if UseImplementationOnly +@_implementationOnly +private struct HiddenLayoutUser { + public var publicField: StructFromDirect + private var privateField: StructFromDirect + private var a: ExposedLayoutPublic + private var b: ExposedLayoutPrivate + private var c: HiddenLayout + + @_neverEmitIntoClient + private func privateFunc(h: HiddenLayout) {} +} + +@_implementationOnly // expected-opt-in-error {{'@_implementationOnly' may not be used on public declarations}} +public struct PublicHiddenStruct {} +#endif From bf443413b7bbc192ffebc4045a79fbeb5a98ff16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 4 Nov 2025 13:24:38 -0800 Subject: [PATCH 5/5] Sema: Fix superfluous error about private decls in internal memory layouts Don't consider implicitly exposed memory layouts when checking for usable from inline correctness. That check applies only to memory layouts marked as exposed explicitly. Consider the implict state only at the general availability checking. --- include/swift/AST/Decl.h | 5 ++++- lib/AST/Availability.cpp | 2 +- lib/AST/Decl.cpp | 10 ++++++---- test/Sema/hidden-memory-layout.swift | 26 ++++++++++++++++++++++---- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 9e21ad0bbd2ad..cd97f870a88db 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -6727,7 +6727,10 @@ 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. - bool isLayoutExposedToClients() const; + /// + /// If \p applyImplicit, consider implicitly exposed layouts as well. + /// This applies to non-resilient modules. + bool isLayoutExposedToClients(bool applyImplicit = false) const; /// Is this a special debugger variable? bool isDebuggerVar() const { return Bits.VarDecl.IsDebuggerVar; } diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 4ddb00084e4f4..2cd0b567ff270 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -1006,7 +1006,7 @@ bool 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()) + if (property->isLayoutExposedToClients(/*applyImplicit=*/true)) return true; return false; diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index d169df168455c..a58afc662aece 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2753,7 +2753,7 @@ bool VarDecl::isInitExposedToClients() const { return hasInitialValue() && isLayoutExposedToClients(); } -bool VarDecl::isLayoutExposedToClients() const { +bool VarDecl::isLayoutExposedToClients(bool applyImplicit) const { auto parent = dyn_cast(getDeclContext()); if (!parent) return false; if (isStatic()) return false; @@ -2763,9 +2763,11 @@ bool VarDecl::isLayoutExposedToClients() const { parent->getFormalAccessScope(/*useDC=*/nullptr, /*treatUsableFromInlineAsPublic=*/true); - // Resilient modules hide layouts by default. - if (!getASTContext().LangOpts.hasFeature(Feature::CheckImplementationOnly) || - M->getResilienceStrategy() == ResilienceStrategy::Resilient) { + // 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; diff --git a/test/Sema/hidden-memory-layout.swift b/test/Sema/hidden-memory-layout.swift index c273daac49da6..4d7b7c11c76d7 100644 --- a/test/Sema/hidden-memory-layout.swift +++ b/test/Sema/hidden-memory-layout.swift @@ -62,9 +62,11 @@ public struct ExposedLayoutPublic { public init() { fatalError() } } +internal struct ExposedLayoutInternal { +} + private struct ExposedLayoutPrivate { // expected-note @-1 2 {{struct 'ExposedLayoutPrivate' is not '@usableFromInline' or public}} -// expected-opt-in-note @-2 {{type declared here}} init() { fatalError() } // expected-note {{initializer 'init()' is not '@usableFromInline' or public}} } @@ -74,7 +76,7 @@ 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 2 {{struct declared here}} -// expected-opt-in-note @-4 {{type declared here}} +// expected-opt-in-note @-4 {{struct declared here}} } #else private struct HiddenLayout { @@ -163,12 +165,26 @@ public struct ExposedLayoutPublicUser { // 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}} private var a: ExposedLayoutPublic + private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate - // expected-opt-in-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; it is a struct marked '@_implementationOnly'}} - // expected-opt-in-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 '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}} +} + +private struct ExposedLayoutInternalUser { + + 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}} + + 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; it is a struct marked '@_implementationOnly'}} private func privateFunc(h: HiddenLayout) {} // expected-embedded-opt-in-error @-1 {{struct 'HiddenLayout' cannot be used in an embedded function not marked '@_neverEmitIntoClient' because it is a struct marked '@_implementationOnly'}} @@ -180,6 +196,7 @@ private struct ExposedLayoutPrivateUser { // 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}} 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; it is a struct marked '@_implementationOnly'}} @@ -194,6 +211,7 @@ private struct HiddenLayoutUser { public var publicField: StructFromDirect private var privateField: StructFromDirect private var a: ExposedLayoutPublic + private var aa: ExposedLayoutInternal private var b: ExposedLayoutPrivate private var c: HiddenLayout