Skip to content

AST, Sema: Teach findProtocolSelfReferences that some stdlib collections preserve variance #34140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2931,8 +2931,8 @@ ERROR(dynamic_self_non_method,none,
"%select{global|local}0 function cannot return 'Self'", (bool))

ERROR(dynamic_self_invalid,none,
"covariant 'Self' can only appear as the type of a property, subscript or method result; "
"did you mean '%0'?", (StringRef))
"covariant 'Self' can only appear as the possibly optional type of a "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this sentence mean simply covariant Self or Self?? I struggle a little with how to parse “possibly optional type.”

Copy link
Collaborator Author

@AnthonyLatsis AnthonyLatsis Feb 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention was to convey that both Self and Self? are supported. Do you happen to have a suggestion for a better wording?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps simply: "covariant 'Self' or 'Self?' can only appear as..."

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, indeed. Thank you, I'll take care of this!

"property, subscript or method result; did you mean '%0'?", (StringRef))
ERROR(dynamic_self_in_mutable_property,none,
"mutable property cannot have covariant 'Self' type", ())
ERROR(dynamic_self_in_stored_property,none,
Expand Down
22 changes: 18 additions & 4 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4814,12 +4814,26 @@ findProtocolSelfReferences(const ProtocolDecl *proto, Type type,
return findProtocolSelfReferences(proto, selfType->getSelfType(), position);
}

// Bound generic types are invariant.
if (auto boundGenericType = type->getAs<BoundGenericType>()) {
// Most bound generic types are invariant.
if (auto *const bgt = type->getAs<BoundGenericType>()) {
auto info = SelfReferenceInfo();
for (auto paramType : boundGenericType->getGenericArgs()) {
info |= findProtocolSelfReferences(proto, paramType,

const auto &ctx = bgt->getDecl()->getASTContext();
if (ctx.getArrayDecl() == bgt->getDecl()) {
// Swift.Array preserves variance in its Value type.
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().front(),
position);
} else if (bgt->getDecl() == ctx.getDictionaryDecl()) {
// Swift.Dictionary preserves variance in its Element type.
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().front(),
SelfReferencePosition::Invariant);
info |= findProtocolSelfReferences(proto, bgt->getGenericArgs().back(),
position);
} else {
for (auto paramType : bgt->getGenericArgs()) {
info |= findProtocolSelfReferences(proto, paramType,
SelfReferencePosition::Invariant);
}
}

return info;
Expand Down
14 changes: 13 additions & 1 deletion lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6800,6 +6800,19 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
return result;
}

case TypeKind::BoundGenericStruct: {
auto toStruct = cast<BoundGenericStructType>(desugaredToType);
if (toStruct->getDecl() != ctx.getArrayDecl() &&
toStruct->getDecl() != ctx.getDictionaryDecl())
break;

if (toStruct->getDecl() == cs.getType(expr)->getAnyNominal())
return buildCollectionUpcastExpr(expr, toType, /*bridged=*/false,
locator);

break;
}

#define SUGARED_TYPE(Name, Parent) case TypeKind::Name:
#define BUILTIN_TYPE(Name, Parent) case TypeKind::Name:
#define UNCHECKED_TYPE(Name, Parent) case TypeKind::Name:
Expand All @@ -6813,7 +6826,6 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
case TypeKind::Struct:
case TypeKind::Class:
case TypeKind::BoundGenericClass:
case TypeKind::BoundGenericStruct:
case TypeKind::Metatype:
case TypeKind::DynamicSelf:
case TypeKind::PrimaryArchetype:
Expand Down
18 changes: 18 additions & 0 deletions test/SILGen/collection_upcast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,21 @@ func testSetUpcastBridged(_ set: Set<BridgedSwift>) {
// CHECK-NOT: destroy_value [[SET]]
let anyObjectSet = set as Set<NSObject>
}

protocol P {
func selfArrayUpcast() -> [Self]
func selfDictionaryUpcast() -> [String : Self]
}

// CHECK-LABEL: sil hidden [ossa] @$s17collection_upcast44testCollectionUpcastWithCovariantSelfErasure{{.*}}F
// CHECK: bb0([[ARG:%0]] : $*P):
func testCollectionUpcastWithCovariantSelfErasure(arg: P) {
let array = arg.selfArrayUpcast() // [P]
// CHECK: open_existential_addr immutable_access [[ARG]] : $*P to $*@opened([[N:".*"]]) P
// CHECK: [[UPCAST_FN:%[0-9]+]] = function_ref @$ss15_arrayForceCast{{.*}}F
// CHECK: apply [[UPCAST_FN]]<@opened([[N]]) P, P>(%{{[0-9]+}}) : $@convention(thin) <τ_0_0, τ_0_1> (@guaranteed Array<τ_0_0>) -> @owned Array<τ_0_1>
let dictionary = arg.selfDictionaryUpcast() // [String : P]
// CHECK: open_existential_addr immutable_access [[ARG]] : $*P to $*@opened([[N:".*"]]) P
// CHECK: [[UPCAST_FN:%[0-9]+]] = function_ref @$ss17_dictionaryUpCast{{.*}}F
// CHECK: apply [[UPCAST_FN]]<String, @opened([[N]]) P, String, P>(%{{[0-9]+}}) : $@convention(thin) <τ_0_0, τ_0_1, τ_0_2, τ_0_3 where τ_0_0 : Hashable, τ_0_2 : Hashable> (@guaranteed Dictionary<τ_0_0, τ_0_1>) -> @owned Dictionary<τ_0_2, τ_0_3>
}
2 changes: 1 addition & 1 deletion test/decl/ext/generic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ extension Array where Element == String { }
extension GenericClass : P3 where T : P3 { }

extension GenericClass where Self : P3 { }
// expected-error@-1{{covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'GenericClass'?}} {{30-34=GenericClass}}
// expected-error@-1{{covariant 'Self' can only appear as the possibly optional type of a property, subscript or method result; did you mean 'GenericClass'?}} {{30-34=GenericClass}}
// expected-error@-2{{'GenericClass<T>' in conformance requirement does not refer to a generic parameter or associated type}}

protocol P4 {
Expand Down
4 changes: 2 additions & 2 deletions test/decl/func/dynamic_self.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ enum E0 {
class C0 {
func f() -> Self { } // okay

func g(_ ds: Self) { } // expected-error{{covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'C0'?}}
func g(_ ds: Self) { } // expected-error{{covariant 'Self' can only appear as the possibly optional type of a property, subscript or method result; did you mean 'C0'?}}

func h(_ ds: Self) -> Self { } // expected-error{{covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'C0'?}}
func h(_ ds: Self) -> Self { } // expected-error{{covariant 'Self' can only appear as the possibly optional type of a property, subscript or method result; did you mean 'C0'?}}
}

protocol P0 {
Expand Down
33 changes: 23 additions & 10 deletions test/decl/protocol/conforms/inherited.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// RUN: %target-typecheck-verify-swift

// Inheritable: method with 'Self' in its signature
// Inheritable: method with 'Self' in contravariant position.
protocol P1 {
func f1(_ x: Self?) -> Bool
func f1a(_ x: Self?) -> Bool

func f1b(_ x: [Self])

func f1c(_ x: [String : Self])
}

// Inheritable: property with 'Self' in its signature.
Expand Down Expand Up @@ -52,12 +56,13 @@ protocol P9 {
static func ==(x: Self, y: Self) -> Bool
}

// Never inheritable: method with 'Self' in a non-contravariant position.
// Never inheritable: method with 'Self' in invariant position.
struct G<T> {}
protocol P10 {
func f10(_ arr: [Self])
func f10(_ arr: G<Self>)
}
protocol P10a {
func f10a(_ arr: [Self])
func f10a(_ arr: G<Self>)
}

// Never inheritable: method with 'Self' in curried position.
Expand Down Expand Up @@ -91,7 +96,11 @@ protocol P15 {
// non-final class.
class A : P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 {
// P1
func f1(_ x: A?) -> Bool { return true }
func f1a(_ x: A?) -> Bool { return true }

func f1b(_ x: [A]) { }

func f1c(_ x: [String : A]) { }

// P2
var prop2: A { // expected-error{{property 'prop2' in non-final class 'A' must specify type 'Self' to conform to protocol 'P2'}}
Expand Down Expand Up @@ -135,10 +144,10 @@ class A : P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 {
required init(int: Int) { }

// P10
func f10(_ arr: [A]) { } // expected-error{{protocol 'P10' requirement 'f10' cannot be satisfied by a non-final class ('A') because it uses 'Self' in a non-parameter, non-result type position}}
func f10(_ arr: G<A>) { } // expected-error{{protocol 'P10' requirement 'f10' cannot be satisfied by a non-final class ('A') because it uses 'Self' in a non-parameter, non-result type position}}

// P10a
func f10a(_ arr: [A]) { } // expected-note {{'f10a' declared here}}
func f10a(_ arr: G<A>) { } // expected-note {{'f10a' declared here}}

// P11
func f11() -> (_ x: A) -> Int { return { x in 5 } }
Expand Down Expand Up @@ -196,7 +205,11 @@ func testB8(_ b8: B8) {
// Class A9 conforms to everything.
final class A9 : P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 {
// P1
func f1(_ x: A9?) -> Bool { return true }
func f1a(_ x: A9?) -> Bool { return true }

func f1b(_ x: [A9]) { }

func f1c(_ x: [String : A9]) { }

// P2
var prop2: A9 {
Expand Down Expand Up @@ -231,7 +244,7 @@ final class A9 : P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 {
required init(int: Int) { }

// P10
func f10(_ arr: [A9]) { }
func f10(_ arr: G<A9>) { }

// P11
func f11() -> (_ x: A9) -> Int { return { x in 5 } }
Expand Down
44 changes: 44 additions & 0 deletions test/decl/protocol/req/existentials_covariant_erasure.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: %target-swift-frontend -typecheck -dump-ast %s | %FileCheck %s

// Test that covariant 'Self' references get erased to the existential base type
// when operating on existential values.

class C {}
protocol P {
func lotsOfSelfFunc(
_: (Self) -> Void,
_: (Self?) -> Void,
_: ([Self]) -> Void,
_: ([Array<Self>?]) -> Void
) -> [String : () -> Self]

var lotsOfSelfProp: (
_: (Self) -> Void,
_: (Self?) -> Void,
_: ([Self]) -> Void,
_: ([Array<Self>?]) -> Void
) -> [String : () -> Self] { get }
}
protocol Q {}

do {
class C {}

func testCovariantSelfErasure(p: P, pq: P & Q, pc: P & C) {
let x1 = p.lotsOfSelfFunc
let x2 = p.lotsOfSelfProp

let x3 = pq.lotsOfSelfFunc
let x4 = pq.lotsOfSelfProp

let x5 = pc.lotsOfSelfFunc
let x6 = pc.lotsOfSelfProp

// CHECK: (pattern_named type='((P) -> Void, (P?) -> Void, ([P]) -> Void, ([Array<P>?]) -> Void) -> [String : () -> P]' 'x1')
// CHECK: (pattern_named type='((P) -> Void, (P?) -> Void, ([P]) -> Void, ([Array<P>?]) -> Void) -> [String : () -> P]' 'x2')
// CHECK: (pattern_named type='((P & Q) -> Void, ((P & Q)?) -> Void, ([P & Q]) -> Void, ([Array<P & Q>?]) -> Void) -> [String : () -> P & Q]' 'x3')
// CHECK: (pattern_named type='((P & Q) -> Void, ((P & Q)?) -> Void, ([P & Q]) -> Void, ([Array<P & Q>?]) -> Void) -> [String : () -> P & Q]' 'x4')
// CHECK: (pattern_named type='((C & P) -> Void, ((C & P)?) -> Void, ([C & P]) -> Void, ([Array<C & P>?]) -> Void) -> [String : () -> C & P]' 'x5')
// CHECK: (pattern_named type='((C & P) -> Void, ((C & P)?) -> Void, ([C & P]) -> Void, ([Array<C & P>?]) -> Void) -> [String : () -> C & P]' 'x6')
}
}
6 changes: 3 additions & 3 deletions test/type/self.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct S0<T> {
}

class C0<T> {
func foo(_ other: Self) { } // expected-error{{covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'C0'?}}
func foo(_ other: Self) { } // expected-error{{covariant 'Self' can only appear as the possibly optional type of a property, subscript or method result; did you mean 'C0'?}}
}

enum E0<T> {
Expand Down Expand Up @@ -47,15 +47,15 @@ final class FinalMario : Mario {

class A<T> {
typealias _Self = Self
// expected-error@-1 {{covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'A'?}}
// expected-error@-1 {{covariant 'Self' can only appear as the possibly optional type of a property, subscript or method result; did you mean 'A'?}}
let b: Int
required init(a: Int) {
print("\(Self.self).\(#function)")
Self.y()
b = a
}
static func z(n: Self? = nil) {
// expected-error@-1 {{covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'A'?}}
// expected-error@-1 {{covariant 'Self' can only appear as the possibly optional type of a property, subscript or method result; did you mean 'A'?}}
print("\(Self.self).\(#function)")
}
class func y() {
Expand Down