Skip to content

Commit 4420ea8

Browse files
committed
Sema: Check 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 4757eeb commit 4420ea8

File tree

7 files changed

+269
-8
lines changed

7 files changed

+269
-8
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", ())

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/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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4784,6 +4784,63 @@ static void diagnoseInvariantSelfRequirement(
47844784
.warnUntilSwiftVersion(6);
47854785
}
47864786

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+
47874844
/// Check whether the type witnesses satisfy the protocol's requirement
47884845
/// signature. Also checks access level of type witnesses and availiability
47894846
/// of associated conformances.
@@ -4906,6 +4963,10 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
49064963
}
49074964
}
49084965

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+
49094970
// Make sure any associated type witnesses don't make reference to a
49104971
// type we can't emit metadata for, or we're going to have trouble at
49114972
// 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+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %swift -typecheck -verify -target %target-cpu-apple-macosx12 %s -swift-version 7
2+
// REQUIRES: OS=macosx
3+
// REQUIRES: swift7
4+
5+
protocol ProtoWithAssocType {
6+
associatedtype A // expected-note {{requirement 'A' declared here}}
7+
}
8+
9+
struct WitnessSame: ProtoWithAssocType {
10+
@available(macOS 12, *)
11+
struct A {} // Ok, A is less available than its parent but available at the deployment target
12+
}
13+
14+
struct WitnessTooNew: ProtoWithAssocType { // expected-error {{type 'WitnessTooNew' does not conform to protocol 'ProtoWithAssocType'}}
15+
@available(macOS 13, *)
16+
struct A {} // expected-error {{protocol 'ProtoWithAssocType' requires 'A' to be available in macOS 12 and newer}}
17+
}
18+
19+
struct WitnessUnavailable: ProtoWithAssocType { // expected-error {{type 'WitnessUnavailable' does not conform to protocol 'ProtoWithAssocType'}}
20+
@available(macOS, unavailable)
21+
struct A {} // expected-error {{unavailable struct 'A' was used to satisfy a requirement of protocol 'ProtoWithAssocType'}}
22+
}

0 commit comments

Comments
 (0)