Sema: Intro ExportedLevel and use it for isExported

This commit is contained in:
Alexis Laferrière
2025-11-12 13:55:32 -08:00
parent 834bb63d5f
commit bf951b1591
6 changed files with 44 additions and 32 deletions

View File

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

View File

@@ -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);
}
@@ -982,32 +982,36 @@ 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 ExportedLevel::Exported;
// 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) &&
@@ -1016,13 +1020,9 @@ bool swift::isExported(const ValueDecl *VD) {
VD->getDeclContext()->getParentModule()->getResilienceStrategy() !=
ResilienceStrategy::Resilient &&
!VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return true;
return ExportedLevel::Exported;
// Case of an enum not marked @_implementationOnly in a non-resilient module?
if (auto *EED = dyn_cast<EnumElementDecl>(VD))
return isExported(EED->getParentEnum());
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;
}

View File

@@ -479,7 +479,7 @@ private:
if (decl->isSPI())
return true;
return !isExported(decl);
return isExported(decl) == ExportedLevel::None;
}
/// Returns the source range which should be refined by declaration. This

View File

@@ -2603,7 +2603,7 @@ public:
auto *valueMember = dyn_cast<ValueDecl>(member);
if (!valueMember)
return false;
return isExported(valueMember);
return isExported(valueMember) == ExportedLevel::Exported;
});
Where = wasWhere.withExported(hasExportedMembers);

View File

@@ -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);

View File

@@ -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