Skip to content

Commit 52d7781

Browse files
authored
Merge pull request #81424 from jckarter/same-type-constraint-stop-copyable
Sema: Allow `T == NonCopyableOrEscapable` same-type constraints without a redundant `T: ~Copyable`.
2 parents a5261ae + e4a6faa commit 52d7781

File tree

7 files changed

+372
-112
lines changed

7 files changed

+372
-112
lines changed

lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ add_swift_host_library(swiftAST STATIC
9595
RawComment.cpp
9696
Requirement.cpp
9797
RequirementEnvironment.cpp
98+
RequirementMachine/ApplyInverses.cpp
9899
RequirementMachine/ConcreteContraction.cpp
99100
RequirementMachine/ConcreteTypeWitness.cpp
100101
RequirementMachine/Diagnostics.cpp
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
//===--- ApplyInverses.cpp - Resolve `~Protocol` anti-constraints ---------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// The `applyInverses` function takes the syntactic representation of a generic
14+
// signature and applies implicit default constraints on generic parameters for
15+
// core type capabilities like `Copyable` and `Escapable`. In doing so, it
16+
// looks for explicit constraint suppression "requirements" like `T: ~Copyable`
17+
// or same-type constraints that would contradict the implicit requirements and
18+
// filters out unwanted default requirements.
19+
//
20+
//===----------------------------------------------------------------------===//
21+
22+
#include "RequirementLowering.h"
23+
#include "swift/AST/ASTContext.h"
24+
#include "swift/AST/ConformanceLookup.h"
25+
#include "swift/AST/Decl.h"
26+
#include "swift/AST/DiagnosticsSema.h"
27+
#include "swift/AST/Requirement.h"
28+
#include "swift/AST/RequirementSignature.h"
29+
#include "swift/AST/TypeCheckRequests.h"
30+
#include "swift/AST/TypeMatcher.h"
31+
#include "swift/AST/TypeRepr.h"
32+
#include "swift/Basic/Assertions.h"
33+
#include "swift/Basic/Defer.h"
34+
#include "llvm/ADT/SmallVector.h"
35+
#include "llvm/ADT/SetVector.h"
36+
#include "Diagnostics.h"
37+
#include "RewriteContext.h"
38+
#include "NameLookup.h"
39+
40+
using namespace swift;
41+
using namespace rewriting;
42+
43+
void swift::rewriting::applyInverses(
44+
ASTContext &ctx,
45+
ArrayRef<Type> gps,
46+
ArrayRef<InverseRequirement> inverseList,
47+
ArrayRef<StructuralRequirement> explicitRequirements,
48+
SmallVectorImpl<StructuralRequirement> &result,
49+
SmallVectorImpl<RequirementError> &errors) {
50+
51+
// Are there even any inverses or same-type requirements to validate?
52+
if (inverseList.empty() && explicitRequirements.empty()) {
53+
return;
54+
}
55+
56+
const bool allowInverseOnAssocType =
57+
ctx.LangOpts.hasFeature(Feature::SuppressedAssociatedTypes);
58+
59+
llvm::DenseMap<CanType, CanType> representativeGPs;
60+
61+
// Start with an identity mapping.
62+
for (auto gp : gps) {
63+
auto canGP = gp->getCanonicalType();
64+
representativeGPs.insert({canGP, canGP});
65+
}
66+
bool hadSameTypeConstraintInScope = false;
67+
68+
// Return the in-scope generic parameter that represents the equivalence class
69+
// for `gp`, or return null if the parameter is constrained out of scope.
70+
auto representativeGPFor = [&](CanType gp) -> CanType {
71+
while (true) {
72+
auto found = representativeGPs.find(gp);
73+
if (found == representativeGPs.end()) {
74+
return CanType();
75+
}
76+
if (found->second == CanType()) {
77+
return CanType();
78+
}
79+
80+
if (found->second == gp) {
81+
return gp;
82+
}
83+
84+
gp = found->second;
85+
}
86+
};
87+
88+
// Look for same-type constraints that equate multiple generic parameters
89+
// within the scope so we can treat the equivalence class as a unit.
90+
for (auto &explicitReqt : explicitRequirements) {
91+
if (explicitReqt.req.getKind() != RequirementKind::SameType) {
92+
continue;
93+
}
94+
95+
// If one end of the same-type requirement is in scope, and the other is
96+
// a concrete type or out-of-scope generic parameter, then the other
97+
// parameter is also effectively out of scope.
98+
auto firstTy = explicitReqt.req.getFirstType()->getCanonicalType();
99+
auto secondTy = explicitReqt.req.getSecondType()->getCanonicalType();
100+
if (!representativeGPs.count(firstTy)
101+
&& !representativeGPs.count(secondTy)) {
102+
// Same type constraint doesn't involve any in-scope generic parameters.
103+
continue;
104+
}
105+
106+
CanType typeInScope;
107+
CanType typeOutOfScope;
108+
109+
if (representativeGPs.count(firstTy)
110+
&& !representativeGPs.count(secondTy)){
111+
// First type is constrained out of scope.
112+
typeInScope = firstTy;
113+
typeOutOfScope = secondTy;
114+
} else if (!representativeGPs.count(firstTy)
115+
&& representativeGPs.count(secondTy)) {
116+
// Second type is constrained out of scope.
117+
typeInScope = secondTy;
118+
typeOutOfScope = firstTy;
119+
} else {
120+
// Otherwise, both ends of the same-type constraint are in scope.
121+
// Fold the lexicographically-greater parameter with the lesser.
122+
auto firstGP = cast<GenericTypeParamType>(firstTy);
123+
auto secondGP = cast<GenericTypeParamType>(secondTy);
124+
125+
if (firstGP == secondGP) {
126+
// `T == T` has no effect.
127+
continue;
128+
}
129+
130+
if (firstGP->getDepth() > secondGP->getDepth()
131+
|| (firstGP->getDepth() == secondGP->getDepth()
132+
&& firstGP->getIndex() > secondGP->getIndex())) {
133+
std::swap(firstGP, secondGP);
134+
}
135+
136+
hadSameTypeConstraintInScope = true;
137+
representativeGPs.insert_or_assign(secondGP, representativeGPFor(firstGP));
138+
continue;
139+
}
140+
141+
// If the out-of-scope type is another type parameter or associated type,
142+
// then ignore this same-type constraint and allow defaulting to continue.
143+
//
144+
// It would probably have been more principled to suppress any defaulting
145+
// in this case, but this behavior shipped in Swift 6.0 and 6.1, so we
146+
// need to maintain source compatibility.
147+
if (typeOutOfScope->isTypeParameter()) {
148+
continue;
149+
}
150+
151+
// If the out-of-scope type contains errors, then similarly, ignore the
152+
// same type constraint. Any additional diagnostics arising from the type
153+
// parameter being left ~Copyable or ~Escapable might be misleading if the
154+
// corrected code is attempting to refer to a Copyable or Escapable type.
155+
if (typeOutOfScope->hasError()) {
156+
continue;
157+
}
158+
159+
representativeGPs.insert_or_assign(representativeGPFor(typeInScope),
160+
CanType());
161+
hadSameTypeConstraintInScope = true;
162+
}
163+
164+
// Summarize the inverses and diagnose ones that are incorrect.
165+
llvm::DenseMap<CanType, InvertibleProtocolSet> inverses;
166+
for (auto inverse : inverseList) {
167+
auto canSubject = inverse.subject->getCanonicalType();
168+
169+
// Inverses on associated types are experimental.
170+
if (!allowInverseOnAssocType && canSubject->is<DependentMemberType>()) {
171+
// Special exception: allow if we're building the stdlib.
172+
if (!ctx.MainModule->isStdlibModule()) {
173+
errors.push_back(RequirementError::forInvalidInverseSubject(inverse));
174+
continue;
175+
}
176+
}
177+
178+
// Noncopyable checking support for parameter packs is not implemented yet.
179+
if (canSubject->isParameterPack()) {
180+
errors.push_back(RequirementError::forInvalidInverseSubject(inverse));
181+
continue;
182+
}
183+
184+
// Value generics never have inverse requirements (or the positive thereof).
185+
if (canSubject->isValueParameter()) {
186+
continue;
187+
}
188+
189+
// If the inverse is on a subject that wasn't permitted by our caller, then
190+
// remove and diagnose as an error. This can happen when an inner context
191+
// has a constraint on some outer generic parameter, e.g.,
192+
//
193+
// protocol P {
194+
// func f() where Self: ~Copyable
195+
// }
196+
//
197+
if (representativeGPs.find(canSubject) == representativeGPs.end()) {
198+
errors.push_back(
199+
RequirementError::forInvalidInverseOuterSubject(inverse));
200+
continue;
201+
}
202+
203+
auto representativeSubject = representativeGPFor(canSubject);
204+
205+
// If the subject is in scope, but same-type constrained to a type out of
206+
// scope, then allow inverses to be stated even though they are redundant.
207+
// This is because older versions of Swift not only accepted but required
208+
// `extension Foo where T == NonCopyableType, T: ~Copyable {}` to be
209+
// written, so we need to continue to accept that formulation for source
210+
// compatibility.
211+
if (!representativeSubject) {
212+
continue;
213+
}
214+
215+
auto state = inverses.getOrInsertDefault(representativeSubject);
216+
217+
// Check if this inverse has already been seen.
218+
auto inverseKind = inverse.getKind();
219+
if (state.contains(inverseKind))
220+
continue;
221+
222+
state.insert(inverseKind);
223+
inverses[representativeSubject] = state;
224+
}
225+
226+
// Fast-path: if there are no valid inverses or same-type constraints, then
227+
// there are no requirements to be removed.
228+
if (inverses.empty() && !hadSameTypeConstraintInScope) {
229+
return;
230+
}
231+
232+
// Scan the structural requirements and cancel out any inferred requirements
233+
// based on the inverses we saw.
234+
result.erase(llvm::remove_if(result, [&](StructuralRequirement structReq) {
235+
auto req = structReq.req;
236+
237+
if (req.getKind() != RequirementKind::Conformance)
238+
return false;
239+
240+
// Only consider requirements involving an invertible protocol.
241+
auto proto = req.getProtocolDecl()->getInvertibleProtocolKind();
242+
if (!proto) {
243+
return false;
244+
}
245+
246+
// See if this subject is in-scope.
247+
auto subject = req.getFirstType()->getCanonicalType();
248+
auto representative = representativeGPs.find(subject);
249+
if (representative == representativeGPs.end()) {
250+
return false;
251+
}
252+
253+
// If this type is same-type constrained into another equivalence class,
254+
// then it doesn't need its own defaulted requirements.
255+
if (representative->second != subject) {
256+
return true;
257+
}
258+
259+
// We now have found the inferred constraint 'Subject : Proto'.
260+
// So, remove it if we have recorded a 'Subject : ~Proto'.
261+
auto foundInverses = inverses.find(subject);
262+
if (foundInverses == inverses.end()) {
263+
return false;
264+
}
265+
auto recordedInverses = foundInverses->getSecond();
266+
return recordedInverses.contains(*proto);
267+
}), result.end());
268+
}

0 commit comments

Comments
 (0)