Skip to content

Commit 2824391

Browse files
committed
Sema: Check the availability of conformance type witnesses.
The type satisfying a protocol requirement must be at least as available as the associated type for the requirement. Resolves rdar://134093006.
1 parent 5a093c7 commit 2824391

File tree

5 files changed

+232
-9
lines changed

5 files changed

+232
-9
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6834,6 +6834,35 @@ ERROR(conformance_availability_only_version_newer, none,
68346834
"conformance of %0 to %1 is only available in %2 %3 or newer",
68356835
(Type, Type, StringRef, llvm::VersionTuple))
68366836

6837+
// Conformance checking type witness availability diagnostics
6838+
6839+
ERROR(type_witness_availability_unavailable, none,
6840+
"%kindonly0 witnessing requirement %1 of %2 is unavailable"
6841+
"%select{ in %4|}3%select{|: %5}5",
6842+
(const ValueDecl *, const AssociatedTypeDecl *, const ProtocolDecl *,
6843+
bool, StringRef, StringRef))
6844+
6845+
NOTE(type_witness_availability_marked_unavailable, none,
6846+
"%kindonly0 witnessing requirement %1 of %2 has been explicitly marked "
6847+
"unavailable here",
6848+
(const ValueDecl *, const AssociatedTypeDecl *, const ProtocolDecl *))
6849+
6850+
NOTE(type_witness_availability_introduced_in_version, none,
6851+
"%kindonly0 witnessing requirement %1 of %2 was introduced in %3 %4",
6852+
(const ValueDecl *, const AssociatedTypeDecl *, const ProtocolDecl *,
6853+
StringRef, llvm::VersionTuple))
6854+
6855+
NOTE(type_witness_availability_obsoleted, none,
6856+
"%kindonly0 witnessing requirement %1 of %2 was obsoleted in %3 %4",
6857+
(const ValueDecl *, const AssociatedTypeDecl *, const ProtocolDecl *,
6858+
StringRef, llvm::VersionTuple))
6859+
6860+
ERROR(type_witness_availability_only_version_newer, none,
6861+
"%kindonly0 witnessing requirement %1 of %2 is "
6862+
"only available in %3 %4 or newer",
6863+
(const ValueDecl *, const AssociatedTypeDecl *, const ProtocolDecl *,
6864+
StringRef, llvm::VersionTuple))
6865+
68376866
//------------------------------------------------------------------------------
68386867
// MARK: @discardableResult
68396868
//------------------------------------------------------------------------------

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,20 @@ computeExportContextBits(ASTContext &Ctx, Decl *D, bool *spi, bool *implicit,
215215
}
216216
}
217217

218+
static AvailabilityContext availabilityAtLocation(SourceLoc loc,
219+
const DeclContext *dc) {
220+
auto &ctx = dc->getASTContext();
221+
return ctx.LangOpts.DisableAvailabilityChecking
222+
? AvailabilityContext::alwaysAvailable()
223+
: TypeChecker::overApproximateAvailabilityAtLocation(loc, dc);
224+
}
225+
218226
ExportContext ExportContext::forDeclSignature(Decl *D) {
219227
auto &Ctx = D->getASTContext();
220228

221229
auto *DC = D->getInnermostDeclContext();
222230
auto fragileKind = DC->getFragileFunctionKind();
223-
auto runningOSVersion =
224-
(Ctx.LangOpts.DisableAvailabilityChecking
225-
? AvailabilityContext::alwaysAvailable()
226-
: TypeChecker::overApproximateAvailabilityAtLocation(D->getLoc(), DC));
231+
auto runningOSVersion = availabilityAtLocation(D->getLoc(), DC);
227232
bool spi = Ctx.LangOpts.LibraryLevel == LibraryLevel::SPI;
228233
bool implicit = false;
229234
bool deprecated = false;
@@ -248,11 +253,7 @@ ExportContext ExportContext::forFunctionBody(DeclContext *DC, SourceLoc loc) {
248253
auto &Ctx = DC->getASTContext();
249254

250255
auto fragileKind = DC->getFragileFunctionKind();
251-
auto runningOSVersion =
252-
(Ctx.LangOpts.DisableAvailabilityChecking
253-
? AvailabilityContext::alwaysAvailable()
254-
: TypeChecker::overApproximateAvailabilityAtLocation(loc, DC));
255-
256+
auto runningOSVersion = availabilityAtLocation(loc, DC);
256257
bool spi = Ctx.LangOpts.LibraryLevel == LibraryLevel::SPI;
257258
bool implicit = false;
258259
bool deprecated = false;
@@ -294,6 +295,14 @@ ExportContext ExportContext::withExported(bool exported) const {
294295
return copy;
295296
}
296297

298+
ExportContext ExportContext::withRefinedAvailability(const Decl *decl) const {
299+
auto copy = *this;
300+
auto runningOSVersion =
301+
availabilityAtLocation(decl->getLoc(), decl->getInnermostDeclContext());
302+
copy.RunningOSVersion.intersectWith(runningOSVersion);
303+
return copy;
304+
}
305+
297306
std::optional<PlatformKind> ExportContext::getUnavailablePlatformKind() const {
298307
if (Unavailable)
299308
return PlatformKind(Platform);
@@ -4536,6 +4545,94 @@ swift::diagnoseSubstitutionMapAvailability(SourceLoc loc,
45364545
return hadAnyIssues;
45374546
}
45384547

4548+
static bool diagnoseExplicitUnavailabilityForTypeWitness(
4549+
const TypeDecl *witness, const AssociatedTypeDecl *assocType,
4550+
const ExportContext &where) {
4551+
auto diagnosticInfo = getExplicitUnavailabilityDiagnosticInfo(witness, where);
4552+
if (!diagnosticInfo)
4553+
return false;
4554+
4555+
ASTContext &ctx = witness->getASTContext();
4556+
auto &diags = ctx.Diags;
4557+
auto proto = assocType->getProtocol();
4558+
StringRef platform = diagnosticInfo->getPlatform();
4559+
const AvailableAttr *attr = diagnosticInfo->getAttr();
4560+
4561+
EncodedDiagnosticMessage EncodedMessage(attr->Message);
4562+
diags.diagnose(witness, diag::type_witness_availability_unavailable, witness,
4563+
assocType, proto, platform.empty(), platform,
4564+
EncodedMessage.Message);
4565+
4566+
switch (diagnosticInfo->getStatus()) {
4567+
case UnavailabilityDiagnosticInfo::Status::AlwaysUnavailable:
4568+
diags
4569+
.diagnose(witness, diag::type_witness_availability_marked_unavailable,
4570+
witness, assocType, proto)
4571+
.highlight(attr->getRange());
4572+
break;
4573+
case UnavailabilityDiagnosticInfo::Status::IntroducedInVersion:
4574+
diags.diagnose(witness,
4575+
diag::type_witness_availability_introduced_in_version,
4576+
witness, assocType, proto,
4577+
diagnosticInfo->getVersionedPlatform(), *attr->Introduced);
4578+
break;
4579+
case UnavailabilityDiagnosticInfo::Status::Obsoleted:
4580+
diags
4581+
.diagnose(witness, diag::type_witness_availability_obsoleted, witness,
4582+
assocType, proto, diagnosticInfo->getVersionedPlatform(),
4583+
*attr->Obsoleted)
4584+
.highlight(attr->getRange());
4585+
break;
4586+
}
4587+
return true;
4588+
}
4589+
4590+
static void diagnosePotentialUnavailabilityForTypeWitness(
4591+
const TypeDecl *witness, const AssociatedTypeDecl *assocType,
4592+
const DeclContext *dc, const UnavailabilityReason &reason) {
4593+
auto &ctx = dc->getASTContext();
4594+
auto proto = assocType->getProtocol();
4595+
auto requiredRange = reason.getRequiredOSVersionRange();
4596+
auto loc = witness->getLoc();
4597+
4598+
{
4599+
auto err = ctx.Diags.diagnose(
4600+
loc, diag::type_witness_availability_only_version_newer, witness,
4601+
assocType, proto, prettyPlatformString(targetPlatform(ctx.LangOpts)),
4602+
requiredRange.getLowerEndpoint());
4603+
4604+
// Direct a fixit to the error if an existing guard is nearly-correct
4605+
if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc, requiredRange,
4606+
ctx, err))
4607+
return;
4608+
}
4609+
4610+
fixAvailability(loc, dc, requiredRange, ctx);
4611+
}
4612+
4613+
bool swift::diagnoseTypeWitnessAvailability(const TypeDecl *witness,
4614+
const AssociatedTypeDecl *assocType,
4615+
const ExportContext &where) {
4616+
assert(!where.isImplicit());
4617+
4618+
if (witness->isInvalid())
4619+
return false;
4620+
4621+
if (diagnoseExplicitUnavailabilityForTypeWitness(witness, assocType, where))
4622+
return true;
4623+
4624+
auto dc = where.getDeclContext();
4625+
auto maybeUnavail = TypeChecker::checkDeclarationAvailability(
4626+
witness, where.withRefinedAvailability(assocType));
4627+
if (maybeUnavail.has_value()) {
4628+
diagnosePotentialUnavailabilityForTypeWitness(witness, assocType, dc,
4629+
*maybeUnavail);
4630+
return true;
4631+
}
4632+
4633+
return false;
4634+
}
4635+
45394636
/// Should we warn that \p decl needs an explicit availability annotation
45404637
/// in -require-explicit-availability mode?
45414638
static bool declNeedsExplicitAvailability(const Decl *decl) {

lib/Sema/TypeCheckAvailability.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ class ExportContext {
153153
/// That is, this will perform a 'bitwise and' on the 'exported' bit.
154154
ExportContext withExported(bool exported) const;
155155

156+
/// Produce a new context with the same properties as this one, except the
157+
/// availability of the resulting context is constrained by the availability
158+
/// of \p decl if it is less available.
159+
ExportContext withRefinedAvailability(const Decl *decl) const;
160+
156161
DeclContext *getDeclContext() const { return DC; }
157162

158163
AvailabilityContext getAvailabilityContext() const {
@@ -239,6 +244,12 @@ bool diagnoseSubstitutionMapAvailability(
239244
bool warnIfConformanceUnavailablePreSwift6 = false,
240245
bool suppressParameterizationCheckForOptional = false);
241246

247+
/// Diagnoses use of unavailable types as witnesses for associated type
248+
/// requirements. Returns true if a diagnostic was emitted.
249+
bool diagnoseTypeWitnessAvailability(const TypeDecl *witness,
250+
const AssociatedTypeDecl *assocType,
251+
const ExportContext &context);
252+
242253
/// Diagnose uses of unavailable declarations. Returns true if a diagnostic
243254
/// was emitted.
244255
bool diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4897,6 +4897,9 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
48974897
ctx.addDelayedConformanceDiag(conformance, false,
48984898
DiagnoseUsableFromInline(typeDecl));
48994899
}
4900+
4901+
// The type witness must be as available as the associated type.
4902+
diagnoseTypeWitnessAvailability(typeDecl, assocType, where);
49004903
}
49014904

49024905
// Make sure any associated type witnesses don't make reference to a
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %swift -typecheck -verify -target %target-cpu-apple-macosx12 %s
2+
// REQUIRES: OS=macosx
3+
4+
protocol ProtoWithAssocType {
5+
associatedtype A
6+
}
7+
8+
struct ConformsToProtoWithAssocType_WitnessOld: ProtoWithAssocType {
9+
@available(macOS 11, *)
10+
struct A {} // Ok, A is less available than its parent but more available than the deployment target
11+
}
12+
13+
struct ConformsToProtoWithAssocType_WitnessSame: ProtoWithAssocType {
14+
@available(macOS 12, *)
15+
struct A {} // Ok, A is less available than its parent but available at the deployment target
16+
}
17+
18+
struct ConformsToProtoWithAssocType_WitnessTooNew: ProtoWithAssocType {
19+
@available(macOS 13, *)
20+
struct A {} // expected-error {{struct witnessing requirement 'A' of 'ProtoWithAssocType' is only available in macOS 13 or newer}}
21+
}
22+
23+
@available(macOS 13, *)
24+
struct ConformsToProtoWithAssocType_Newer: ProtoWithAssocType {
25+
struct A {} // Ok, A is as available as the conformance
26+
}
27+
28+
@available(macOS 13, *)
29+
struct ConformsToProtoWithAssocType_NewerAndWitnessTooNew: ProtoWithAssocType {
30+
@available(macOS 14, *)
31+
struct A {} // expected-error {{struct witnessing requirement 'A' of 'ProtoWithAssocType' is only available in macOS 14 or newer}}
32+
}
33+
34+
struct ConformsToProtoWithAssocType_WitnessUnavailable: ProtoWithAssocType {
35+
@available(macOS, unavailable)
36+
struct A {} // expected-error {{struct witnessing requirement 'A' of 'ProtoWithAssocType' is unavailable in macOS}}
37+
// expected-note@-1 {{struct witnessing requirement 'A' of 'ProtoWithAssocType' has been explicitly marked unavailable here}}
38+
}
39+
40+
struct ConformsToProtoWithAssocType_WitnessUnavailableMessage: ProtoWithAssocType {
41+
@available(*, unavailable, message: "Just don't")
42+
struct A {} // expected-error {{struct witnessing requirement 'A' of 'ProtoWithAssocType' is unavailable: Just don't}}
43+
// expected-note@-1 {{struct witnessing requirement 'A' of 'ProtoWithAssocType' has been explicitly marked unavailable here}}
44+
}
45+
46+
struct ConformsToProtoWithAssocType_WitnessObsoleted: ProtoWithAssocType {
47+
@available(macOS, obsoleted: 11)
48+
struct A {} // expected-error {{struct witnessing requirement 'A' of 'ProtoWithAssocType' is unavailable in macOS}}
49+
// expected-note@-1 {{struct witnessing requirement 'A' of 'ProtoWithAssocType' was obsoleted in macOS 11}}
50+
}
51+
52+
struct ConformsToProtoWithAssocType_WitnessIntroInSwift99: ProtoWithAssocType {
53+
@available(swift, introduced: 99)
54+
struct A {} // expected-error {{struct witnessing requirement 'A' of 'ProtoWithAssocType' is unavailable}}
55+
// expected-note@-1 {{struct witnessing requirement 'A' of 'ProtoWithAssocType' was introduced in Swift 99}}
56+
}
57+
58+
59+
@available(macOS, unavailable)
60+
struct ConformsToProtoWithAssocType_WitnessAndConformanceUnavailable: ProtoWithAssocType {
61+
@available(macOS, unavailable)
62+
struct A {} // Ok, the conformance is unavailable too.
63+
}
64+
65+
66+
protocol ProtoWithNewAssocType {
67+
@available(macOS 13, *)
68+
associatedtype A
69+
}
70+
71+
struct ConformsToProtoWithNewAssocType_WitnessOld: ProtoWithNewAssocType {
72+
struct A {} // Ok, A has always been available
73+
}
74+
75+
struct ConformsToProtoWithNewAssocType_WitnessSame: ProtoWithNewAssocType {
76+
@available(macOS 13, *)
77+
struct A {} // Ok, A is as available as the associated type requirement
78+
}
79+
80+
struct ConformsToProtoWithNewAssocType_WitnessTooNew: ProtoWithNewAssocType {
81+
@available(macOS 14, *)
82+
struct A {} // expected-error {{struct witnessing requirement 'A' of 'ProtoWithNewAssocType' is only available in macOS 14 or newer}}
83+
}

0 commit comments

Comments
 (0)