Skip to content

Sema: Check the availability of conformance type witnesses #75938

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
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsCommon.def
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ WARNING(error_in_future_swift_version,none,
"%0; this is an error in the Swift %1 language mode",
(DiagnosticInfo *, unsigned))

WARNING(error_in_a_future_swift_version,none,
"%0; this will be an error in a future Swift language mode",
(DiagnosticInfo *))

// Generic disambiguation
NOTE(while_parsing_as_left_angle_bracket,none,
"while parsing this '<' as a type parameter bracket", ())
Expand Down
4 changes: 2 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3126,7 +3126,7 @@ NOTE(declared_protocol_conformance_here,none,

ERROR(witness_unavailable,none,
"unavailable %kind0 was used to satisfy a requirement of protocol %1%select{|: %2}2",
(const ValueDecl *, Identifier, StringRef))
(const ValueDecl *, const ProtocolDecl *, StringRef))

WARNING(witness_deprecated,none,
"deprecated default implementation is used to satisfy %kind0 required by "
Expand Down Expand Up @@ -6781,7 +6781,7 @@ WARNING(availability_enum_element_no_potential_warn,

ERROR(availability_protocol_requires_version,
none, "protocol %0 requires %1 to be available in %2 %3 and newer",
(Identifier, DeclName, StringRef, llvm::VersionTuple))
(const ProtocolDecl *, const ValueDecl *, StringRef, llvm::VersionTuple))

NOTE(availability_protocol_requirement_here, none,
"protocol requirement here", ())
Expand Down
9 changes: 7 additions & 2 deletions lib/AST/DiagnosticEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,13 @@ InFlightDiagnostic::limitBehaviorUntilSwiftVersion(
// in a message that this will become an error in a later Swift
// version. We do this before limiting the behavior, because
// wrapIn will result in the behavior of the wrapping diagnostic.
if (limit >= DiagnosticBehavior::Warning)
wrapIn(diag::error_in_future_swift_version, majorVersion);
if (limit >= DiagnosticBehavior::Warning) {
if (majorVersion > 6) {
wrapIn(diag::error_in_a_future_swift_version);
} else {
wrapIn(diag::error_in_future_swift_version, majorVersion);
}
}

limitBehavior(limit);
}
Expand Down
21 changes: 13 additions & 8 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ static bool isInsideCompatibleUnavailableDeclaration(
inheritsAvailabilityFromPlatform(platform, *referencedPlatform));
}

const AvailableAttr *
ExportContext::shouldDiagnoseDeclAsUnavailable(const Decl *D) const {
auto attr = AvailableAttr::isUnavailable(D);
if (!attr)
return nullptr;

if (isInsideCompatibleUnavailableDeclaration(D, *this, attr))
return nullptr;

return attr;
}

static bool shouldAllowReferenceToUnavailableInSwiftDeclaration(
const Decl *D, const ExportContext &where) {
auto *DC = where.getDeclContext();
Expand Down Expand Up @@ -2901,17 +2913,10 @@ class UnavailabilityDiagnosticInfo {
static std::optional<UnavailabilityDiagnosticInfo>
getExplicitUnavailabilityDiagnosticInfo(const Decl *decl,
const ExportContext &where) {
auto *attr = AvailableAttr::isUnavailable(decl);
auto *attr = where.shouldDiagnoseDeclAsUnavailable(decl);
if (!attr)
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(decl, where, attr))
return std::nullopt;

ASTContext &ctx = decl->getASTContext();
StringRef platform = "";
StringRef versionedPlatform = "";
Expand Down
6 changes: 6 additions & 0 deletions lib/Sema/TypeCheckAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ class ExportContext {
/// Get the ExportabilityReason for diagnostics. If this is 'None', there
/// are no restrictions on referencing unexported declarations.
std::optional<ExportabilityReason> 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.
const AvailableAttr *shouldDiagnoseDeclAsUnavailable(const Decl *decl) const;
};

/// Check if a declaration is exported as part of a module's external interface.
Expand Down
5 changes: 3 additions & 2 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1689,8 +1689,9 @@ SelfAccessKindRequest::evaluate(Evaluator &evaluator, FuncDecl *FD) const {
}

bool TypeChecker::isAvailabilitySafeForConformance(
ProtocolDecl *proto, ValueDecl *requirement, ValueDecl *witness,
DeclContext *dc, AvailabilityContext &requirementInfo) {
const ProtocolDecl *proto, const ValueDecl *requirement,
const ValueDecl *witness, const DeclContext *dc,
AvailabilityContext &requirementInfo) {

// We assume conformances in
// non-SourceFiles have already been checked for availability.
Expand Down
69 changes: 64 additions & 5 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4313,8 +4313,7 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
diags.diagnose(
diagLoc, diag::availability_protocol_requires_version,
conformance->getProtocol()->getName(),
witness->getName(),
conformance->getProtocol(), witness,
prettyPlatformString(targetPlatform(ctx.LangOpts)),
check.RequiredAvailability.getOSVersion().getLowerEndpoint());
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
Expand Down Expand Up @@ -4385,9 +4384,8 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
auto *attr = AvailableAttr::isUnavailable(witness);
EncodedDiagnosticMessage EncodedMessage(attr->Message);
diags.diagnose(diagLoc, diag::witness_unavailable,
witness, conformance->getProtocol()->getName(),
EncodedMessage.Message);
diags.diagnose(diagLoc, diag::witness_unavailable, witness,
conformance->getProtocol(), EncodedMessage.Message);
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
diags.diagnose(requirement, diag::kind_declname_declared_here,
DescriptiveDeclKind::Requirement,
Expand Down Expand Up @@ -4786,6 +4784,63 @@ static void diagnoseInvariantSelfRequirement(
.warnUntilSwiftVersion(6);
}

static bool diagnoseTypeWitnessAvailability(
NormalProtocolConformance *conformance, const TypeDecl *witness,
const AssociatedTypeDecl *assocType, const ExportContext &where) {
auto dc = conformance->getDeclContext();
auto &ctx = dc->getASTContext();
if (ctx.LangOpts.DisableAvailabilityChecking)
return false;

// In Swift 6 and earlier type witness availability diagnostics are warnings.
const unsigned warnBeforeVersion = 7;
bool shouldError =
ctx.LangOpts.EffectiveLanguageVersion.isVersionAtLeast(warnBeforeVersion);

if (auto attr = where.shouldDiagnoseDeclAsUnavailable(witness)) {
ctx.addDelayedConformanceDiag(
conformance, shouldError,
[witness, assocType, attr](NormalProtocolConformance *conformance) {
SourceLoc loc = getLocForDiagnosingWitness(conformance, witness);
EncodedDiagnosticMessage encodedMessage(attr->Message);
auto &ctx = conformance->getDeclContext()->getASTContext();
ctx.Diags
.diagnose(loc, diag::witness_unavailable, witness,
conformance->getProtocol(), encodedMessage.Message)
.warnUntilSwiftVersion(warnBeforeVersion);

emitDeclaredHereIfNeeded(ctx.Diags, loc, witness);
ctx.Diags.diagnose(assocType, diag::kind_declname_declared_here,
DescriptiveDeclKind::Requirement,
assocType->getName());
});
}

auto requiredAvailability = AvailabilityContext::alwaysAvailable();
if (!TypeChecker::isAvailabilitySafeForConformance(conformance->getProtocol(),
assocType, witness, dc,
requiredAvailability)) {
auto requiredRange = requiredAvailability.getOSVersion();
ctx.addDelayedConformanceDiag(
conformance, shouldError,
[witness, requiredRange](NormalProtocolConformance *conformance) {
SourceLoc loc = getLocForDiagnosingWitness(conformance, witness);
auto &ctx = conformance->getDeclContext()->getASTContext();
ctx.Diags
.diagnose(loc, diag::availability_protocol_requires_version,
conformance->getProtocol(), witness,
prettyPlatformString(targetPlatform(ctx.LangOpts)),
requiredRange.getLowerEndpoint())
.warnUntilSwiftVersion(warnBeforeVersion);

emitDeclaredHereIfNeeded(ctx.Diags, loc, witness);
});
return true;
}

return false;
}

/// Check whether the type witnesses satisfy the protocol's requirement
/// signature. Also checks access level of type witnesses and availiability
/// of associated conformances.
Expand Down Expand Up @@ -4908,6 +4963,10 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
}
}

// The type witness must be as available as the associated type.
if (auto witness = type->getAnyNominal())
diagnoseTypeWitnessAvailability(conformance, witness, assocType, where);

// Make sure any associated type witnesses don't make reference to a
// type we can't emit metadata for, or we're going to have trouble at
// runtime.
Expand Down
8 changes: 4 additions & 4 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -990,10 +990,10 @@ bool diagnoseConformanceExportability(SourceLoc loc,
/// is sufficient to safely conform to the requirement in the context
/// the provided conformance. On return, requiredAvailability holds th
/// availability levels required for conformance.
bool
isAvailabilitySafeForConformance(ProtocolDecl *proto, ValueDecl *requirement,
ValueDecl *witness, DeclContext *dc,
AvailabilityContext &requiredAvailability);
bool isAvailabilitySafeForConformance(
const ProtocolDecl *proto, const ValueDecl *requirement,
const ValueDecl *witness, const DeclContext *dc,
AvailabilityContext &requiredAvailability);

/// Returns an over-approximation of the range of operating system versions
/// that could the passed-in location could be executing upon for
Expand Down
168 changes: 168 additions & 0 deletions test/decl/protocol/associated_type_witness_availability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// RUN: %swift -typecheck -verify -target %target-cpu-apple-macosx12 %s
// REQUIRES: OS=macosx

protocol ProtoWithAssocType {
associatedtype A // expected-note * {{requirement 'A' declared here}}
}

struct ConformsToProtoWithAssocType_WitnessOld: ProtoWithAssocType {
@available(macOS 11, *)
struct A {} // Ok, A is less available than its parent but more available than the deployment target
}

struct ConformsToProtoWithAssocType_WitnessSame: ProtoWithAssocType {
@available(macOS 12, *)
struct A {} // Ok, A is less available than its parent but available at the deployment target
}

struct ConformsToProtoWithAssocType_WitnessTooNew: ProtoWithAssocType {
@available(macOS 13, *)
struct A {} // expected-warning {{protocol 'ProtoWithAssocType' requires 'A' to be available in macOS 12 and newer; this will be an error in a future Swift language mode}}
}

struct ConformsToProtoWithAssocTypeInExtension_WitnessOld {}

extension ConformsToProtoWithAssocTypeInExtension_WitnessOld: ProtoWithAssocType {
@available(macOS 11, *)
struct A {} // Ok, A is less available than its parent but more available than the deployment target
}

struct ConformsToProtoWithAssocTypeInExtension_WitnessSame {}

extension ConformsToProtoWithAssocTypeInExtension_WitnessSame: ProtoWithAssocType {
@available(macOS 12, *)
struct A {} // Ok, A is less available than its parent but available at the deployment target
}

struct ConformsToProtoWithAssocTypeInExtension_WitnessTooNew {}

extension ConformsToProtoWithAssocTypeInExtension_WitnessTooNew: ProtoWithAssocType {
@available(macOS 13, *)
struct A {} // expected-warning {{protocol 'ProtoWithAssocType' requires 'A' to be available in macOS 12 and newer; this will be an error in a future Swift language mode}}
}

@available(macOS 13, *)
struct ConformsToProtoWithAssocType_NewerConformance: ProtoWithAssocType {
struct A {} // Ok, A is as available as the conformance
}

struct ConformsToProtoWithAssocTypeInExtension_NewerConformance {}

@available(macOS 13, *)
extension ConformsToProtoWithAssocTypeInExtension_NewerConformance: ProtoWithAssocType {
struct A {} // Ok, A is as available as the conformance
}

@available(macOS 13, *)
struct ConformsToProtoWithAssocType_NewerAndWitnessTooNew: ProtoWithAssocType {
@available(macOS 14, *)
struct A {} // expected-warning {{protocol 'ProtoWithAssocType' requires 'A' to be available in macOS 13 and newer; this will be an error in a future Swift language mode}}
}

struct ConformsToProtoWithAssocType_WitnessUnavailable: ProtoWithAssocType {
@available(macOS, unavailable)
struct A {} // expected-warning {{unavailable struct 'A' was used to satisfy a requirement of protocol 'ProtoWithAssocType'; this will be an error in a future Swift language mode}}
}

struct ConformsToProtoWithAssocType_WitnessUnavailableInExtension: ProtoWithAssocType {
// expected-warning@-1 {{unavailable struct 'A' was used to satisfy a requirement of protocol 'ProtoWithAssocType'; this will be an error in a future Swift language mode}}
}

extension ConformsToProtoWithAssocType_WitnessUnavailableInExtension {
@available(macOS, unavailable)
struct A {} // expected-note {{'A' declared here}}
}

struct ConformsToProtoWithAssocType_WitnessInUnavailableExtension: ProtoWithAssocType {
// expected-warning@-1 {{unavailable struct 'A' was used to satisfy a requirement of protocol 'ProtoWithAssocType'; this will be an error in a future Swift language mode}}
}

@available(macOS, unavailable)
extension ConformsToProtoWithAssocType_WitnessInUnavailableExtension {
struct A {} // expected-note {{'A' declared here}}
}

struct ConformsToProtoWithAssocType_WitnessUnavailableMessage: ProtoWithAssocType {
@available(*, unavailable, message: "Just don't")
struct A {} // expected-warning {{unavailable struct 'A' was used to satisfy a requirement of protocol 'ProtoWithAssocType': Just don't; this will be an error in a future Swift language mode}}
}

struct ConformsToProtoWithAssocType_WitnessObsoleted: ProtoWithAssocType {
@available(macOS, obsoleted: 11)
struct A {} // expected-warning {{unavailable struct 'A' was used to satisfy a requirement of protocol 'ProtoWithAssocType'; this will be an error in a future Swift language mode}}
}

struct ConformsToProtoWithAssocType_WitnessIntroInSwift99: ProtoWithAssocType {
@available(swift, introduced: 99)
struct A {} // expected-warning {{unavailable struct 'A' was used to satisfy a requirement of protocol 'ProtoWithAssocType'; this will be an error in a future Swift language mode}}
}

@available(macOS, unavailable)
struct ConformsToProtoWithAssocType_Unavailable: ProtoWithAssocType {
struct A {} // Ok, the conformance is unavailable too.
}

@available(macOS, unavailable)
struct ConformsToProtoWithAssocType_WitnessAndConformanceUnavailable: ProtoWithAssocType {
@available(macOS, unavailable)
struct A {} // Ok, the conformance is unavailable too.
}

protocol ProtoWithNewAssocType {
@available(macOS 13, *)
associatedtype A
}

struct ConformsToProtoWithNewAssocType_WitnessOld: ProtoWithNewAssocType {
struct A {} // Ok, A has always been available
}

struct ConformsToProtoWithNewAssocType_WitnessSame: ProtoWithNewAssocType {
@available(macOS 13, *)
struct A {} // Ok, A is as available as the associated type requirement
}

struct ConformsToProtoWithNewAssocType_WitnessTooNew: ProtoWithNewAssocType {
@available(macOS 14, *)
struct A {} // expected-warning {{protocol 'ProtoWithNewAssocType' requires 'A' to be available in macOS 13 and newer; this will be an error in a future Swift language mode}}
}

protocol ProtoWithAssocTypeAndReq {
associatedtype A

@available(macOS 13, *)
func req(_ a: A)
}

@available(macOS 11, *)
struct InferredOld {} // Ok, InferredOld is less available than its parent but more available than the deployment target

struct ConformsToProtoWithAssocTypeAndReq_InferredWitnessOld: ProtoWithAssocTypeAndReq {

func req(_ a: InferredOld) {}
}

@available(macOS 12, *)
struct InferredSame {} // Ok, InferredSame is less available than its parent but available at the deployment target

struct ConformsToProtoWithAssocTypeAndReq_InferredWitnessSame: ProtoWithAssocTypeAndReq {
func req(_ a: InferredSame) {}
}

@available(macOS 13, *)
struct InferredTooNew {} // expected-note {{'InferredTooNew' declared here}}

struct ConformsToProtoWithAssocTypeAndReq_InferredWitnessTooNew: ProtoWithAssocTypeAndReq {
// expected-warning@-1 {{protocol 'ProtoWithAssocTypeAndReq' requires 'InferredTooNew' to be available in macOS 12 and newer; this will be an error in a future Swift language mode}}

@available(macOS 13, *)
func req(_ a: InferredTooNew) {}
}

@available(macOS, unavailable)
struct ConformsToProtoWithAssocTypeAndReq_InferredUnavailable: ProtoWithAssocTypeAndReq {
@available(macOS, unavailable)
struct InferredUnavailable {}

func req(_ a: InferredUnavailable) {}
}
Loading