Skip to content

Commit 6f28111

Browse files
committed
Sema: discard self should not be allowed in inlinable methods of non-frozen types.
`discard self` requires knowledge of the internal layout of the type in order to clean up its fields while bypassing its public `deinit`. Inlinable code can get copied into and run from outside of the defining module, so this is impossible to implement. Fixes rdar://160815058.
1 parent 860f2db commit 6f28111

File tree

5 files changed

+105
-9
lines changed

5 files changed

+105
-9
lines changed

include/swift/AST/DeclContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ struct FragileFunctionKind {
220220
friend bool operator==(FragileFunctionKind lhs, FragileFunctionKind rhs) {
221221
return lhs.kind == rhs.kind;
222222
}
223+
friend bool operator!=(FragileFunctionKind lhs, FragileFunctionKind rhs) {
224+
return lhs.kind != rhs.kind;
225+
}
223226

224227
/// Casts to `unsigned` for diagnostic %selects.
225228
unsigned getSelector() { return static_cast<unsigned>(kind); }

include/swift/AST/DiagnosticsSema.def

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5343,12 +5343,27 @@ GROUPED_ERROR(opaque_type_unsupported_availability,OpaqueTypeInference,none,
53435343
//------------------------------------------------------------------------------
53445344
// MARK: Discard Statement
53455345
//------------------------------------------------------------------------------
5346+
#define FRAGILE_FUNC_KIND \
5347+
"%select{a '@_transparent' function|" \
5348+
"an '@inlinable' function|" \
5349+
"an '@_alwaysEmitIntoClient' function|" \
5350+
"a default argument value|" \
5351+
"a property initializer in a '@frozen' type|" \
5352+
"a '@backDeployed' function|" \
5353+
"an embedded function not marked '@_neverEmitIntoClient'}"
5354+
53465355
ERROR(discard_wrong_context_decl,none,
53475356
"'discard' statement cannot appear in %kindonly0",
53485357
(const Decl *))
53495358
ERROR(discard_no_deinit,none,
53505359
"'discard' has no effect for type %0 unless it has a deinitializer",
53515360
(Type))
5361+
ERROR(discard_in_inlinable_method,none,
5362+
"'discard' statement cannot be used in " FRAGILE_FUNC_KIND "0 inside of type %1, which is not '@frozen'",
5363+
(unsigned, Type))
5364+
WARNING(discard_in_inlinable_method_warning,none,
5365+
"'discard' statement cannot be used in " FRAGILE_FUNC_KIND "0 inside of type %1, which is not '@frozen'; this will become an error",
5366+
(unsigned, Type))
53525367
ERROR(discard_wrong_context_closure,none,
53535368
"'discard' statement cannot appear in closure",
53545369
())
@@ -7306,15 +7321,6 @@ WARNING(inlinable_implies_usable_from_inline,none,
73067321
ERROR(usable_from_inline_attr_in_protocol,none,
73077322
"'@usableFromInline' attribute cannot be used in protocols", ())
73087323

7309-
#define FRAGILE_FUNC_KIND \
7310-
"%select{a '@_transparent' function|" \
7311-
"an '@inlinable' function|" \
7312-
"an '@_alwaysEmitIntoClient' function|" \
7313-
"a default argument value|" \
7314-
"a property initializer in a '@frozen' type|" \
7315-
"a '@backDeployed' function|" \
7316-
"an embedded function not marked '@_neverEmitIntoClient'}"
7317-
73187324
ERROR(local_type_in_inlinable_function,
73197325
none, "type %0 cannot be nested inside " FRAGILE_FUNC_KIND "1",
73207326
(Identifier, unsigned))

lib/Sema/TypeCheckStmt.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/ASTScope.h"
2626
#include "swift/AST/ASTVisitor.h"
2727
#include "swift/AST/ASTWalker.h"
28+
#include "swift/AST/Attr.h"
2829
#include "swift/AST/ConformanceLookup.h"
2930
#include "swift/AST/DiagnosticSuppression.h"
3031
#include "swift/AST/DiagnosticsSema.h"
@@ -1325,6 +1326,22 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
13251326
nominalType)
13261327
.fixItRemove(DS->getSourceRange());
13271328
diagnosed = true;
1329+
// if the type is public and not frozen, then the method must not be
1330+
// inlinable.
1331+
} else if (auto fragileKind = fn->getFragileFunctionKind();
1332+
!nominalDecl->getAttrs().hasAttribute<FrozenAttr>()
1333+
&& fragileKind != FragileFunctionKind{FragileFunctionKind::None}) {
1334+
ctx.Diags.diagnose(DS->getDiscardLoc(),
1335+
// Code in ABI stable SDKs has already used the `@inlinable`
1336+
// attribute on functions using `discard self`.
1337+
// Phase this in as a warning until those APIs
1338+
// can be updated.
1339+
fragileKind == FragileFunctionKind{FragileFunctionKind::Inlinable}
1340+
? diag::discard_in_inlinable_method_warning
1341+
: diag::discard_in_inlinable_method,
1342+
fragileKind.getSelector(), nominalType)
1343+
.fixItRemove(DS->getSourceRange());
1344+
diagnosed = true;
13281345
} else {
13291346
// Set the contextual type for the sub-expression before we typecheck.
13301347
contextualInfo = {nominalType, CTP_DiscardStmt};

test/ModuleInterface/discard_interface.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// CHECK: @_alwaysEmitIntoClient public consuming func AEIC_discard() { discard self }
99
// CHECK: @inlinable public consuming func inlinable_discard() { discard self }
1010

11+
@frozen
1112
public struct MoveOnlyStruct: ~Copyable {
1213
let x = 0
1314

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
public struct NotFrozen: ~Copyable {
4+
deinit {}
5+
6+
public consuming func notInlinable() {
7+
discard self
8+
}
9+
10+
@inlinable
11+
public consuming func inlinable() {
12+
// expected-warning @+1 {{'discard' statement cannot be used in an '@inlinable' function inside of type 'NotFrozen', which is not '@frozen'}}
13+
discard self
14+
}
15+
16+
@_alwaysEmitIntoClient
17+
public consuming func aeic() {
18+
// expected-error @+1 {{'discard' statement cannot be used in an '@_alwaysEmitIntoClient' function inside of type 'NotFrozen', which is not '@frozen'}}
19+
discard self
20+
}
21+
22+
@_transparent
23+
public consuming func transparent() {
24+
// expected-error @+1 {{'discard' statement cannot be used in a '@_transparent' function inside of type 'NotFrozen', which is not '@frozen'}}
25+
discard self
26+
}
27+
}
28+
29+
@frozen
30+
public struct Frozen: ~Copyable {
31+
deinit {}
32+
33+
public consuming func notInlinable() {
34+
discard self
35+
}
36+
37+
@inlinable
38+
public consuming func inlinable() {
39+
discard self
40+
}
41+
}
42+
43+
@usableFromInline
44+
internal struct NotFrozenUFI: ~Copyable {
45+
deinit {}
46+
47+
public consuming func notInlinable() {
48+
discard self
49+
}
50+
51+
@inlinable
52+
public consuming func inlinable() {
53+
// expected-warning @+1 {{'discard' statement cannot be used in an '@inlinable' function inside of type 'NotFrozenUFI', which is not '@frozen'}}
54+
discard self
55+
}
56+
57+
@_alwaysEmitIntoClient
58+
public consuming func aeic() {
59+
// expected-error @+1 {{'discard' statement cannot be used in an '@_alwaysEmitIntoClient' function inside of type 'NotFrozenUFI', which is not '@frozen'}}
60+
discard self
61+
}
62+
63+
@_transparent
64+
public consuming func transparent() {
65+
// expected-error @+1 {{'discard' statement cannot be used in a '@_transparent' function inside of type 'NotFrozenUFI', which is not '@frozen'}}
66+
discard self
67+
}
68+
}
69+

0 commit comments

Comments
 (0)