Skip to content

Commit 28890ba

Browse files
committed
[Sema] no Copyable conformance if marked ~Copyable
Much like when the type defines a deinit, if the nominal's declaration is marked with ~Copyable, that prevents conformance to Copyable. This meant that when trying to auto-derive Copyable conformance, we'll skip types marked with ~Copyable. But now, we'll also reject explicitly-requested conformances to Copyable because of that marking too. In reality right now, all marker protocols have conformances that are always trivally checked and marked "complete", so when I refer to rejecting a conformance here, I mean raising a diagnostic while checking for validity.
1 parent 07f069c commit 28890ba

File tree

6 files changed

+76
-37
lines changed

6 files changed

+76
-37
lines changed

Diff for: include/swift/AST/DiagnosticsSema.def

+3
Original file line numberDiff line numberDiff line change
@@ -7539,6 +7539,9 @@ ERROR(noncopyable_but_copyable, none,
75397539
ERROR(noncopyable_class, none,
75407540
"classes cannot be noncopyable",
75417541
())
7542+
ERROR(inverse_extension, none,
7543+
"cannot apply inverse %0 to extension",
7544+
(Type))
75427545
ERROR(copyable_illegal_deinit, none,
75437546
"deinitializer cannot be declared in %kind0 that conforms to 'Copyable'",
75447547
(const ValueDecl *))

Diff for: lib/Sema/TypeCheckDecl.cpp

+2-6
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ NoncopyableAnnotationRequest::evaluate(Evaluator &evaluator,
963963
return false;
964964
};
965965

966-
auto isInverseTarget = [](Type t) -> bool {
966+
auto isInverseTarget = [&](Type t) -> bool {
967967
if (auto inverse = t->getAs<InverseType>())
968968
return inverse->getInverseKind() == TARGET;
969969
else if (auto pct = t->getAs<ProtocolCompositionType>())
@@ -1038,7 +1038,7 @@ NoncopyableAnnotationRequest::evaluate(Evaluator &evaluator,
10381038
// Try to find a good location.
10391039
SourceLoc loc;
10401040
if (repr && !repr->isInvalid())
1041-
if (auto *constraintRepr = repr->getSecondTypeRepr())
1041+
if (auto *constraintRepr = repr->getConstraintRepr())
10421042
if (!repr->isInvalid())
10431043
loc = constraintRepr->getLoc();
10441044

@@ -1057,10 +1057,6 @@ NoncopyableAnnotationRequest::evaluate(Evaluator &evaluator,
10571057
auto nominal = cast<NominalTypeDecl>(decl);
10581058
assert(!isa<BuiltinTupleDecl>(nominal));
10591059

1060-
// Classes can't be noncopyable; it's diagnosed elsewhere.
1061-
if (isa<ClassDecl>(nominal))
1062-
return InverseMarking::forInverse(Kind::None);
1063-
10641060
/// Protocols are handled specially since they do not infer an inverse based
10651061
/// on their associated types.
10661062
if (auto proto = dyn_cast<ProtocolDecl>(nominal)) {

Diff for: lib/Sema/TypeCheckDeclPrimary.cpp

+4-6
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ static void checkInheritanceClause(
205205
if (inheritedTy->isConstraintType()) {
206206
auto layout = inheritedTy->getExistentialLayout();
207207

208+
// An inverse on an extension is an error.
209+
if (isa<ExtensionDecl>(decl) && inheritedTy->is<InverseType>())
210+
decl->diagnose(diag::inverse_extension, inheritedTy);
211+
208212
// Subclass existentials are not allowed except on classes and
209213
// non-@objc protocols.
210214
if (layout.explicitSuperclass &&
@@ -227,12 +231,6 @@ static void checkInheritanceClause(
227231
continue;
228232
}
229233

230-
// Classes are always copyable.
231-
if (layout.hasInverseCopyable && isa<ClassDecl>(decl)) {
232-
decl->diagnose(diag::noncopyable_class);
233-
continue;
234-
}
235-
236234
// If the existential did not have a class constraint, we're done.
237235
if (!layout.explicitSuperclass)
238236
continue;

Diff for: lib/Sema/TypeCheckInvertible.cpp

+38-23
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,36 @@ bool checkCopyableConformance(ProtocolConformance *conformance) {
189189
if (!nom)
190190
return false;
191191

192+
auto &ctx = nom->getASTContext();
193+
bool conforms = true;
194+
195+
// An explicit `~Copyable` prevents conformance if any of these are true:
196+
//
197+
// 1. It appears on a class.
198+
// 2. Appears on the same declaration that also declares the conformance.
199+
// So, if the nominal has `~Copyable` but this conformance is
200+
// written in an extension, then we do not raise an error.
201+
auto marking = nom->getNoncopyableMarking();
202+
if (marking.getInverse().getKind() == InverseMarking::Kind::Explicit) {
203+
if (isa<ClassDecl>(nom)) {
204+
ctx.Diags.diagnose(marking.getInverse().getLoc(),
205+
diag::noncopyable_class);
206+
conforms &= false;
207+
} else if (conformance->getDeclContext() == nom) {
208+
ctx.Diags.diagnose(marking.getInverse().getLoc(),
209+
diag::noncopyable_but_copyable,
210+
nom);
211+
conforms &= false;
212+
}
213+
}
214+
192215
// All classes can store noncopyable values.
193216
if (isa<ClassDecl>(nom))
194-
return true;
217+
return conforms;
195218

196219
// Protocols do not directly define any storage.
197-
if (isa<ProtocolDecl>(nom))
198-
return true;
199-
200-
if (isa<BuiltinTupleDecl>(nom))
201-
llvm_unreachable("TODO: BuiltinTupleDecl");
220+
if (isa<ProtocolDecl, BuiltinTupleDecl>(nom))
221+
llvm_unreachable("unexpected nominal to check Copyable conformance");
202222

203223
// A deinit prevents a struct or enum from conforming to Copyable.
204224
if (auto *deinit = nom->getValueTypeDestructor()) {
@@ -207,7 +227,7 @@ bool checkCopyableConformance(ProtocolConformance *conformance) {
207227
KnownProtocolKind::Copyable,
208228
nom->getNoncopyableMarking(),
209229
nom);
210-
return false;
230+
conforms &= false;
211231
}
212232

213233

@@ -256,8 +276,12 @@ bool checkCopyableConformance(ProtocolConformance *conformance) {
256276
};
257277

258278
// This nominal cannot be Copyable if it contains noncopyable storage.
259-
return !HasNoncopyable(nom, conformance->getDeclContext(),
279+
bool haveNoncopyableStorage =
280+
HasNoncopyable(nom, conformance->getDeclContext(),
260281
/*diagnose=*/true).visit();
282+
conforms &= !haveNoncopyableStorage;
283+
284+
return conforms;
261285
}
262286

263287
/// Visit the instance storage of the given nominal type as seen through
@@ -372,23 +396,14 @@ ProtocolConformance *deriveConformanceForInvertible(Evaluator &evaluator,
372396

373397
switch (*ip) {
374398
case InvertibleProtocolKind::Copyable: {
375-
auto marking = nominal->getNoncopyableMarking();
376-
377-
// An explicit Copyable takes precedece over any ~Copyable marking.
378-
if (marking.getPositive().getKind() == InverseMarking::Kind::Explicit) {
379-
// If they also explicitly wrote ~Copyable, then diagnose that.
380-
auto inverse = marking.getInverse();
381-
if (inverse.getKind() == InverseMarking::Kind::Explicit) {
382-
ctx.Diags.diagnose(inverse.getLoc(),
383-
diag::noncopyable_but_copyable,
384-
nominal);
385-
}
386-
399+
// Always derive unconditional Copyable conformance for classes
400+
if (isa<ClassDecl>(nominal))
387401
return generateConformance(nominal);
388-
}
389402

390-
// Unexpected to have Kind::Inferred marking for Copyable; it's assumed.
391-
assert(marking.getPositive().getKind() == InverseMarking::Kind::None);
403+
auto marking = nominal->getNoncopyableMarking();
404+
405+
// Unexpected to have any marking for Copyable if we're deriving it.
406+
assert(!marking.getPositive().isPresent());
392407

393408
// Check what kind of inverse we have to determine whether to generate a
394409
// conformance for Copyable.

Diff for: test/Generics/inverse_copyable_generics.swift

+28-1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func chk(_ T: RequireCopyable<ConditionalContainment<Int>>) {}
8181
/// ----------------
8282

8383
struct AlwaysCopyableDeinit<T: ~Copyable> : Copyable {
84+
let nc: NC // expected-error {{stored property 'nc' of 'Copyable'-conforming generic struct 'AlwaysCopyableDeinit' has noncopyable type 'NC'}}
8485
deinit {} // expected-error {{deinitializer cannot be declared in generic struct 'AlwaysCopyableDeinit' that conforms to 'Copyable'}}
8586
}
8687

@@ -115,7 +116,7 @@ struct RequireCopyable<T> {
115116
}
116117

117118
struct NC: ~Copyable {
118-
// expected-note@-1 2{{struct 'NC' has '~Copyable' constraint preventing 'Copyable' conformance}}
119+
// expected-note@-1 3{{struct 'NC' has '~Copyable' constraint preventing 'Copyable' conformance}}
119120
deinit {}
120121
}
121122

@@ -150,3 +151,29 @@ struct CornerCase<T: ~Copyable> {
150151
func chk(_ t: CornerCase<NC>) {}
151152
// expected-error@-1 {{parameter of noncopyable type 'CornerCase<NC>' must specify ownership}}
152153
// expected-note@-2 3{{add}}
154+
155+
156+
/// MARK: tests that we diagnose ~Copyable that became invalid because it's required to be copyable
157+
158+
protocol NeedsCopyable {}
159+
160+
struct Silly: ~Copyable, Copyable {} // expected-error {{struct 'Silly' required to be 'Copyable' but is marked with '~Copyable'}}
161+
enum Sally: Copyable, ~Copyable, NeedsCopyable {} // expected-error {{enum 'Sally' required to be 'Copyable' but is marked with '~Copyable'}}
162+
class NiceTry: ~Copyable, Copyable {} // expected-error {{classes cannot be noncopyable}}
163+
164+
struct OopsConformance1: ~Copyable, NeedsCopyable {}
165+
// expected-error@-1 {{type 'OopsConformance1' does not conform to protocol 'NeedsCopyable'}}
166+
// expected-error@-2 {{type 'OopsConformance1' does not conform to protocol 'Copyable'}}
167+
168+
protocol Nothing: ~Copyable, NeedsCopyable {}
169+
// expected-warning@-1 {{protocol 'Nothing' should be declared to refine 'Copyable' due to a same-type constraint on 'Self'}}
170+
171+
172+
struct Extendo: ~Copyable {}
173+
extension Extendo: Copyable, ~Copyable {} // expected-error {{cannot apply inverse '~Copyable' to extension}}
174+
175+
enum EnumExtendo {}
176+
extension EnumExtendo: ~Copyable {} // expected-error {{cannot apply inverse '~Copyable' to extension}}
177+
178+
extension NeedsCopyable where Self: ~Copyable {}
179+
// expected-error@-1 {{cannot add inverse constraint 'Self: ~Copyable' on generic parameter 'Self' defined in outer scope}}

Diff for: test/Parse/inverses.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ protocol Rope<Element>: Hashable, ~ Copyable {
5959
associatedtype Element: ~Copyable
6060
}
6161

62-
extension S: ~Copyable {}
62+
extension S: ~Copyable {} // expected-error {{cannot apply inverse '~Copyable' to extension}}
6363

6464
struct S: ~U, // expected-error {{type 'U' is not invertible}}
6565
~Copyable {}

0 commit comments

Comments
 (0)