Skip to content

Commit

Permalink
Sema: Allow non-final classes to satisfy properties and subscripts wi…
Browse files Browse the repository at this point in the history
…th covariant Self
  • Loading branch information
AnthonyLatsis committed Sep 20, 2020
1 parent 1e0a9ca commit 103a821
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 59 deletions.
6 changes: 4 additions & 2 deletions include/swift/AST/Decl.h
Expand Up @@ -4123,12 +4123,14 @@ struct SelfReferenceKind {
return SelfReferenceKind(false, false, false, false);
}

/// The type refers to 'Self', but only as the result type of a method.
/// The type refers to 'Self', but only as the type of a property or
/// the result type of a method/subscript.
static SelfReferenceKind Result() {
return SelfReferenceKind(true, false, false, false);
}

/// The type refers to 'Self', but only as the parameter type of a method.
/// The type refers to 'Self', but only as the parameter type
/// of a method/subscript.
static SelfReferenceKind Parameter() {
return SelfReferenceKind(false, true, false, false);
}
Expand Down
15 changes: 8 additions & 7 deletions include/swift/AST/DiagnosticsSema.def
Expand Up @@ -1992,14 +1992,15 @@ NOTE(witness_self_weaken_same_type,none,
"consider weakening the same-type requirement %0 == %1 to a superclass "
"requirement", (Type, Type))
ERROR(witness_requires_dynamic_self,none,
"method %0 in non-final class %1 must return 'Self' to conform to "
"protocol %2",
(DeclName, Type, Type))
"%select{%error|method|property|subscript}0 %1 in non-final class %2 "
"must %select{%error|return|specify type|return}0 'Self' "
"to conform to protocol %3",
(RequirementKind, DeclName, Type, Type))
ERROR(witness_requires_class_implementation,none,
"method %0 in non-final class %1 cannot be implemented in a "
"protocol extension because it returns 'Self' and has associated type "
"requirements",
(DeclName, Type))
"%select{%error|method|%error|subscript}0 %1 in non-final class %2 "
"cannot be implemented in a protocol extension because it returns 'Self' "
"and has associated type requirements",
(RequirementKind, DeclName, Type))
ERROR(witness_not_accessible_proto,none,
"%select{initializer %1|method %1|%select{|setter for }2property %1"
"|subscript%select{| setter}2}0 must be declared "
Expand Down
11 changes: 6 additions & 5 deletions lib/AST/Decl.cpp
Expand Up @@ -4965,12 +4965,13 @@ ProtocolDecl::findProtocolSelfReferences(const ValueDecl *value,
return ::findProtocolSelfReferences(this, type,
skipAssocTypes);
} else {
if (::findProtocolSelfReferences(this, type,
skipAssocTypes)) {
return SelfReferenceKind::Other();
}
return SelfReferenceKind::None();
assert(isa<VarDecl>(value));

return ::findProtocolSelfReferences(this, type,
skipAssocTypes);
}

return SelfReferenceKind::None();
}

bool ProtocolDecl::isAvailableInExistential(const ValueDecl *decl) const {
Expand Down
48 changes: 22 additions & 26 deletions lib/Sema/TypeCheckProtocol.cpp
Expand Up @@ -2078,7 +2078,7 @@ static Type getRequirementTypeForDisplay(ModuleDecl *module,
return FunctionType::get(params, result, fnTy->getExtInfo());
}

return substType(type, /*result*/false);
return substType(type, /*result*/ true);
}

diag::RequirementKind
Expand Down Expand Up @@ -3343,41 +3343,36 @@ void ConformanceChecker::checkNonFinalClassWitness(ValueDecl *requirement,
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
});
} else if (selfKind.result) {
// The reference to Self occurs in the result type. A non-final class
// can satisfy this requirement with a method that returns Self.
// The reference to Self occurs in the result type of a method/subscript
// or the type of a property. A non-final class can satisfy this requirement
// by holding onto Self accordingly.
if (witness->getDeclContext()->getSelfClassDecl()) {
const bool hasDynamicSelfResult = [&] {
if (auto func = dyn_cast<AbstractFunctionDecl>(witness)) {
return func->hasDynamicSelfResult();
} else if (auto var = dyn_cast<VarDecl>(witness)) {
return var->getInterfaceType()->hasDynamicSelfType();
}

// If the function has a dynamic Self, it's okay.
if (auto func = dyn_cast<FuncDecl>(witness)) {
if (func->getDeclContext()->getSelfClassDecl() &&
!func->hasDynamicSelfResult()) {
return cast<SubscriptDecl>(witness)
->getElementInterfaceType()
->hasDynamicSelfType();
}();

if (!hasDynamicSelfResult) {
diagnoseOrDefer(requirement, false,
[witness, requirement](NormalProtocolConformance *conformance) {
auto proto = conformance->getProtocol();
auto &diags = proto->getASTContext().Diags;
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance,witness);
diags.diagnose(diagLoc,
diag::witness_requires_dynamic_self,
diags.diagnose(diagLoc, diag::witness_requires_dynamic_self,
getProtocolRequirementKind(requirement),
requirement->getName(),
conformance->getType(),
proto->getDeclaredInterfaceType());
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
});
}

// Constructors conceptually also have a dynamic Self
// return type, so they're okay.
} else if (!isa<ConstructorDecl>(witness)) {
diagnoseOrDefer(requirement, false,
[witness, requirement](NormalProtocolConformance *conformance) {
auto proto = conformance->getProtocol();
auto &diags = proto->getASTContext().Diags;
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
diags.diagnose(diagLoc, diag::witness_self_non_subtype,
proto->getDeclaredInterfaceType(),
requirement->getName(),
conformance->getType());
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
});
}
} else if (selfKind.requirement) {
if (auto targetPair = getAdopteeSelfSameTypeConstraint(classDecl,
Expand Down Expand Up @@ -3413,8 +3408,8 @@ void ConformanceChecker::checkNonFinalClassWitness(ValueDecl *requirement,
// constraint that either the requirement not produce 'Self' in a
// covariant position, or the type of the requirement does not involve
// associated types.
if (auto func = dyn_cast<FuncDecl>(witness)) {
if (func->getDeclContext()->getExtendedProtocolDecl()) {
if (isa<FuncDecl>(witness) || isa<SubscriptDecl>(witness)) {
if (witness->getDeclContext()->getExtendedProtocolDecl()) {
auto selfKindWithAssocTypes = Proto->findProtocolSelfReferences(
requirement,
/*allowCovariantParameters=*/false,
Expand All @@ -3427,6 +3422,7 @@ void ConformanceChecker::checkNonFinalClassWitness(ValueDecl *requirement,
auto &diags = proto->getASTContext().Diags;
diags.diagnose(conformance->getLoc(),
diag::witness_requires_class_implementation,
getProtocolRequirementKind(requirement),
requirement->getName(),
conformance->getType());
diags.diagnose(witness, diag::decl_declared_here,
Expand Down
10 changes: 5 additions & 5 deletions test/decl/protocol/conforms/inherited.swift
Expand Up @@ -5,15 +5,15 @@ protocol P1 {
func f1(_ x: Self?) -> Bool
}

// Never inheritable: property with 'Self' in its signature.
// Inheritable: property with 'Self' in its signature.
protocol P2 {
var prop2: Self { get set }
}
protocol P2a {
var prop2a: Self { get set }
}

// Never inheritable: subscript with 'Self' in its result type.
// Inheritable: subscript with 'Self' in its result type.
protocol P3 {
subscript (i: Int) -> Self { get }
}
Expand Down Expand Up @@ -94,7 +94,7 @@ class A : P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 {
func f1(_ x: A?) -> Bool { return true }

// P2
var prop2: A { // expected-error{{protocol 'P2' requirement 'prop2' cannot be satisfied by a non-final class ('A') because it uses 'Self' in a non-parameter, non-result type position}}
var prop2: A { // expected-error{{property 'prop2' in non-final class 'A' must specify type 'Self' to conform to protocol 'P2'}}
get { return self }
set {}
}
Expand All @@ -106,7 +106,7 @@ class A : P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 {
}

// P3
subscript (i: Int) -> A { // expected-error{{protocol 'P3' requirement 'subscript(_:)' cannot be satisfied by a non-final class ('A') because it uses 'Self' in a non-parameter, non-result type position}}
subscript (i: Int) -> A { // expected-error{{subscript 'subscript(_:)' in non-final class 'A' must return 'Self' to conform to protocol 'P3'}}
get {
return self
}
Expand Down Expand Up @@ -145,7 +145,7 @@ class A : P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 {
}

extension A: P2a, P5a, P10a {}
// expected-error@-1 {{protocol 'P2a' requirement 'prop2a' cannot be satisfied by a non-final class ('A') because it uses 'Self' in a non-parameter, non-result type position}}
// expected-error@-1 {{property 'prop2a' in non-final class 'A' must specify type 'Self' to conform to protocol 'P2a'}}
// expected-error@-2 {{method 'f5a()' in non-final class 'A' must return 'Self' to conform to protocol 'P5a'}}
// expected-error@-3 {{protocol 'P10a' requirement 'f10a' cannot be satisfied by a non-final class ('A') because it uses 'Self' in a non-parameter, non-result type position}}

Expand Down
16 changes: 14 additions & 2 deletions test/decl/protocol/conforms/self.swift
Expand Up @@ -7,6 +7,8 @@ protocol P {
func returnsSelf() -> Self
func hasDefaultTakesT(_: T)
func returnsSelfTakesT(_: T) -> Self

subscript(_: T) -> Self { get }
}

extension P {
Expand All @@ -21,19 +23,29 @@ extension P {
func returnsSelfTakesT(_: T) -> Self { // expected-note {{'returnsSelfTakesT' declared here}}
return self
}

subscript(_: T) -> Self { self } // expected-note {{'subscript(_:)' declared here}}
}

// This fails
class Class : P {} // 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}}
class Class : P {}
// expected-error@-1 {{method 'returnsSelfTakesT' in non-final class 'Class' cannot be implemented in a protocol extension because it returns 'Self' and has associated type requirements}}
// expected-error@-2 {{subscript 'subscript(_:)' in non-final class 'Class' cannot be implemented in a protocol extension because it returns 'Self' and has associated type requirements}}

// This succeeds, because the class is final
final class FinalClass : P {}

// This succeeds, because we're not using the default implementation
// This succeeds, because we're not using the default implementations
class NonFinalClass : P {
// FIXME: An explicit type witness is necessary to avoid an unrelated
// associated type inference bug.
typealias T = Never

func returnsSelfTakesT(_: T) -> Self {
return self
}

subscript(_: T) -> Self { self }
}

// Test for default implementation that comes from a constrained extension
Expand Down
54 changes: 42 additions & 12 deletions test/decl/protocol/req/dynamic_self.swift
@@ -1,58 +1,88 @@
// RUN: %target-typecheck-verify-swift

protocol P {
var p: Self { get }
// expected-note@-1{{protocol requires property 'p' with type 'Self'}}
// expected-note@-2{{protocol requires property 'p' with type 'EError'}}
// expected-note@-3{{protocol requires property 'p' with type 'SError'}}
subscript() -> Self { get }
// expected-note@-1{{protocol requires subscript with type '() -> Self'}}
// expected-note@-2{{protocol requires subscript with type '() -> EError'}}
// expected-note@-3{{protocol requires subscript with type '() -> SError'}}
func f() -> Self
// expected-note@-1{{protocol requires function 'f()' with type '() -> Self'}}
// expected-note@-2{{protocol requires function 'f()' with type '() -> EError'}}
// expected-note@-3{{protocol requires function 'f()' with type '() -> SError'}}
}

// Error: Missing Self method in a class.
func takesP(_: P) {} // OK

// Error: Missing witnesses.
class W : P {} // expected-error{{type 'W' does not conform to protocol 'P'}}

// Okay: Self method in class.
class X : P {
func f() -> Self { return self }
var p: Self { self }
subscript() -> Self { self }
func f() -> Self { self }
}

class Y {
func f() -> Self { return self }
var p: Self { self }
subscript() -> Self { self }
func f() -> Self { self }
}

class GX<T> : P {
func f() -> Self { return self }
var p: Self { self }
subscript() -> Self { self }
func f() -> Self { self }
}

// Okay: dynamic Self method in superclass.
class Z : Y, P { }

// Erro: Z2 conforms, but subclass would not
// Error: Z2 conforms, but subclass would not.
class Z2 : P {
func f() -> Z2 { return self } // expected-error{{method 'f()' in non-final class 'Z2' must return 'Self' to conform to protocol 'P'}}
var p: Z2 { self } //expected-error{{property 'p' in non-final class 'Z2' must specify type 'Self' to conform to protocol 'P'}}
subscript() -> Z2 { self } //expected-error{{subscript 'subscript()' in non-final class 'Z2' must return 'Self' to conform to protocol 'P'}}
func f() -> Z2 { self } // expected-error{{method 'f()' in non-final class 'Z2' must return 'Self' to conform to protocol 'P'}}
}

// Okay: struct conforms by returning itself
struct S : P {
func f() -> S { return self }
var p: S { self }
subscript() -> S { self }
func f() -> S { self }
}

struct GS<T> : P {
func f() -> GS { return self }
var p: GS { self }
subscript() -> GS { self }
func f() -> GS { self }
}

struct SError : P { // expected-error{{type 'SError' does not conform to protocol 'P'}}
func f() -> Int { return 0 } // expected-note{{candidate has non-matching type '() -> Int'}}
var p: Int { 0 } // expected-note{{candidate has non-matching type 'Int'}}
subscript() -> Int { 0 } // expected-note{{candidate has non-matching type '() -> Int'}}
func f() -> Int { 0 } // expected-note{{candidate has non-matching type '() -> Int'}}
}

// Okay: enum conforms by returning itself
enum E : P {
func f() -> E { return self }
var p: E { self }
subscript() -> E { self }
func f() -> E { self }
}

enum GE<T> : P {
func f() -> GE { return self }
var p: GE { self }
subscript() -> GE { self }
func f() -> GE { self }
}

enum EError : P { // expected-error{{type 'EError' does not conform to protocol 'P'}}
func f() -> Int { return 0 } // expected-note{{candidate has non-matching type '() -> Int'}}
var p: Int { 0 } // expected-note{{candidate has non-matching type 'Int'}}
subscript() -> Int { 0 } // expected-note{{candidate has non-matching type '() -> Int'}}
func f() -> Int { 0 } // expected-note{{candidate has non-matching type '() -> Int'}}
}

0 comments on commit 103a821

Please sign in to comment.