Skip to content

Sema: Refactor unavailable declaration diagnostics #75936

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 1 commit into from
Aug 17, 2024
Merged
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
291 changes: 150 additions & 141 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2841,36 +2841,72 @@ bool swift::diagnoseExplicitUnavailability(const ValueDecl *D, SourceRange R,
});
}

/// Emit a diagnostic for references to declarations that have been
/// marked as unavailable, either through "unavailable" or "obsoleted:".
bool swift::diagnoseExplicitUnavailability(SourceLoc loc,
const RootProtocolConformance *rootConf,
const ExtensionDecl *ext,
const ExportContext &where,
bool warnIfConformanceUnavailablePreSwift6) {
auto *attr = AvailableAttr::isUnavailable(ext);
/// Represents common information needed to emit diagnostics about explicitly
/// unavailable declarations.
class UnavailabilityDiagnosticInfo {
public:
enum class Status {
/// The declaration is marked `unavailable`, potentially on a specific
/// platform.
AlwaysUnavailable,

/// The declaration is not available until, for example, a later Swift
/// language mode.
IntroducedInVersion,

/// The declaration was obsoleted in a previous version.
Obsoleted,
};

private:
Status DiagnosticStatus;
const AvailableAttr *Attr;
StringRef Platform;
StringRef VersionedPlatform;

public:
UnavailabilityDiagnosticInfo(Status status, const AvailableAttr *attr,
StringRef platform, StringRef versionedPlatform)
: DiagnosticStatus(status), Attr(attr), Platform(platform),
VersionedPlatform(versionedPlatform) {
assert(attr);
assert(status == Status::AlwaysUnavailable || !VersionedPlatform.empty());
};

Status getStatus() const { return DiagnosticStatus; }
const AvailableAttr *getAttr() const { return Attr; }

/// Returns the platform name (or "Swift" for a declaration that is
/// unavailable in Swift) to print in the main unavailability diangostic. May
/// be empty.
StringRef getPlatform() const { return Platform; }

/// Returns the platform name to print in diagnostic notes about the version
/// in which a declaration either will become available or previously became
/// obsoleted.
StringRef getVersionedPlatform() const {
assert(DiagnosticStatus != Status::AlwaysUnavailable);
return VersionedPlatform;
}
};

static std::optional<UnavailabilityDiagnosticInfo>
getExplicitUnavailabilityDiagnosticInfo(const Decl *decl,
const ExportContext &where) {
auto *attr = AvailableAttr::isUnavailable(decl);
if (!attr)
return false;
return std::nullopt;

// Calling unavailable code from within code with the same
// unavailability is OK -- the eventual caller can't call the
// enclosing code in the same situations it wouldn't be able to
// call this code.
if (isInsideCompatibleUnavailableDeclaration(ext, where, attr))
return false;

// Invertible protocols are never unavailable.
if (rootConf->getProtocol()->getInvertibleProtocolKind())
return false;

ASTContext &ctx = ext->getASTContext();
auto &diags = ctx.Diags;

auto type = rootConf->getType();
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
if (isInsideCompatibleUnavailableDeclaration(decl, where, attr))
return std::nullopt;

StringRef platform;
auto behavior = DiagnosticBehavior::Unspecified;
ASTContext &ctx = decl->getASTContext();
StringRef platform = "";
StringRef versionedPlatform = "";
switch (attr->getPlatformAgnosticAvailability()) {
case PlatformAgnosticAvailabilityKind::Deprecated:
llvm_unreachable("shouldn't see deprecations in explicit unavailability");
Expand All @@ -2881,73 +2917,96 @@ bool swift::diagnoseExplicitUnavailability(SourceLoc loc,
case PlatformAgnosticAvailabilityKind::None:
case PlatformAgnosticAvailabilityKind::Unavailable:
if (attr->Platform != PlatformKind::none) {
// This was platform-specific; indicate the platform.
platform = attr->prettyPlatformString();
break;
versionedPlatform = platform;
}

// Downgrade unavailable Sendable conformance diagnostics where
// appropriate.
behavior = behaviorLimitForExplicitUnavailability(
rootConf, where.getDeclContext());
LLVM_FALLTHROUGH;

break;
case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
versionedPlatform = "Swift";
break;
case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
// We don't want to give further detail about these.
platform = "";
versionedPlatform = "PackageDescription";
break;

case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
// This API is explicitly unavailable in Swift.
platform = "Swift";
break;
}

EncodedDiagnosticMessage EncodedMessage(attr->Message);
diags.diagnose(loc, diag::conformance_availability_unavailable,
type, proto,
platform.empty(), platform, EncodedMessage.Message)
.limitBehaviorUntilSwiftVersion(behavior, 6)
.warnUntilSwiftVersionIf(warnIfConformanceUnavailablePreSwift6, 6);

switch (attr->getVersionAvailability(ctx)) {
case AvailableVersionComparison::Available:
case AvailableVersionComparison::PotentiallyUnavailable:
llvm_unreachable("These aren't considered unavailable");

case AvailableVersionComparison::Unavailable:
if ((attr->isLanguageVersionSpecific() ||
attr->isPackageDescriptionVersionSpecific())
&& attr->Introduced.has_value())
diags.diagnose(ext, diag::conformance_availability_introduced_in_version,
type, proto,
(attr->isLanguageVersionSpecific() ?
"Swift" : "PackageDescription"),
*attr->Introduced)
.highlight(attr->getRange());
else
diags.diagnose(ext, diag::conformance_availability_marked_unavailable,
type, proto)
.highlight(attr->getRange());
attr->isPackageDescriptionVersionSpecific()) &&
attr->Introduced.has_value()) {
return UnavailabilityDiagnosticInfo(
UnavailabilityDiagnosticInfo::Status::IntroducedInVersion, attr,
platform, versionedPlatform);
} else {
return UnavailabilityDiagnosticInfo(
UnavailabilityDiagnosticInfo::Status::AlwaysUnavailable, attr,
platform, versionedPlatform);
}
break;

case AvailableVersionComparison::Obsoleted:
// FIXME: Use of the platformString here is non-awesome for application
// extensions.

StringRef platformDisplayString;
if (attr->isLanguageVersionSpecific()) {
platformDisplayString = "Swift";
} else if (attr->isPackageDescriptionVersionSpecific()) {
platformDisplayString = "PackageDescription";
} else {
platformDisplayString = platform;
}
return UnavailabilityDiagnosticInfo(
UnavailabilityDiagnosticInfo::Status::Obsoleted, attr, platform,
versionedPlatform);
}
}

diags.diagnose(ext, diag::conformance_availability_obsoleted,
type, proto, platformDisplayString, *attr->Obsoleted)
.highlight(attr->getRange());
bool swift::diagnoseExplicitUnavailability(
SourceLoc loc, const RootProtocolConformance *rootConf,
const ExtensionDecl *ext, const ExportContext &where,
bool warnIfConformanceUnavailablePreSwift6) {
// Invertible protocols are never unavailable.
if (rootConf->getProtocol()->getInvertibleProtocolKind())
return false;

auto diagnosticInfo = getExplicitUnavailabilityDiagnosticInfo(ext, where);
if (!diagnosticInfo)
return false;

ASTContext &ctx = ext->getASTContext();
auto &diags = ctx.Diags;

auto type = rootConf->getType();
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
StringRef platform = diagnosticInfo->getPlatform();
const AvailableAttr *attr = diagnosticInfo->getAttr();

// Downgrade unavailable Sendable conformance diagnostics where
// appropriate.
auto behavior =
behaviorLimitForExplicitUnavailability(rootConf, where.getDeclContext());

EncodedDiagnosticMessage EncodedMessage(attr->Message);
diags
.diagnose(loc, diag::conformance_availability_unavailable, type, proto,
platform.empty(), platform, EncodedMessage.Message)
.limitBehaviorUntilSwiftVersion(behavior, 6)
.warnUntilSwiftVersionIf(warnIfConformanceUnavailablePreSwift6, 6);

switch (diagnosticInfo->getStatus()) {
case UnavailabilityDiagnosticInfo::Status::AlwaysUnavailable:
diags
.diagnose(ext, diag::conformance_availability_marked_unavailable, type,
proto)
.highlight(attr->getRange());
break;
case UnavailabilityDiagnosticInfo::Status::IntroducedInVersion:
diags.diagnose(ext, diag::conformance_availability_introduced_in_version,
type, proto, diagnosticInfo->getVersionedPlatform(),
*attr->Introduced);
break;
case UnavailabilityDiagnosticInfo::Status::Obsoleted:
diags
.diagnose(ext, diag::conformance_availability_obsoleted, type, proto,
diagnosticInfo->getVersionedPlatform(), *attr->Obsoleted)
.highlight(attr->getRange());
break;
}
return true;
Expand Down Expand Up @@ -3282,52 +3341,21 @@ bool swift::diagnoseExplicitUnavailability(
const ExportContext &Where,
DeclAvailabilityFlags Flags,
llvm::function_ref<void(InFlightDiagnostic &)> attachRenameFixIts) {
auto *Attr = AvailableAttr::isUnavailable(D);
if (!Attr)
auto diagnosticInfo = getExplicitUnavailabilityDiagnosticInfo(D, Where);
if (!diagnosticInfo)
return false;

// Calling unavailable code from within code with the same
// unavailability is OK -- the eventual caller can't call the
// enclosing code in the same situations it wouldn't be able to
// call this code.
if (isInsideCompatibleUnavailableDeclaration(D, Where, Attr))
return false;
auto *Attr = diagnosticInfo->getAttr();
if (Attr->getPlatformAgnosticAvailability() ==
PlatformAgnosticAvailabilityKind::UnavailableInSwift) {
if (shouldAllowReferenceToUnavailableInSwiftDeclaration(D, Where))
return false;
}

SourceLoc Loc = R.Start;

ASTContext &ctx = D->getASTContext();
auto &diags = ctx.Diags;

StringRef platform;
switch (Attr->getPlatformAgnosticAvailability()) {
case PlatformAgnosticAvailabilityKind::Deprecated:
llvm_unreachable("shouldn't see deprecations in explicit unavailability");

case PlatformAgnosticAvailabilityKind::NoAsync:
llvm_unreachable("shouldn't see noasync with explicit unavailability");

case PlatformAgnosticAvailabilityKind::None:
case PlatformAgnosticAvailabilityKind::Unavailable:
if (Attr->Platform != PlatformKind::none) {
// This was platform-specific; indicate the platform.
platform = Attr->prettyPlatformString();
break;
}
LLVM_FALLTHROUGH;

case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
// We don't want to give further detail about these.
platform = "";
break;

case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
if (shouldAllowReferenceToUnavailableInSwiftDeclaration(D, Where))
return true;

platform = "Swift";
break;
}
StringRef platform = diagnosticInfo->getPlatform();

// TODO: Consider removing this.
// ObjC keypaths components weren't checked previously, so errors are demoted
Expand Down Expand Up @@ -3372,41 +3400,22 @@ bool swift::diagnoseExplicitUnavailability(
.limitBehavior(limit);
}

switch (Attr->getVersionAvailability(ctx)) {
case AvailableVersionComparison::Available:
case AvailableVersionComparison::PotentiallyUnavailable:
llvm_unreachable("These aren't considered unavailable");

case AvailableVersionComparison::Unavailable:
if ((Attr->isLanguageVersionSpecific() ||
Attr->isPackageDescriptionVersionSpecific())
&& Attr->Introduced.has_value())
diags.diagnose(D, diag::availability_introduced_in_version, D,
(Attr->isLanguageVersionSpecific() ?
"Swift" : "PackageDescription"),
*Attr->Introduced)
switch (diagnosticInfo->getStatus()) {
case UnavailabilityDiagnosticInfo::Status::AlwaysUnavailable:
diags.diagnose(D, diag::availability_marked_unavailable, D)
.highlight(Attr->getRange());
else
diags.diagnose(D, diag::availability_marked_unavailable, D)
break;
case UnavailabilityDiagnosticInfo::Status::IntroducedInVersion:
diags
.diagnose(D, diag::availability_introduced_in_version, D,
diagnosticInfo->getVersionedPlatform(), *Attr->Introduced)
.highlight(Attr->getRange());
break;

case AvailableVersionComparison::Obsoleted:
// FIXME: Use of the platformString here is non-awesome for application
// extensions.

StringRef platformDisplayString;
if (Attr->isLanguageVersionSpecific()) {
platformDisplayString = "Swift";
} else if (Attr->isPackageDescriptionVersionSpecific()) {
platformDisplayString = "PackageDescription";
} else {
platformDisplayString = platform;
}

diags.diagnose(D, diag::availability_obsoleted, D, platformDisplayString,
*Attr->Obsoleted)
.highlight(Attr->getRange());
case UnavailabilityDiagnosticInfo::Status::Obsoleted:
diags
.diagnose(D, diag::availability_obsoleted, D,
diagnosticInfo->getVersionedPlatform(), *Attr->Obsoleted)
.highlight(Attr->getRange());
break;
}
return true;
Expand Down