Skip to content

Commit 6d01f25

Browse files
authored
Merge pull request #65556 from kavon/barebones-suppress-copyable
Build support for `~Copyable` atop `@_moveOnly`
2 parents dd813af + ee819c8 commit 6d01f25

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
@@ -944,6 +944,19 @@ ERROR(expected_rparen_layout_constraint,none,
944944
ERROR(layout_constraints_only_inside_specialize_attr,none,
945945
"layout constraints are only allowed inside '_specialize' attributes", ())
946946

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

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
@@ -1195,9 +1195,18 @@ class Parser {
11951195

11961196
ParserResult<ImportDecl> parseDeclImport(ParseDeclOptions Flags,
11971197
DeclAttributes &Attributes);
1198+
1199+
/// Parse an inheritance clause into a vector of InheritedEntry's.
1200+
///
1201+
/// \param allowClassRequirement whether to permit parsing of 'class'
1202+
/// \param allowAnyObject whether to permit parsing of 'AnyObject'
1203+
/// \param parseTildeCopyable if non-null, permits parsing of `~Copyable`
1204+
/// and writes out a valid source location if it was parsed. If null, then a
1205+
/// parsing error will be emitted upon the appearance of `~` in the clause.
11981206
ParserStatus parseInheritance(SmallVectorImpl<InheritedEntry> &Inherited,
11991207
bool allowClassRequirement,
1200-
bool allowAnyObject);
1208+
bool allowAnyObject,
1209+
SourceLoc *parseTildeCopyable = nullptr);
12011210
ParserStatus parseDeclItem(bool &PreviousHadSemi,
12021211
ParseDeclOptions Options,
12031212
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
@@ -5678,6 +5678,29 @@ ParserResult<ImportDecl> Parser::parseDeclImport(ParseDeclOptions Flags,
56785678
return DCC.fixupParserResult(ID);
56795679
}
56805680

5681+
static void addMoveOnlyAttrIf(SourceLoc const &parsedTildeCopyable,
5682+
ASTContext &Context,
5683+
Decl *decl) {
5684+
if (parsedTildeCopyable.isInvalid())
5685+
return;
5686+
5687+
auto &attrs = decl->getAttrs();
5688+
5689+
// Don't add if it's already explicitly written on the decl, but error about
5690+
// the duplication and point to the `~Copyable`.
5691+
if (auto attr = attrs.getAttribute<MoveOnlyAttr>()) {
5692+
const bool sayModifier = false;
5693+
Context.Diags.diagnose(attr->getLocation(), diag::duplicate_attribute,
5694+
sayModifier)
5695+
.fixItRemove(attr->getRange());
5696+
Context.Diags.diagnose(parsedTildeCopyable, diag::previous_attribute,
5697+
sayModifier);
5698+
return;
5699+
}
5700+
5701+
attrs.add(new(Context) MoveOnlyAttr(/*IsImplicit=*/true));
5702+
}
5703+
56815704
/// Parse an inheritance clause.
56825705
///
56835706
/// \verbatim
@@ -5687,15 +5710,19 @@ ParserResult<ImportDecl> Parser::parseDeclImport(ParseDeclOptions Flags,
56875710
/// inherited:
56885711
/// 'class'
56895712
/// type-identifier
5713+
/// '~' 'Copyable'
56905714
/// \endverbatim
56915715
ParserStatus Parser::parseInheritance(
56925716
SmallVectorImpl<InheritedEntry> &Inherited,
5693-
bool allowClassRequirement, bool allowAnyObject) {
5717+
bool allowClassRequirement,
5718+
bool allowAnyObject,
5719+
SourceLoc *parseTildeCopyable) {
56945720
consumeToken(tok::colon);
56955721

56965722
SourceLoc classRequirementLoc;
56975723

56985724
ParserStatus Status;
5725+
SourceLoc TildeCopyableLoc;
56995726
SourceLoc prevComma;
57005727
bool HasNextType;
57015728
do {
@@ -5749,6 +5776,38 @@ ParserStatus Parser::parseInheritance(
57495776
continue;
57505777
}
57515778

5779+
// Is suppression permitted?
5780+
if (parseTildeCopyable) {
5781+
// Try to find '~' 'Copyable'
5782+
//
5783+
// We do this knowing that Copyable is not a real type as of now, so we
5784+
// can't rely on parseType.
5785+
if (Tok.isTilde()) {
5786+
const auto &nextTok = peekToken(); // lookahead
5787+
if (isIdentifier(nextTok, Context.Id_Copyable.str())) {
5788+
auto tildeLoc = consumeToken();
5789+
consumeToken(); // the 'Copyable' token
5790+
5791+
if (TildeCopyableLoc)
5792+
diagnose(tildeLoc, diag::already_suppressed, Context.Id_Copyable);
5793+
else
5794+
TildeCopyableLoc = tildeLoc;
5795+
5796+
continue;
5797+
}
5798+
5799+
// can't suppress whatever is between '~' and ',' or '{'.
5800+
diagnose(Tok, diag::only_suppress_copyable);
5801+
consumeToken();
5802+
}
5803+
5804+
} else if (Tok.isTilde()) {
5805+
// a suppression isn't allowed here, so emit an error eat the token to
5806+
// prevent further parsing errors.
5807+
diagnose(Tok, diag::cannot_suppress_here);
5808+
consumeToken();
5809+
}
5810+
57525811
auto ParsedTypeResult = parseType();
57535812
Status |= ParsedTypeResult;
57545813

@@ -5757,6 +5816,9 @@ ParserStatus Parser::parseInheritance(
57575816
Inherited.push_back(InheritedEntry(ParsedTypeResult.get()));
57585817
} while (HasNextType);
57595818

5819+
if (parseTildeCopyable)
5820+
*parseTildeCopyable = TildeCopyableLoc;
5821+
57605822
return Status;
57615823
}
57625824

@@ -8219,10 +8281,14 @@ ParserResult<EnumDecl> Parser::parseDeclEnum(ParseDeclOptions Flags,
82198281
// Parse optional inheritance clause within the context of the enum.
82208282
if (Tok.is(tok::colon)) {
82218283
SmallVector<InheritedEntry, 2> Inherited;
8284+
SourceLoc parsedTildeCopyable;
82228285
Status |= parseInheritance(Inherited,
82238286
/*allowClassRequirement=*/false,
8224-
/*allowAnyObject=*/false);
8287+
/*allowAnyObject=*/false,
8288+
&parsedTildeCopyable);
82258289
ED->setInherited(Context.AllocateCopy(Inherited));
8290+
8291+
addMoveOnlyAttrIf(parsedTildeCopyable, Context, ED);
82268292
}
82278293

82288294
diagnoseWhereClauseInGenericParamList(GenericParams);
@@ -8482,10 +8548,14 @@ ParserResult<StructDecl> Parser::parseDeclStruct(ParseDeclOptions Flags,
84828548
// Parse optional inheritance clause within the context of the struct.
84838549
if (Tok.is(tok::colon)) {
84848550
SmallVector<InheritedEntry, 2> Inherited;
8551+
SourceLoc parsedTildeCopyable;
84858552
Status |= parseInheritance(Inherited,
84868553
/*allowClassRequirement=*/false,
8487-
/*allowAnyObject=*/false);
8554+
/*allowAnyObject=*/false,
8555+
&parsedTildeCopyable);
84888556
SD->setInherited(Context.AllocateCopy(Inherited));
8557+
8558+
addMoveOnlyAttrIf(parsedTildeCopyable, Context, SD);
84898559
}
84908560

84918561
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)