AST: Adopt new AvailableAttr constructor in attribute inference.

It was difficult to preserve the existing, buggy behavior of availability
attribute inference with respect to attributes specifying availability for
non-platform-specific domains. Instead, this change improves attribute merging
by tracking every domain independently, and only merging attributes from the
same domain.
This commit is contained in:
Allan Shortlidge
2025-01-09 21:51:39 -08:00
parent 8cd9319a3e
commit 11abffb2f4
5 changed files with 98 additions and 53 deletions

View File

@@ -836,6 +836,23 @@ public:
Bits.AvailableAttr.PlatformAgnostic);
}
/// Returns the kind of availability the attribute specifies.
Kind getKind() const {
switch (getPlatformAgnosticAvailability()) {
case PlatformAgnosticAvailabilityKind::None:
case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
return Kind::Default;
case PlatformAgnosticAvailabilityKind::Deprecated:
return Kind::Deprecated;
case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
case PlatformAgnosticAvailabilityKind::Unavailable:
return Kind::Unavailable;
case PlatformAgnosticAvailabilityKind::NoAsync:
return Kind::NoAsync;
}
}
/// Create an `AvailableAttr` that specifies universal unavailability, e.g.
/// `@available(*, unavailable)`.
static AvailableAttr *createUniversallyUnavailable(ASTContext &C,

View File

@@ -33,14 +33,14 @@ public:
/// universally unavailable or deprecated, for example.
Universal,
/// Represents availability for a specific operating system platform.
Platform,
/// Represents availability with respect to Swift language mode.
SwiftLanguage,
/// Represents PackageDescription availability.
PackageDescription,
/// Represents availability for a specific operating system platform.
Platform,
};
private:
@@ -98,6 +98,40 @@ public:
/// Returns the string to use when printing an `@available` attribute.
llvm::StringRef getNameForAttributePrinting() const;
bool operator==(const AvailabilityDomain &other) const {
if (getKind() != other.getKind())
return false;
switch (getKind()) {
case Kind::Universal:
case Kind::SwiftLanguage:
case Kind::PackageDescription:
// These availability domains are singletons.
return true;
case Kind::Platform:
return getPlatformKind() == other.getPlatformKind();
}
}
bool operator!=(const AvailabilityDomain &other) const {
return !(*this == other);
}
bool operator<(const AvailabilityDomain &other) const {
if (getKind() != other.getKind())
return getKind() < other.getKind();
switch (getKind()) {
case Kind::Universal:
case Kind::SwiftLanguage:
case Kind::PackageDescription:
// These availability domains are singletons.
return false;
case Kind::Platform:
return getPlatformKind() < other.getPlatformKind();
}
}
};
} // end namespace swift

View File

@@ -109,12 +109,14 @@ namespace {
/// The inferred availability required to access a group of declarations
/// on a single platform.
struct InferredAvailability {
PlatformAgnosticAvailabilityKind PlatformAgnostic
= PlatformAgnosticAvailabilityKind::None;
AvailableAttr::Kind Kind = AvailableAttr::Kind::Default;
std::optional<llvm::VersionTuple> Introduced;
std::optional<llvm::VersionTuple> Deprecated;
std::optional<llvm::VersionTuple> Obsoleted;
StringRef Message;
StringRef Rename;
bool IsSPI = false;
};
@@ -148,10 +150,9 @@ mergeIntoInferredVersion(const std::optional<llvm::VersionTuple> &Version,
static void mergeWithInferredAvailability(SemanticAvailableAttr Attr,
InferredAvailability &Inferred) {
auto *ParsedAttr = Attr.getParsedAttr();
Inferred.PlatformAgnostic = static_cast<PlatformAgnosticAvailabilityKind>(
std::max(static_cast<unsigned>(Inferred.PlatformAgnostic),
static_cast<unsigned>(
ParsedAttr->getPlatformAgnosticAvailability())));
Inferred.Kind = static_cast<AvailableAttr::Kind>(
std::max(static_cast<unsigned>(Inferred.Kind),
static_cast<unsigned>(ParsedAttr->getKind())));
// The merge of two introduction versions is the maximum of the two versions.
if (mergeIntoInferredVersion(Attr.getIntroduced(), Inferred.Introduced,
@@ -162,21 +163,24 @@ static void mergeWithInferredAvailability(SemanticAvailableAttr Attr,
// The merge of deprecated and obsoleted versions takes the minimum.
mergeIntoInferredVersion(Attr.getDeprecated(), Inferred.Deprecated, std::min);
mergeIntoInferredVersion(Attr.getObsoleted(), Inferred.Obsoleted, std::min);
if (Inferred.Message.empty() && !Attr.getMessage().empty())
Inferred.Message = Attr.getMessage();
if (Inferred.Rename.empty() && !Attr.getRename().empty())
Inferred.Rename = Attr.getRename();
}
/// Create an implicit availability attribute for the given platform
/// Create an implicit availability attribute for the given domain
/// and with the inferred availability.
static AvailableAttr *createAvailableAttr(PlatformKind Platform,
static AvailableAttr *createAvailableAttr(AvailabilityDomain Domain,
const InferredAvailability &Inferred,
StringRef Message,
StringRef Rename,
ValueDecl *RenameDecl,
ASTContext &Context) {
// If there is no information that would go into the availability attribute,
// don't create one.
if (Inferred.PlatformAgnostic == PlatformAgnosticAvailabilityKind::None &&
!Inferred.Introduced && !Inferred.Deprecated && !Inferred.Obsoleted &&
Message.empty() && Rename.empty() && !RenameDecl)
if (Inferred.Kind == AvailableAttr::Kind::Default && !Inferred.Introduced &&
!Inferred.Deprecated && !Inferred.Obsoleted && Inferred.Message.empty() &&
Inferred.Rename.empty())
return nullptr;
llvm::VersionTuple Introduced =
@@ -186,36 +190,26 @@ static AvailableAttr *createAvailableAttr(PlatformKind Platform,
llvm::VersionTuple Obsoleted =
Inferred.Obsoleted.value_or(llvm::VersionTuple());
return new (Context)
AvailableAttr(SourceLoc(), SourceRange(), Platform, Message, Rename,
Introduced, SourceRange(), Deprecated, SourceRange(),
Obsoleted, SourceRange(), Inferred.PlatformAgnostic,
/*Implicit=*/true, Inferred.IsSPI);
return new (Context) AvailableAttr(
SourceLoc(), SourceRange(), Domain, Inferred.Kind, Inferred.Message,
Inferred.Rename, Introduced, SourceRange(), Deprecated, SourceRange(),
Obsoleted, SourceRange(), /*Implicit=*/true, Inferred.IsSPI);
}
void AvailabilityInference::applyInferredAvailableAttrs(
Decl *ToDecl, ArrayRef<const Decl *> InferredFromDecls) {
auto &Context = ToDecl->getASTContext();
// Let the new AvailabilityAttr inherit the message and rename.
// The first encountered message / rename will win; this matches the
// behaviour of diagnostics for 'non-inherited' AvailabilityAttrs.
StringRef Message;
StringRef Rename;
ValueDecl *RenameDecl = nullptr;
// Iterate over the declarations and infer required availability on
// a per-platform basis.
// FIXME: [availability] Generalize to AvailabilityDomain.
std::map<PlatformKind, InferredAvailability> Inferred;
std::map<AvailabilityDomain, InferredAvailability> Inferred;
for (const Decl *D : InferredFromDecls) {
llvm::SmallVector<SemanticAvailableAttr, 8> MergedAttrs;
do {
llvm::SmallVector<SemanticAvailableAttr, 8> PendingAttrs;
for (auto AvAttr :
D->getSemanticAvailableAttrs()) {
for (auto AvAttr : D->getSemanticAvailableAttrs()) {
// Skip an attribute from an outer declaration if it is for a platform
// that was already handled implicitly by an attribute from an inner
// declaration.
@@ -226,14 +220,8 @@ void AvailabilityInference::applyInferredAvailableAttrs(
}))
continue;
mergeWithInferredAvailability(AvAttr, Inferred[AvAttr.getPlatform()]);
mergeWithInferredAvailability(AvAttr, Inferred[AvAttr.getDomain()]);
PendingAttrs.push_back(AvAttr);
if (Message.empty() && !AvAttr.getMessage().empty())
Message = AvAttr.getMessage();
if (Rename.empty() && !AvAttr.getRename().empty())
Rename = AvAttr.getRename();
}
MergedAttrs.append(PendingAttrs);
@@ -245,20 +233,12 @@ void AvailabilityInference::applyInferredAvailableAttrs(
}
DeclAttributes &Attrs = ToDecl->getAttrs();
auto *ToValueDecl = dyn_cast<ValueDecl>(ToDecl);
// Create an availability attribute for each observed platform and add
// to ToDecl.
for (auto &Pair : Inferred) {
auto *Attr = createAvailableAttr(Pair.first, Pair.second, Message,
Rename, RenameDecl, Context);
if (Attr) {
if (RenameDecl && ToValueDecl)
ToValueDecl->setRenamedDecl(Attr, RenameDecl);
if (auto Attr = createAvailableAttr(Pair.first, Pair.second, Context))
Attrs.add(Attr);
}
}
}

View File

@@ -31,12 +31,12 @@ llvm::StringRef AvailabilityDomain::getNameForDiagnostics() const {
switch (kind) {
case Kind::Universal:
return "";
case Kind::Platform:
return swift::prettyPlatformString(getPlatformKind());
case Kind::SwiftLanguage:
return "Swift";
case Kind::PackageDescription:
return "PackageDescription";
case Kind::Platform:
return swift::prettyPlatformString(getPlatformKind());
}
}
@@ -44,11 +44,11 @@ llvm::StringRef AvailabilityDomain::getNameForAttributePrinting() const {
switch (kind) {
case Kind::Universal:
return "*";
case Kind::Platform:
return swift::platformString(getPlatformKind());
case Kind::SwiftLanguage:
return "swift";
case Kind::PackageDescription:
return "_PackageDescription";
case Kind::Platform:
return swift::platformString(getPlatformKind());
}
}

View File

@@ -39,6 +39,20 @@ public actor UnavailableActor {
// CHECK-NEXT: }
}
// CHECK: @_hasMissingDesignatedInitializers @available(*, deprecated, message: "Will be unavailable Swift 6")
// CHECK-NEXT: @available(swift, obsoleted: 6)
// CHECK-NEXT: public actor DeprecatedAndObsoleteInSwift6Actor {
@available(*, deprecated, message: "Will be unavailable Swift 6")
@available(swift, obsoleted: 6)
public actor DeprecatedAndObsoleteInSwift6Actor {
// CHECK: @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
// CHECK-NEXT: @available(*, deprecated, message: "Will be unavailable Swift 6")
// CHECK-NEXT: @available(swift, obsoleted: 6)
// CHECK-NEXT: @_semantics("defaultActor") nonisolated final public var unownedExecutor: _Concurrency.UnownedSerialExecutor {
// CHECK-NEXT: get
// CHECK-NEXT: }
}
// CHECK: @available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *)
// CHECK-NEXT: public enum Enum {
@available(SwiftStdlib 5.2, *)