Skip to content

Commit a4e9919

Browse files
committed
[Sema] require value deinit absence for Copyable
A value type like a struct or enum cannot conform to Copyable if it has a deinit. When NoncopyableGenerics is enabled, we make that part of what is required to verify that such a nominal type conforms to Copyable. This change also does some refactoring to share common code to point out how to make a type noncopyable.
1 parent 0956cfa commit a4e9919

File tree

6 files changed

+111
-54
lines changed

6 files changed

+111
-54
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,6 @@ ERROR(destructor_decl_outside_class_or_noncopyable,none,
429429
"deinitializers may only be declared within a class, actor, or noncopyable type", ())
430430
ERROR(destructor_decl_on_noncopyable_enum,none,
431431
"deinitializers are not yet supported on noncopyable enums", ())
432-
ERROR(destructor_decl_on_objc_enum,none,
433-
"deinitializers cannot be declared on an @objc enum type", ())
434432
ERROR(expected_lbrace_destructor,PointsToFirstBadToken,
435433
"expected '{' for deinitializer", ())
436434
ERROR(destructor_has_name,PointsToFirstBadToken,

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
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(copyable_illegal_deinit, none,
7543+
"deinitializer cannot be declared in %kind0 that conforms to 'Copyable'",
7544+
(const ValueDecl *))
75427545
ERROR(noncopyable_type_member_in_copyable,none,
75437546
"%select{stored property %2|associated value %2}1 of "
75447547
"'Copyable'-conforming %kind3 has noncopyable type %0",

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4071,33 +4071,33 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
40714071
}
40724072

40734073
void visitDestructorDecl(DestructorDecl *DD) {
4074+
auto haveFeature = [=](Feature f) -> bool {
4075+
return DD->getASTContext().LangOpts.hasFeature(f);
4076+
};
4077+
40744078
// Only check again for destructor decl outside of a class if our destructor
40754079
// is not marked as invalid.
40764080
if (!DD->isInvalid()) {
40774081
auto *nom = dyn_cast<NominalTypeDecl>(
40784082
DD->getDeclContext()->getImplementedObjCContext());
4079-
if (!nom || (!isa<ClassDecl>(nom) && !nom->canBeNoncopyable())) {
4083+
if (!nom || isa<ProtocolDecl>(nom)) {
4084+
DD->diagnose(diag::destructor_decl_outside_class_or_noncopyable);
4085+
4086+
} else if (!haveFeature(Feature::NoncopyableGenerics)
4087+
&& !isa<ClassDecl>(nom)
4088+
&& !nom->canBeNoncopyable()) {
4089+
// When we have NoncopyableGenerics, deinits get validated as part of
4090+
// Copyable-conformance checking.
40804091
DD->diagnose(diag::destructor_decl_outside_class_or_noncopyable);
40814092
}
40824093

40834094
// Temporarily ban deinit on noncopyable enums, unless the experimental
40844095
// feature flag is set.
4085-
if (!DD->getASTContext().LangOpts.hasFeature(
4086-
Feature::MoveOnlyEnumDeinits) &&
4087-
nom->canBeNoncopyable() && isa<EnumDecl>(nom)) {
4096+
if (!haveFeature(Feature::MoveOnlyEnumDeinits)
4097+
&& isa<EnumDecl>(nom)
4098+
&& nom->canBeNoncopyable()) {
40884099
DD->diagnose(diag::destructor_decl_on_noncopyable_enum);
40894100
}
4090-
4091-
// If we have a noncopyable type, check if we have an @objc enum with a
4092-
// deinit and emit a specialized error. We will have technically already
4093-
// emitted an error since @objc enum cannot be marked noncopyable, but
4094-
// this at least makes it a bit clearer to the user that the deinit is
4095-
// also incorrect.
4096-
if (auto *e = dyn_cast_or_null<EnumDecl>(nom)) {
4097-
if (e->isObjC()) {
4098-
DD->diagnose(diag::destructor_decl_on_objc_enum);
4099-
}
4100-
}
41014101
}
41024102

41034103
TypeChecker::checkDeclAttributes(DD);

lib/Sema/TypeCheckInvertible.cpp

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,39 +47,65 @@ static void addConformanceFixIt(const NominalTypeDecl *nominal,
4747
}
4848
}
4949

50-
/// Emit fix-it's to help the user resolve a containment issue where the
51-
/// \c nonConformingTy needs to be made to conform to \c kp to resolve a
52-
/// containment issue.
53-
/// \param enclosingNom is the nominal type containing a nonconforming value
54-
/// \param nonConformingTy is the type of the nonconforming value
55-
static void tryEmitContainmentFixits(NominalTypeDecl *enclosingNom,
56-
Type nonConformingTy,
57-
KnownProtocolKind kp) {
58-
auto *module = enclosingNom->getParentModule();
59-
auto &ctx = enclosingNom->getASTContext();
50+
// If there is not already an inverse ~KP applied to this type, suggest it.
51+
// The goal here is that we want to tell users how they can suppress or remove
52+
// a conformance to KP.
53+
static void emitAdviceToApplyInverseAfter(InFlightDiagnostic &&diag,
54+
KnownProtocolKind kp,
55+
InverseMarking marking,
56+
NominalTypeDecl *nominal) {
57+
// Immediately flush, then emit notes, so they're associated.
58+
diag.flush();
6059

61-
// Check the enclosing type's markings to see what to suggest.
62-
assert(kp == KnownProtocolKind::Copyable);
63-
auto enclosingMarking = enclosingNom->getNoncopyableMarking();
60+
auto &ctx = nominal->getASTContext();
61+
// Not expecting the positive KP constraint to be classified as "Inferred".
62+
assert(marking.getPositive().getKind() != InverseMarking::Kind::Inferred);
6463

65-
switch (enclosingMarking.getInverse().getKind()) {
64+
// Have no advice for situations where the KP conformance is explicit.
65+
if (marking.getPositive().isPresent())
66+
return;
67+
68+
switch (marking.getInverse().getKind()) {
6669
case InverseMarking::Kind::Inferred:
6770
// Note that the enclosing type is conditionally conforming to KP first.
68-
ctx.Diags.diagnose(enclosingMarking.getInverse().getLoc(),
71+
ctx.Diags.diagnose(marking.getInverse().getLoc(),
6972
diag::note_inverse_preventing_conformance_implicit,
70-
enclosingNom, getProtocolName(kp));
73+
nominal, getProtocolName(kp));
7174
LLVM_FALLTHROUGH;
7275
case InverseMarking::Kind::None: {
7376
// Suggest adding ~KP to make it non-KP.
74-
auto diag = enclosingNom->diagnose(diag::add_inverse,
75-
enclosingNom,
77+
auto diag = nominal->diagnose(diag::add_inverse,
78+
nominal,
7679
getProtocolName(kp));
77-
addConformanceFixIt(enclosingNom, diag, kp, /*inverse=*/true);
80+
addConformanceFixIt(nominal, diag, kp, /*inverse=*/true);
7881
}
7982
break;
8083
case InverseMarking::Kind::Explicit:
84+
// FIXME: we can probably do better here. Look for the extension where the
85+
// inverse came from.
8186
break;
8287
};
88+
}
89+
90+
/// Emit fix-it's to help the user resolve a containment issue where the
91+
/// \c nonConformingTy needs to be made to conform to \c kp to resolve a
92+
/// containment issue.
93+
/// \param enclosingNom is the nominal type containing a nonconforming value
94+
/// \param nonConformingTy is the type of the nonconforming value
95+
static void tryEmitContainmentFixits(InFlightDiagnostic &&diag,
96+
NominalTypeDecl *enclosingNom,
97+
Type nonConformingTy,
98+
KnownProtocolKind kp) {
99+
auto *module = enclosingNom->getParentModule();
100+
auto &ctx = enclosingNom->getASTContext();
101+
102+
// Check the enclosing type's markings to see what to suggest.
103+
assert(kp == KnownProtocolKind::Copyable);
104+
auto enclosingMarking = enclosingNom->getNoncopyableMarking();
105+
106+
// First, the generic advice.
107+
emitAdviceToApplyInverseAfter(std::move(diag), kp,
108+
enclosingMarking, enclosingNom);
83109

84110
// If it's a generic parameter defined in the same module, point to the
85111
// parameter that must have had the inverse applied to it somewhere.
@@ -174,8 +200,16 @@ bool checkCopyableConformance(ProtocolConformance *conformance) {
174200
if (isa<BuiltinTupleDecl>(nom))
175201
llvm_unreachable("TODO: BuiltinTupleDecl");
176202

177-
// NOTE: A deinit prevents a struct or enum from conforming to Copyable, but
178-
// we will emit an error for that elsewhere already.
203+
// A deinit prevents a struct or enum from conforming to Copyable.
204+
if (auto *deinit = nom->getValueTypeDestructor()) {
205+
auto diag = deinit->diagnose(diag::copyable_illegal_deinit, nom);
206+
emitAdviceToApplyInverseAfter(std::move(diag),
207+
KnownProtocolKind::Copyable,
208+
nom->getNoncopyableMarking(),
209+
nom);
210+
return false;
211+
}
212+
179213

180214
// Otherwise, we have to check its storage to ensure it is all Copyable.
181215

@@ -200,10 +234,11 @@ bool checkCopyableConformance(ProtocolConformance *conformance) {
200234
if (!Diagnosing)
201235
return true; // it's noncopyable
202236

203-
storage->diagnose(diag::noncopyable_type_member_in_copyable, type,
204-
isEnum, storage->getName(), Nominal);
237+
auto diag = storage->diagnose(diag::noncopyable_type_member_in_copyable,
238+
type, isEnum, storage->getName(), Nominal);
205239

206-
tryEmitContainmentFixits(Nominal, type, KnownProtocolKind::Copyable);
240+
tryEmitContainmentFixits(std::move(diag),
241+
Nominal, type, KnownProtocolKind::Copyable);
207242
return true;
208243
}
209244

test/Generics/inverse_copyable_generics.swift

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,12 @@ protocol RemovedAgain where Self: ~Copyable {
4646
}
4747

4848
struct StructContainment<T: ~Copyable> : Copyable {
49-
// expected-note@-1 {{consider adding '~Copyable' to generic struct 'StructContainment'}}{{50-50=, ~Copyable}}
50-
// expected-note@-2 {{generic struct 'StructContainment' has '~Copyable' constraint on a generic parameter, making its 'Copyable' conformance conditional}}
51-
5249
var storage: Maybe<T>
5350
// expected-error@-1 {{stored property 'storage' of 'Copyable'-conforming generic struct 'StructContainment' has noncopyable type 'Maybe<T>'}}
5451
}
5552

5653
enum EnumContainment<T: ~Copyable> : Copyable {
5754
// expected-note@-1 {{'T' has '~Copyable' constraint preventing implicit 'Copyable' conformance}}
58-
// expected-note@-2{{consider adding '~Copyable' to generic enum 'EnumContainment'}}{{46-46=, ~Copyable}}
59-
// expected-note@-3{{generic enum 'EnumContainment' has '~Copyable' constraint on a generic parameter, making its 'Copyable' conformance conditional}}
6055

6156
case some(T) // expected-error {{associated value 'some' of 'Copyable'-conforming generic enum 'EnumContainment' has noncopyable type 'T'}}
6257
case other(Int)
@@ -69,6 +64,8 @@ class ClassContainment<T: ~Copyable> {
6964
storage = t
7065
checkCopyable(t) // expected-error {{noncopyable type 'T' cannot be substituted for copyable generic parameter 'T' in 'checkCopyable'}}
7166
}
67+
68+
deinit {}
7269
}
7370

7471
// expected-note@+2 {{generic struct 'ConditionalContainment' has '~Copyable' constraint on a generic parameter, making its 'Copyable' conformance conditional}}
@@ -83,20 +80,44 @@ func chk(_ T: RequireCopyable<ConditionalContainment<Int>>) {}
8380

8481
/// ----------------
8582

86-
// expected-note@+1 {{generic enum 'Maybe' has '~Copyable' constraint on a generic parameter, making its 'Copyable' conformance conditional}}
83+
struct AlwaysCopyableDeinit<T: ~Copyable> : Copyable {
84+
deinit {} // expected-error {{deinitializer cannot be declared in generic struct 'AlwaysCopyableDeinit' that conforms to 'Copyable'}}
85+
}
86+
87+
struct SometimesCopyableDeinit<T: ~Copyable> : ~Copyable {
88+
deinit {} // expected-error {{deinitializer cannot be declared in generic struct 'SometimesCopyableDeinit' that conforms to 'Copyable'}}
89+
}
90+
extension SometimesCopyableDeinit: Copyable where T: Copyable {}
91+
92+
struct NeverCopyableDeinit<T: ~Copyable>: ~Copyable {
93+
deinit {}
94+
}
95+
96+
/// ---------------
97+
98+
// expected-note@+2 {{consider adding '~Copyable' to generic enum 'Maybe'}}
99+
// expected-note@+1 2{{generic enum 'Maybe' has '~Copyable' constraint on a generic parameter, making its 'Copyable' conformance conditional}}
87100
enum Maybe<Wrapped: ~Copyable> {
88101
case just(Wrapped)
89102
case none
103+
104+
deinit {} // expected-error {{deinitializer cannot be declared in generic enum 'Maybe' that conforms to 'Copyable'}}
105+
// expected-error@-1 {{deinitializers are not yet supported on noncopyable enums}}
90106
}
91107

92-
struct RequireCopyable<T> {}
93-
// expected-note@-1{{requirement specified as 'NC' : 'Copyable'}}
94-
// expected-note@-2{{requirement from conditional conformance of 'Maybe<NC>' to 'Copyable'}}
95-
// expected-note@-3{{requirement specified as 'Wrapped' : 'Copyable'}}
96-
// expected-note@-4{{requirement from conditional conformance of 'Maybe<Wrapped>' to 'Copyable'}}
108+
// expected-note@+4{{requirement specified as 'NC' : 'Copyable'}}
109+
// expected-note@+3{{requirement from conditional conformance of 'Maybe<NC>' to 'Copyable'}}
110+
// expected-note@+2{{requirement specified as 'Wrapped' : 'Copyable'}}
111+
// expected-note@+1{{requirement from conditional conformance of 'Maybe<Wrapped>' to 'Copyable'}}
112+
struct RequireCopyable<T> {
113+
// expected-note@-1 {{consider adding '~Copyable' to generic struct 'RequireCopyable'}}{{27-27=: ~Copyable}}
114+
deinit {} // expected-error {{deinitializer cannot be declared in generic struct 'RequireCopyable' that conforms to 'Copyable'}}
115+
}
97116

98-
struct NC: ~Copyable {}
117+
struct NC: ~Copyable {
99118
// expected-note@-1 2{{struct 'NC' has '~Copyable' constraint preventing 'Copyable' conformance}}
119+
deinit {}
120+
}
100121

101122
typealias ok1 = RequireCopyable<Int>
102123
typealias ok2 = RequireCopyable<Maybe<Int>>

test/Sema/moveonly_objc_enum.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@objc enum Foo : Int { // expected-error {{noncopyable enums cannot be marked '@objc'}}
99
// expected-error@-1 {{'Foo' declares raw type 'Int', but cannot yet conform to RawRepresentable because it is noncopyable}}
1010
case X, Y, Z
11-
deinit {} // expected-error {{deinitializers cannot be declared on an @objc enum type}}
11+
deinit {}
1212
}
1313

1414
@_moveOnly

0 commit comments

Comments
 (0)