Skip to content

Commit 6453128

Browse files
authored
Merge pull request #65591 from kavon/5.9-barebones-copyable
[5.9 🍒] Build support for `~Copyable` atop `@_moveOnly`
2 parents 5fb9c58 + 9628a7c commit 6453128

File tree

8 files changed

+168
-6
lines changed

8 files changed

+168
-6
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,19 @@ ERROR(expected_rparen_layout_constraint,none,
943943
ERROR(layout_constraints_only_inside_specialize_attr,none,
944944
"layout constraints are only allowed inside '_specialize' attributes", ())
945945

946+
//------------------------------------------------------------------------------
947+
// MARK: Conformance suppression diagnostics
948+
//------------------------------------------------------------------------------
949+
950+
ERROR(cannot_suppress_here,none,
951+
"cannot suppress conformances here", ())
952+
953+
ERROR(only_suppress_copyable,none,
954+
"can only suppress 'Copyable'", ())
955+
956+
ERROR(already_suppressed,none,
957+
"duplicate suppression of %0", (Identifier))
958+
946959
//------------------------------------------------------------------------------
947960
// MARK: Pattern parsing diagnostics
948961
//------------------------------------------------------------------------------

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ IDENTIFIER(Context)
5656
IDENTIFIER(CoreGraphics)
5757
IDENTIFIER(CoreMedia)
5858
IDENTIFIER(CGFloat)
59+
IDENTIFIER(Copyable)
5960
IDENTIFIER(CoreFoundation)
6061
IDENTIFIER(count)
6162
IDENTIFIER(CVarArg)

include/swift/Parse/Parser.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1200,9 +1200,18 @@ class Parser {
12001200

12011201
ParserResult<ImportDecl> parseDeclImport(ParseDeclOptions Flags,
12021202
DeclAttributes &Attributes);
1203+
1204+
/// Parse an inheritance clause into a vector of InheritedEntry's.
1205+
///
1206+
/// \param allowClassRequirement whether to permit parsing of 'class'
1207+
/// \param allowAnyObject whether to permit parsing of 'AnyObject'
1208+
/// \param parseTildeCopyable if non-null, permits parsing of `~Copyable`
1209+
/// and writes out a valid source location if it was parsed. If null, then a
1210+
/// parsing error will be emitted upon the appearance of `~` in the clause.
12031211
ParserStatus parseInheritance(SmallVectorImpl<InheritedEntry> &Inherited,
12041212
bool allowClassRequirement,
1205-
bool allowAnyObject);
1213+
bool allowAnyObject,
1214+
SourceLoc *parseTildeCopyable = nullptr);
12061215
ParserStatus parseDeclItem(bool &PreviousHadSemi,
12071216
ParseDeclOptions Options,
12081217
llvm::function_ref<void(Decl*)> handler);

include/swift/Parse/Token.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ class Token {
124124
return !isEllipsis();
125125
}
126126

127+
bool isTilde() const {
128+
return isAnyOperator() && Text == "~";
129+
}
130+
127131
/// Determine whether this token occurred at the start of a line.
128132
bool isAtStartOfLine() const { return AtStartOfLine; }
129133

lib/Parse/ParseDecl.cpp

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5655,6 +5655,29 @@ ParserResult<ImportDecl> Parser::parseDeclImport(ParseDeclOptions Flags,
56555655
return DCC.fixupParserResult(ID);
56565656
}
56575657

5658+
static void addMoveOnlyAttrIf(SourceLoc const &parsedTildeCopyable,
5659+
ASTContext &Context,
5660+
Decl *decl) {
5661+
if (parsedTildeCopyable.isInvalid())
5662+
return;
5663+
5664+
auto &attrs = decl->getAttrs();
5665+
5666+
// Don't add if it's already explicitly written on the decl, but error about
5667+
// the duplication and point to the `~Copyable`.
5668+
if (auto attr = attrs.getAttribute<MoveOnlyAttr>()) {
5669+
const bool sayModifier = false;
5670+
Context.Diags.diagnose(attr->getLocation(), diag::duplicate_attribute,
5671+
sayModifier)
5672+
.fixItRemove(attr->getRange());
5673+
Context.Diags.diagnose(parsedTildeCopyable, diag::previous_attribute,
5674+
sayModifier);
5675+
return;
5676+
}
5677+
5678+
attrs.add(new(Context) MoveOnlyAttr(/*IsImplicit=*/true));
5679+
}
5680+
56585681
/// Parse an inheritance clause.
56595682
///
56605683
/// \verbatim
@@ -5664,15 +5687,19 @@ ParserResult<ImportDecl> Parser::parseDeclImport(ParseDeclOptions Flags,
56645687
/// inherited:
56655688
/// 'class'
56665689
/// type-identifier
5690+
/// '~' 'Copyable'
56675691
/// \endverbatim
56685692
ParserStatus Parser::parseInheritance(
56695693
SmallVectorImpl<InheritedEntry> &Inherited,
5670-
bool allowClassRequirement, bool allowAnyObject) {
5694+
bool allowClassRequirement,
5695+
bool allowAnyObject,
5696+
SourceLoc *parseTildeCopyable) {
56715697
consumeToken(tok::colon);
56725698

56735699
SourceLoc classRequirementLoc;
56745700

56755701
ParserStatus Status;
5702+
SourceLoc TildeCopyableLoc;
56765703
SourceLoc prevComma;
56775704
bool HasNextType;
56785705
do {
@@ -5726,6 +5753,38 @@ ParserStatus Parser::parseInheritance(
57265753
continue;
57275754
}
57285755

5756+
// Is suppression permitted?
5757+
if (parseTildeCopyable) {
5758+
// Try to find '~' 'Copyable'
5759+
//
5760+
// We do this knowing that Copyable is not a real type as of now, so we
5761+
// can't rely on parseType.
5762+
if (Tok.isTilde()) {
5763+
const auto &nextTok = peekToken(); // lookahead
5764+
if (isIdentifier(nextTok, Context.Id_Copyable.str())) {
5765+
auto tildeLoc = consumeToken();
5766+
consumeToken(); // the 'Copyable' token
5767+
5768+
if (TildeCopyableLoc)
5769+
diagnose(tildeLoc, diag::already_suppressed, Context.Id_Copyable);
5770+
else
5771+
TildeCopyableLoc = tildeLoc;
5772+
5773+
continue;
5774+
}
5775+
5776+
// can't suppress whatever is between '~' and ',' or '{'.
5777+
diagnose(Tok, diag::only_suppress_copyable);
5778+
consumeToken();
5779+
}
5780+
5781+
} else if (Tok.isTilde()) {
5782+
// a suppression isn't allowed here, so emit an error eat the token to
5783+
// prevent further parsing errors.
5784+
diagnose(Tok, diag::cannot_suppress_here);
5785+
consumeToken();
5786+
}
5787+
57295788
auto ParsedTypeResult = parseType();
57305789
Status |= ParsedTypeResult;
57315790

@@ -5734,6 +5793,9 @@ ParserStatus Parser::parseInheritance(
57345793
Inherited.push_back(InheritedEntry(ParsedTypeResult.get()));
57355794
} while (HasNextType);
57365795

5796+
if (parseTildeCopyable)
5797+
*parseTildeCopyable = TildeCopyableLoc;
5798+
57375799
return Status;
57385800
}
57395801

@@ -8197,10 +8259,14 @@ ParserResult<EnumDecl> Parser::parseDeclEnum(ParseDeclOptions Flags,
81978259
// Parse optional inheritance clause within the context of the enum.
81988260
if (Tok.is(tok::colon)) {
81998261
SmallVector<InheritedEntry, 2> Inherited;
8262+
SourceLoc parsedTildeCopyable;
82008263
Status |= parseInheritance(Inherited,
82018264
/*allowClassRequirement=*/false,
8202-
/*allowAnyObject=*/false);
8265+
/*allowAnyObject=*/false,
8266+
&parsedTildeCopyable);
82038267
ED->setInherited(Context.AllocateCopy(Inherited));
8268+
8269+
addMoveOnlyAttrIf(parsedTildeCopyable, Context, ED);
82048270
}
82058271

82068272
diagnoseWhereClauseInGenericParamList(GenericParams);
@@ -8460,10 +8526,14 @@ ParserResult<StructDecl> Parser::parseDeclStruct(ParseDeclOptions Flags,
84608526
// Parse optional inheritance clause within the context of the struct.
84618527
if (Tok.is(tok::colon)) {
84628528
SmallVector<InheritedEntry, 2> Inherited;
8529+
SourceLoc parsedTildeCopyable;
84638530
Status |= parseInheritance(Inherited,
84648531
/*allowClassRequirement=*/false,
8465-
/*allowAnyObject=*/false);
8532+
/*allowAnyObject=*/false,
8533+
&parsedTildeCopyable);
84668534
SD->setInherited(Context.AllocateCopy(Inherited));
8535+
8536+
addMoveOnlyAttrIf(parsedTildeCopyable, Context, SD);
84678537
}
84688538

84698539
diagnoseWhereClauseInGenericParamList(GenericParams);

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,11 +2754,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
27542754
/// check to see if a move-only type can ever conform to the given type.
27552755
/// \returns true iff a diagnostic was emitted because it was not compatible
27562756
static bool diagnoseIncompatibleWithMoveOnlyType(SourceLoc loc,
2757-
NominalTypeDecl *moveonlyType,
2758-
Type type) {
2757+
NominalTypeDecl *moveonlyType,
2758+
Type type) {
27592759
assert(type && "got an empty type?");
27602760
assert(moveonlyType->isMoveOnly());
27612761

2762+
// no need to emit a diagnostic if the type itself is already problematic.
2763+
if (type->hasError())
2764+
return false;
2765+
27622766
auto canType = type->getCanonicalType();
27632767
if (auto prot = canType->getAs<ProtocolType>()) {
27642768
// Permit conformance to marker protocol Sendable.

test/ModuleInterface/moveonly_interface_flag.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@
88
// CHECK: #if compiler(>=5.3) && $MoveOnly
99
// CHECK-NEXT: @_moveOnly public struct MoveOnlyStruct {
1010

11+
// CHECK: #if compiler(>=5.3) && $MoveOnly
12+
// CHECK-NEXT: @_moveOnly public struct MoveOnlyStructSupp {
13+
1114
// CHECK: #if compiler(>=5.3) && $MoveOnly
1215
// CHECK-NEXT: @_moveOnly public enum MoveOnlyEnum {
1316

17+
// CHECK: #if compiler(>=5.3) && $MoveOnly
18+
// CHECK-NEXT: @_moveOnly public enum MoveOnlyEnumSupp {
19+
1420
// CHECK: #if compiler(>=5.3) && $MoveOnly
1521
// CHECK-NEXT: public func someFn() -> Library.MoveOnlyEnum
1622

@@ -25,10 +31,18 @@
2531
let x = 0
2632
}
2733

34+
public struct MoveOnlyStructSupp: ~Copyable {
35+
let x = 0
36+
}
37+
2838
@_moveOnly public enum MoveOnlyEnum {
2939
case depth
3040
}
3141

42+
public enum MoveOnlyEnumSupp: ~Copyable {
43+
case depth
44+
}
45+
3246
public func someFn() -> MoveOnlyEnum { return .depth }
3347

3448
public class What {

test/Parse/without_copyable.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
protocol Sando { func make() } // expected-note 2{{protocol requires function 'make()'}}
4+
5+
struct S: ~U, // expected-error {{can only suppress 'Copyable'}}
6+
// expected-error@-1 {{inheritance from non-protocol type 'U'}}
7+
~Copyable {}
8+
9+
struct U: // expected-error {{move-only struct 'U' cannot conform to 'Sando'}}
10+
// expected-error@-1 {{type 'U' does not conform to protocol 'Sando'}}
11+
~Copyable,
12+
Sando,
13+
~Copyable // expected-error {{duplicate suppression of 'Copyable'}}
14+
{}
15+
16+
17+
// The expected behavior for '~' in the inheritance clause of a decl not supporting
18+
// suppression is to emit an error and then to treat it as if it's inheriting from
19+
// the type, rather than suppressing. That is, it treats it like the '~' wasn't there
20+
// after emitting an error.
21+
22+
class C: // expected-error {{type 'C' does not conform to protocol 'Sando'}}
23+
~Copyable, // expected-error {{cannot suppress conformances here}}
24+
// expected-error@-1 {{cannot find type 'Copyable' in scope}}
25+
~Sando // expected-error {{cannot suppress conformances here}}
26+
{}
27+
28+
protocol Rope<Element>: ~Copyable { // expected-error {{cannot suppress conformances here}}
29+
// expected-error@-1 {{cannot find type 'Copyable' in scope}}
30+
31+
associatedtype Element: ~Copyable // expected-error {{cannot suppress conformances here}}
32+
// expected-error@-1 {{cannot find type 'Copyable' in scope}}
33+
}
34+
35+
extension S: ~Copyable {} // expected-error {{cannot suppress conformances here}}
36+
// expected-error@-1 {{cannot find type 'Copyable' in scope}}
37+
38+
func takeNoncopyableGeneric<T: ~Copyable>(_ t: T) {} // expected-error {{expected a class type or protocol-constrained type restricting 'T'}}
39+
40+
@_moveOnly struct ExtraNonCopyable: // expected-error {{duplicate attribute}}{{1-12=}}
41+
~Copyable // expected-note {{attribute already specified here}}
42+
{}
43+
44+
// basic test to ensure it's viewed as a noncopyable struct:
45+
struct HasADeinit: ~Copyable {
46+
deinit {}
47+
}

0 commit comments

Comments
 (0)