Skip to content

Sema: Improve diagnostics for overrides of unavailable decls #65456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2755,9 +2755,9 @@ bool TypeChecker::diagnoseIfDeprecated(SourceLoc loc,
return true;
}

void swift::diagnoseUnavailableOverride(ValueDecl *override,
const ValueDecl *base,
const AvailableAttr *attr) {
void swift::diagnoseOverrideOfUnavailableDecl(ValueDecl *override,
const ValueDecl *base,
const AvailableAttr *attr) {
ASTContext &ctx = override->getASTContext();
auto &diags = ctx.Diags;
if (attr->Rename.empty()) {
Expand Down
8 changes: 5 additions & 3 deletions lib/Sema/TypeCheckAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,11 @@ bool diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
const Expr *call, const ExportContext &where,
DeclAvailabilityFlags flags = None);

void diagnoseUnavailableOverride(ValueDecl *override,
const ValueDecl *base,
const AvailableAttr *attr);
/// Emit a diagnostic for an available declaration that overrides an
/// unavailable declaration.
void diagnoseOverrideOfUnavailableDecl(ValueDecl *override,
const ValueDecl *base,
const AvailableAttr *attr);

/// Emit a diagnostic for references to declarations that have been
/// marked as unavailable, either through "unavailable" or "obsoleted:".
Expand Down
67 changes: 62 additions & 5 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,52 @@ static bool diagnoseOverrideForAvailability(ValueDecl *override,
return true;
}

enum class OverrideUnavailabilityStatus {
/// The unavailability of the base decl and override decl are compatible.
Compatible,
/// The base decl is unavailable but the override decl is not.
BaseUnavailable,
/// Do not diagnose the unavailability of these decls.
Ignored,
};

static std::pair<OverrideUnavailabilityStatus, const AvailableAttr *>
checkOverrideUnavailability(ValueDecl *override, ValueDecl *base) {
if (auto *overrideParent = override->getDeclContext()->getAsDecl()) {
// If the parent of the override is unavailable, then the unavailability of
// the override decl is irrelevant.
if (overrideParent->getSemanticUnavailableAttr())
return {OverrideUnavailabilityStatus::Ignored, nullptr};
}

if (auto *baseAccessor = dyn_cast<AccessorDecl>(base)) {
// Ignore implicit accessors since the diagnostics are likely to duplicate
// the diagnostics for the explicit accessors that availability was inferred
// from.
if (baseAccessor->isImplicit())
return {OverrideUnavailabilityStatus::Ignored, nullptr};

if (auto *overrideAccessor = dyn_cast<AccessorDecl>(override)) {
// If base and override are accessors, check whether the unavailability of
// their storage matches. Diagnosing accessors with invalid storage
// produces redundant diagnostics.
if (checkOverrideUnavailability(overrideAccessor->getStorage(),
baseAccessor->getStorage())
.first != OverrideUnavailabilityStatus::Compatible)
return {OverrideUnavailabilityStatus::Ignored, nullptr};
}
}

auto &ctx = override->getASTContext();
auto *baseUnavailableAttr = base->getAttrs().getUnavailable(ctx);
auto *overrideUnavailableAttr = override->getAttrs().getUnavailable(ctx);

if (baseUnavailableAttr && !overrideUnavailableAttr)
return {OverrideUnavailabilityStatus::BaseUnavailable, baseUnavailableAttr};

return {OverrideUnavailabilityStatus::Compatible, nullptr};
}

static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
// This can happen with circular inheritance.
// FIXME: This shouldn't be possible once name lookup goes through the
Expand Down Expand Up @@ -2066,17 +2112,28 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
}

// FIXME: Possibly should extend to more availability checking.
if (auto *attr = base->getAttrs().getUnavailable(ctx)) {
diagnoseUnavailableOverride(override, base, attr);
auto unavailabilityStatusAndAttr =
checkOverrideUnavailability(override, base);
auto *unavailableAttr = unavailabilityStatusAndAttr.second;
Comment on lines +2115 to +2117
Copy link
Contributor

@LucianoPAlmeida LucianoPAlmeida Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: If we are in c++ 17 maybe we could use structured binding here =]

Suggested change
auto unavailabilityStatusAndAttr =
checkOverrideUnavailability(override, base);
auto *unavailableAttr = unavailabilityStatusAndAttr.second;
auto [status, unavailableAttr] =
checkOverrideUnavailability(override, base);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does seem nicer. I'm not going to revise this solely for that right now but I'll keep an eye out for opportunities to improve readability with this syntax in the future now that the project uses C++17.


switch (unavailabilityStatusAndAttr.first) {
case OverrideUnavailabilityStatus::BaseUnavailable: {
diagnoseOverrideOfUnavailableDecl(override, base, unavailableAttr);

if (isUnavailableInAllVersions(base)) {
auto modifier = override->getAttrs().getAttribute<OverrideAttr>();
if (modifier && modifier->isValid()) {
diags.diagnose(override, diag::suggest_removing_override,
override->getBaseName())
.fixItRemove(modifier->getRange());
diags
.diagnose(override, diag::suggest_removing_override,
override->getBaseName())
.fixItRemove(modifier->getRange());
}
}
break;
}
case OverrideUnavailabilityStatus::Compatible:
case OverrideUnavailabilityStatus::Ignored:
break;
}

if (!ctx.LangOpts.DisableAvailabilityChecking) {
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4384,7 +4384,7 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {

case CheckKind::Unavailable: {
auto *attr = requirement->getAttrs().getUnavailable(getASTContext());
diagnoseUnavailableOverride(witness, requirement, attr);
diagnoseOverrideOfUnavailableDecl(witness, requirement, attr);
break;
}

Expand Down
58 changes: 0 additions & 58 deletions test/Sema/availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,64 +31,6 @@ func foo(x : NSUInteger) { // expected-error {{'NSUInteger' is unavailable: use
// expected-error@-1 {{cannot convert value of type 'Int' to specified type 'Outer.NSUInteger'}}
}

// Test preventing overrides (but allowing shadowing) of unavailable methods.
class ClassWithUnavailable {
@available(*, unavailable)
func doNotOverride() {} // expected-note {{'doNotOverride()' has been explicitly marked unavailable here}}

// FIXME: extraneous diagnostic here
@available(*, unavailable)
init(int _: Int) {} // expected-note 3 {{'init(int:)' has been explicitly marked unavailable here}}

@available(*, unavailable)
convenience init(otherInt: Int) {
self.init(int: otherInt) // expected-error {{'init(int:)' is unavailable}}
}

@available(*, unavailable)
required init(necessaryInt: Int) {}

@available(*, unavailable)
subscript (i: Int) -> Int { // expected-note 2 {{'subscript(_:)' has been explicitly marked unavailable here}}
return i
}
}

class ClassWithOverride : ClassWithUnavailable {
override func doNotOverride() {}
// expected-error@-1 {{cannot override 'doNotOverride' which has been marked unavailable}}
// expected-note@-2 {{remove 'override' modifier to declare a new 'doNotOverride'}} {{3-12=}}

override init(int: Int) {}
// expected-error@-1 {{cannot override 'init' which has been marked unavailable}}
// expected-note@-2 {{remove 'override' modifier to declare a new 'init'}} {{3-12=}}

// can't override convenience inits
// can't override required inits

override subscript (i: Int) -> Int { return i }
// expected-error@-1 {{cannot override 'subscript' which has been marked unavailable}}
// expected-note@-2 {{remove 'override' modifier to declare a new 'subscript'}} {{3-12=}}
}

class ClassWithShadowing : ClassWithUnavailable {
func doNotOverride() {} // no-error
init(int: Int) {} // no-error
convenience init(otherInt: Int) { self.init(int: otherInt) } // no-error
required init(necessaryInt: Int) {} // no-error
subscript (i: Int) -> Int { return i } // no-error
}

func testInit() {
ClassWithUnavailable(int: 0) // expected-error {{'init(int:)' is unavailable}} // expected-warning{{unused}}
ClassWithShadowing(int: 0) // expected-warning {{unused}}
}

func testSubscript(cwu: ClassWithUnavailable, cws: ClassWithShadowing) {
_ = cwu[5] // expected-error{{'subscript(_:)' is unavailable}}
_ = cws[5] // no-error
}

/* FIXME 'nil == a' fails to type-check with a bogus error message
* <rdar://problem/17540796>
func markUsed<T>(t: T) {}
Expand Down
Loading