Sema: Improve diagnostics for decls more available than their containers.

Adopt new request-based utilities for looking up the enclosing declaration's availability when type checking an `@available` attribute. This consolidates implementations of the lookup and improves diagnostics by catching more cases where declarations are more available than their containers.
This commit is contained in:
Allan Shortlidge
2023-01-06 14:11:24 -08:00
parent 6d657c90af
commit abf4d127a4
4 changed files with 50 additions and 44 deletions

View File

@@ -1101,17 +1101,21 @@ public:
/// Retrieve the @available attribute that provides the OS version range that /// Retrieve the @available attribute that provides the OS version range that
/// this declaration is available in. /// this declaration is available in.
/// ///
/// The attribute may come from another declaration, since availability /// This attribute may come from an enclosing decl since availability is
/// could be inherited from a parent declaration. /// inherited. The second member of the returned pair is the decl that owns
/// the attribute.
Optional<std::pair<const AvailableAttr *, const Decl *>> Optional<std::pair<const AvailableAttr *, const Decl *>>
getSemanticAvailableRangeAttr() const; getSemanticAvailableRangeAttr() const;
/// Retrieve the @available attribute that makes this declaration unavailable, /// Retrieve the @available attribute that makes this declaration unavailable,
/// if any. /// if any.
/// ///
/// The attribute may come from another declaration, since unavailability /// This attribute may come from an enclosing decl since availability is
/// could be inherited from a parent declaration. This is a broader notion of /// inherited. The second member of the returned pair is the decl that owns
/// unavailability than is checked by \c AvailableAttr::isUnavailable. /// the attribute.
///
/// Note that this notion of unavailability is broader than that which is
/// checked by \c AvailableAttr::isUnavailable.
Optional<std::pair<const AvailableAttr *, const Decl *>> Optional<std::pair<const AvailableAttr *, const Decl *>>
getSemanticUnavailableAttr() const; getSemanticUnavailableAttr() const;

View File

@@ -1882,53 +1882,50 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) {
// is fully contained within that declaration's range. If there is no such // is fully contained within that declaration's range. If there is no such
// enclosing declaration, then there is nothing to check. // enclosing declaration, then there is nothing to check.
Optional<AvailabilityContext> EnclosingAnnotatedRange; Optional<AvailabilityContext> EnclosingAnnotatedRange;
bool EnclosingDeclIsUnavailable = false;
Decl *EnclosingDecl = getEnclosingDeclForDecl(D);
while (EnclosingDecl) {
if (EnclosingDecl->getAttrs().getUnavailable(Ctx)) {
EnclosingDeclIsUnavailable = true;
break;
}
EnclosingAnnotatedRange =
AvailabilityInference::annotatedAvailableRange(EnclosingDecl, Ctx);
if (EnclosingAnnotatedRange.has_value())
break;
EnclosingDecl = getEnclosingDeclForDecl(EnclosingDecl);
}
AvailabilityContext AttrRange{ AvailabilityContext AttrRange{
VersionRange::allGTE(attr->Introduced.value())}; VersionRange::allGTE(attr->Introduced.value())};
if (EnclosingDecl) { if (auto *parent = getEnclosingDeclForDecl(D)) {
if (EnclosingDeclIsUnavailable) { if (auto enclosingUnavailable = parent->getSemanticUnavailableAttr()) {
if (!AttrRange.isKnownUnreachable()) { if (!AttrRange.isKnownUnreachable()) {
diagnose(D->isImplicit() ? EnclosingDecl->getLoc() const Decl *enclosingDecl = enclosingUnavailable.value().second;
diagnose(D->isImplicit() ? enclosingDecl->getLoc()
: attr->getLocation(), : attr->getLocation(),
diag::availability_decl_more_than_unavailable_enclosing, diag::availability_decl_more_than_unavailable_enclosing,
D->getDescriptiveKind()); D->getDescriptiveKind());
diagnose(EnclosingDecl->getLoc(), diagnose(parent->getLoc(),
diag::availability_decl_more_than_unavailable_enclosing_here); diag::availability_decl_more_than_unavailable_enclosing_here);
} }
} else if (!AttrRange.isContainedIn(EnclosingAnnotatedRange.value())) { } else if (auto enclosingAvailable =
diagnose(D->isImplicit() ? EnclosingDecl->getLoc() : attr->getLocation(), parent->getSemanticAvailableRangeAttr()) {
const AvailableAttr *enclosingAttr = enclosingAvailable.value().first;
const Decl *enclosingDecl = enclosingAvailable.value().second;
EnclosingAnnotatedRange.emplace(
VersionRange::allGTE(enclosingAttr->Introduced.value()));
if (!AttrRange.isContainedIn(*EnclosingAnnotatedRange)) {
// Members of extensions of nominal types with available ranges were
// not diagnosed previously, so only emit a warning in that case.
auto limit = (enclosingDecl != parent && isa<ExtensionDecl>(parent))
? DiagnosticBehavior::Warning
: DiagnosticBehavior::Unspecified;
diagnose(D->isImplicit() ? enclosingDecl->getLoc()
: attr->getLocation(),
diag::availability_decl_more_than_enclosing, diag::availability_decl_more_than_enclosing,
D->getDescriptiveKind()); D->getDescriptiveKind())
.limitBehavior(limit);
if (D->isImplicit()) if (D->isImplicit())
diagnose(EnclosingDecl->getLoc(), diagnose(enclosingDecl->getLoc(),
diag::availability_implicit_decl_here, diag::availability_implicit_decl_here,
D->getDescriptiveKind(), D->getDescriptiveKind(),
prettyPlatformString(targetPlatform(Ctx.LangOpts)), prettyPlatformString(targetPlatform(Ctx.LangOpts)),
AttrRange.getOSVersion().getLowerEndpoint()); AttrRange.getOSVersion().getLowerEndpoint());
diagnose(EnclosingDecl->getLoc(), diagnose(enclosingDecl->getLoc(),
diag::availability_decl_more_than_enclosing_here, diag::availability_decl_more_than_enclosing_here,
prettyPlatformString(targetPlatform(Ctx.LangOpts)), prettyPlatformString(targetPlatform(Ctx.LangOpts)),
EnclosingAnnotatedRange->getOSVersion().getLowerEndpoint()); EnclosingAnnotatedRange->getOSVersion().getLowerEndpoint());
} }
} }
}
Optional<Diag<>> MaybeNotAllowed = Optional<Diag<>> MaybeNotAllowed =
TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D); TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D);

View File

@@ -80,8 +80,8 @@ func doSomethingDeprecatedOniOS() { }
doSomethingDeprecatedOniOS() // okay doSomethingDeprecatedOniOS() // okay
@available(macOS 10.10, *)
struct TestStruct {} struct TestStruct {} // expected-note {{enclosing scope requires availability of macOS 10.10 or newer}}
@available(macOS 10.10, *) @available(macOS 10.10, *)
extension TestStruct { // expected-note {{enclosing scope requires availability of macOS 10.10 or newer}} extension TestStruct { // expected-note {{enclosing scope requires availability of macOS 10.10 or newer}}
@@ -104,6 +104,11 @@ extension TestStruct { // expected-note {{enclosing scope requires availability
func doDeprecatedThing() {} func doDeprecatedThing() {}
} }
extension TestStruct {
@available(macOS 10.9, *) // expected-warning {{instance method cannot be more available than enclosing scope}}
func doFifthThing() {}
}
@available(macOS 10.11, *) @available(macOS 10.11, *)
func testMemberAvailability() { func testMemberAvailability() {
TestStruct().doTheThing() // expected-error {{'doTheThing()' is unavailable}} TestStruct().doTheThing() // expected-error {{'doTheThing()' is unavailable}}

View File

@@ -55,7 +55,7 @@ public struct AtInliningTarget {
} }
@available(macOS 10.14.5, *) @available(macOS 10.14.5, *)
public struct BetweenTargets { public struct BetweenTargets { // expected-note {{enclosing scope requires availability of macOS 10.14.5 or newer}}
@usableFromInline internal init() {} @usableFromInline internal init() {}
} }
@@ -1102,7 +1102,7 @@ extension BetweenTargets {
} }
extension BetweenTargets { extension BetweenTargets {
@available(macOS 10.10, *) @available(macOS 10.10, *) // expected-warning {{instance method cannot be more available than enclosing scope}}
func excessivelyAvailableInternalFuncInExtension() {} func excessivelyAvailableInternalFuncInExtension() {}
} }