Skip to content

Commit 005b45c

Browse files
committed
[Sema] Diagnose deprecated default implementations in the witness checker.
If a protocol provides a deprecated default implementation for a requirement that is not deprecated, the compiler should emit a warning so the programmer can provide an explicit implementation of the requirement. This is helpful for staging in new protocol requirements that should be implemented in conforming types.
1 parent 128a8bc commit 005b45c

File tree

5 files changed

+113
-5
lines changed

5 files changed

+113
-5
lines changed

include/swift/AST/Attr.h

+4
Original file line numberDiff line numberDiff line change
@@ -2705,6 +2705,10 @@ class DeclAttributes {
27052705
return getUnavailable(ctx) != nullptr;
27062706
}
27072707

2708+
bool isDeprecated(const ASTContext &ctx) const {
2709+
return getDeprecated(ctx) != nullptr;
2710+
}
2711+
27082712
/// Determine whether there is a swiftVersionSpecific attribute that's
27092713
/// unavailable relative to the provided language version.
27102714
bool

include/swift/AST/DiagnosticsSema.def

+5
Original file line numberDiff line numberDiff line change
@@ -3137,6 +3137,11 @@ ERROR(witness_unavailable,none,
31373137
"unavailable %kind0 was used to satisfy a requirement of protocol %1%select{|: %2}2",
31383138
(const ValueDecl *, Identifier, StringRef))
31393139

3140+
WARNING(witness_deprecated,none,
3141+
"deprecated default implementation is used to satisfy %kind0 required by "
3142+
"protocol %1%select{|: %2}2",
3143+
(const ValueDecl *, Identifier, StringRef))
3144+
31403145
ERROR(redundant_conformance,none,
31413146
"redundant conformance of %0 to protocol %1", (Type, Identifier))
31423147
ERROR(redundant_conformance_conditional,none,

include/swift/AST/RequirementMatch.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ enum class CheckKind : unsigned {
239239

240240
/// The witness itself is inaccessible.
241241
WitnessUnavailable,
242+
243+
/// The witness is a deprecated default implementation provided by the
244+
/// protocol.
245+
DefaultWitnessDeprecated,
242246
};
243247
/// Describes the suitability of the chosen witness for
244248
/// the requirement.
@@ -464,4 +468,4 @@ struct RequirementMatch {
464468

465469
}
466470

467-
#endif // SWIFT_AST_REQUIREMENTMATCH_H
471+
#endif // SWIFT_AST_REQUIREMENTMATCH_H

lib/Sema/TypeCheckProtocol.cpp

+39-4
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,8 @@ checkWitnessAvailability(ValueDecl *requirement,
17401740

17411741
RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
17421742
const RequirementMatch &match) {
1743+
auto &ctx = getASTContext();
1744+
17431745
if (!match.OptionalAdjustments.empty())
17441746
return CheckKind::OptionalityConflict;
17451747

@@ -1769,7 +1771,7 @@ RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
17691771
return RequirementCheck(CheckKind::Availability, requiredAvailability);
17701772
}
17711773

1772-
if (requirement->getAttrs().isUnavailable(getASTContext()) &&
1774+
if (requirement->getAttrs().isUnavailable(ctx) &&
17731775
match.Witness->getDeclContext() == DC) {
17741776
return RequirementCheck(CheckKind::Unavailable);
17751777
}
@@ -1792,11 +1794,11 @@ RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
17921794
}
17931795
}
17941796

1795-
if (match.Witness->getAttrs().isUnavailable(getASTContext()) &&
1796-
!requirement->getAttrs().isUnavailable(getASTContext())) {
1797+
if (match.Witness->getAttrs().isUnavailable(ctx) &&
1798+
!requirement->getAttrs().isUnavailable(ctx)) {
17971799
auto nominalOrExtensionIsUnavailable = [&]() {
17981800
if (auto extension = dyn_cast<ExtensionDecl>(DC)) {
1799-
if (extension->getAttrs().isUnavailable(getASTContext()))
1801+
if (extension->getAttrs().isUnavailable(ctx))
18001802
return true;
18011803
}
18021804

@@ -1813,6 +1815,20 @@ RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
18131815
return CheckKind::WitnessUnavailable;
18141816
}
18151817

1818+
// Warn about deprecated default implementations if the requirement is
1819+
// not deprecated, and the conformance is not deprecated.
1820+
bool isDefaultWitness = false;
1821+
if (auto *nominal = match.Witness->getDeclContext()->getSelfNominalTypeDecl())
1822+
isDefaultWitness = isa<ProtocolDecl>(nominal);
1823+
if (isDefaultWitness &&
1824+
match.Witness->getAttrs().isDeprecated(ctx) &&
1825+
!requirement->getAttrs().isDeprecated(ctx)) {
1826+
auto conformanceContext = ExportContext::forConformance(DC, Proto);
1827+
if (!conformanceContext.isDeprecated()) {
1828+
return RequirementCheck(CheckKind::DefaultWitnessDeprecated);
1829+
}
1830+
}
1831+
18161832
return CheckKind::Success;
18171833
}
18181834

@@ -4374,6 +4390,25 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
43744390
requirement->getName());
43754391
});
43764392
break;
4393+
4394+
case CheckKind::DefaultWitnessDeprecated:
4395+
getASTContext().addDelayedConformanceDiag(
4396+
Conformance, /*isError=*/false,
4397+
[witness, requirement](NormalProtocolConformance *conformance) {
4398+
auto &ctx = witness->getASTContext();
4399+
auto &diags = ctx.Diags;
4400+
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
4401+
auto *attr = witness->getAttrs().getDeprecated(ctx);
4402+
EncodedDiagnosticMessage EncodedMessage(attr->Message);
4403+
diags.diagnose(diagLoc, diag::witness_deprecated,
4404+
witness, conformance->getProtocol()->getName(),
4405+
EncodedMessage.Message);
4406+
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
4407+
diags.diagnose(requirement, diag::kind_declname_declared_here,
4408+
DescriptiveDeclKind::Requirement,
4409+
requirement->getName());
4410+
});
4411+
break;
43774412
}
43784413

43794414
if (auto *classDecl = DC->getSelfClassDecl()) {
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol DeprecatedRequirement {
4+
@available(*, deprecated)
5+
func f()
6+
}
7+
8+
extension DeprecatedRequirement {
9+
@available(*, deprecated)
10+
func f() {}
11+
}
12+
13+
// No warning if both the requirement and the default implementation are deprecated
14+
struct S1: DeprecatedRequirement {}
15+
16+
protocol DeprecatedDefault {
17+
func f() // expected-note {{requirement 'f()' declared here}}
18+
}
19+
20+
extension DeprecatedDefault {
21+
@available(*, deprecated)
22+
func f() {} // expected-note {{'f()' declared here}}
23+
}
24+
25+
// expected-warning@+1 {{deprecated default implementation is used to satisfy instance method 'f()' required by protocol 'DeprecatedDefault'}}
26+
struct S2: DeprecatedDefault {}
27+
28+
// No warning if the conformance itself is deprecated
29+
@available(*, deprecated)
30+
struct S3: DeprecatedDefault {
31+
}
32+
33+
struct S4: DeprecatedDefault {
34+
func f() {}
35+
}
36+
37+
struct S5 {}
38+
39+
// No warning if the conformance itself is deprecated
40+
@available(*, deprecated)
41+
extension S5: DeprecatedDefault {}
42+
43+
@available(*, deprecated)
44+
enum UnavailableEnum {
45+
struct Nested: DeprecatedDefault {}
46+
}
47+
48+
// Include message string from @available attribute if provided
49+
protocol DeprecatedDefaultWithMessage {
50+
func f() // expected-note {{requirement 'f()' declared here}}
51+
}
52+
53+
extension DeprecatedDefaultWithMessage {
54+
@available(*, deprecated, message: "write it yourself")
55+
func f() {} // expected-note {{'f()' declared here}}
56+
}
57+
58+
59+
// expected-warning@+1 {{deprecated default implementation is used to satisfy instance method 'f()' required by protocol 'DeprecatedDefaultWithMessage': write it yourself}}
60+
struct S6: DeprecatedDefaultWithMessage {}

0 commit comments

Comments
 (0)