Skip to content

Commit dbd17c5

Browse files
committed
[NCGenerics] allow feature-guarded uses of ~Copyable
We need to delay the pre-NCGenerics errors about illegal uses of ~Copyable from Parse until Sema. Otherwise, such uses can't appear within a false
1 parent b55c4f1 commit dbd17c5

File tree

5 files changed

+114
-63
lines changed

5 files changed

+114
-63
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -961,8 +961,8 @@ ERROR(cannot_suppress_here,none,
961961
ERROR(only_suppress_copyable,none,
962962
"can only suppress 'Copyable'", ())
963963

964-
ERROR(already_suppressed,none,
965-
"duplicate suppression of %0", (Identifier))
964+
ERROR(already_suppressed_copyable,none,
965+
"duplicate suppression of 'Copyable'", ())
966966

967967
//------------------------------------------------------------------------------
968968
// MARK: Pattern parsing diagnostics

include/swift/Parse/Parser.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace llvm {
4242

4343
namespace swift {
4444
class IdentTypeRepr;
45+
class ErrorTypeRepr;
4546
class CodeCompletionCallbacks;
4647
class DoneParsingCallback;
4748
class IDEInspectionCallbacksFactory;
@@ -2035,11 +2036,6 @@ class Parser {
20352036
void performIDEInspectionSecondPassImpl(
20362037
IDEInspectionDelayedDeclState &info);
20372038

2038-
/// Returns true if the caller should skip calling `parseType` afterwards.
2039-
bool parseLegacyTildeCopyable(SourceLoc *parseTildeCopyable,
2040-
ParserStatus &Status,
2041-
SourceLoc &TildeCopyableLoc);
2042-
20432039
//===--------------------------------------------------------------------===//
20442040
// ASTGen support.
20452041

lib/Parse/ParseDecl.cpp

Lines changed: 41 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6426,51 +6426,6 @@ static void addMoveOnlyAttrIf(SourceLoc const &parsedTildeCopyable,
64266426
attrs.add(new(Context) MoveOnlyAttr(/*IsImplicit=*/true));
64276427
}
64286428

6429-
bool Parser::parseLegacyTildeCopyable(SourceLoc *parseTildeCopyable,
6430-
ParserStatus &Status,
6431-
SourceLoc &TildeCopyableLoc) {
6432-
// Is suppression permitted?
6433-
if (parseTildeCopyable) {
6434-
// Try to find '~' 'Copyable'
6435-
//
6436-
// We do this knowing that Copyable is not a real type as of now, so we
6437-
// can't rely on parseType.
6438-
if (Tok.isTilde()) {
6439-
const auto &nextTok = peekToken(); // lookahead
6440-
if (isIdentifier(nextTok, Context.Id_Copyable.str())) {
6441-
auto tildeLoc = consumeToken();
6442-
consumeToken(); // the 'Copyable' token
6443-
6444-
if (TildeCopyableLoc)
6445-
diagnose(tildeLoc, diag::already_suppressed, Context.Id_Copyable);
6446-
else
6447-
TildeCopyableLoc = tildeLoc;
6448-
6449-
return true;
6450-
} else if (nextTok.is(tok::code_complete)) {
6451-
consumeToken(); // consume '~'
6452-
Status.setHasCodeCompletionAndIsError();
6453-
if (CodeCompletionCallbacks) {
6454-
CodeCompletionCallbacks->completeWithoutConstraintType();
6455-
}
6456-
consumeToken(tok::code_complete);
6457-
}
6458-
6459-
// can't suppress whatever is between '~' and ',' or '{'.
6460-
diagnose(Tok, diag::only_suppress_copyable);
6461-
consumeToken();
6462-
}
6463-
6464-
} else if (Tok.isTilde()) {
6465-
// a suppression isn't allowed here, so emit an error eat the token to
6466-
// prevent further parsing errors.
6467-
diagnose(Tok, diag::cannot_suppress_here);
6468-
consumeToken();
6469-
}
6470-
6471-
return false;
6472-
}
6473-
64746429
/// Parse an inheritance clause.
64756430
///
64766431
/// \verbatim
@@ -6546,11 +6501,47 @@ ParserStatus Parser::parseInheritance(
65466501
continue;
65476502
}
65486503

6549-
if (!EnabledNoncopyableGenerics) {
6550-
if (parseLegacyTildeCopyable(parseTildeCopyable,
6551-
Status,
6552-
TildeCopyableLoc))
6553-
continue;
6504+
if (!EnabledNoncopyableGenerics && Tok.isTilde()) {
6505+
ErrorTypeRepr *error = nullptr;
6506+
if (parseTildeCopyable) {
6507+
const auto &nextTok = peekToken(); // lookahead
6508+
if (isIdentifier(nextTok, Context.Id_Copyable.str())) {
6509+
auto tildeLoc = consumeToken();
6510+
consumeToken(); // the 'Copyable' token
6511+
6512+
if (TildeCopyableLoc)
6513+
Inherited.push_back(InheritedEntry(
6514+
ErrorTypeRepr::create(Context, tildeLoc,
6515+
diag::already_suppressed_copyable)));
6516+
else
6517+
TildeCopyableLoc = tildeLoc;
6518+
6519+
continue; // success
6520+
}
6521+
6522+
if (nextTok.is(tok::code_complete)) {
6523+
consumeToken(); // consume '~'
6524+
Status.setHasCodeCompletionAndIsError();
6525+
if (CodeCompletionCallbacks) {
6526+
CodeCompletionCallbacks->completeWithoutConstraintType();
6527+
}
6528+
consumeToken(tok::code_complete);
6529+
}
6530+
6531+
// can't suppress whatever is between '~' and ',' or '{'.
6532+
error = ErrorTypeRepr::create(Context, consumeToken(),
6533+
diag::only_suppress_copyable);
6534+
} else {
6535+
// Otherwise, a suppression isn't allowed here unless noncopyable
6536+
// generics is enabled, so record a delayed error diagnostic and
6537+
// eat the token to prevent further parsing errors.
6538+
error = ErrorTypeRepr::create(Context, consumeToken(),
6539+
diag::cannot_suppress_here);
6540+
}
6541+
6542+
// Record the error parsing ~Copyable, but continue on to parseType.
6543+
if (error)
6544+
Inherited.push_back(InheritedEntry(error));
65546545
}
65556546

65566547
auto ParsedTypeResult = parseType();

lib/Parse/ParseType.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,6 @@ ParserResult<TypeRepr> Parser::parseTypeSimple(
172172
SourceLoc tildeLoc;
173173
if (Tok.isTilde() && !isInSILMode()) {
174174
tildeLoc = consumeToken();
175-
if (!EnabledNoncopyableGenerics)
176-
diagnose(tildeLoc, diag::cannot_suppress_here)
177-
.fixItRemoveChars(tildeLoc, tildeLoc);
178175
}
179176

180177
switch (Tok.getKind()) {
@@ -310,9 +307,15 @@ ParserResult<TypeRepr> Parser::parseTypeSimple(
310307
}
311308

312309
// Wrap in an InverseTypeRepr if needed.
313-
if (EnabledNoncopyableGenerics && tildeLoc) {
314-
ty = makeParserResult(ty,
315-
new(Context) InverseTypeRepr(tildeLoc, ty.get()));
310+
if (tildeLoc) {
311+
TypeRepr *repr;
312+
if (EnabledNoncopyableGenerics)
313+
repr = new (Context) InverseTypeRepr(tildeLoc, ty.get());
314+
else
315+
repr =
316+
ErrorTypeRepr::create(Context, tildeLoc, diag::cannot_suppress_here);
317+
318+
ty = makeParserResult(ty, repr);
316319
}
317320

318321
return ty;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %swift-frontend -typecheck %s
2+
// RUN: %swift-frontend -typecheck %s -DHAVE_NCGENERICS -enable-experimental-feature NoncopyableGenerics
3+
// RUN: %swift-frontend -typecheck %s -verify -DHAVE_NCGENERICS
4+
5+
// REQUIRES: asserts
6+
7+
/// This test checks that you can write ~Copyable in places that were illegal
8+
/// in Swift 5.9, as long as those illegal appearances are guarded within
9+
/// a disabled #if block.
10+
///
11+
/// We previously would emit errors during Parse for those illegal appearances,
12+
/// which would make it impossible to feature-guard new, legal uses of those
13+
/// annotations. Now we delay those diagnostics until Sema.
14+
///
15+
/// So, the first RUN line here checks that there are no error diagnostics emitted
16+
/// when not providing HAVE_NCGENERICS. Then, we enable that experimental feature to
17+
/// ensure that what's in the #if has no unexpected errors. Finally, we run without that
18+
/// feature to ensure we get the expected errors when the #if-block is allowed to be
19+
/// typechecked.
20+
21+
struct NC: ~Copyable {}
22+
23+
#if HAVE_NCGENERICS
24+
25+
struct Blah: ~Copyable, ~Copyable {}
26+
// expected-error@-1 {{duplicate suppression of 'Copyable'}}
27+
28+
protocol Compare where Self: ~Copyable { // expected-error {{cannot suppress conformances here}}
29+
func lessThan(_ other: borrowing Self) -> Bool
30+
}
31+
32+
protocol Sortable<Element>: ~Copyable { // expected-error {{cannot suppress conformances here}}
33+
associatedtype Element: ~Copyable, Compare // expected-error {{cannot suppress conformances here}} // expected-note {{protocol requires nested type 'Element'; add nested type 'Element' for conformance}}
34+
35+
mutating func sort()
36+
}
37+
38+
extension NC: Compare { // expected-error {{noncopyable struct 'NC' cannot conform to 'Compare'}}
39+
func lessThan(_ other: borrowing Self) -> Bool { false }
40+
}
41+
42+
extension NC: Sortable { // expected-error {{noncopyable struct 'NC' cannot conform to 'Sortable'}}
43+
// expected-error@-1 {{type 'NC' does not conform to protocol 'Sortable'}}
44+
typealias Element = NC // expected-note {{possibly intended match 'NC.Element' (aka 'NC') does not conform to 'Copyable'}}
45+
46+
mutating func sort() { }
47+
}
48+
49+
func f<T>(_ t: inout T) // expected-note {{where 'T.Element' = 'NC.Element'}}
50+
where T: ~Copyable, // expected-error {{cannot suppress conformances here}}
51+
T: Sortable,
52+
T.Element == NC {
53+
t.sort()
54+
}
55+
56+
do {
57+
var x = NC()
58+
f(&x) // expected-error {{global function 'f' requires the types 'NC.Element' and 'NC' be equivalent}}
59+
}
60+
61+
#endif

0 commit comments

Comments
 (0)