Skip to content

Commit fbc0463

Browse files
authored
Merge pull request swiftlang#33493 from slavapestov/type-witness-where-clause-check
Sema: Check 'where' clause requirements on type witnesses
2 parents a0d69b9 + 6d84c18 commit fbc0463

12 files changed

+212
-59
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3877,10 +3877,32 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
38773877
SmallVector<std::pair<TypeDecl *, CheckTypeWitnessResult>, 2> nonViable;
38783878
for (auto candidate : candidates) {
38793879
// Skip nested generic types.
3880-
if (auto *genericDecl = dyn_cast<GenericTypeDecl>(candidate.Member))
3880+
if (auto *genericDecl = dyn_cast<GenericTypeDecl>(candidate.Member)) {
3881+
// If the declaration has generic parameters, it cannot witness an
3882+
// associated type.
38813883
if (genericDecl->isGeneric())
38823884
continue;
38833885

3886+
// As a narrow fix for a source compatibility issue with SwiftUI's
3887+
// swiftinterface, allow the conformance if the underlying type of
3888+
// the typealias is Never.
3889+
//
3890+
// FIXME: This should be conditionalized on a new language version.
3891+
bool skipRequirementCheck = false;
3892+
if (auto *typeAliasDecl = dyn_cast<TypeAliasDecl>(candidate.Member)) {
3893+
if (typeAliasDecl->getUnderlyingType()->isUninhabited())
3894+
skipRequirementCheck = true;
3895+
}
3896+
3897+
// If the type comes from a constrained extension or has a 'where'
3898+
// clause, check those requirements now.
3899+
if (!skipRequirementCheck &&
3900+
!TypeChecker::checkContextualRequirements(genericDecl, Adoptee,
3901+
SourceLoc(), DC)) {
3902+
continue;
3903+
}
3904+
}
3905+
38843906
// Skip typealiases with an unbound generic type as their underlying type.
38853907
if (auto *typeAliasDecl = dyn_cast<TypeAliasDecl>(candidate.Member))
38863908
if (typeAliasDecl->getDeclaredInterfaceType()->is<UnboundGenericType>())
@@ -3908,8 +3930,6 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
39083930
// If there is a single viable candidate, form a substitution for it.
39093931
if (viable.size() == 1) {
39103932
auto interfaceType = viable.front().MemberType;
3911-
if (interfaceType->hasArchetype())
3912-
interfaceType = interfaceType->mapTypeOutOfContext();
39133933
recordTypeWitness(assocType, interfaceType, viable.front().Member);
39143934
return ResolveWitnessResult::Success;
39153935
}

lib/Sema/TypeCheckType.cpp

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -597,26 +597,18 @@ static bool isPointerToVoid(ASTContext &Ctx, Type Ty, bool &IsMutable) {
597597
return BGT->getGenericArgs().front()->isVoid();
598598
}
599599

600-
static Type checkContextualRequirements(Type type,
601-
SourceLoc loc,
602-
DeclContext *dc) {
603-
// Even if the type is not generic, it might be inside of a generic
604-
// context, so we need to check requirements.
605-
GenericTypeDecl *decl;
606-
Type parentTy;
607-
if (auto *aliasTy = dyn_cast<TypeAliasType>(type.getPointer())) {
608-
decl = aliasTy->getDecl();
609-
parentTy = aliasTy->getParent();
610-
} else if (auto *nominalTy = type->getAs<NominalType>()) {
611-
decl = nominalTy->getDecl();
612-
parentTy = nominalTy->getParent();
613-
} else {
614-
return type;
615-
}
616-
600+
/// Even if the type is not generic, it might be inside of a generic
601+
/// context or have a free-standing 'where' clause, so we need to
602+
/// those check requirements too.
603+
///
604+
/// Return true on success.
605+
bool TypeChecker::checkContextualRequirements(GenericTypeDecl *decl,
606+
Type parentTy,
607+
SourceLoc loc,
608+
DeclContext *dc) {
617609
if (!parentTy || parentTy->hasUnboundGenericType() ||
618610
parentTy->hasTypeVariable()) {
619-
return type;
611+
return true;
620612
}
621613

622614
auto &ctx = dc->getASTContext();
@@ -631,7 +623,7 @@ static Type checkContextualRequirements(Type type,
631623
else if (ext && ext->isConstrainedExtension())
632624
noteLoc = ext->getLoc();
633625
else
634-
return type;
626+
return true;
635627

636628
if (noteLoc.isInvalid())
637629
noteLoc = loc;
@@ -640,25 +632,28 @@ static Type checkContextualRequirements(Type type,
640632
const auto subMap = parentTy->getContextSubstitutions(decl->getDeclContext());
641633
const auto genericSig = decl->getGenericSignature();
642634
if (!genericSig) {
643-
ctx.Diags.diagnose(loc, diag::recursive_decl_reference,
644-
decl->getDescriptiveKind(), decl->getName());
645-
decl->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
646-
return ErrorType::get(ctx);
635+
if (loc.isValid()) {
636+
ctx.Diags.diagnose(loc, diag::recursive_decl_reference,
637+
decl->getDescriptiveKind(), decl->getName());
638+
decl->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
639+
}
640+
return false;
647641
}
648642

649643
const auto result =
650644
TypeChecker::checkGenericArguments(
651-
dc, loc, noteLoc, type,
645+
dc, loc, noteLoc,
646+
decl->getDeclaredInterfaceType(),
652647
genericSig->getGenericParams(),
653648
genericSig->getRequirements(),
654649
QueryTypeSubstitutionMap{subMap});
655650

656651
switch (result) {
657652
case RequirementCheckResult::Failure:
658653
case RequirementCheckResult::SubstitutionFailure:
659-
return ErrorType::get(ctx);
654+
return false;
660655
case RequirementCheckResult::Success:
661-
return type;
656+
return true;
662657
}
663658
llvm_unreachable("invalid requirement check type");
664659
}
@@ -709,7 +704,22 @@ static Type applyGenericArguments(Type type, TypeResolution resolution,
709704
if (resolution.getStage() == TypeResolutionStage::Structural)
710705
return type;
711706

712-
return checkContextualRequirements(type, loc, dc);
707+
GenericTypeDecl *decl;
708+
Type parentTy;
709+
if (auto *aliasTy = dyn_cast<TypeAliasType>(type.getPointer())) {
710+
decl = aliasTy->getDecl();
711+
parentTy = aliasTy->getParent();
712+
} else if (auto *nominalTy = type->getAs<NominalType>()) {
713+
decl = nominalTy->getDecl();
714+
parentTy = nominalTy->getParent();
715+
} else {
716+
return type;
717+
}
718+
719+
if (TypeChecker::checkContextualRequirements(decl, parentTy, loc, dc))
720+
return type;
721+
722+
return ErrorType::get(resolution.getASTContext());
713723
}
714724

715725
if (type->hasError()) {

lib/Sema/TypeChecker.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,11 @@ RequirementCheckResult checkGenericArguments(
601601
ArrayRef<Requirement> requirements, TypeSubstitutionFn substitutions,
602602
SubstOptions options = None);
603603

604+
bool checkContextualRequirements(GenericTypeDecl *decl,
605+
Type parentTy,
606+
SourceLoc loc,
607+
DeclContext *dc);
608+
604609
/// Add any implicitly-defined constructors required for the given
605610
/// struct or class.
606611
void addImplicitConstructors(NominalTypeDecl *typeDecl);

test/Constraints/conditionally_defined_types.swift

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@ let _ = SameType<X>.Decl3.self
3636
let _ = SameType<X>.Decl4<X>.self
3737
let _ = SameType<X>.Decl5<X>.self
3838

39-
let _ = SameType<Y>.TypeAlias1.self // expected-error {{'SameType<Y>.TypeAlias1' (aka 'X') requires the types 'Y' and 'X' be equivalent}}
40-
let _ = SameType<Y>.TypeAlias2.self // expected-error {{'SameType<Y>.TypeAlias2' (aka 'Y') requires the types 'Y' and 'X' be equivalent}}
39+
let _ = SameType<Y>.TypeAlias1.self // expected-error {{'SameType<T>.TypeAlias1' requires the types 'Y' and 'X' be equivalent}}
40+
let _ = SameType<Y>.TypeAlias2.self // expected-error {{'SameType<T>.TypeAlias2' (aka 'Y') requires the types 'Y' and 'X' be equivalent}}
4141
let _ = SameType<Y>.TypeAlias3<X>.self // expected-error {{'SameType<Y>.TypeAlias3' requires the types 'Y' and 'X' be equivalent}}
42-
let _ = SameType<Y>.Decl1.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
43-
let _ = SameType<Y>.Decl2.self // expected-error {{'SameType<Y>.Decl2' requires the types 'Y' and 'X' be equivalent}}
44-
let _ = SameType<Y>.Decl3.self // expected-error {{'SameType<Y>.Decl3' requires the types 'Y' and 'X' be equivalent}}
42+
let _ = SameType<Y>.Decl1.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
43+
let _ = SameType<Y>.Decl2.self // expected-error {{'SameType<T>.Decl2' requires the types 'Y' and 'X' be equivalent}}
44+
let _ = SameType<Y>.Decl3.self // expected-error {{'SameType<T>.Decl3' requires the types 'Y' and 'X' be equivalent}}
4545
let _ = SameType<Y>.Decl4<X>.self // expected-error {{'SameType<Y>.Decl4' requires the types 'Y' and 'X' be equivalent}}
4646
let _ = SameType<Y>.Decl5<X>.self // expected-error {{'SameType<Y>.Decl5' requires the types 'Y' and 'X' be equivalent}}
4747

4848
extension SameType: AssociatedType where T == X {}
4949
// expected-note@-1 {{requirement specified as 'T' == 'X' [with T = Y]}}
5050

5151
let _ = SameType<X>.T.self
52-
let _ = SameType<Y>.T.self // expected-error {{'SameType<Y>.T' (aka 'X') requires the types 'Y' and 'X' be equivalent}}
52+
let _ = SameType<Y>.T.self // expected-error {{'SameType<T>.T' (aka 'X') requires the types 'Y' and 'X' be equivalent}}
5353

5454

5555
struct Conforms<T> {}
@@ -112,14 +112,14 @@ let _ = SameType<X>.Decl1.Decl3.self
112112
let _ = SameType<X>.Decl1.Decl4<X>.self
113113
let _ = SameType<X>.Decl1.Decl5<X>.self
114114

115-
let _ = SameType<Y>.Decl1.TypeAlias1.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
116-
let _ = SameType<Y>.Decl1.TypeAlias2.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
117-
let _ = SameType<Y>.Decl1.TypeAlias3<X>.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
118-
let _ = SameType<Y>.Decl1.Decl1.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
119-
let _ = SameType<Y>.Decl1.Decl2.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
120-
let _ = SameType<Y>.Decl1.Decl3.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
121-
let _ = SameType<Y>.Decl1.Decl4<X>.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
122-
let _ = SameType<Y>.Decl1.Decl5<X>.self // expected-error {{'SameType<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
115+
let _ = SameType<Y>.Decl1.TypeAlias1.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
116+
let _ = SameType<Y>.Decl1.TypeAlias2.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
117+
let _ = SameType<Y>.Decl1.TypeAlias3<X>.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
118+
let _ = SameType<Y>.Decl1.Decl1.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
119+
let _ = SameType<Y>.Decl1.Decl2.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
120+
let _ = SameType<Y>.Decl1.Decl3.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
121+
let _ = SameType<Y>.Decl1.Decl4<X>.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
122+
let _ = SameType<Y>.Decl1.Decl5<X>.self // expected-error {{'SameType<T>.Decl1' requires the types 'Y' and 'X' be equivalent}}
123123

124124
extension SameType.Decl4 where U == X { // expected-note 5 {{requirement specified as 'U' == 'X' [with U = Y]}}
125125
typealias TypeAlias1 = T
@@ -144,12 +144,12 @@ let _ = SameType<X>.Decl4<X>.Decl3.self
144144
let _ = SameType<X>.Decl4<X>.Decl4<X>.self
145145
let _ = SameType<X>.Decl4<X>.Decl5<X>.self
146146

147-
let _ = SameType<X>.Decl4<Y>.TypeAlias1.self // expected-error {{'SameType<X>.Decl4<Y>.TypeAlias1' (aka 'X') requires the types 'Y' and 'X' be equivalent}}
148-
let _ = SameType<X>.Decl4<Y>.TypeAlias2.self // expected-error {{'SameType<X>.Decl4<Y>.TypeAlias2' (aka 'Y') requires the types 'Y' and 'X' be equivalent}}
147+
let _ = SameType<X>.Decl4<Y>.TypeAlias1.self // expected-error {{'SameType<T>.Decl4<U>.TypeAlias1' requires the types 'Y' and 'X' be equivalent}}
148+
let _ = SameType<X>.Decl4<Y>.TypeAlias2.self // expected-error {{'SameType<T>.Decl4<U>.TypeAlias2' (aka 'Y') requires the types 'Y' and 'X' be equivalent}}
149149
let _ = SameType<X>.Decl4<Y>.TypeAlias3<X>.self // expected-error {{'SameType<X>.Decl4<Y>.TypeAlias3' requires the types 'Y' and 'X' be equivalent}}
150-
let _ = SameType<X>.Decl4<Y>.Decl1.self // expected-error {{'SameType<X>.Decl4<Y>.Decl1' requires the types 'Y' and 'X' be equivalent}}
151-
let _ = SameType<X>.Decl4<Y>.Decl2.self // expected-error {{'SameType<X>.Decl4<Y>.Decl2' requires the types 'Y' and 'X' be equivalent}}
152-
let _ = SameType<X>.Decl4<Y>.Decl3.self // expected-error {{'SameType<X>.Decl4<Y>.Decl3' requires the types 'Y' and 'X' be equivalent}}
150+
let _ = SameType<X>.Decl4<Y>.Decl1.self // expected-error {{'SameType<T>.Decl4<U>.Decl1' requires the types 'Y' and 'X' be equivalent}}
151+
let _ = SameType<X>.Decl4<Y>.Decl2.self // expected-error {{'SameType<T>.Decl4<U>.Decl2' requires the types 'Y' and 'X' be equivalent}}
152+
let _ = SameType<X>.Decl4<Y>.Decl3.self // expected-error {{'SameType<T>.Decl4<U>.Decl3' requires the types 'Y' and 'X' be equivalent}}
153153
let _ = SameType<X>.Decl4<Y>.Decl4<X>.self // expected-error {{'SameType<X>.Decl4<Y>.Decl4' requires the types 'Y' and 'X' be equivalent}}
154154
let _ = SameType<X>.Decl4<Y>.Decl5<X>.self // expected-error {{'SameType<X>.Decl4<Y>.Decl5' requires the types 'Y' and 'X' be equivalent}}
155155

test/Constraints/rdar39931339.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ _ = B<C>.S1() // Ok
3232
_ = B<Int>.S2() // Ok
3333
_ = B<Float>.S1() // expected-error {{type 'Float' does not conform to protocol 'P'}}
3434
_ = B<String>.S2()
35-
// expected-error@-1 {{'B<String>.S2' (aka 'Int') requires the types '[String]' and '[Int]' be equivalent}}
35+
// expected-error@-1 {{'A<T, U>.S2' (aka 'Int') requires the types '[String]' and '[Int]' be equivalent}}
3636

3737
_ = S<C>.A() // Ok
3838
_ = S<Int>.A() // expected-error {{type 'Int' does not conform to protocol 'P'}}
3939
_ = S<String>.B<Int>() // expected-error {{type 'String' does not conform to protocol 'P'}}
40-
_ = S<Int>.C() // expected-error {{'S<Int>.C' (aka 'Int') requires the types 'Int' and 'Float' be equivalent}}
40+
_ = S<Int>.C() // expected-error {{'S<T>.C' (aka 'Int') requires the types 'Int' and 'Float' be equivalent}}
4141

4242
func foo<T>(_ s: S<T>.Type) {
4343
_ = s.A() // expected-error {{referencing type alias 'A' on 'S' requires that 'T' conform to 'P'}}

test/Constraints/requirement_failures_in_contextual_type.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ extension A where T == Int32 { // expected-note 3{{requirement specified as 'T'
1414
}
1515

1616
let _: A<Int>.B = 0
17-
// expected-error@-1 {{'A<Int>.B' requires the types 'Int' and 'Int32' be equivalent}}
17+
// expected-error@-1 {{'A<T>.B' requires the types 'Int' and 'Int32' be equivalent}}
1818
let _: A<Int>.C = 0
19-
// expected-error@-1 {{'A<Int>.C' (aka 'Int') requires the types 'Int' and 'Int32' be equivalent}}
19+
// expected-error@-1 {{'A<T>.C' (aka 'Int') requires the types 'Int' and 'Int32' be equivalent}}
2020
let _: A<Int>.B.E = 0
21-
// expected-error@-1 {{'A<Int>.B' requires the types 'Int' and 'Int32' be equivalent}}
21+
// expected-error@-1 {{'A<T>.B' requires the types 'Int' and 'Int32' be equivalent}}
2222

2323

2424
protocol P {}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol P {
4+
associatedtype A
5+
// expected-note@-1 3{{protocol requires nested type 'A'; do you want to add it?}}
6+
}
7+
8+
struct S1<T> {}
9+
10+
extension S1 where T : P {
11+
typealias A = Int
12+
}
13+
14+
// This is rejected because S1.A is not a suitable witness for P.A.
15+
extension S1 : P {}
16+
// expected-error@-1 {{type 'S1<T>' does not conform to protocol 'P'}}
17+
18+
struct S2<T> {}
19+
20+
extension S2 where T : P {
21+
typealias A = Never
22+
}
23+
24+
// Hack: This is OK to make SwiftUI work, which accidentally relies on the
25+
// incorrect behavior with a typealias whose underlying type is 'Never'
26+
// (so it didn't hit the compiler crash).
27+
extension S2 : P {}
28+
29+
// Here we have a suitable witness
30+
struct S3<T> {}
31+
32+
extension S3 where T == Int {
33+
typealias A = Int
34+
}
35+
36+
extension S3 : P where T == Int {}
37+
38+
// Check where clause on the type itself
39+
40+
struct S4<T> {
41+
typealias A = Int where T : P
42+
}
43+
44+
extension S4 : P {}
45+
// expected-error@-1 {{type 'S4<T>' does not conform to protocol 'P'}}
46+
47+
struct S5<T> {
48+
typealias A = Never where T : P
49+
}
50+
51+
extension S5 : P {}
52+
53+
struct S6<T> {
54+
typealias A = Int where T == Int
55+
}
56+
57+
extension S6 : P where T == Int {}
58+
59+
// Witness in a constrained protocol extension
60+
protocol Q {
61+
associatedtype B
62+
}
63+
64+
extension Q where B == Int {
65+
typealias A = Int
66+
}
67+
68+
struct S7 : Q, P {
69+
typealias B = Int
70+
}
71+
72+
struct S8 : Q, P {
73+
// expected-error@-1 {{type 'S8' does not conform to protocol 'P'}}
74+
typealias B = String
75+
}

test/Generics/where_clause_contextually_generic_decls.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift -typecheck %s -verify -swift-version 4
1+
// RUN: %target-typecheck-verify-swift -swift-version 4
22

33
func bet() where A : B {} // expected-error {{'where' clause cannot be applied to a non-generic top-level declaration}}
44

@@ -148,7 +148,7 @@ _ = Container<String>.NestedAlias2.self // expected-error {{type 'String' does n
148148
_ = Container<Container<Bool>>.NestedClass.self // expected-error {{type 'Container<Bool>' does not conform to protocol 'Equatable'}}
149149
_ = Container<Void>.NestedStruct.self // expected-error {{type 'Void' does not conform to protocol 'Sequence'}}
150150
_ = Container<Array<Void>>.NestedStruct2.self // expected-error {{type 'Void' does not conform to protocol 'Comparable'}}
151-
_ = Container<String>.NestedStruct2.NestedEnum.self // expected-error {{'Container<String>.NestedStruct2.NestedEnum' requires the types 'String.Element' (aka 'Character') and 'Double' be equivalent}}
151+
_ = Container<String>.NestedStruct2.NestedEnum.self // expected-error {{'Container<T>.NestedStruct2.NestedEnum' requires the types 'String.Element' (aka 'Character') and 'Double' be equivalent}}
152152
_ = Container<Int>.NestedAlias2.self
153153
_ = Container<Bool>.NestedClass.self
154154
_ = Container<String>.NestedStruct.self

test/decl/protocol/req/missing_conformance.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ extension CountSteps1 // expected-error {{type 'CountSteps1<T>' does not conform
132132
where T : Equatable
133133
{
134134
typealias Index = Int
135+
// expected-error@-1 {{invalid redeclaration of synthesized implementation for protocol requirement 'Index'}}
135136
func index(_ i: Index, offsetBy d: Int) -> Index {
136137
return i + d
137138
}

test/decl/var/property_wrappers.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1732,7 +1732,7 @@ extension SR_11288_P4 where Self: AnyObject { // expected-note {{requirement spe
17321732
}
17331733

17341734
struct SR_11288_S4: SR_11288_P4 {
1735-
@SR_11288_Wrapper4 var answer = 42 // expected-error {{'SR_11288_S4.SR_11288_Wrapper4' (aka 'SR_11288_S0') requires that 'SR_11288_S4' be a class type}}
1735+
@SR_11288_Wrapper4 var answer = 42 // expected-error {{'Self.SR_11288_Wrapper4' (aka 'SR_11288_S0') requires that 'SR_11288_S4' be a class type}}
17361736
}
17371737

17381738
class SR_11288_C0: SR_11288_P4 {

0 commit comments

Comments
 (0)