Skip to content

Commit 30b6cca

Browse files
authored
Merge pull request swiftlang#75938 from tshortli/associated-type-witness-availability
Sema: Check the availability of conformance type witnesses
2 parents ee17ef1 + 4420ea8 commit 30b6cca

10 files changed

+293
-23
lines changed

include/swift/AST/DiagnosticsCommon.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ WARNING(error_in_future_swift_version,none,
5151
"%0; this is an error in the Swift %1 language mode",
5252
(DiagnosticInfo *, unsigned))
5353

54+
WARNING(error_in_a_future_swift_version,none,
55+
"%0; this will be an error in a future Swift language mode",
56+
(DiagnosticInfo *))
57+
5458
// Generic disambiguation
5559
NOTE(while_parsing_as_left_angle_bracket,none,
5660
"while parsing this '<' as a type parameter bracket", ())

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3126,7 +3126,7 @@ NOTE(declared_protocol_conformance_here,none,
31263126

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

31313131
WARNING(witness_deprecated,none,
31323132
"deprecated default implementation is used to satisfy %kind0 required by "
@@ -6781,7 +6781,7 @@ WARNING(availability_enum_element_no_potential_warn,
67816781

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

67866786
NOTE(availability_protocol_requirement_here, none,
67876787
"protocol requirement here", ())

lib/AST/DiagnosticEngine.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,13 @@ InFlightDiagnostic::limitBehaviorUntilSwiftVersion(
405405
// in a message that this will become an error in a later Swift
406406
// version. We do this before limiting the behavior, because
407407
// wrapIn will result in the behavior of the wrapping diagnostic.
408-
if (limit >= DiagnosticBehavior::Warning)
409-
wrapIn(diag::error_in_future_swift_version, majorVersion);
408+
if (limit >= DiagnosticBehavior::Warning) {
409+
if (majorVersion > 6) {
410+
wrapIn(diag::error_in_a_future_swift_version);
411+
} else {
412+
wrapIn(diag::error_in_future_swift_version, majorVersion);
413+
}
414+
}
410415

411416
limitBehavior(limit);
412417
}

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,18 @@ static bool isInsideCompatibleUnavailableDeclaration(
366366
inheritsAvailabilityFromPlatform(platform, *referencedPlatform));
367367
}
368368

369+
const AvailableAttr *
370+
ExportContext::shouldDiagnoseDeclAsUnavailable(const Decl *D) const {
371+
auto attr = AvailableAttr::isUnavailable(D);
372+
if (!attr)
373+
return nullptr;
374+
375+
if (isInsideCompatibleUnavailableDeclaration(D, *this, attr))
376+
return nullptr;
377+
378+
return attr;
379+
}
380+
369381
static bool shouldAllowReferenceToUnavailableInSwiftDeclaration(
370382
const Decl *D, const ExportContext &where) {
371383
auto *DC = where.getDeclContext();
@@ -2901,17 +2913,10 @@ class UnavailabilityDiagnosticInfo {
29012913
static std::optional<UnavailabilityDiagnosticInfo>
29022914
getExplicitUnavailabilityDiagnosticInfo(const Decl *decl,
29032915
const ExportContext &where) {
2904-
auto *attr = AvailableAttr::isUnavailable(decl);
2916+
auto *attr = where.shouldDiagnoseDeclAsUnavailable(decl);
29052917
if (!attr)
29062918
return std::nullopt;
29072919

2908-
// Calling unavailable code from within code with the same
2909-
// unavailability is OK -- the eventual caller can't call the
2910-
// enclosing code in the same situations it wouldn't be able to
2911-
// call this code.
2912-
if (isInsideCompatibleUnavailableDeclaration(decl, where, attr))
2913-
return std::nullopt;
2914-
29152920
ASTContext &ctx = decl->getASTContext();
29162921
StringRef platform = "";
29172922
StringRef versionedPlatform = "";

lib/Sema/TypeCheckAvailability.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,12 @@ class ExportContext {
187187
/// Get the ExportabilityReason for diagnostics. If this is 'None', there
188188
/// are no restrictions on referencing unexported declarations.
189189
std::optional<ExportabilityReason> getExportabilityReason() const;
190+
191+
/// If \p decl is unconditionally unavailable in this context, and the context
192+
/// is not also unavailable in the same way, then this returns the specific
193+
/// `@available` attribute that makes the decl unavailable. Otherwise, returns
194+
/// nullptr.
195+
const AvailableAttr *shouldDiagnoseDeclAsUnavailable(const Decl *decl) const;
190196
};
191197

192198
/// Check if a declaration is exported as part of a module's external interface.

lib/Sema/TypeCheckDecl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,8 +1689,9 @@ SelfAccessKindRequest::evaluate(Evaluator &evaluator, FuncDecl *FD) const {
16891689
}
16901690

16911691
bool TypeChecker::isAvailabilitySafeForConformance(
1692-
ProtocolDecl *proto, ValueDecl *requirement, ValueDecl *witness,
1693-
DeclContext *dc, AvailabilityContext &requirementInfo) {
1692+
const ProtocolDecl *proto, const ValueDecl *requirement,
1693+
const ValueDecl *witness, const DeclContext *dc,
1694+
AvailabilityContext &requirementInfo) {
16941695

16951696
// We assume conformances in
16961697
// non-SourceFiles have already been checked for availability.

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4313,8 +4313,7 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
43134313
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
43144314
diags.diagnose(
43154315
diagLoc, diag::availability_protocol_requires_version,
4316-
conformance->getProtocol()->getName(),
4317-
witness->getName(),
4316+
conformance->getProtocol(), witness,
43184317
prettyPlatformString(targetPlatform(ctx.LangOpts)),
43194318
check.RequiredAvailability.getOSVersion().getLowerEndpoint());
43204319
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
@@ -4385,9 +4384,8 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
43854384
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
43864385
auto *attr = AvailableAttr::isUnavailable(witness);
43874386
EncodedDiagnosticMessage EncodedMessage(attr->Message);
4388-
diags.diagnose(diagLoc, diag::witness_unavailable,
4389-
witness, conformance->getProtocol()->getName(),
4390-
EncodedMessage.Message);
4387+
diags.diagnose(diagLoc, diag::witness_unavailable, witness,
4388+
conformance->getProtocol(), EncodedMessage.Message);
43914389
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
43924390
diags.diagnose(requirement, diag::kind_declname_declared_here,
43934391
DescriptiveDeclKind::Requirement,
@@ -4786,6 +4784,63 @@ static void diagnoseInvariantSelfRequirement(
47864784
.warnUntilSwiftVersion(6);
47874785
}
47884786

4787+
static bool diagnoseTypeWitnessAvailability(
4788+
NormalProtocolConformance *conformance, const TypeDecl *witness,
4789+
const AssociatedTypeDecl *assocType, const ExportContext &where) {
4790+
auto dc = conformance->getDeclContext();
4791+
auto &ctx = dc->getASTContext();
4792+
if (ctx.LangOpts.DisableAvailabilityChecking)
4793+
return false;
4794+
4795+
// In Swift 6 and earlier type witness availability diagnostics are warnings.
4796+
const unsigned warnBeforeVersion = 7;
4797+
bool shouldError =
4798+
ctx.LangOpts.EffectiveLanguageVersion.isVersionAtLeast(warnBeforeVersion);
4799+
4800+
if (auto attr = where.shouldDiagnoseDeclAsUnavailable(witness)) {
4801+
ctx.addDelayedConformanceDiag(
4802+
conformance, shouldError,
4803+
[witness, assocType, attr](NormalProtocolConformance *conformance) {
4804+
SourceLoc loc = getLocForDiagnosingWitness(conformance, witness);
4805+
EncodedDiagnosticMessage encodedMessage(attr->Message);
4806+
auto &ctx = conformance->getDeclContext()->getASTContext();
4807+
ctx.Diags
4808+
.diagnose(loc, diag::witness_unavailable, witness,
4809+
conformance->getProtocol(), encodedMessage.Message)
4810+
.warnUntilSwiftVersion(warnBeforeVersion);
4811+
4812+
emitDeclaredHereIfNeeded(ctx.Diags, loc, witness);
4813+
ctx.Diags.diagnose(assocType, diag::kind_declname_declared_here,
4814+
DescriptiveDeclKind::Requirement,
4815+
assocType->getName());
4816+
});
4817+
}
4818+
4819+
auto requiredAvailability = AvailabilityContext::alwaysAvailable();
4820+
if (!TypeChecker::isAvailabilitySafeForConformance(conformance->getProtocol(),
4821+
assocType, witness, dc,
4822+
requiredAvailability)) {
4823+
auto requiredRange = requiredAvailability.getOSVersion();
4824+
ctx.addDelayedConformanceDiag(
4825+
conformance, shouldError,
4826+
[witness, requiredRange](NormalProtocolConformance *conformance) {
4827+
SourceLoc loc = getLocForDiagnosingWitness(conformance, witness);
4828+
auto &ctx = conformance->getDeclContext()->getASTContext();
4829+
ctx.Diags
4830+
.diagnose(loc, diag::availability_protocol_requires_version,
4831+
conformance->getProtocol(), witness,
4832+
prettyPlatformString(targetPlatform(ctx.LangOpts)),
4833+
requiredRange.getLowerEndpoint())
4834+
.warnUntilSwiftVersion(warnBeforeVersion);
4835+
4836+
emitDeclaredHereIfNeeded(ctx.Diags, loc, witness);
4837+
});
4838+
return true;
4839+
}
4840+
4841+
return false;
4842+
}
4843+
47894844
/// Check whether the type witnesses satisfy the protocol's requirement
47904845
/// signature. Also checks access level of type witnesses and availiability
47914846
/// of associated conformances.
@@ -4908,6 +4963,10 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
49084963
}
49094964
}
49104965

4966+
// The type witness must be as available as the associated type.
4967+
if (auto witness = type->getAnyNominal())
4968+
diagnoseTypeWitnessAvailability(conformance, witness, assocType, where);
4969+
49114970
// Make sure any associated type witnesses don't make reference to a
49124971
// type we can't emit metadata for, or we're going to have trouble at
49134972
// runtime.

lib/Sema/TypeChecker.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -990,10 +990,10 @@ bool diagnoseConformanceExportability(SourceLoc loc,
990990
/// is sufficient to safely conform to the requirement in the context
991991
/// the provided conformance. On return, requiredAvailability holds th
992992
/// availability levels required for conformance.
993-
bool
994-
isAvailabilitySafeForConformance(ProtocolDecl *proto, ValueDecl *requirement,
995-
ValueDecl *witness, DeclContext *dc,
996-
AvailabilityContext &requiredAvailability);
993+
bool isAvailabilitySafeForConformance(
994+
const ProtocolDecl *proto, const ValueDecl *requirement,
995+
const ValueDecl *witness, const DeclContext *dc,
996+
AvailabilityContext &requiredAvailability);
997997

998998
/// Returns an over-approximation of the range of operating system versions
999999
/// that could the passed-in location could be executing upon for
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// RUN: %swift -typecheck -verify -target %target-cpu-apple-macosx12 %s
2+
// REQUIRES: OS=macosx
3+
4+
protocol ProtoWithAssocType {
5+
associatedtype A // expected-note * {{requirement 'A' declared here}}
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-warning {{protocol 'ProtoWithAssocType' requires 'A' to be available in macOS 12 and newer; this will be an error in a future Swift language mode}}
21+
}
22+
23+
struct ConformsToProtoWithAssocTypeInExtension_WitnessOld {}
24+
25+
extension ConformsToProtoWithAssocTypeInExtension_WitnessOld: ProtoWithAssocType {
26+
@available(macOS 11, *)
27+
struct A {} // Ok, A is less available than its parent but more available than the deployment target
28+
}
29+
30+
struct ConformsToProtoWithAssocTypeInExtension_WitnessSame {}
31+
32+
extension ConformsToProtoWithAssocTypeInExtension_WitnessSame: ProtoWithAssocType {
33+
@available(macOS 12, *)
34+
struct A {} // Ok, A is less available than its parent but available at the deployment target
35+
}
36+
37+
struct ConformsToProtoWithAssocTypeInExtension_WitnessTooNew {}
38+
39+
extension ConformsToProtoWithAssocTypeInExtension_WitnessTooNew: ProtoWithAssocType {
40+
@available(macOS 13, *)
41+
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}}
42+
}
43+
44+
@available(macOS 13, *)
45+
struct ConformsToProtoWithAssocType_NewerConformance: ProtoWithAssocType {
46+
struct A {} // Ok, A is as available as the conformance
47+
}
48+
49+
struct ConformsToProtoWithAssocTypeInExtension_NewerConformance {}
50+
51+
@available(macOS 13, *)
52+
extension ConformsToProtoWithAssocTypeInExtension_NewerConformance: ProtoWithAssocType {
53+
struct A {} // Ok, A is as available as the conformance
54+
}
55+
56+
@available(macOS 13, *)
57+
struct ConformsToProtoWithAssocType_NewerAndWitnessTooNew: ProtoWithAssocType {
58+
@available(macOS 14, *)
59+
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}}
60+
}
61+
62+
struct ConformsToProtoWithAssocType_WitnessUnavailable: ProtoWithAssocType {
63+
@available(macOS, unavailable)
64+
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}}
65+
}
66+
67+
struct ConformsToProtoWithAssocType_WitnessUnavailableInExtension: ProtoWithAssocType {
68+
// 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}}
69+
}
70+
71+
extension ConformsToProtoWithAssocType_WitnessUnavailableInExtension {
72+
@available(macOS, unavailable)
73+
struct A {} // expected-note {{'A' declared here}}
74+
}
75+
76+
struct ConformsToProtoWithAssocType_WitnessInUnavailableExtension: ProtoWithAssocType {
77+
// 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}}
78+
}
79+
80+
@available(macOS, unavailable)
81+
extension ConformsToProtoWithAssocType_WitnessInUnavailableExtension {
82+
struct A {} // expected-note {{'A' declared here}}
83+
}
84+
85+
struct ConformsToProtoWithAssocType_WitnessUnavailableMessage: ProtoWithAssocType {
86+
@available(*, unavailable, message: "Just don't")
87+
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}}
88+
}
89+
90+
struct ConformsToProtoWithAssocType_WitnessObsoleted: ProtoWithAssocType {
91+
@available(macOS, obsoleted: 11)
92+
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}}
93+
}
94+
95+
struct ConformsToProtoWithAssocType_WitnessIntroInSwift99: ProtoWithAssocType {
96+
@available(swift, introduced: 99)
97+
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}}
98+
}
99+
100+
@available(macOS, unavailable)
101+
struct ConformsToProtoWithAssocType_Unavailable: ProtoWithAssocType {
102+
struct A {} // Ok, the conformance is unavailable too.
103+
}
104+
105+
@available(macOS, unavailable)
106+
struct ConformsToProtoWithAssocType_WitnessAndConformanceUnavailable: ProtoWithAssocType {
107+
@available(macOS, unavailable)
108+
struct A {} // Ok, the conformance is unavailable too.
109+
}
110+
111+
protocol ProtoWithNewAssocType {
112+
@available(macOS 13, *)
113+
associatedtype A
114+
}
115+
116+
struct ConformsToProtoWithNewAssocType_WitnessOld: ProtoWithNewAssocType {
117+
struct A {} // Ok, A has always been available
118+
}
119+
120+
struct ConformsToProtoWithNewAssocType_WitnessSame: ProtoWithNewAssocType {
121+
@available(macOS 13, *)
122+
struct A {} // Ok, A is as available as the associated type requirement
123+
}
124+
125+
struct ConformsToProtoWithNewAssocType_WitnessTooNew: ProtoWithNewAssocType {
126+
@available(macOS 14, *)
127+
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}}
128+
}
129+
130+
protocol ProtoWithAssocTypeAndReq {
131+
associatedtype A
132+
133+
@available(macOS 13, *)
134+
func req(_ a: A)
135+
}
136+
137+
@available(macOS 11, *)
138+
struct InferredOld {} // Ok, InferredOld is less available than its parent but more available than the deployment target
139+
140+
struct ConformsToProtoWithAssocTypeAndReq_InferredWitnessOld: ProtoWithAssocTypeAndReq {
141+
142+
func req(_ a: InferredOld) {}
143+
}
144+
145+
@available(macOS 12, *)
146+
struct InferredSame {} // Ok, InferredSame is less available than its parent but available at the deployment target
147+
148+
struct ConformsToProtoWithAssocTypeAndReq_InferredWitnessSame: ProtoWithAssocTypeAndReq {
149+
func req(_ a: InferredSame) {}
150+
}
151+
152+
@available(macOS 13, *)
153+
struct InferredTooNew {} // expected-note {{'InferredTooNew' declared here}}
154+
155+
struct ConformsToProtoWithAssocTypeAndReq_InferredWitnessTooNew: ProtoWithAssocTypeAndReq {
156+
// 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}}
157+
158+
@available(macOS 13, *)
159+
func req(_ a: InferredTooNew) {}
160+
}
161+
162+
@available(macOS, unavailable)
163+
struct ConformsToProtoWithAssocTypeAndReq_InferredUnavailable: ProtoWithAssocTypeAndReq {
164+
@available(macOS, unavailable)
165+
struct InferredUnavailable {}
166+
167+
func req(_ a: InferredUnavailable) {}
168+
}

0 commit comments

Comments
 (0)