Skip to content

Commit 80ada27

Browse files
committed
Sema: Lift restriction on classes conforming to protocols with default implementations returning 'Self'
Now that we pass in the correct type metadata for 'Self', it is sound for a class to conform to a protocol with a default implementation for a method returning 'Self'. Fixes <rdar://problem/23671426>.
1 parent 2160114 commit 80ada27

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,11 @@ ERROR(witness_requires_dynamic_self,none,
14521452
"method %0 in non-final class %1 must return `Self` to conform to "
14531453
"protocol %2",
14541454
(DeclName, Type, Type))
1455+
ERROR(witness_requires_class_implementation,none,
1456+
"method %0 in non-final class %1 cannot be implemented in a "
1457+
"protocol extension because it returns `Self` and has associated type "
1458+
"requirements",
1459+
(DeclName, Type))
14551460
ERROR(witness_not_accessible_proto,none,
14561461
"%select{initializer %1|method %1|%select{|setter for }2property %1"
14571462
"|subscript%select{| setter}2}0 must be declared "

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3095,7 +3095,8 @@ void ConformanceChecker::checkNonFinalClassWitness(ValueDecl *requirement,
30953095

30963096
// If the function has a dynamic Self, it's okay.
30973097
if (auto func = dyn_cast<FuncDecl>(witness)) {
3098-
if (!func->hasDynamicSelf()) {
3098+
if (func->getDeclContext()->getAsClassOrClassExtensionContext() &&
3099+
!func->hasDynamicSelf()) {
30993100
diagnoseOrDefer(requirement, false,
31003101
[witness, requirement](NormalProtocolConformance *conformance) {
31013102
auto proto = conformance->getProtocol();
@@ -3149,6 +3150,31 @@ void ConformanceChecker::checkNonFinalClassWitness(ValueDecl *requirement,
31493150
// A non-final class can model a protocol requirement with a
31503151
// contravariant Self, because here the witness will always have
31513152
// a more general type than the requirement.
3153+
3154+
// If the witness is in a protocol extension, there's an additional
3155+
// constraint that either the requirement not produce 'Self' in a
3156+
// covariant position, or the type of the requirement does not involve
3157+
// associated types.
3158+
if (auto func = dyn_cast<FuncDecl>(witness)) {
3159+
if (func->getDeclContext()->getAsProtocolExtensionContext()) {
3160+
auto selfKindWithAssocTypes = Proto->findProtocolSelfReferences(
3161+
requirement,
3162+
/*allowCovariantParameters=*/false,
3163+
/*skipAssocTypes=*/false);
3164+
if (selfKindWithAssocTypes.other &&
3165+
selfKindWithAssocTypes.result) {
3166+
diagnoseOrDefer(requirement, false,
3167+
[witness, requirement](NormalProtocolConformance *conformance) {
3168+
auto proto = conformance->getProtocol();
3169+
auto &diags = proto->getASTContext().Diags;
3170+
diags.diagnose(witness->getLoc(),
3171+
diag::witness_requires_class_implementation,
3172+
requirement->getFullName(),
3173+
conformance->getType());
3174+
});
3175+
}
3176+
}
3177+
}
31523178
}
31533179

31543180
ResolveWitnessResult
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol P {
4+
associatedtype T = Int
5+
6+
func hasDefault()
7+
func returnsSelf() -> Self
8+
func hasDefaultTakesT(_: T)
9+
func returnsSelfTakesT(_: T) -> Self
10+
}
11+
12+
extension P {
13+
func hasDefault() {}
14+
15+
func returnsSelf() -> Self {
16+
return self
17+
}
18+
19+
func hasDefaultTakesT(_: T) {}
20+
21+
func returnsSelfTakesT(_: T) -> Self { // expected-error {{method 'returnsSelfTakesT' in non-final class 'Class' cannot be implemented in a protocol extension because it returns `Self` and has associated type requirements}}
22+
return self
23+
}
24+
}
25+
26+
// This fails
27+
class Class : P {}
28+
29+
// This succeeds, because the class is final
30+
final class FinalClass : P {}
31+
32+
// This succeeds, because we're not using the default implementation
33+
class NonFinalClass : P {
34+
func returnsSelfTakesT(_: T) -> Self {
35+
return self
36+
}
37+
}

0 commit comments

Comments
 (0)