Skip to content
Open
6 changes: 2 additions & 4 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ namespace swift {
class DiagnosticEngine;
class DynamicSelfType;
class Type;
enum class ExportedLevel;
class Expr;
struct ExternalSourceLocs;
class CaptureListExpr;
Expand Down Expand Up @@ -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; }
Expand Down
21 changes: 17 additions & 4 deletions include/swift/AST/DeclExportabilityVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -102,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.
Expand Down Expand Up @@ -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.
Expand Down
34 changes: 13 additions & 21 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3840,14 +3840,18 @@ 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|" \
"in a property declaration member of a type not marked '@_implementationOnly'}"

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|"
Expand All @@ -3859,14 +3863,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|"
"<<ERROR>>|"
"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|"
"<<ERROR>>|"
Expand All @@ -3878,12 +3875,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|"
"<<ERROR>>|<<ERROR>>|"
"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|"
Expand Down
50 changes: 24 additions & 26 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValueDecl>(D)) {
return isExported(VD);
}
Expand All @@ -982,32 +982,35 @@ bool swift::isExported(const Decl *D) {
return isExported(VD);
}

return false;
return ExportedLevel::None;
}
if (auto *ED = dyn_cast<ExtensionDecl>(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<ImplementationOnlyAttr>())
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<VarDecl>(VD))
if (property->isLayoutExposedToClients(/*applyImplicit=*/true))
return true;
return property->isLayoutExposedToClients();

// Case of an enum not marked @_implementationOnly in a non-resilient module?
if (auto *EED = dyn_cast<EnumElementDecl>(VD))
return isExported(EED->getParentEnum());

// Is this a type exposed by default in a non-resilient module?
if (isa<NominalTypeDecl>(VD) &&
Expand All @@ -1016,13 +1019,9 @@ bool swift::isExported(const ValueDecl *VD) {
VD->getDeclContext()->getParentModule()->getResilienceStrategy() !=
ResilienceStrategy::Resilient &&
!VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return true;

// Case of an enum not marked @_implementationOnly in a non-resilient module?
if (auto *EED = dyn_cast<EnumElementDecl>(VD))
return isExported(EED->getParentEnum());
return ExportedLevel::ImplicitlyExported;

return false;
return ExportedLevel::None;
}

bool swift::hasConformancesToPublicProtocols(const ExtensionDecl *ED) {
Expand All @@ -1046,23 +1045,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;
}
2 changes: 1 addition & 1 deletion lib/AST/AvailabilityScopeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 27 additions & 27 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
if (!parent) return ExportedLevel::None;
if (isStatic()) return ExportedLevel::None;

auto M = getDeclContext()->getParentModule();
// Does it contribute to the layout?
if (!hasStorage() &&
!getAttrs().hasAttribute<LazyAttr>() &&
!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<FrozenAttr>() ||
parent->getAttrs().hasAttribute<FixedLayoutAttr>()))
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<ImplementationOnlyAttr>())
return ExportedLevel::None;

if (!parent->getAttrs().hasAttribute<FrozenAttr>() &&
!parent->getAttrs().hasAttribute<FixedLayoutAttr>())
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<ImplementationOnlyAttr>())
return false;
// Resilient modules hide layouts by default.
return ExportedLevel::None;
}

if (!hasStorage() &&
!getAttrs().hasAttribute<LazyAttr>() &&
!hasAttachedPropertyWrapper()) {
return false;
}

return true;
}

/// Check whether the given type representation will be
Expand Down
10 changes: 5 additions & 5 deletions lib/Sema/ResilienceDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ static bool shouldDiagnoseDeclAccess(const ValueDecl *D,
case ExportabilityReason::ResultBuilder:
case ExportabilityReason::PropertyWrapper:
case ExportabilityReason::PublicVarDecl:
case ExportabilityReason::ImplicitlyPublicVarDecl:
return false;
}
}
Expand Down Expand Up @@ -294,6 +295,9 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D,
}
});

if (where.canReferenceOrigin(originKind))
return false;

auto fragileKind = where.getFragileFunctionKind();
switch (originKind) {
case DisallowedOriginKind::None:
Expand Down Expand Up @@ -326,14 +330,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:
Expand Down
16 changes: 12 additions & 4 deletions lib/Sema/TypeCheckAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<NominalTypeDecl>(VD->getDeclContext());

return nullptr;
Expand Down Expand Up @@ -2383,8 +2383,12 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
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,
Expand All @@ -2405,9 +2409,13 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
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) {
Expand Down Expand Up @@ -2603,7 +2611,7 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
auto *valueMember = dyn_cast<ValueDecl>(member);
if (!valueMember)
return false;
return isExported(valueMember);
return isExported(valueMember) == ExportedLevel::Exported;
});

Where = wasWhere.withExported(hasExportedMembers);
Expand Down
Loading