Skip to content

Commit 602fb3f

Browse files
committed
Allow @sendable contravariance in witness params
If a requirement calls for a parameter's function type to be @sendable, Swift will now permit a witness that does *not* make that parameter @sendable. This is fine because @sendable does not affect the calling convention and the witness may not need to exploit the opportunity to send the closure anywhere. In other words, this code is now valid: ``` protocol P { func fn(_: @sendable () -> Void) } struct S: P { func fn(_: () -> Void) } ``` There's an edge case where this still isn't allowed when an associated type is @sendable.
1 parent da64dc4 commit 602fb3f

File tree

4 files changed

+100
-21
lines changed

4 files changed

+100
-21
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,31 @@ getTypesToCompare(ValueDecl *reqt, Type reqtType, bool reqtTypeIsIUO,
139139
Type witnessType, bool witnessTypeIsIUO,
140140
VarianceKind variance) {
141141
// If the witness type is noescape but the requirement type is not,
142-
// adjust the witness type to be escaping. This permits a limited form of
143-
// covariance.
144-
bool reqNoescapeToEscaping = false;
145-
(void)adjustInferredAssociatedType(reqtType, reqNoescapeToEscaping);
146-
bool witnessNoescapeToEscaping = false;
147-
Type adjustedWitnessType =
148-
adjustInferredAssociatedType(witnessType, witnessNoescapeToEscaping);
149-
if (witnessNoescapeToEscaping && !reqNoescapeToEscaping)
150-
witnessType = adjustedWitnessType;
142+
// adjust the witness type to be escaping; likewisse for sendability. This
143+
// permits a limited form of covariance.
144+
auto applyAdjustment = [&](TypeAdjustment adjustment) {
145+
// Sometimes the witness has a function type, but the requirement has
146+
// something else (a dependent type we need to infer, in the most relevant
147+
// case). In that situation, should we behave as though the requirement type
148+
// *did* need the adjustment, or as though it *did not*?
149+
//
150+
// For noescape, we want to behave as though it was necessary because any
151+
// function type not in a parameter is, more or less, implicitly @escaping.
152+
// For Sendable, we want to behave as though it was not necessary because
153+
// function types that aren't in a parameter can be Sendable or not.
154+
// FIXME: Should we check for a Sendable bound on the requirement type?
155+
bool inRequirement = (adjustment != TypeAdjustment::NoescapeToEscaping);
156+
(void)adjustInferredAssociatedType(adjustment, reqtType, inRequirement);
157+
158+
bool inWitness = false;
159+
Type adjustedWitnessType =
160+
adjustInferredAssociatedType(adjustment, witnessType, inWitness);
161+
if (inWitness && !inRequirement)
162+
witnessType = adjustedWitnessType;
163+
};
164+
165+
applyAdjustment(TypeAdjustment::NoescapeToEscaping);
166+
applyAdjustment(TypeAdjustment::NonsendableToSendable);
151167

152168
// For @objc protocols, deal with differences in the optionality.
153169
// FIXME: It probably makes sense to extend this to non-@objc

lib/Sema/TypeCheckProtocol.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,12 +1128,19 @@ matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache,
11281128
AssociatedTypeDecl *getReferencedAssocTypeOfProtocol(Type type,
11291129
ProtocolDecl *proto);
11301130

1131+
enum class TypeAdjustment : uint8_t {
1132+
NoescapeToEscaping, NonsendableToSendable
1133+
};
1134+
11311135
/// Perform any necessary adjustments to the inferred associated type to
11321136
/// make it suitable for later use.
11331137
///
1134-
/// \param noescapeToEscaping Will be set \c true if this operation performed
1135-
/// the noescape-to-escaping adjustment.
1136-
Type adjustInferredAssociatedType(Type type, bool &noescapeToEscaping);
1138+
/// \param performed Will be set \c true if this operation performed
1139+
/// the adjustment, or \c false if the operation found a type that the
1140+
/// adjustment could have applied to but did not actually need to adjust it.
1141+
/// Unchanged otherwise.
1142+
Type adjustInferredAssociatedType(TypeAdjustment adjustment, Type type,
1143+
bool &performed);
11371144

11381145
/// Find the @objc requirement that are witnessed by the given
11391146
/// declaration.

lib/Sema/TypeCheckProtocolInference.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -633,24 +633,37 @@ AssociatedTypeInference::inferTypeWitnessesViaAssociatedType(
633633
return result;
634634
}
635635

636-
Type swift::adjustInferredAssociatedType(Type type, bool &noescapeToEscaping) {
636+
Type swift::adjustInferredAssociatedType(TypeAdjustment adjustment, Type type,
637+
bool &performed) {
637638
// If we have an optional type, adjust its wrapped type.
638639
if (auto optionalObjectType = type->getOptionalObjectType()) {
639640
auto newOptionalObjectType =
640-
adjustInferredAssociatedType(optionalObjectType, noescapeToEscaping);
641+
adjustInferredAssociatedType(adjustment, optionalObjectType, performed);
641642
if (newOptionalObjectType.getPointer() == optionalObjectType.getPointer())
642643
return type;
643644

644645
return OptionalType::get(newOptionalObjectType);
645646
}
646647

648+
auto needsAdjustment = [=](FunctionType *funcType) -> bool {
649+
if (adjustment == TypeAdjustment::NoescapeToEscaping)
650+
return funcType->isNoEscape();
651+
else
652+
return !funcType->isSendable();
653+
};
654+
auto adjust = [=](const ASTExtInfo &info) -> ASTExtInfo {
655+
if (adjustment == TypeAdjustment::NoescapeToEscaping)
656+
return info.withNoEscape(false);
657+
else
658+
return info.withConcurrent(true);
659+
};
660+
647661
// If we have a noescape function type, make it escaping.
648662
if (auto funcType = type->getAs<FunctionType>()) {
649-
if (funcType->isNoEscape()) {
650-
noescapeToEscaping = true;
663+
performed = needsAdjustment(funcType);
664+
if (performed)
651665
return FunctionType::get(funcType->getParams(), funcType->getResult(),
652-
funcType->getExtInfo().withNoEscape(false));
653-
}
666+
adjust(funcType->getExtInfo()));
654667
}
655668
return type;
656669
}
@@ -713,7 +726,8 @@ AssociatedTypeInference::inferTypeWitnessesViaValueWitness(ValueDecl *req,
713726
// Adjust the type to a type that can be written explicitly.
714727
bool noescapeToEscaping = false;
715728
Type inferredType =
716-
adjustInferredAssociatedType(secondType, noescapeToEscaping);
729+
adjustInferredAssociatedType(TypeAdjustment::NoescapeToEscaping,
730+
secondType, noescapeToEscaping);
717731
if (!inferredType->isMaterializable())
718732
return true;
719733

test/decl/protocol/conforms/associated_type.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,40 @@ class Foo: FooType {
3333
}
3434

3535
// rdar://problem/35297911: noescape function types
36+
// (and @Sendable equivalents of some cases)
3637
protocol P1 {
3738
associatedtype A
3839

39-
func f(_: A)
40+
func f(_: A) // expected-note {{protocol requires function 'f' with type '(@escaping SendableX1b.A) -> ()' (aka '(@escaping @Sendable (Int) -> Int) -> ()'); do you want to add a stub?}}
4041
}
4142

4243
struct X1a : P1 {
4344
func f(_: @escaping (Int) -> Int) { }
4445
}
4546

47+
struct SendableX1a : P1 {
48+
func f(_: @Sendable (Int) -> Int) { }
49+
}
50+
4651
struct X1b : P1 {
4752
typealias A = (Int) -> Int
4853

4954
func f(_: @escaping (Int) -> Int) { }
5055
}
5156

57+
// FIXME: We would like this case to work. It doesn't because, by the time we
58+
// actually look through the DependentMemberType and see that `A` is a
59+
// @Sendable function type, we are simplifying a Bind constraint and
60+
// don't have the flexibility to adjust the witness's type. Perhaps
61+
// instead of adjusting types before adding them to the constraint
62+
// graph, we should introduce a new constraint kind that allows only a
63+
// witness's adjustments.
64+
struct SendableX1b : P1 { // expected-error {{type 'SendableX1b' does not conform to protocol 'P1'}}
65+
typealias A = @Sendable (Int) -> Int
66+
67+
func f(_: (Int) -> Int) { } // expected-note {{candidate has non-matching type '((Int) -> Int) -> ()'}}
68+
}
69+
5270
struct X1c : P1 {
5371
typealias A = (Int) -> Int
5472

@@ -60,17 +78,41 @@ struct X1d : P1 {
6078
}
6179

6280
protocol P2 {
63-
func f(_: (Int) -> Int) // expected-note{{protocol requires function 'f' with type '((Int) -> Int) -> ()'; do you want to add a stub?}}
81+
func f(_: (Int) -> Int) // expected-note 3 {{protocol requires function 'f' with type '((Int) -> Int) -> ()'; do you want to add a stub?}}
82+
func g(_: @escaping (Int) -> Int) // expected-note 2 {{protocol requires function 'g' with type '(@escaping (Int) -> Int) -> ()'; do you want to add a stub?}}
83+
func h(_: @Sendable (Int) -> Int) // expected-note 2 {{protocol requires function 'h' with type '(@Sendable (Int) -> Int) -> ()'; do you want to add a stub?}}
84+
func i(_: @escaping @Sendable (Int) -> Int)
6485
}
6586

6687
struct X2a : P2 {
6788
func f(_: (Int) -> Int) { }
89+
func g(_: (Int) -> Int) { }
90+
func h(_: (Int) -> Int) { }
91+
func i(_: (Int) -> Int) { }
6892
}
6993

7094
struct X2b : P2 { // expected-error{{type 'X2b' does not conform to protocol 'P2'}}
7195
func f(_: @escaping (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@escaping (Int) -> Int) -> ()'}}
96+
func g(_: @escaping (Int) -> Int) { }
97+
func h(_: @escaping (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@escaping (Int) -> Int) -> ()'}}
98+
func i(_: @escaping (Int) -> Int) { }
7299
}
73100

101+
struct X2c : P2 { // expected-error{{type 'X2c' does not conform to protocol 'P2'}}
102+
func f(_: @Sendable (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@Sendable (Int) -> Int) -> ()'}}
103+
func g(_: @Sendable (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@Sendable (Int) -> Int) -> ()'}}
104+
func h(_: @Sendable (Int) -> Int) { }
105+
func i(_: @Sendable (Int) -> Int) { }
106+
}
107+
108+
struct X2d : P2 { // expected-error{{type 'X2d' does not conform to protocol 'P2'}}
109+
func f(_: @escaping @Sendable (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@escaping @Sendable (Int) -> Int) -> ()'}}
110+
func g(_: @escaping @Sendable (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@escaping @Sendable (Int) -> Int) -> ()'}}
111+
func h(_: @escaping @Sendable (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@escaping @Sendable (Int) -> Int) -> ()'}}
112+
func i(_: @escaping @Sendable (Int) -> Int) { }
113+
}
114+
115+
74116
// SR-12707
75117

76118
class SR_12707_C<T> {}

0 commit comments

Comments
 (0)