Skip to content

Commit 5456eb9

Browse files
committed
Sema: Allow T == NonCopyableOrEscapable same-type constraints without a redundant T: ~Copyable.
Enhance the logic in `applyInverses` to also take into account same-type constraints spelled in the generic signature, so that same-type-constraining a type parameter to a type that is itself not `Copyable` or `Escapable` suppresses the default application of those constraints on the type parameter. Fixes rdar://147757973.
1 parent d6ce27b commit 5456eb9

File tree

5 files changed

+234
-24
lines changed

5 files changed

+234
-24
lines changed

lib/AST/RequirementMachine/RequirementLowering.cpp

Lines changed: 149 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
#include "swift/AST/TypeMatcher.h"
160160
#include "swift/AST/TypeRepr.h"
161161
#include "swift/Basic/Assertions.h"
162+
#include "swift/Basic/Defer.h"
162163
#include "llvm/ADT/SmallVector.h"
163164
#include "llvm/ADT/SetVector.h"
164165
#include "Diagnostics.h"
@@ -802,15 +803,122 @@ void swift::rewriting::applyInverses(
802803
ASTContext &ctx,
803804
ArrayRef<Type> gps,
804805
ArrayRef<InverseRequirement> inverseList,
806+
ArrayRef<StructuralRequirement> explicitRequirements,
805807
SmallVectorImpl<StructuralRequirement> &result,
806808
SmallVectorImpl<RequirementError> &errors) {
807809

808-
// No inverses to even validate.
809-
if (inverseList.empty())
810+
// Are there even any inverses or same-type requirements to validate?
811+
if (inverseList.empty() && explicitRequirements.empty()) {
810812
return;
813+
}
811814

812815
const bool allowInverseOnAssocType =
813816
ctx.LangOpts.hasFeature(Feature::SuppressedAssociatedTypes);
817+
818+
llvm::DenseMap<CanType, CanType> representativeGPs;
819+
820+
// Start with an identity mapping.
821+
for (auto gp : gps) {
822+
auto canGP = gp->getCanonicalType();
823+
representativeGPs.insert({canGP, canGP});
824+
}
825+
bool hadSameTypeConstraintInScope = false;
826+
827+
// Return the in-scope generic parameter that represents the equivalence class
828+
// for `gp`, or return null if the parameter is constrained out of scope.
829+
auto representativeGPFor = [&](CanType gp) -> CanType {
830+
while (true) {
831+
auto found = representativeGPs.find(gp);
832+
if (found == representativeGPs.end()) {
833+
return CanType();
834+
}
835+
if (found->second == CanType()) {
836+
return CanType();
837+
}
838+
839+
if (found->second == gp) {
840+
return gp;
841+
}
842+
843+
gp = found->second;
844+
}
845+
};
846+
847+
// Look for same-type constraints that equate multiple generic parameters
848+
// within the scope so we can treat the equivalence class as a unit.
849+
for (auto &explicitReqt : explicitRequirements) {
850+
if (explicitReqt.req.getKind() != RequirementKind::SameType) {
851+
continue;
852+
}
853+
854+
// If one end of the same-type requirement is in scope, and the other is
855+
// a concrete type or out-of-scope generic parameter, then the other
856+
// parameter is also effectively out of scope.
857+
auto firstTy = explicitReqt.req.getFirstType()->getCanonicalType();
858+
auto secondTy = explicitReqt.req.getSecondType()->getCanonicalType();
859+
if (!representativeGPs.count(firstTy)
860+
&& !representativeGPs.count(secondTy)) {
861+
// Same type constraint doesn't involve any in-scope generic parameters.
862+
continue;
863+
}
864+
865+
CanType typeInScope;
866+
CanType typeOutOfScope;
867+
868+
if (representativeGPs.count(firstTy)
869+
&& !representativeGPs.count(secondTy)){
870+
// First type is constrained out of scope.
871+
typeInScope = firstTy;
872+
typeOutOfScope = secondTy;
873+
} else if (!representativeGPs.count(firstTy)
874+
&& representativeGPs.count(secondTy)) {
875+
// Second type is constrained out of scope.
876+
typeInScope = secondTy;
877+
typeOutOfScope = firstTy;
878+
} else {
879+
// Otherwise, both ends of the same-type constraint are in scope.
880+
// Fold the lexicographically-greater parameter with the lesser.
881+
auto firstGP = cast<GenericTypeParamType>(firstTy);
882+
auto secondGP = cast<GenericTypeParamType>(secondTy);
883+
884+
if (firstGP == secondGP) {
885+
// `T == T` has no effect.
886+
continue;
887+
}
888+
889+
if (firstGP->getDepth() > secondGP->getDepth()
890+
|| (firstGP->getDepth() == secondGP->getDepth()
891+
&& firstGP->getIndex() > secondGP->getIndex())) {
892+
std::swap(firstGP, secondGP);
893+
}
894+
895+
hadSameTypeConstraintInScope = true;
896+
representativeGPs.insert_or_assign(secondGP, representativeGPFor(firstGP));
897+
continue;
898+
}
899+
900+
// If the out-of-scope type is another type parameter or associated type,
901+
// then ignore this same-type constraint and allow defaulting to continue.
902+
//
903+
// It would probably have been more principled to suppress any defaulting
904+
// in this case, but this behavior shipped in Swift 6.0 and 6.1, so we
905+
// need to maintain source compatibility.
906+
if (typeOutOfScope->isTypeParameter()) {
907+
continue;
908+
}
909+
910+
// If the out-of-scope type contains errors, then similarly, ignore the
911+
// same type constraint. Any additional diagnostics arising from the type
912+
// parameter being left ~Copyable or ~Escapable might be misleading if the
913+
// corrected code is attempting to refer to a Copyable or Escapable type.
914+
if (typeOutOfScope->hasError()) {
915+
continue;
916+
}
917+
918+
representativeGPs.insert_or_assign(representativeGPFor(typeInScope),
919+
CanType());
920+
hadSameTypeConstraintInScope = true;
921+
}
814922

815923
// Summarize the inverses and diagnose ones that are incorrect.
816924
llvm::DenseMap<CanType, InvertibleProtocolSet> inverses;
@@ -837,11 +945,6 @@ void swift::rewriting::applyInverses(
837945
continue;
838946
}
839947

840-
// WARNING: possible quadratic behavior, but should be OK in practice.
841-
auto notInScope = llvm::none_of(gps, [=](Type t) {
842-
return t->getCanonicalType() == canSubject;
843-
});
844-
845948
// If the inverse is on a subject that wasn't permitted by our caller, then
846949
// remove and diagnose as an error. This can happen when an inner context
847950
// has a constraint on some outer generic parameter, e.g.,
@@ -850,27 +953,40 @@ void swift::rewriting::applyInverses(
850953
// func f() where Self: ~Copyable
851954
// }
852955
//
853-
if (notInScope) {
956+
if (representativeGPs.find(canSubject) == representativeGPs.end()) {
854957
errors.push_back(
855958
RequirementError::forInvalidInverseOuterSubject(inverse));
856959
continue;
857960
}
858961

859-
auto state = inverses.getOrInsertDefault(canSubject);
962+
auto representativeSubject = representativeGPFor(canSubject);
963+
964+
// If the subject is in scope, but same-type constrained to a type out of
965+
// scope, then allow inverses to be stated even though they are redundant.
966+
// This is because older versions of Swift not only accepted but required
967+
// `extension Foo where T == NonCopyableType, T: ~Copyable {}` to be
968+
// written, so we need to continue to accept that formulation for source
969+
// compatibility.
970+
if (!representativeSubject) {
971+
continue;
972+
}
973+
974+
auto state = inverses.getOrInsertDefault(representativeSubject);
860975

861976
// Check if this inverse has already been seen.
862977
auto inverseKind = inverse.getKind();
863978
if (state.contains(inverseKind))
864979
continue;
865980

866981
state.insert(inverseKind);
867-
inverses[canSubject] = state;
982+
inverses[representativeSubject] = state;
868983
}
869984

870-
// Fast-path: if there are no valid inverses, then there are no requirements
871-
// to be removed.
872-
if (inverses.empty())
985+
// Fast-path: if there are no valid inverses or same-type constraints, then
986+
// there are no requirements to be removed.
987+
if (inverses.empty() && !hadSameTypeConstraintInScope) {
873988
return;
989+
}
874990

875991
// Scan the structural requirements and cancel out any inferred requirements
876992
// based on the inverses we saw.
@@ -882,18 +998,30 @@ void swift::rewriting::applyInverses(
882998

883999
// Only consider requirements involving an invertible protocol.
8841000
auto proto = req.getProtocolDecl()->getInvertibleProtocolKind();
885-
if (!proto)
1001+
if (!proto) {
8861002
return false;
1003+
}
8871004

8881005
// See if this subject is in-scope.
8891006
auto subject = req.getFirstType()->getCanonicalType();
890-
auto result = inverses.find(subject);
891-
if (result == inverses.end())
1007+
auto representative = representativeGPs.find(subject);
1008+
if (representative == representativeGPs.end()) {
8921009
return false;
1010+
}
1011+
1012+
// If this type is same-type constrained into another equivalence class,
1013+
// then it doesn't need its own defaulted requirements.
1014+
if (representative->second != subject) {
1015+
return true;
1016+
}
8931017

8941018
// We now have found the inferred constraint 'Subject : Proto'.
8951019
// So, remove it if we have recorded a 'Subject : ~Proto'.
896-
auto recordedInverses = result->getSecond();
1020+
auto foundInverses = inverses.find(subject);
1021+
if (foundInverses == inverses.end()) {
1022+
return false;
1023+
}
1024+
auto recordedInverses = foundInverses->getSecond();
8971025
return recordedInverses.contains(*proto);
8981026
}), result.end());
8991027
}
@@ -1002,7 +1130,8 @@ StructuralRequirementsRequest::evaluate(Evaluator &evaluator,
10021130

10031131
SmallVector<StructuralRequirement, 2> defaults;
10041132
InverseRequirement::expandDefaults(ctx, needsDefaultRequirements, defaults);
1005-
applyInverses(ctx, needsDefaultRequirements, inverses, defaults, errors);
1133+
applyInverses(ctx, needsDefaultRequirements, inverses, result,
1134+
defaults, errors);
10061135
result.append(defaults);
10071136

10081137
diagnoseRequirementErrors(ctx, errors,
@@ -1076,7 +1205,8 @@ StructuralRequirementsRequest::evaluate(Evaluator &evaluator,
10761205
&& !proto->isSpecificProtocol(KnownProtocolKind::Sendable))
10771206
InverseRequirement::expandDefaults(ctx, needsDefaultRequirements, defaults);
10781207

1079-
applyInverses(ctx, needsDefaultRequirements, inverses, defaults, errors);
1208+
applyInverses(ctx, needsDefaultRequirements, inverses, result,
1209+
defaults, errors);
10801210
result.append(defaults);
10811211

10821212
diagnoseRequirementErrors(ctx, errors,

lib/AST/RequirementMachine/RequirementLowering.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ void realizeInheritedRequirements(TypeDecl *decl, Type type,
6464
void applyInverses(ASTContext &ctx,
6565
ArrayRef<Type> gps,
6666
ArrayRef<InverseRequirement> inverseList,
67+
ArrayRef<StructuralRequirement> explicitRequirements,
6768
SmallVectorImpl<StructuralRequirement> &result,
6869
SmallVectorImpl<RequirementError> &errors);
6970

lib/AST/RequirementMachine/RequirementMachineRequests.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,8 @@ AbstractGenericSignatureRequest::evaluate(
671671

672672
SmallVector<StructuralRequirement, 2> defaults;
673673
InverseRequirement::expandDefaults(ctx, paramsAsTypes, defaults);
674-
applyInverses(ctx, paramsAsTypes, inverses, defaults, errors);
674+
applyInverses(ctx, paramsAsTypes, inverses, requirements,
675+
defaults, errors);
675676
requirements.append(defaults);
676677

677678
auto &rewriteCtx = ctx.getRewriteContext();
@@ -884,7 +885,8 @@ InferredGenericSignatureRequest::evaluate(
884885

885886
SmallVector<StructuralRequirement, 2> defaults;
886887
InverseRequirement::expandDefaults(ctx, paramTypes, defaults);
887-
applyInverses(ctx, paramTypes, inverses, defaults, errors);
888+
applyInverses(ctx, paramTypes, inverses, requirements,
889+
defaults, errors);
888890

889891
// Any remaining implicit defaults in a conditional inverse requirement
890892
// extension must be made explicit.

test/Generics/inverse_generics.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,9 @@ func conflict13<T>(_ t: T)
365365
{}
366366

367367
// expected-warning@+1 {{same-type requirement makes generic parameters 'U' and 'T' equivalent}}
368-
func conflict14<T, U>(_ t: T, _ u: U)
369-
where T: ~Copyable, // expected-error {{'T' required to be 'Copyable' but is marked with '~Copyable'}}
370-
U: ~Escapable, // expected-error {{'U' required to be 'Escapable' but is marked with '~Escapable'}}
368+
func conflict14<T, U>(_ t: borrowing T, _ u: borrowing U)
369+
where T: ~Copyable,
370+
U: ~Escapable,
371371
T == U {}
372372

373373
protocol Conflict15 {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s
2+
3+
struct Bar: ~Copyable {}
4+
5+
struct Foo<T: ~Copyable> {
6+
func foo() -> T { fatalError() }
7+
}
8+
9+
extension Foo where T == Bar {
10+
func bar() -> Bar {
11+
return foo()
12+
}
13+
}
14+
15+
struct Foo2<T: ~Copyable, U: ~Copyable> {
16+
func foo1() -> T { fatalError() }
17+
func foo2() -> U { fatalError() }
18+
}
19+
20+
extension Foo2 where T == Bar, U == Bar {
21+
func bar1_1() -> Bar {
22+
return foo1()
23+
}
24+
func bar1_2() -> Bar {
25+
return foo2()
26+
}
27+
}
28+
29+
extension Foo2 where T == U, U == Bar {
30+
func bar2_1() -> Bar {
31+
return foo1()
32+
}
33+
func bar2_2() -> Bar {
34+
return foo2()
35+
}
36+
}
37+
38+
extension Foo2 where T == Bar, U == T {
39+
func bar3_1() -> Bar {
40+
return foo1()
41+
}
42+
func bar3_2() -> Bar {
43+
return foo2()
44+
}
45+
}
46+
47+
func needsCopyable<T>(_: T) {}
48+
49+
// T and U are still Copyable by default here, since the equivalence class of
50+
// the same-type-constrained parameters is entirely within the scope of the
51+
// extension, and there is no explicit suppression of the default.
52+
extension Foo2 where T == U {
53+
func test(_ x: T) {
54+
needsCopyable(x)
55+
}
56+
}
57+
58+
// Explicitly suppressing Copyable on one type parameter also prevents the other
59+
// parameters in the equivalence class from defaulting to Copyable.
60+
extension Foo2 where T == U, T: ~Copyable {
61+
func bar4_1() -> T {
62+
return foo1()
63+
}
64+
func bar4_2() -> T {
65+
return foo2()
66+
}
67+
}
68+
69+
extension Foo2 where T == U, U: ~Copyable {
70+
func bar5_1() -> T {
71+
return foo1()
72+
}
73+
func bar5_2() -> T {
74+
return foo2()
75+
}
76+
}
77+

0 commit comments

Comments
 (0)