Skip to content

Sema: Ban unavailable overrides of available decls #65426

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

Closed
wants to merge 1 commit into from
Closed
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
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2908,6 +2908,9 @@ ERROR(override_unavailable, none,
NOTE(suggest_removing_override, none,
"remove 'override' modifier to declare a new %0",
(DeclBaseName))
ERROR(unavailable_override, none,
"override of %0 cannot be marked unavailable with '@available'",
(DeclName))

ERROR(override_less_available,none,
"overriding %0 must be as available as declaration it overrides",
Expand Down
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
78 changes: 73 additions & 5 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,58 @@ static bool diagnoseOverrideForAvailability(ValueDecl *override,
return true;
}

enum class OverrideUnavailabilityStatus {
/// The effective unavailability of the base decl and override decl match.
Match,
/// The base decl is unavailable but the override decl is not.
BaseUnavailable,
/// The override decl is unavailable but the base decl is not.
OverrideUnavailable,
/// 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.
auto storageStatusAndAttr = checkOverrideUnavailability(
overrideAccessor->getStorage(), baseAccessor->getStorage());
if (storageStatusAndAttr.first != OverrideUnavailabilityStatus::Match)
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};

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

return {OverrideUnavailabilityStatus::Match, 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 +2118,33 @@ 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;

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::OverrideUnavailable: {
diags.diagnose(unavailableAttr->getLocation(), diag::unavailable_override,
override->getName());
break;
}
case OverrideUnavailabilityStatus::Match:
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
2 changes: 1 addition & 1 deletion test/SILGen/vtables.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class A {
func bar() {}
func bas() {}
func qux() {}
func flux() {}
@available(*, unavailable) func flux() {}
}

// CHECK: sil_vtable A {
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