Sema: Use ExportedLevel for isLayoutExposedToClients

This commit is contained in:
Alexis Laferrière
2025-11-14 16:13:27 -08:00
parent bf951b1591
commit 60731d957c
6 changed files with 37 additions and 39 deletions

View File

@@ -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 @@ public:
///
/// 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; }

View File

@@ -115,7 +115,7 @@ public:
}
bool visitVarDecl(const VarDecl *var) {
if (var->isLayoutExposedToClients())
if (var->isLayoutExposedToClients() == ExportedLevel::Exported)
return true;
// Consider all lazy var storage as exportable.

View File

@@ -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<VarDecl>(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<EnumElementDecl>(VD))
@@ -1020,7 +1019,7 @@ ExportedLevel swift::isExported(const ValueDecl *VD) {
VD->getDeclContext()->getParentModule()->getResilienceStrategy() !=
ResilienceStrategy::Resilient &&
!VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return ExportedLevel::Exported;
return ExportedLevel::ImplicitlyExported;
return ExportedLevel::None;
}

View File

@@ -2774,44 +2774,44 @@ bool VarDecl::isInitExposedToClients() const {
if (getAttrs().hasAttribute<LazyAttr>())
return false;
return hasInitialValue() && isLayoutExposedToClients();
return hasInitialValue() &&
isLayoutExposedToClients() == ExportedLevel::Exported;
}
bool VarDecl::isLayoutExposedToClients(bool applyImplicit) const {
ExportedLevel VarDecl::isLayoutExposedToClients() const {
auto parent = dyn_cast<NominalTypeDecl>(getDeclContext());
if (!parent) return false;
if (isStatic()) return false;
auto M = getDeclContext()->getParentModule();
auto nominalAccess =
parent->getFormalAccessScope(/*useDC=*/nullptr,
/*treatUsableFromInlineAsPublic=*/true);
// 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<FrozenAttr>() &&
!parent->getAttrs().hasAttribute<FixedLayoutAttr>())
return false;
} else {
// Non-resilient module: layouts are exposed by default unless marked
// otherwise.
if (parent->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return false;
}
if (!parent) return ExportedLevel::None;
if (isStatic()) return ExportedLevel::None;
// Does it contribute to the layout?
if (!hasStorage() &&
!getAttrs().hasAttribute<LazyAttr>() &&
!hasAttachedPropertyWrapper()) {
return false;
return ExportedLevel::None;
}
return true;
// Is it a member of a frozen type?
auto nominalAccess =
parent->getFormalAccessScope(/*useDC=*/nullptr,
/*treatUsableFromInlineAsPublic=*/true);
if (nominalAccess.isPublic() &&
(parent->getAttrs().hasAttribute<FrozenAttr>() ||
parent->getAttrs().hasAttribute<FixedLayoutAttr>()))
return ExportedLevel::Exported;
// Is it a member of an `@_implementationOnly` type?
if (parent->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return ExportedLevel::None;
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 {
// Resilient modules hide layouts by default.
return ExportedLevel::None;
}
}
/// Check whether the given type representation will be

View File

@@ -1498,7 +1498,7 @@ public:
/// 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<NominalTypeDecl>(VD->getDeclContext());
return nullptr;

View File

@@ -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<VarDecl>(VD)) {
if (auto NTD = dyn_cast<NominalTypeDecl>(D->getDeclContext())) {
if (property->isLayoutExposedToClients() && !NTD->isSPI()) {
if (property->isLayoutExposedToClients() == ExportedLevel::Exported && !NTD->isSPI()) {
diagnoseAndRemoveAttr(attr,
diag::spi_attribute_on_frozen_stored_properties,
VD);