Skip to content

Commit 790625a

Browse files
committed
Allow a witness's noescape parameter to match a requirement's escaping parameter
Extend protocol conformance checking to allow a requirement with an escaping parameter (of function type) to be satisfied by a witness with a corresponding non-escaping parameter. This limited form of covariance was already supported by SILGen and is needed to address the source-compatibility "regression" introduced by inferring escaping function types for associated type witnesses. It's also obviously a good idea :) Fixes rdar://problem/35297911.
1 parent b8efde9 commit 790625a

File tree

5 files changed

+56
-18
lines changed

5 files changed

+56
-18
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,17 @@ static std::tuple<Type, Type, OptionalAdjustmentKind>
138138
getTypesToCompare(ValueDecl *reqt, Type reqtType, bool reqtTypeIsIUO,
139139
Type witnessType, bool witnessTypeIsIUO,
140140
VarianceKind variance) {
141+
// 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;
151+
141152
// For @objc protocols, deal with differences in the optionality.
142153
// FIXME: It probably makes sense to extend this to non-@objc
143154
// protocols as well, but this requires more testing.
@@ -152,8 +163,7 @@ getTypesToCompare(ValueDecl *reqt, Type reqtType, bool reqtTypeIsIUO,
152163
reqtType = reqtValueType;
153164
}
154165
bool witnessIsOptional = false;
155-
if (Type witnessValueType =
156-
witnessType->getOptionalObjectType()) {
166+
if (Type witnessValueType = witnessType->getOptionalObjectType()) {
157167
witnessIsOptional = true;
158168
witnessType = witnessValueType;
159169
}

lib/Sema/TypeCheckProtocol.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,13 @@ RequirementMatch matchWitness(TypeChecker &tc,
852852
AssociatedTypeDecl *getReferencedAssocTypeOfProtocol(Type type,
853853
ProtocolDecl *proto);
854854

855+
/// Perform any necessary adjustments to the inferred associated type to
856+
/// make it suitable for later use.
857+
///
858+
/// \param noescapeToEscaping Will be set \c true if this operation performed
859+
/// the noescape-to-escaping adjustment.
860+
Type adjustInferredAssociatedType(Type type, bool &noescapeToEscaping);
861+
855862
}
856863

857864
#endif // SWIFT_SEMA_PROTOCOL_H

lib/Sema/TypeCheckProtocolInference.cpp

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -607,13 +607,11 @@ AssociatedTypeInference::inferTypeWitnessesViaAssociatedType(
607607
return result;
608608
}
609609

610-
/// Perform any necessary adjustments to the inferred associated type to
611-
/// make it suitable for later use.
612-
static Type adjustInferredAssociatedType(Type type) {
610+
Type swift::adjustInferredAssociatedType(Type type, bool &noescapeToEscaping) {
613611
// If we have an optional type, adjust its wrapped type.
614612
if (auto optionalObjectType = type->getOptionalObjectType()) {
615613
auto newOptionalObjectType =
616-
adjustInferredAssociatedType(optionalObjectType);
614+
adjustInferredAssociatedType(optionalObjectType, noescapeToEscaping);
617615
if (newOptionalObjectType.getPointer() == optionalObjectType.getPointer())
618616
return type;
619617

@@ -623,14 +621,11 @@ static Type adjustInferredAssociatedType(Type type) {
623621
// If we have a noescape function type, make it escaping.
624622
if (auto funcType = type->getAs<FunctionType>()) {
625623
if (funcType->isNoEscape()) {
624+
noescapeToEscaping = true;
626625
return FunctionType::get(funcType->getParams(), funcType->getResult(),
627626
funcType->getExtInfo().withNoEscape(false));
628627
}
629628
}
630-
631-
// We can only infer materializable types.
632-
if (!type->isMaterializable()) return nullptr;
633-
634629
return type;
635630
}
636631

@@ -689,8 +684,11 @@ AssociatedTypeInference::inferTypeWitnessesViaValueWitness(ValueDecl *req,
689684
if (secondType->hasError())
690685
return true;
691686

692-
Type inferredType = adjustInferredAssociatedType(secondType);
693-
if (!inferredType)
687+
// Adjust the type to a type that can be written explicitly.
688+
bool noescapeToEscaping = false;
689+
Type inferredType =
690+
adjustInferredAssociatedType(secondType, noescapeToEscaping);
691+
if (!inferredType->isMaterializable())
694692
return true;
695693

696694
auto proto = Conformance->getProtocol();

test/SILGen/witnesses.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,16 @@ class CrashableBase {
512512
// CHECK-NEXT: return [[RESULT]] : $()
513513

514514
class GenericCrashable<T> : CrashableBase, Crashable {}
515+
516+
// rdar://problem/35297911: allow witness with a noescape parameter to
517+
// match a requirement with an escaping paameter.
518+
protocol EscapingReq {
519+
func f(_: @escaping (Int) -> Int)
520+
}
521+
522+
// CHECK-LABEL: sil private [transparent] [thunk] @$S9witnesses18EscapingCovarianceVAA0B3ReqA2aDP1fyyS2icFTW : $@convention(witness_method: EscapingReq) (@guaranteed @callee_guaranteed (Int) -> Int, @in_guaranteed EscapingCovariance) -> ()
523+
// CHECK-NOT: return
524+
// CHECK: convert_escape_to_noescape %0
525+
struct EscapingCovariance: EscapingReq {
526+
func f(_: (Int) -> Int) { }
527+
}

test/decl/protocol/conforms/associated_type.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ protocol P1 {
3838
associatedtype A
3939

4040
func f(_: A)
41-
// expected-note@-1 {{protocol requires function 'f' with type '(X1c.A) -> ()' (aka '((Int) -> Int) -> ()'); do you want to add a stub?}}
42-
// expected-note@-2 {{protocol requires function 'f' with type '((Int) -> Int) -> ()'; do you want to add a stub?}}
4341
}
4442

4543
struct X1a : P1 {
@@ -52,12 +50,24 @@ struct X1b : P1 {
5250
func f(_: @escaping (Int) -> Int) { }
5351
}
5452

55-
struct X1c : P1 { // expected-error{{type 'X1c' does not conform to protocol 'P1'}}
53+
struct X1c : P1 {
5654
typealias A = (Int) -> Int
5755

58-
func f(_: (Int) -> Int) { } // expected-note{{candidate has non-matching type '((Int) -> Int) -> ()'}}
56+
func f(_: (Int) -> Int) { }
5957
}
6058

61-
struct X1d : P1 { // expected-error{{type 'X1d' does not conform to protocol 'P1'}}
62-
func f(_: (Int) -> Int) { } // expected-note{{candidate has non-matching type '((Int) -> Int) -> ()' [with A = (Int) -> Int]}}
59+
struct X1d : P1 {
60+
func f(_: (Int) -> Int) { }
61+
}
62+
63+
protocol P2 {
64+
func f(_: (Int) -> Int) // expected-note{{protocol requires function 'f' with type '((Int) -> Int) -> ()'; do you want to add a stub?}}
65+
}
66+
67+
struct X2a : P2 {
68+
func f(_: (Int) -> Int) { }
69+
}
70+
71+
struct X2b : P2 { // expected-error{{type 'X2b' does not conform to protocol 'P2'}}
72+
func f(_: @escaping (Int) -> Int) { } // expected-note{{candidate has non-matching type '(@escaping (Int) -> Int) -> ()'}}
6373
}

0 commit comments

Comments
 (0)