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/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..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)) @@ -3946,6 +3949,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 +4134,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, @@ -7346,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, @@ -7358,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/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/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 9ad2c3c8f61c4..a58afc662aece 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -2753,20 +2753,33 @@ 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; - + 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 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; + + 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() && 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) { 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/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); diff --git a/test/Sema/hidden-memory-layout.swift b/test/Sema/hidden-memory-layout.swift new file mode 100644 index 0000000000000..4d7b7c11c76d7 --- /dev/null +++ b/test/Sema/hidden-memory-layout.swift @@ -0,0 +1,224 @@ +/// 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() } +} + +internal struct ExposedLayoutInternal { +} + +private struct ExposedLayoutPrivate { +// expected-note @-1 2 {{struct 'ExposedLayoutPrivate' is not '@usableFromInline' or public}} + 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 {{struct 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 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'}} +} + +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'}} +} + +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 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'}} +} + +#if UseImplementationOnly +@_implementationOnly +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 + + @_neverEmitIntoClient + private func privateFunc(h: HiddenLayout) {} +} + +@_implementationOnly // expected-opt-in-error {{'@_implementationOnly' may not be used on public declarations}} +public struct PublicHiddenStruct {} +#endif