-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathTypeCheckInvertible.cpp
354 lines (307 loc) · 13.3 KB
/
TypeCheckInvertible.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
//===--- TypeCheckInvertible.cpp - Type checking invertible protocols ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements semantic analysis for evaluating whether a type
// conforms to an invertible protocol. An invertible protocol is a known
// protocol KP for which the type ~KP exists.
//
//===----------------------------------------------------------------------===//
#include "TypeCheckInvertible.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ClangModuleLoader.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/Basic/Assertions.h"
#include "TypeChecker.h"
using namespace swift;
/// MARK: diagnostic utilities
/// Adds the appropriate fix-it to make the given nominal conform to \c proto.
static void addConformanceFixIt(const NominalTypeDecl *nominal,
InFlightDiagnostic &diag,
KnownProtocolKind proto,
bool inverse) {
SmallString<64> text;
if (nominal->getInherited().empty()) {
SourceLoc fixItLoc = nominal->getBraces().Start;
text.append(": ");
if (inverse) text.append("~");
text.append(getProtocolName(proto));
text.append(" ");
diag.fixItInsert(fixItLoc, text);
} else {
auto fixItLoc = nominal->getInherited().getEndLoc();
text.append(", ");
if (inverse) text.append("~");
text.append(getProtocolName(proto));
diag.fixItInsertAfter(fixItLoc, text);
}
}
// If there is not already an inverse ~KP applied to this type, suggest it.
// The goal here is that we want to tell users how they can suppress or remove
// a conformance to KP.
static void emitAdviceToApplyInverseAfter(InvertibleProtocolKind ip,
bool canAddInverse,
NominalTypeDecl *nominal) {
auto kp = getKnownProtocolKind(ip);
if (canAddInverse) {
auto diag = nominal->diagnose(diag::add_inverse,
nominal,
getProtocolName(kp));
addConformanceFixIt(nominal, diag, kp, /*inverse=*/true);
}
}
/// Emit fix-it's to help the user resolve a containment issue where the
/// \c nonConformingTy needs to be made to conform to \c kp to resolve a
/// containment issue.
/// \param enclosingNom is the nominal type containing a nonconforming value
/// \param nonConformingTy is the type of the nonconforming value
static void tryEmitContainmentFixits(NominalTypeDecl *enclosingNom,
bool canAddInverse,
Type nonConformingTy,
InvertibleProtocolKind ip) {
auto *module = enclosingNom->getParentModule();
auto &ctx = enclosingNom->getASTContext();
auto kp = getKnownProtocolKind(ip);
// First, the generic advice.
emitAdviceToApplyInverseAfter(ip, canAddInverse, enclosingNom);
// If it's a generic parameter defined in the same module, point to the
// parameter that must have had the inverse applied to it somewhere.
if (auto genericArchetype = nonConformingTy->getAs<ArchetypeType>()) {
auto interfaceType = genericArchetype->getInterfaceType();
if (auto genericParamType =
interfaceType->getAs<GenericTypeParamType>()) {
auto *genericParamTypeDecl = genericParamType->getDecl();
if (genericParamTypeDecl &&
genericParamTypeDecl->getModuleContext() == module) {
genericParamTypeDecl->diagnose(
diag::note_inverse_preventing_conformance,
nonConformingTy, getProtocolName(kp));
}
}
return;
}
// If the offending type is a nominal with a SourceLoc, explain why it's
// not IP.
if (auto nominal = nonConformingTy->getAnyNominal()) {
if (nominal->getLoc(/*SerializedOK=*/false)) {
ctx.Diags.diagnose(nominal->getLoc(),
diag::note_inverse_preventing_conformance_explicit,
nominal, getProtocolName(kp));
}
}
}
/// MARK: conformance checking
static void checkInvertibleConformanceCommon(DeclContext *dc,
ProtocolConformanceRef conformance,
InvertibleProtocolKind ip) {
assert(!conformance.isInvalid());
const auto kp = getKnownProtocolKind(ip);
assert(conformance.getRequirement()->isSpecificProtocol(kp));
auto *nominalDecl = dc->getSelfNominalTypeDecl();
assert(isa<StructDecl>(nominalDecl) ||
isa<EnumDecl>(nominalDecl) ||
isa<ClassDecl>(nominalDecl));
auto &ctx = nominalDecl->getASTContext();
InvertibleProtocolSet inverses;
bool anyObject = false;
(void) getDirectlyInheritedNominalTypeDecls(nominalDecl, inverses, anyObject);
// Handle deprecated attributes.
if (nominalDecl->getAttrs().hasAttribute<MoveOnlyAttr>())
inverses.insert(InvertibleProtocolKind::Copyable);
if (nominalDecl->getAttrs().hasAttribute<NonEscapableAttr>())
inverses.insert(InvertibleProtocolKind::Escapable);
bool hasExplicitInverse = inverses.contains(ip);
bool hasUnconditionalConformance = conformance.isAbstract();
SourceLoc conformanceLoc = nominalDecl->getLoc();
if (conformance.isConcrete()) {
auto concrete = conformance.getConcrete();
if (auto *normalConf = dyn_cast<NormalProtocolConformance>(concrete)) {
conformanceLoc = normalConf->getLoc();
assert(conformanceLoc);
// Conformance must be defined in the same source file as the nominal.
auto conformanceDC = concrete->getDeclContext();
if (auto *sourceFile = conformanceDC->getOutermostParentSourceFile()) {
if (sourceFile != nominalDecl->getOutermostParentSourceFile()) {
ctx.Diags.diagnose(conformanceLoc,
diag::invertible_conformance_other_source_file,
getInvertibleProtocolKindName(ip), nominalDecl);
}
}
auto condReqs = normalConf->getConditionalRequirements();
hasUnconditionalConformance = condReqs.empty();
auto *thisProto = normalConf->getProtocol();
// Ensure that conditional conformance to an invertible protocol IP only
// depends conformance requirements involving IP, and its subject is not
// a dependent member type.
//
// In theory, it could depend on any invertible protocol, but it may be
// confusing if we permitted that and this simplifies the model a bit.
for (auto req : condReqs) {
Type illegalSecondType;
// If we are diagnosing, fill-in the second-type string of this req.
switch (req.getKind()) {
case RequirementKind::Layout:
assert(req.getLayoutConstraint()->isClass());
illegalSecondType = ctx.getAnyObjectType();
break;
case RequirementKind::Conformance:
if (req.getProtocolDecl() == thisProto
&& !req.getFirstType()->is<DependentMemberType>())
break; // permitted, don't fill-in.
LLVM_FALLTHROUGH;
case RequirementKind::Superclass:
case RequirementKind::SameType:
case RequirementKind::SameShape:
illegalSecondType = req.getSecondType();
break;
}
static_assert((unsigned)RequirementKind::LAST_KIND == 4,
"update %select in diagnostic!");
if (illegalSecondType) {
auto t = ctx.Diags.diagnose(conformanceLoc,
diag::inverse_cannot_be_conditional_on_requirement,
thisProto,
req.getFirstType(),
static_cast<unsigned>(req.getKind()),
illegalSecondType);
}
}
}
}
assert(!conformance.isPack() && "not handled");
if (!isa<ClassDecl>(nominalDecl) ||
ctx.LangOpts.hasFeature(Feature::MoveOnlyClasses)) {
// If the inheritance clause contains ~Copyable, reject an unconditional
// conformance to Copyable.
if (hasExplicitInverse && hasUnconditionalConformance) {
ctx.Diags.diagnose(conformanceLoc,
diag::inverse_but_also_conforms,
nominalDecl, getProtocolName(kp));
}
}
// All classes can store noncopyable/nonescaping values.
if (isa<ClassDecl>(nominalDecl))
return;
bool canAddInverse = !hasExplicitInverse && !hasUnconditionalConformance;
// A deinit prevents a struct or enum from conforming to Copyable.
if (ip == InvertibleProtocolKind::Copyable) {
if (auto *deinit = nominalDecl->getValueTypeDestructor()) {
deinit->diagnose(diag::copyable_illegal_deinit, nominalDecl);
emitAdviceToApplyInverseAfter(ip, canAddInverse, nominalDecl);
}
}
// Check storage for conformance to Copyable/Escapable.
class LacksMatchingStorage: public StorageVisitor {
NominalTypeDecl *Nominal;
DeclContext *DC;
InvertibleProtocolKind IP;
bool CanAddInverse;
public:
LacksMatchingStorage(NominalTypeDecl *nom,
DeclContext *dc,
bool canAddInverse,
InvertibleProtocolKind ip)
: Nominal(nom), DC(dc), IP(ip),
CanAddInverse(canAddInverse) {}
bool visit() { return StorageVisitor::visit(Nominal, DC); }
bool check(ValueDecl *storage, Type type, bool isEnum) {
// ignore invalid storage.
if (type->hasError())
return false;
// For a type conforming to IP, ensure that the storage conforms to IP.
switch (IP) {
case InvertibleProtocolKind::Copyable:
if (!type->isNoncopyable())
return false;
break;
case InvertibleProtocolKind::Escapable:
if (type->isEscapable())
return false;
break;
}
storage->diagnose(diag::inverse_type_member_in_conforming_type,
type, isEnum, storage->getName(), Nominal,
getProtocolName(getKnownProtocolKind(IP)));
tryEmitContainmentFixits(Nominal, CanAddInverse, type, IP);
return true;
}
/// Handle a stored property.
/// \returns true iff this visitor should stop its walk over the nominal.
bool operator()(VarDecl *property, Type propertyType) override {
return check(property, propertyType, /*isEnum=*/false);
}
/// Handle an enum associated value.
/// \returns true iff this visitor should stop its walk over the nominal.
bool operator()(EnumElementDecl *element, Type elementType) override {
return check(element, elementType, /*isEnum=*/true);
}
};
// This nominal cannot conform to IP if it contains storage that does not
// conform to IP.
LacksMatchingStorage(nominalDecl, dc, canAddInverse, ip).visit();
}
void swift::checkEscapableConformance(DeclContext *dc,
ProtocolConformanceRef conformance) {
checkInvertibleConformanceCommon(dc, conformance,
InvertibleProtocolKind::Escapable);
}
void swift::checkCopyableConformance(DeclContext *dc,
ProtocolConformanceRef conformance) {
checkInvertibleConformanceCommon(dc, conformance,
InvertibleProtocolKind::Copyable);
}
/// Visit the instance storage of the given nominal type as seen through
/// the given declaration context.
bool StorageVisitor::visit(NominalTypeDecl *nominal, DeclContext *dc) {
// Walk the stored properties of classes and structs.
if (isa<StructDecl>(nominal) || isa<ClassDecl>(nominal)) {
for (auto property : nominal->getStoredProperties()) {
auto propertyType = dc->mapTypeIntoContext(
property->getValueInterfaceType());
if ((*this)(property, propertyType))
return true;
}
// If this is a C++ struct, walk the members of its base types.
if (auto cxxRecordDecl =
dyn_cast_or_null<clang::CXXRecordDecl>(nominal->getClangDecl())) {
for (auto cxxBase : cxxRecordDecl->bases()) {
if (auto cxxBaseDecl = cxxBase.getType()->getAsCXXRecordDecl()) {
auto clangModuleLoader = dc->getASTContext().getClangModuleLoader();
auto importedDecl =
clangModuleLoader->importDeclDirectly(cxxBaseDecl);
if (auto nominalBaseDecl =
dyn_cast_or_null<NominalTypeDecl>(importedDecl)) {
if (visit(nominalBaseDecl, dc))
return true;
}
}
}
}
return false;
}
// Walk the enum elements that have associated values.
if (auto enumDecl = dyn_cast<EnumDecl>(nominal)) {
for (auto caseDecl : enumDecl->getAllCases()) {
for (auto element : caseDecl->getElements()) {
if (!element->hasAssociatedValues())
continue;
// Check that the associated value type is Sendable.
auto elementType = dc->mapTypeIntoContext(
element->getPayloadInterfaceType());
if ((*this)(element, elementType))
return true;
}
}
return false;
}
assert(!isa<ProtocolDecl>(nominal) || !isa<BuiltinTupleDecl>(nominal));
return false;
}