From 3c8a57f86d848adf9633763910660c6e38947727 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Sun, 9 Feb 2025 13:43:16 -0800 Subject: [PATCH 1/5] AST: Use consolidated availability constraint query for diagnostics. Switch to calling `swift::getAvailabilityConstraintsForDecl()` to get the unsatisfied availability constraints that should be diagnosed. This was intended to be NFC, but it turns out it fixed a bug in the recently introduced objc_implementation_direct_to_storage.swift test. In the test, the stored properties are as unavailable as the context that is accessing them so the accesses should not be diagnosed. However, this test demonstrates a bigger issue with `@objc @implementation`, which is that it allows the implementations of Obj-C interfaces to be less available than the interface, which effectively provides an availability checking loophole that can be used to invoke unavailable code. --- include/swift/AST/AvailabilityConstraint.h | 26 ++-- lib/AST/Availability.cpp | 48 +------- lib/AST/AvailabilityConstraint.cpp | 115 +++++++++++++++--- lib/Sema/TypeCheckAvailability.cpp | 44 +------ test/Sema/availability_scopes.swift | 2 +- test/attr/attr_availability_swiftpm_v4.swift | 16 +++ .../attr_availability_transitive_osx.swift | 24 ++-- ...bjc_implementation_direct_to_storage.swift | 15 ++- 8 files changed, 160 insertions(+), 130 deletions(-) diff --git a/include/swift/AST/AvailabilityConstraint.h b/include/swift/AST/AvailabilityConstraint.h index 6f6a8efbddd9f..03dcd148855e1 100644 --- a/include/swift/AST/AvailabilityConstraint.h +++ b/include/swift/AST/AvailabilityConstraint.h @@ -33,6 +33,10 @@ class Decl; /// certain context. class AvailabilityConstraint { public: + /// The reason that the availability constraint is unsatisfied. + /// + /// NOTE: The order of this enum matters. Reasons are defined in descending + /// priority order. enum class Reason { /// The declaration is referenced in a context in which it is generally /// unavailable. For example, a reference to a declaration that is @@ -133,38 +137,34 @@ class AvailabilityConstraint { /// Returns the required range for `IntroducedInNewerVersion` requirements, or /// `std::nullopt` otherwise. std::optional - getRequiredNewerAvailabilityRange(ASTContext &ctx) const; + getRequiredNewerAvailabilityRange(const ASTContext &ctx) const; /// Some availability constraints are active for type-checking but cannot /// be translated directly into an `if #available(...)` runtime query. - bool isActiveForRuntimeQueries(ASTContext &ctx) const; + bool isActiveForRuntimeQueries(const ASTContext &ctx) const; }; /// Represents a set of availability constraints that restrict use of a -/// declaration in a particular context. +/// declaration in a particular context. There can only be one active constraint +/// for a given `AvailabilityDomain`, but there may be multiple active +/// constraints from separate domains. class DeclAvailabilityConstraints { using Storage = llvm::SmallVector; Storage constraints; public: DeclAvailabilityConstraints() {} + DeclAvailabilityConstraints(const Storage &&constraints) + : constraints(constraints) {} - void addConstraint(const AvailabilityConstraint &constraint) { - constraints.emplace_back(constraint); - } + /// Returns the strongest availability constraint or `std::nullopt` if empty. + std::optional getPrimaryConstraint() const; using const_iterator = Storage::const_iterator; const_iterator begin() const { return constraints.begin(); } const_iterator end() const { return constraints.end(); } }; -/// Returns the `AvailabilityConstraint` that describes how \p attr restricts -/// use of \p decl in \p context or `std::nullopt` if there is no restriction. -std::optional -getAvailabilityConstraintForAttr(const Decl *decl, - const SemanticAvailableAttr &attr, - const AvailabilityContext &context); - /// Returns the set of availability constraints that restrict use of \p decl /// when it is referenced from the given context. In other words, it is the /// collection of of `@available` attributes with unsatisfied conditions. diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 208ee716b8430..4cb496d164daf 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -573,49 +573,13 @@ bool Decl::isUnavailableInCurrentSwiftVersion() const { return false; } -std::optional getDeclUnavailableAttr(const Decl *D) { - auto &ctx = D->getASTContext(); - std::optional result; - auto bestActive = D->getActiveAvailableAttrForCurrentPlatform(); - - for (auto attr : D->getSemanticAvailableAttrs(/*includingInactive=*/false)) { - // If this is a platform-specific attribute and it isn't the most - // specific attribute for the current platform, we're done. - if (attr.isPlatformSpecific() && (!bestActive || attr != bestActive)) - continue; - - // Unconditional unavailable. - if (attr.isUnconditionallyUnavailable()) - return attr; - - switch (attr.getVersionAvailability(ctx)) { - case AvailableVersionComparison::Available: - case AvailableVersionComparison::PotentiallyUnavailable: - break; - - case AvailableVersionComparison::Obsoleted: - case AvailableVersionComparison::Unavailable: - result.emplace(attr); - break; - } - } - return result; -} - std::optional Decl::getUnavailableAttr() const { - if (auto attr = getDeclUnavailableAttr(this)) - return attr; - - // If D is an extension member, check if the extension is unavailable. - // - // Skip decls imported from Clang, they could be associated to the wrong - // extension and inherit undesired unavailability. The ClangImporter - // associates Objective-C protocol members to the first category where the - // protocol is directly or indirectly adopted, no matter its availability - // and the availability of other categories. rdar://problem/53956555 - if (!getClangNode()) - if (auto ext = dyn_cast(getDeclContext())) - return ext->getUnavailableAttr(); + auto context = AvailabilityContext::forDeploymentTarget(getASTContext()); + if (auto constraint = getAvailabilityConstraintsForDecl(this, context) + .getPrimaryConstraint()) { + if (constraint->isUnavailable()) + return constraint->getAttr(); + } return std::nullopt; } diff --git a/lib/AST/AvailabilityConstraint.cpp b/lib/AST/AvailabilityConstraint.cpp index 678ea455df63e..0223365248b0a 100644 --- a/lib/AST/AvailabilityConstraint.cpp +++ b/lib/AST/AvailabilityConstraint.cpp @@ -24,7 +24,7 @@ PlatformKind AvailabilityConstraint::getPlatform() const { std::optional AvailabilityConstraint::getRequiredNewerAvailabilityRange( - ASTContext &ctx) const { + const ASTContext &ctx) const { switch (getReason()) { case Reason::UnconditionallyUnavailable: case Reason::Obsoleted: @@ -35,7 +35,8 @@ AvailabilityConstraint::getRequiredNewerAvailabilityRange( } } -bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const { +bool AvailabilityConstraint::isActiveForRuntimeQueries( + const ASTContext &ctx) const { if (getAttr().getPlatform() == PlatformKind::none) return true; @@ -44,6 +45,78 @@ bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const { /*forRuntimeQuery=*/true); } +static bool constraintIsStronger(const AvailabilityConstraint &lhs, + const AvailabilityConstraint &rhs) { + DEBUG_ASSERT(lhs.getDomain() == rhs.getDomain()); + + // If the constraints have matching domains but different reasons, the + // constraint with the lowest reason is "strongest". + if (lhs.getReason() != rhs.getReason()) + return lhs.getReason() < rhs.getReason(); + + switch (lhs.getReason()) { + case AvailabilityConstraint::Reason::UnconditionallyUnavailable: + // Just keep the first. + return false; + + case AvailabilityConstraint::Reason::Obsoleted: + // Pick the earliest obsoleted version. + return *lhs.getAttr().getObsoleted() < *rhs.getAttr().getObsoleted(); + + case AvailabilityConstraint::Reason::IntroducedInLaterVersion: + case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion: + // Pick the latest introduced version. + return *lhs.getAttr().getIntroduced() > *rhs.getAttr().getIntroduced(); + } +} + +void addConstraint(llvm::SmallVector &constraints, + const AvailabilityConstraint &constraint, + const ASTContext &ctx) { + + auto iter = llvm::find_if( + constraints, [&constraint](AvailabilityConstraint &existing) { + return constraint.getDomain() == existing.getDomain(); + }); + + // There's no existing constraint for the same domain so just add it. + if (iter == constraints.end()) { + constraints.emplace_back(constraint); + return; + } + + if (constraintIsStronger(constraint, *iter)) { + constraints.erase(iter); + constraints.emplace_back(constraint); + } +} + +std::optional +DeclAvailabilityConstraints::getPrimaryConstraint() const { + std::optional result; + + auto isStrongerConstraint = [](const AvailabilityConstraint &lhs, + const AvailabilityConstraint &rhs) { + // Constraint reasons are defined in descending order of strength. + if (lhs.getReason() != rhs.getReason()) + return lhs.getReason() < rhs.getReason(); + + // Pick the constraint from the broader domain. + if (lhs.getDomain() != rhs.getDomain()) + return rhs.getDomain().contains(lhs.getDomain()); + + return false; + }; + + // Pick the strongest constraint. + for (auto const &constraint : constraints) { + if (!result || isStrongerConstraint(constraint, *result)) + result.emplace(constraint); + } + + return result; +} + static bool isInsideCompatibleUnavailableDeclaration(const Decl *decl, const SemanticAvailableAttr &attr, @@ -65,13 +138,12 @@ isInsideCompatibleUnavailableDeclaration(const Decl *decl, return context.containsUnavailableDomain(domain); } -std::optional -swift::getAvailabilityConstraintForAttr(const Decl *decl, - const SemanticAvailableAttr &attr, - const AvailabilityContext &context) { - if (isInsideCompatibleUnavailableDeclaration(decl, attr, context)) - return std::nullopt; - +/// Returns the `AvailabilityConstraint` that describes how \p attr restricts +/// use of \p decl in \p context or `std::nullopt` if there is no restriction. +static std::optional +getAvailabilityConstraintForAttr(const Decl *decl, + const SemanticAvailableAttr &attr, + const AvailabilityContext &context) { if (attr.isUnconditionallyUnavailable()) return AvailabilityConstraint::unconditionallyUnavailable(attr); @@ -128,10 +200,10 @@ activePlatformDomainForDecl(const Decl *decl) { return activeDomain; } -static void -getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints, - const Decl *decl, - const AvailabilityContext &context) { +static void getAvailabilityConstraintsForDecl( + llvm::SmallVector &constraints, const Decl *decl, + const AvailabilityContext &context) { + auto &ctx = decl->getASTContext(); auto activePlatformDomain = activePlatformDomainForDecl(decl); for (auto attr : @@ -141,20 +213,27 @@ getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints, !activePlatformDomain->contains(domain)) continue; - if (auto constraint = - swift::getAvailabilityConstraintForAttr(decl, attr, context)) - constraints.addConstraint(*constraint); + if (auto constraint = getAvailabilityConstraintForAttr(decl, attr, context)) + addConstraint(constraints, *constraint, ctx); } + + // After resolving constraints, remove any constraints that indicate the + // declaration is unconditionally unavailable in a domain for which + // the context is already unavailable. + llvm::erase_if(constraints, [&](const AvailabilityConstraint &constraint) { + return isInsideCompatibleUnavailableDeclaration(decl, constraint.getAttr(), + context); + }); } DeclAvailabilityConstraints swift::getAvailabilityConstraintsForDecl(const Decl *decl, const AvailabilityContext &context) { - DeclAvailabilityConstraints constraints; + llvm::SmallVector constraints; // Generic parameters are always available. if (isa(decl)) - return constraints; + return DeclAvailabilityConstraints(); decl = abstractSyntaxDeclForAvailableAttribute(decl); diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 0c51c7245e2f5..90debf924bd68 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -3000,11 +3000,10 @@ bool shouldHideDomainNameForConstraintDiagnostic( case AvailabilityDomain::Kind::Universal: case AvailabilityDomain::Kind::Embedded: case AvailabilityDomain::Kind::Custom: + case AvailabilityDomain::Kind::PackageDescription: return true; case AvailabilityDomain::Kind::Platform: return false; - - case AvailabilityDomain::Kind::PackageDescription: case AvailabilityDomain::Kind::SwiftLanguage: switch (constraint.getReason()) { case AvailabilityConstraint::Reason::UnconditionallyUnavailable: @@ -3081,44 +3080,9 @@ bool diagnoseExplicitUnavailability(SourceLoc loc, std::optional swift::getUnsatisfiedAvailabilityConstraint( const Decl *decl, AvailabilityContext availabilityContext) { - auto &ctx = decl->getASTContext(); - - // Generic parameters are always available. - if (isa(decl)) - return std::nullopt; - - if (auto attr = decl->getUnavailableAttr()) { - if (isInsideCompatibleUnavailableDeclaration(decl, availabilityContext, - *attr)) - return std::nullopt; - - switch (attr->getVersionAvailability(ctx)) { - case AvailableVersionComparison::Available: - case AvailableVersionComparison::PotentiallyUnavailable: - llvm_unreachable("Decl should be unavailable"); - - case AvailableVersionComparison::Unavailable: - if ((attr->isSwiftLanguageModeSpecific() || - attr->isPackageDescriptionVersionSpecific()) && - attr->getIntroduced().has_value()) - return AvailabilityConstraint::introducedInLaterVersion(*attr); - - return AvailabilityConstraint::unconditionallyUnavailable(*attr); - - case AvailableVersionComparison::Obsoleted: - return AvailabilityConstraint::obsoleted(*attr); - } - } - - // Check whether the declaration is available in a newer platform version. - if (auto rangeAttr = decl->getAvailableAttrForPlatformIntroduction()) { - auto range = rangeAttr->getIntroducedRange(ctx); - if (!availabilityContext.getPlatformRange().isContainedIn(range)) - return AvailabilityConstraint::introducedInLaterDynamicVersion( - *rangeAttr); - } - - return std::nullopt; + auto constraints = + swift::getAvailabilityConstraintsForDecl(decl, availabilityContext); + return constraints.getPrimaryConstraint(); } std::optional diff --git a/test/Sema/availability_scopes.swift b/test/Sema/availability_scopes.swift index 69d1d672fb754..b1bb6f5715586 100644 --- a/test/Sema/availability_scopes.swift +++ b/test/Sema/availability_scopes.swift @@ -358,7 +358,7 @@ extension SomeEnum { // CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum // CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeEnum // CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=availableMacOS_52 -// CHECK-NEXT: {{^}} (decl version=52 unavailable=macOS decl=availableMacOS_52 +// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=availableMacOS_52 // CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=neverAvailable() @available(macOS, unavailable) diff --git a/test/attr/attr_availability_swiftpm_v4.swift b/test/attr/attr_availability_swiftpm_v4.swift index b8eb9b4cd08ea..5af875097b9c7 100644 --- a/test/attr/attr_availability_swiftpm_v4.swift +++ b/test/attr/attr_availability_swiftpm_v4.swift @@ -37,6 +37,9 @@ func fourPointOh() {} @available(_PackageDescription 4) class ShortFour {} +@available(_PackageDescription 99) +func ninetyNine() {} // expected-note {{'ninetyNine()' was introduced in PackageDescription 99}} + shortThree() threePointOh() threePointOhOnly() // expected-error {{is unavailable}} @@ -51,6 +54,7 @@ shortFourPointOh() four() fourPointOh() let aa : ShortFour +ninetyNine() // expected-error {{'ninetyNine()' is unavailable}} @available(_PackageDescription, introduced: 4.0) @available(*, deprecated, message: "test deprecated") @@ -63,3 +67,15 @@ func shouldBeAlone() {} @available(_PackageDescription 4.0, swift 2.0, *) // expected-error {{'_PackageDescription' version-availability must be specified alone}} // expected-error {{'swift' version-availability must be specified alone}} func shouldBeAlone2() {} + +@available(*, unavailable, renamed: "shortFour") +@available(_PackageDescription 3) +func unconditionallyRenamed() {} // expected-note {{'unconditionallyRenamed()' has been explicitly marked unavailable here}} + +unconditionallyRenamed() // expected-error {{'unconditionallyRenamed()' has been renamed to 'shortFour'}} + +@available(*, unavailable, renamed: "shortFour") +@available(_PackageDescription 5) +func unconditionallyRenamedAndIntroducedLater() {} // expected-note {{'unconditionallyRenamedAndIntroducedLater()' has been explicitly marked unavailable here}} + +unconditionallyRenamedAndIntroducedLater() // expected-error {{'unconditionallyRenamedAndIntroducedLater()' has been renamed to 'shortFour'}} diff --git a/test/attr/attr_availability_transitive_osx.swift b/test/attr/attr_availability_transitive_osx.swift index 91610e60cf94a..b0a17ee2f3b93 100644 --- a/test/attr/attr_availability_transitive_osx.swift +++ b/test/attr/attr_availability_transitive_osx.swift @@ -533,6 +533,9 @@ func available_func_call_extension_methods(_ e: ExtendMe) { // expected-note {{a // expected-note@-1 {{add 'if #available' version check}} } +@available(OSX, obsoleted: 10.9) +struct OSXObsoleted {} // expected-note 2 {{'OSXObsoleted' was obsoleted in macOS 10.9}} + @available(OSX, unavailable) @available(OSX, introduced: 99) struct OSXUnavailableAndIntroducedInFuture {} @@ -547,32 +550,33 @@ struct OSXIntroducedInFutureAndUnavailable {} @available(OSX, unavailable) func osx_unavailable_func( _ s1: OSXFutureAvailable, - _ s2: OSXUnavailableAndIntroducedInFuture, - _ s3: OSXUnavailableAndIntroducedInFutureSameAttribute, - _ s4: OSXIntroducedInFutureAndUnavailable, + _ s2: OSXObsoleted, + _ s3: OSXUnavailableAndIntroducedInFuture, + _ s4: OSXUnavailableAndIntroducedInFutureSameAttribute, + _ s5: OSXIntroducedInFutureAndUnavailable, ) -> ( OSXFutureAvailable, + OSXObsoleted, OSXUnavailableAndIntroducedInFuture, OSXUnavailableAndIntroducedInFutureSameAttribute, OSXIntroducedInFutureAndUnavailable ) { + // FIXME: [availability] Stop diagnosing potential unavailability or obsoletion in an unavailable context. _ = OSXFutureAvailable() // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}} // expected-note@-1 {{add 'if #available' version check}} - // FIXME: [availability] The following diagnostic is incorrect - _ = OSXUnavailableAndIntroducedInFuture() // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}} - // expected-note@-1 {{add 'if #available' version check}} + _ = OSXObsoleted() // expected-error {{'OSXObsoleted' is unavailable in macOS}} + _ = OSXUnavailableAndIntroducedInFuture() _ = OSXUnavailableAndIntroducedInFutureSameAttribute() _ = OSXIntroducedInFutureAndUnavailable() func takesType(_ t: T.Type) {} takesType(OSXFutureAvailable.self) // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}} // expected-note@-1 {{add 'if #available' version check}} - // FIXME: [availability] The following diagnostic is incorrect - takesType(OSXUnavailableAndIntroducedInFuture.self) // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}} - // expected-note@-1 {{add 'if #available' version check}} + takesType(OSXObsoleted.self) // expected-error {{'OSXObsoleted' is unavailable in macOS}} + takesType(OSXUnavailableAndIntroducedInFuture.self) takesType(OSXUnavailableAndIntroducedInFutureSameAttribute.self) takesType(OSXIntroducedInFutureAndUnavailable.self) - return (s1, s2, s3, s4) + return (s1, s2, s3, s4, s5) } diff --git a/test/decl/ext/objc_implementation_direct_to_storage.swift b/test/decl/ext/objc_implementation_direct_to_storage.swift index 52db9da80003a..b6dd6a43b2c17 100644 --- a/test/decl/ext/objc_implementation_direct_to_storage.swift +++ b/test/decl/ext/objc_implementation_direct_to_storage.swift @@ -4,22 +4,20 @@ import objc_implementation_internal +// FIXME: [availability] An implementation that is less available than the interface it implements should be diagnosed @available(*, unavailable) @objc @implementation extension ObjCPropertyTest { - // FIXME: Shouldn't this be on the `@available` above? - // expected-note@+1 {{'prop1' has been explicitly marked unavailable here}} let prop1: Int32 - // expected-note@+1 2 {{'prop2' has been explicitly marked unavailable here}} var prop2: Int32 { didSet { - _ = prop2 // expected-error {{'prop2' is unavailable}} + _ = prop2 } } override init() { - self.prop1 = 1 // expected-error {{'prop1' is unavailable}} - self.prop2 = 2 // expected-error {{'prop2' is unavailable}} + self.prop1 = 1 + self.prop2 = 2 super.init() } @@ -28,3 +26,8 @@ import objc_implementation_internal _ = self.prop2 } } + +func takesObjCPropertyTest(_ o: ObjCPropertyTest) { + _ = o.prop1 + _ = o.prop2 +} From f67749070495ce4e736ed2b17d4e26ea3e35283e Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Mon, 10 Feb 2025 18:08:56 -0800 Subject: [PATCH 2/5] AST: Fix a regression in constraining an AvailabilityContext for a decl. When building up AvailabilityContexts, we assume that all of the enclosing decls have already been accounted for in the AvailabilityContext that we are constraining. Therefore, it doesn't make sense to merge availability constraints from the enclosing extension of the target decl. --- include/swift/AST/AvailabilityConstraint.h | 17 ++++++++++++++--- lib/AST/AvailabilityConstraint.cpp | 6 +++++- lib/AST/AvailabilityContext.cpp | 5 ++++- test/Sema/availability_scopes.swift | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/AvailabilityConstraint.h b/include/swift/AST/AvailabilityConstraint.h index 03dcd148855e1..b206d14f24b68 100644 --- a/include/swift/AST/AvailabilityConstraint.h +++ b/include/swift/AST/AvailabilityConstraint.h @@ -22,6 +22,7 @@ #include "swift/AST/AvailabilityRange.h" #include "swift/AST/PlatformKind.h" #include "swift/Basic/LLVM.h" +#include "swift/Basic/OptionSet.h" namespace swift { @@ -165,12 +166,22 @@ class DeclAvailabilityConstraints { const_iterator end() const { return constraints.end(); } }; +enum class AvailabilityConstraintFlag : uint8_t { + /// By default, the availability constraints for the members of extensions + /// include the constraints for `@available` attributes that were written on + /// the enclosing extension, since these members can be referred to without + /// referencing the extension. When this flag is specified, though, only the + /// attributes directly attached to the declaration are considered. + SkipEnclosingExtension = 1 << 0, +}; +using AvailabilityConstraintFlags = OptionSet; + /// Returns the set of availability constraints that restrict use of \p decl /// when it is referenced from the given context. In other words, it is the /// collection of of `@available` attributes with unsatisfied conditions. -DeclAvailabilityConstraints -getAvailabilityConstraintsForDecl(const Decl *decl, - const AvailabilityContext &context); +DeclAvailabilityConstraints getAvailabilityConstraintsForDecl( + const Decl *decl, const AvailabilityContext &context, + AvailabilityConstraintFlags flags = std::nullopt); } // end namespace swift #endif diff --git a/lib/AST/AvailabilityConstraint.cpp b/lib/AST/AvailabilityConstraint.cpp index 0223365248b0a..11a225062cbee 100644 --- a/lib/AST/AvailabilityConstraint.cpp +++ b/lib/AST/AvailabilityConstraint.cpp @@ -228,7 +228,8 @@ static void getAvailabilityConstraintsForDecl( DeclAvailabilityConstraints swift::getAvailabilityConstraintsForDecl(const Decl *decl, - const AvailabilityContext &context) { + const AvailabilityContext &context, + AvailabilityConstraintFlags flags) { llvm::SmallVector constraints; // Generic parameters are always available. @@ -239,6 +240,9 @@ swift::getAvailabilityConstraintsForDecl(const Decl *decl, getAvailabilityConstraintsForDecl(constraints, decl, context); + if (flags.contains(AvailabilityConstraintFlag::SkipEnclosingExtension)) + return constraints; + // If decl is an extension member, query the attributes of the extension, too. // // Skip decls imported from Clang, though, as they could be associated to the diff --git a/lib/AST/AvailabilityContext.cpp b/lib/AST/AvailabilityContext.cpp index 505756a28f075..956ad09674220 100644 --- a/lib/AST/AvailabilityContext.cpp +++ b/lib/AST/AvailabilityContext.cpp @@ -202,7 +202,10 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange( bool isConstrained = false; Info info{storage->info}; - auto constraints = swift::getAvailabilityConstraintsForDecl(decl, *this); + AvailabilityConstraintFlags flags = + AvailabilityConstraintFlag::SkipEnclosingExtension; + auto constraints = + swift::getAvailabilityConstraintsForDecl(decl, *this, flags); isConstrained |= info.constrainWith(constraints, decl->getASTContext()); isConstrained |= CONSTRAIN_BOOL(info.IsDeprecated, decl->isDeprecated()); isConstrained |= constrainRange(info.Range, platformRange); diff --git a/test/Sema/availability_scopes.swift b/test/Sema/availability_scopes.swift index b1bb6f5715586..69d1d672fb754 100644 --- a/test/Sema/availability_scopes.swift +++ b/test/Sema/availability_scopes.swift @@ -358,7 +358,7 @@ extension SomeEnum { // CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum // CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeEnum // CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=availableMacOS_52 -// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=availableMacOS_52 +// CHECK-NEXT: {{^}} (decl version=52 unavailable=macOS decl=availableMacOS_52 // CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=neverAvailable() @available(macOS, unavailable) From 7d4ab8d66d0d94b2427bc97ac1785940573a21a9 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Sun, 9 Feb 2025 16:10:49 -0800 Subject: [PATCH 3/5] Sema: Adopt getAvailabilityConstraintsForDecl(). Replaces `getUnsatisfiedAvailabilityConstraint()`. NFC. --- .../DerivedConformanceRawRepresentable.cpp | 5 ++-- lib/Sema/TypeCheckAvailability.cpp | 24 +++++++++---------- lib/Sema/TypeCheckAvailability.h | 8 ------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/lib/Sema/DerivedConformanceRawRepresentable.cpp b/lib/Sema/DerivedConformanceRawRepresentable.cpp index 26a02f12c5158..84e1f34ef69a9 100644 --- a/lib/Sema/DerivedConformanceRawRepresentable.cpp +++ b/lib/Sema/DerivedConformanceRawRepresentable.cpp @@ -20,6 +20,7 @@ #include "TypeCheckAvailability.h" #include "TypeCheckDecl.h" #include "TypeChecker.h" +#include "swift/AST/AvailabilityConstraint.h" #include "swift/AST/AvailabilitySpec.h" #include "swift/AST/Decl.h" #include "swift/AST/Expr.h" @@ -240,8 +241,8 @@ checkAvailability(const EnumElementDecl *elt, AvailabilityContext availabilityContext, std::optional &versionCheck) { auto &C = elt->getASTContext(); - auto constraint = - getUnsatisfiedAvailabilityConstraint(elt, availabilityContext); + auto constraint = getAvailabilityConstraintsForDecl(elt, availabilityContext) + .getPrimaryConstraint(); // Is it always available? if (!constraint) diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 90debf924bd68..6e3cf0d63225c 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -22,6 +22,7 @@ #include "TypeCheckUnsafe.h" #include "TypeChecker.h" #include "swift/AST/ASTWalker.h" +#include "swift/AST/AvailabilityConstraint.h" #include "swift/AST/AvailabilityDomain.h" #include "swift/AST/AvailabilityInference.h" #include "swift/AST/AvailabilityScope.h" @@ -2949,7 +2950,8 @@ void swift::diagnoseOverrideOfUnavailableDecl(ValueDecl *override, // recomputing it. ExportContext where = ExportContext::forDeclSignature(override, nullptr); auto constraint = - getUnsatisfiedAvailabilityConstraint(base, where.getAvailability()); + getAvailabilityConstraintsForDecl(base, where.getAvailability()) + .getPrimaryConstraint(); if (!constraint) return; @@ -3077,20 +3079,14 @@ bool diagnoseExplicitUnavailability(SourceLoc loc, return true; } -std::optional -swift::getUnsatisfiedAvailabilityConstraint( - const Decl *decl, AvailabilityContext availabilityContext) { - auto constraints = - swift::getAvailabilityConstraintsForDecl(decl, availabilityContext); - return constraints.getPrimaryConstraint(); -} - std::optional swift::getUnsatisfiedAvailabilityConstraint(const Decl *decl, const DeclContext *referenceDC, SourceLoc referenceLoc) { - return getUnsatisfiedAvailabilityConstraint( - decl, TypeChecker::availabilityAtLocation(referenceLoc, referenceDC)); + return getAvailabilityConstraintsForDecl( + decl, + TypeChecker::availabilityAtLocation(referenceLoc, referenceDC)) + .getPrimaryConstraint(); } /// Check if this is a subscript declaration inside String or @@ -4158,7 +4154,8 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R, auto &ctx = DC->getASTContext(); auto constraint = - getUnsatisfiedAvailabilityConstraint(D, Where.getAvailability()); + getAvailabilityConstraintsForDecl(D, Where.getAvailability()) + .getPrimaryConstraint(); if (constraint) { if (diagnoseExplicitUnavailability(D, R, *constraint, Where, call, Flags)) @@ -4679,7 +4676,8 @@ swift::diagnoseConformanceAvailability(SourceLoc loc, } auto constraint = - getUnsatisfiedAvailabilityConstraint(ext, where.getAvailability()); + getAvailabilityConstraintsForDecl(ext, where.getAvailability()) + .getPrimaryConstraint(); if (constraint) { if (diagnoseExplicitUnavailability(loc, *constraint, rootConf, ext, where, warnIfConformanceUnavailablePreSwift6, diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index 2df91b6725ebe..08a7873ea8dbc 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -250,14 +250,6 @@ void diagnoseOverrideOfUnavailableDecl(ValueDecl *override, const ValueDecl *base, SemanticAvailableAttr attr); -/// Checks whether a declaration should be considered unavailable when referred -/// to in the given declaration context and availability context and, if so, -/// returns a result that describes the unsatisfied constraint. -/// Returns `std::nullopt` if the declaration is available. -std::optional -getUnsatisfiedAvailabilityConstraint(const Decl *decl, - AvailabilityContext availabilityContext); - /// Checks whether a declaration should be considered unavailable when referred /// to at the given source location in the given decl context and, if so, /// returns a result that describes the unsatisfied constraint. From 949a6c68d7d3e9ce619b7b2a8f2299b4e024de25 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Sun, 9 Feb 2025 22:31:01 -0800 Subject: [PATCH 4/5] AST/Sema: Retire SemanticAvailableAttr::getVersionAvailability(). Query for availability constraints instead of calling getVersionAvailability(). --- include/swift/AST/Attr.h | 23 ------ lib/AST/Attr.cpp | 44 ---------- lib/Sema/TypeCheckAttr.cpp | 81 +++++++++++-------- lib/Sema/TypeCheckDeclOverride.cpp | 20 +++-- test/Parse/invalid.swift | 2 +- .../associated_type_availability.swift | 2 +- test/decl/protocol/req/unavailable.swift | 11 ++- 7 files changed, 73 insertions(+), 110 deletions(-) diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 4f9a699b9dfc8..9d3bf148f8089 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -703,23 +703,6 @@ class SwiftNativeObjCRuntimeBaseAttr : public DeclAttribute { } }; -/// Determine the result of comparing an availability attribute to a specific -/// platform or language version. -enum class AvailableVersionComparison { - /// The entity is guaranteed to be available. - Available, - - /// The entity is never available. - Unavailable, - - /// The entity might be unavailable at runtime, because it was introduced - /// after the requested minimum platform version. - PotentiallyUnavailable, - - /// The entity has been obsoleted. - Obsoleted, -}; - /// Defines the @available attribute. class AvailableAttr : public DeclAttribute { public: @@ -3378,12 +3361,6 @@ class SemanticAvailableAttr final { /// version for PackageDescription version-specific availability. llvm::VersionTuple getActiveVersion(const ASTContext &ctx) const; - /// Compare this attribute's version information against the platform or - /// language version (assuming the this attribute pertains to the active - /// platform). - AvailableVersionComparison - getVersionAvailability(const ASTContext &ctx) const; - /// Returns true if this attribute is considered active in the current /// compilation context. bool isActive(ASTContext &ctx) const; diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index f80e6e16c8ac4..faf30e60fd12d 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -2294,50 +2294,6 @@ SemanticAvailableAttr::getActiveVersion(const ASTContext &ctx) const { } } -AvailableVersionComparison -SemanticAvailableAttr::getVersionAvailability(const ASTContext &ctx) const { - - // Unconditional unavailability. - if (attr->isUnconditionallyUnavailable()) - return AvailableVersionComparison::Unavailable; - - llvm::VersionTuple queryVersion = getActiveVersion(ctx); - std::optional ObsoletedVersion = getObsoleted(); - - StringRef ObsoletedPlatform; - llvm::VersionTuple RemappedObsoletedVersion; - if (AvailabilityInference::updateObsoletedPlatformForFallback( - *this, ctx, ObsoletedPlatform, RemappedObsoletedVersion)) - ObsoletedVersion = RemappedObsoletedVersion; - - // If this entity was obsoleted before or at the query platform version, - // consider it obsolete. - if (ObsoletedVersion && *ObsoletedVersion <= queryVersion) - return AvailableVersionComparison::Obsoleted; - - std::optional IntroducedVersion = getIntroduced(); - StringRef IntroducedPlatform; - llvm::VersionTuple RemappedIntroducedVersion; - if (AvailabilityInference::updateIntroducedPlatformForFallback( - *this, ctx, IntroducedPlatform, RemappedIntroducedVersion)) - IntroducedVersion = RemappedIntroducedVersion; - - // If this entity was introduced after the query version and we're doing a - // platform comparison, true availability can only be determined dynamically; - // if we're doing a _language_ version check, the query version is a - // static requirement, so we treat "introduced later" as just plain - // unavailable. - if (IntroducedVersion && *IntroducedVersion > queryVersion) { - if (isSwiftLanguageModeSpecific() || isPackageDescriptionVersionSpecific()) - return AvailableVersionComparison::Unavailable; - else - return AvailableVersionComparison::PotentiallyUnavailable; - } - - // The entity is available. - return AvailableVersionComparison::Available; -} - SpecializeAttr::SpecializeAttr(SourceLoc atLoc, SourceRange range, TrailingWhereClause *clause, bool exported, SpecializationKind kind, diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 7ca4677bbcb1d..c9d6ca4850a31 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -2417,27 +2417,6 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *parsedAttr) { } } - SourceLoc attrLoc = parsedAttr->getLocation(); - auto versionAvailability = attr->getVersionAvailability(Ctx); - if (versionAvailability == AvailableVersionComparison::Obsoleted || - versionAvailability == AvailableVersionComparison::Unavailable) { - if (auto cannotBeUnavailable = - TypeChecker::diagnosticIfDeclCannotBeUnavailable(D)) { - diagnose(attrLoc, cannotBeUnavailable.value()); - return; - } - - if (auto *PD = dyn_cast(DC)) { - if (auto *VD = dyn_cast(D)) { - if (VD->isProtocolRequirement() && !PD->isObjC()) { - diagnoseAndRemoveAttr(parsedAttr, - diag::unavailable_method_non_objc_protocol); - return; - } - } - } - } - // The remaining diagnostics are only for attributes with introduced versions // for specific platforms. if (!attr->isPlatformSpecific() || !attr->getIntroduced().has_value()) @@ -2486,18 +2465,6 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *parsedAttr) { } } } - - std::optional MaybeNotAllowed = - TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D); - if (MaybeNotAllowed.has_value()) { - AvailabilityRange DeploymentRange = - AvailabilityRange::forDeploymentTarget(Ctx); - if (EnclosingAnnotatedRange.has_value()) - DeploymentRange.intersectWith(*EnclosingAnnotatedRange); - - if (!DeploymentRange.isContainedIn(AttrRange)) - diagnose(attrLoc, MaybeNotAllowed.value()); - } } static bool canDeclareSymbolName(StringRef symbol, ModuleDecl *fromModule) { @@ -5003,6 +4970,54 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef Attrs) { diagnose(D->getLoc(), diag::spi_preferred_over_spi_available); } } + + if (Ctx.LangOpts.DisableAvailabilityChecking) + return; + + // Compute availability constraints for the decl, relative to its parent + // declaration or to the deployment target. + auto availabilityContext = AvailabilityContext::forDeploymentTarget(Ctx); + if (auto parent = + AvailabilityInference::parentDeclForInferredAvailability(D)) { + auto parentAvailability = TypeChecker::availabilityForDeclSignature(parent); + availabilityContext.constrainWithContext(parentAvailability, Ctx); + } + + auto availabilityConstraint = + getAvailabilityConstraintsForDecl(D, availabilityContext) + .getPrimaryConstraint(); + if (!availabilityConstraint) + return; + + // If the decl is unavailable relative to its parent and it's not a + // declaration that is allowed to be unavailable, diagnose. + if (availabilityConstraint->isUnavailable()) { + auto attr = availabilityConstraint->getAttr(); + if (auto diag = TypeChecker::diagnosticIfDeclCannotBeUnavailable(D)) { + diagnose(attr.getParsedAttr()->getLocation(), diag.value()); + return; + } + + if (auto *PD = dyn_cast(D->getDeclContext())) { + if (auto *VD = dyn_cast(D)) { + if (VD->isProtocolRequirement() && !PD->isObjC()) { + diagnoseAndRemoveAttr( + const_cast(attr.getParsedAttr()), + diag::unavailable_method_non_objc_protocol); + return; + } + } + } + } + + // If the decl is potentially unavailable relative to its parent and it's + // not a declaration that is allowed to be potentially unavailable, diagnose. + if (availabilityConstraint->isPotentiallyAvailable()) { + auto attr = availabilityConstraint->getAttr(); + if (auto diag = + TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D)) + diagnose(attr.getParsedAttr()->getLocation(), diag.value()); + } } void AttributeChecker::checkBackDeployedAttrs( diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index efd7c5d6ad164..7b89e8cb4f8ba 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -245,15 +245,21 @@ bool swift::isOverrideBasedOnType(const ValueDecl *decl, Type declTy, static bool isUnavailableInAllVersions(ValueDecl *decl) { ASTContext &ctx = decl->getASTContext(); - auto attr = decl->getUnavailableAttr(); - if (!attr) - return false; - if (attr->isUnconditionallyUnavailable()) - return true; + auto deploymentContext = AvailabilityContext::forDeploymentTarget(ctx); + auto constraints = getAvailabilityConstraintsForDecl(decl, deploymentContext); + for (auto constraint : constraints) { + switch (constraint.getReason()) { + case AvailabilityConstraint::Reason::UnconditionallyUnavailable: + case AvailabilityConstraint::Reason::IntroducedInLaterVersion: + return true; + case AvailabilityConstraint::Reason::Obsoleted: + case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion: + break; + } + } - return attr->getVersionAvailability(ctx) == - AvailableVersionComparison::Unavailable; + return false; } /// Perform basic checking to determine whether a declaration can override a diff --git a/test/Parse/invalid.swift b/test/Parse/invalid.swift index 377173aa3d898..e886233d6e5c3 100644 --- a/test/Parse/invalid.swift +++ b/test/Parse/invalid.swift @@ -140,5 +140,5 @@ class C_50734<@NSApplicationMain T: AnyObject> {} // expected-error {{@NSApplica func f6_50734<@discardableResult T>(x: T) {} // expected-error {{'@discardableResult' attribute cannot be applied to this declaration}} enum E_50734<@indirect T> {} // expected-error {{'indirect' is a declaration modifier, not an attribute}} expected-error {{'indirect' modifier cannot be applied to this declaration}} protocol P { - @available(swift, introduced: 4) associatedtype Assoc + @available(macOS, introduced: 10.9) associatedtype Assoc } diff --git a/test/decl/protocol/associated_type_availability.swift b/test/decl/protocol/associated_type_availability.swift index c7e05e5aa89e6..1a8bb85a3ea39 100644 --- a/test/decl/protocol/associated_type_availability.swift +++ b/test/decl/protocol/associated_type_availability.swift @@ -89,6 +89,6 @@ protocol P3 { @available(macOS, obsoleted: 12) // expected-error{{associated type cannot be marked unavailable with '@available'}} associatedtype A1 - @available(macOS, obsoleted: 99) // FIXME: this should probably be diagnosed + @available(macOS, obsoleted: 99) associatedtype A2 } diff --git a/test/decl/protocol/req/unavailable.swift b/test/decl/protocol/req/unavailable.swift index c7a585ee68db0..13837f23a8e10 100644 --- a/test/decl/protocol/req/unavailable.swift +++ b/test/decl/protocol/req/unavailable.swift @@ -104,6 +104,15 @@ protocol UnavailableAssoc { @available(*, unavailable) // expected-error {{associated type cannot be marked unavailable with '@available'}} associatedtype A1 - @available(swift, introduced: 99) // expected-error {{associated type cannot be marked unavailable with '@available'}} + @available(swift, introduced: 4) associatedtype A2 + + @available(swift, introduced: 99) // expected-error {{associated type cannot be marked unavailable with '@available'}} + associatedtype A3 + + @available(swift, obsoleted: 4) // expected-error {{associated type cannot be marked unavailable with '@available'}} + associatedtype A4 + + @available(swift, obsoleted: 99) + associatedtype A5 } From e804c937ebaf1ecaa28db108e37559d40324c248 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Sun, 9 Feb 2025 22:54:43 -0800 Subject: [PATCH 5/5] Sema: Retire ExportContext::shouldDiagnoseDeclAsUnavailable(). NFC. --- lib/Sema/TypeCheckAvailability.cpp | 34 ------------------------------ lib/Sema/TypeCheckAvailability.h | 7 ------ lib/Sema/TypeCheckProtocol.cpp | 8 +++++-- 3 files changed, 6 insertions(+), 43 deletions(-) diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 6e3cf0d63225c..f17d58d1fd4fa 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -346,40 +346,6 @@ static bool computeContainedByDeploymentTarget(AvailabilityScope *scope, AvailabilityRange::forDeploymentTarget(ctx)); } -/// Returns true if the reference or any of its parents is an -/// unconditional unavailable declaration for the same platform. -static bool isInsideCompatibleUnavailableDeclaration( - const Decl *D, AvailabilityContext availabilityContext, - const SemanticAvailableAttr &attr) { - if (!availabilityContext.isUnavailable()) - return false; - - if (!attr.isUnconditionallyUnavailable()) - return false; - - // Refuse calling universally unavailable functions from unavailable code, - // but allow the use of types. - auto declDomain = attr.getDomain(); - if (!isa(D) && !isa(D)) { - if (declDomain.isUniversal() || declDomain.isSwiftLanguage()) - return false; - } - - return availabilityContext.containsUnavailableDomain(declDomain); -} - -std::optional -ExportContext::shouldDiagnoseDeclAsUnavailable(const Decl *D) const { - auto attr = D->getUnavailableAttr(); - if (!attr) - return std::nullopt; - - if (isInsideCompatibleUnavailableDeclaration(D, Availability, *attr)) - return std::nullopt; - - return attr; -} - static bool shouldAllowReferenceToUnavailableInSwiftDeclaration( const Decl *D, const ExportContext &where) { auto *DC = where.getDeclContext(); diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index 08a7873ea8dbc..be9970aa0c427 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -201,13 +201,6 @@ class ExportContext { /// Get the ExportabilityReason for diagnostics. If this is 'None', there /// are no restrictions on referencing unexported declarations. std::optional getExportabilityReason() const; - - /// If \p decl is unconditionally unavailable in this context, and the context - /// is not also unavailable in the same way, then this returns the specific - /// `@available` attribute that makes the decl unavailable. Otherwise, returns - /// nullptr. - std::optional - shouldDiagnoseDeclAsUnavailable(const Decl *decl) const; }; /// Check if a declaration is exported as part of a module's external interface. diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index dfa9f721dfc9a..b7b58ee0d2953 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -4964,12 +4964,16 @@ static bool diagnoseTypeWitnessAvailability( bool shouldError = ctx.LangOpts.EffectiveLanguageVersion.isVersionAtLeast(warnBeforeVersion); - if (auto attr = where.shouldDiagnoseDeclAsUnavailable(witness)) { + auto constraint = + getAvailabilityConstraintsForDecl(witness, where.getAvailability()) + .getPrimaryConstraint(); + if (constraint && constraint->isUnavailable()) { + auto attr = constraint->getAttr(); ctx.addDelayedConformanceDiag( conformance, shouldError, [witness, assocType, attr](NormalProtocolConformance *conformance) { SourceLoc loc = getLocForDiagnosingWitness(conformance, witness); - EncodedDiagnosticMessage encodedMessage(attr->getMessage()); + EncodedDiagnosticMessage encodedMessage(attr.getMessage()); auto &ctx = conformance->getDeclContext()->getASTContext(); ctx.Diags .diagnose(loc, diag::witness_unavailable, witness,