[Custom availability] Serialize custom domains described on the command line

When a custom domain is described on the command line, there is no
backing declaration for it. Serialize such custom domains by
identifier and look them up globally at the point of deserialization.
When that fails, warn and drop the annotation.

This is all a stopgap until we have a way to spell custom availability
domains in the Swift language itself.
This commit is contained in:
Doug Gregor
2025-12-02 11:05:52 -08:00
parent 6b3935201d
commit e624915ed9
4 changed files with 107 additions and 21 deletions

View File

@@ -893,7 +893,8 @@ ERROR(serialization_xref_to_hidden_dependency,none,
"invalid reference to implementation-only imported module %0"
"%select{| for %1}1",
(const ModuleDecl *, const Decl *))
WARNING(serialization_dropped_custom_availability,none,
"ignoring unresolved custom availability domain %0", (Identifier))
WARNING(can_import_invalid_swiftmodule,none,
"canImport() evaluated to false due to invalid swiftmodule: %0", (StringRef))

View File

@@ -5777,9 +5777,9 @@ decodeDomainKind(uint8_t kind) {
}
}
static std::optional<AvailabilityDomain>
decodeAvailabilityDomain(AvailabilityDomainKind domainKind,
PlatformKind platformKind, ValueDecl *decl) {
static AvailabilityDomain
decodeNonCustomAvailabilityDomain(AvailabilityDomainKind domainKind,
PlatformKind platformKind) {
switch (domainKind) {
case AvailabilityDomainKind::Universal:
return AvailabilityDomain::forUniversal();
@@ -5794,7 +5794,8 @@ decodeAvailabilityDomain(AvailabilityDomainKind domainKind,
case AvailabilityDomainKind::Platform:
return AvailabilityDomain::forPlatform(platformKind);
case AvailabilityDomainKind::Custom:
return AvailabilityDomain::forCustom(decl);
llvm_unreachable("custom domains aren't handled here");
return AvailabilityDomain::forUniversal();
}
}
@@ -5808,7 +5809,7 @@ DeclDeserializer::readAvailable_DECL_ATTR(SmallVectorImpl<uint64_t> &scratch,
bool isSPI;
uint8_t rawDomainKind;
unsigned rawPlatform;
DeclID domainDeclID;
DeclID customDomainID;
DEF_VER_TUPLE_PIECES(Introduced);
DEF_VER_TUPLE_PIECES(Deprecated);
DEF_VER_TUPLE_PIECES(Obsoleted);
@@ -5817,7 +5818,7 @@ DeclDeserializer::readAvailable_DECL_ATTR(SmallVectorImpl<uint64_t> &scratch,
// Decode the record, pulling the version tuple information.
serialization::decls_block::AvailableDeclAttrLayout::readRecord(
scratch, isImplicit, isUnavailable, isDeprecated, isNoAsync, isSPI,
rawDomainKind, rawPlatform, domainDeclID,
rawDomainKind, rawPlatform, customDomainID,
LIST_VER_TUPLE_PIECES(Introduced), LIST_VER_TUPLE_PIECES(Deprecated),
LIST_VER_TUPLE_PIECES(Obsoleted), messageSize, renameSize);
@@ -5849,24 +5850,49 @@ DeclDeserializer::readAvailable_DECL_ATTR(SmallVectorImpl<uint64_t> &scratch,
else
kind = AvailableAttr::Kind::Default;
ValueDecl *domainDecl = nullptr;
if (domainDeclID) {
Decl *decodedDomainDecl = nullptr;
SET_OR_RETURN_ERROR(decodedDomainDecl, MF.getDeclChecked(domainDeclID));
AvailabilityDomain domain;
if (domainKind == AvailabilityDomainKind::Custom) {
if (customDomainID & 0x01) {
IdentifierID customDomainNameID = customDomainID >> 1;
Identifier customDomainName = MF.getIdentifier(customDomainNameID);
SmallVector<AvailabilityDomain, 1> foundDomains;
if (ctx.MainModule) {
ctx.MainModule->lookupAvailabilityDomains(
customDomainName, foundDomains);
}
if (decodedDomainDecl) {
domainDecl = dyn_cast<ValueDecl>(decodedDomainDecl);
if (!domainDecl)
if (foundDomains.size() == 1) {
domain = foundDomains[0];
} else {
ctx.Diags.diagnose(
SourceLoc(), diag::serialization_dropped_custom_availability,
customDomainName);
domain = AvailabilityDomain::forUniversal();
}
} else {
DeclID domainDeclID = customDomainID >> 1;
Decl *decodedDomainDecl = nullptr;
SET_OR_RETURN_ERROR(decodedDomainDecl, MF.getDeclChecked(domainDeclID));
if (decodedDomainDecl) {
auto domainDecl = dyn_cast<ValueDecl>(decodedDomainDecl);
if (!domainDecl)
return llvm::make_error<InavalidAvailabilityDomainError>();
if (auto customDomain = AvailabilityDomain::forCustom(domainDecl))
domain = *customDomain;
else
return llvm::make_error<InavalidAvailabilityDomainError>();
} else {
return llvm::make_error<InavalidAvailabilityDomainError>();
}
}
} else {
domain = decodeNonCustomAvailabilityDomain(domainKind, platform);
}
auto domain = decodeAvailabilityDomain(domainKind, platform, domainDecl);
if (!domain)
return llvm::make_error<InavalidAvailabilityDomainError>();
auto attr = new (ctx)
AvailableAttr(SourceLoc(), SourceRange(), *domain, SourceLoc(), kind,
AvailableAttr(SourceLoc(), SourceRange(), domain, SourceLoc(), kind,
message, rename, Introduced, SourceRange(), Deprecated,
SourceRange(), Obsoleted, SourceRange(), isImplicit, isSPI);
return attr;

View File

@@ -3182,7 +3182,18 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
};
auto domainKind = getDomainKind(domain);
const Decl *domainDecl = domain.getDecl();
// Custom availablity domains provided via the command line don't have
// corresponding declarations. Serialize them as identifiers instead.
DeclID customDomainID = 0;
if (auto custom = domain.getCustomDomain()) {
if (auto customDecl = custom->getDecl()) {
customDomainID = S.addDeclRef(customDecl) << 1;
} else {
// emit the name,
customDomainID = (S.addDeclBaseNameRef(custom->getName()) << 1) | 0x1;
}
}
llvm::SmallString<32> blob;
blob.append(theAttr->getMessage());
@@ -3197,7 +3208,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
theAttr->isSPI(),
static_cast<uint8_t>(domainKind),
static_cast<unsigned>(domain.getPlatformKind()),
S.addDeclRef(domainDecl),
customDomainID,
LIST_VER_TUPLE_PIECES(Introduced),
LIST_VER_TUPLE_PIECES(Deprecated),
LIST_VER_TUPLE_PIECES(Obsoleted),

View File

@@ -0,0 +1,48 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -o %t -module-name Library %s -DLIBRARY \
// RUN: -enable-experimental-feature CustomAvailability \
// RUN: -define-enabled-availability-domain EnabledDomain \
// RUN: -define-always-enabled-availability-domain AlwaysEnabledDomain \
// RUN: -define-disabled-availability-domain DisabledDomain \
// RUN: -define-dynamic-availability-domain DynamicDomain
// RUN: %target-typecheck-verify-swift -I %t -enable-experimental-feature CustomAvailability \
// RUN: -define-enabled-availability-domain EnabledDomain \
// RUN: -define-always-enabled-availability-domain AlwaysEnabledDomain \
// RUN: -define-disabled-availability-domain DisabledDomain \
// RUN: -define-dynamic-availability-domain DynamicDomain
// RUN: not %target-swift-frontend -typecheck -I %t -enable-experimental-feature CustomAvailability %s > %t/missing.log 2>&1
// RUN: %FileCheck %s < %t/missing.log
// REQUIRES: swift_feature_CustomAvailability
#if LIBRARY
@available(EnabledDomain)
public func availableInEnabledDomain() { }
@available(AlwaysEnabledDomain)
public func availableInAlwaysEnabledDomain() { }
#else
import Library
func test1() { // expected-note{{add '@available' attribute to enclosing global function}}
availableInEnabledDomain() // expected-error{{'availableInEnabledDomain()' is only available in EnabledDomain}}
// expected-note@-1{{add 'if #available' version check}}
availableInAlwaysEnabledDomain()
}
@available(EnabledDomain)
func test2() {
availableInEnabledDomain()
availableInAlwaysEnabledDomain()
}
#endif
// CHECK: error: unrecognized platform name 'EnabledDomain'
// CHECK: warning: ignoring unresolved custom availability domain 'EnabledDomain'
// CHECK: warning: ignoring unresolved custom availability domain 'AlwaysEnabledDomain'