]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c: special-case some "bool" errors with C23 (v2) [PR117629]
authorDavid Malcolm <dmalcolm@redhat.com>
Thu, 2 Jan 2025 20:10:15 +0000 (15:10 -0500)
committerDavid Malcolm <dmalcolm@redhat.com>
Thu, 2 Jan 2025 20:10:15 +0000 (15:10 -0500)
Changed in v2:
- distinguish between "bool" and "_Bool" when determining
  standard version

This patch attempts to provide better error messages for
code compiled with C23 that hasn't been updated for
"bool", "true", and "false" becoming keywords.

Specifically:

(1) with "typedef int bool;" previously we emitted:

t1.c:7:13: error: two or more data types in declaration specifiers
    7 | typedef int bool;
      |             ^~~~
t1.c:7:1: warning: useless type name in empty declaration
    7 | typedef int bool;
      | ^~~~~~~

whereas with this patch we emit:

t1.c:7:13: error: 'bool' cannot be defined via 'typedef'
    7 | typedef int bool;
      |             ^~~~
t1.c:7:13: note: 'bool' is a keyword with '-std=c23' onwards
t1.c:7:1: warning: useless type name in empty declaration
    7 | typedef int bool;
      | ^~~~~~~

(2) with "int bool;" previously we emitted:

t2.c:7:5: error: two or more data types in declaration specifiers
    7 | int bool;
      |     ^~~~
t2.c:7:1: warning: useless type name in empty declaration
    7 | int bool;
      | ^~~

whereas with this patch we emit:

t2.c:7:5: error: 'bool' cannot be used here
    7 | int bool;
      |     ^~~~
t2.c:7:5: note: 'bool' is a keyword with '-std=c23' onwards
t2.c:7:1: warning: useless type name in empty declaration
    7 | int bool;
      | ^~~

(3) with "typedef enum { false = 0, true = 1 } _Bool;" previously we
emitted:

t3.c:7:16: error: expected identifier before 'false'
    7 | typedef enum { false = 0, true = 1 } _Bool;
      |                ^~~~~
t3.c:7:38: error: expected ';', identifier or '(' before '_Bool'
    7 | typedef enum { false = 0, true = 1 } _Bool;
      |                                      ^~~~~
t3.c:7:38: warning: useless type name in empty declaration

whereas with this patch we emit:

t3.c:7:16: error: cannot use keyword 'false' as enumeration constant
    7 | typedef enum { false = 0, true = 1 } _Bool;
      |                ^~~~~
t3.c:7:16: note: 'false' is a keyword with '-std=c23' onwards
t3.c:7:38: error: expected ';', identifier or '(' before '_Bool'
    7 | typedef enum { false = 0, true = 1 } _Bool;
      |                                      ^~~~~
t3.c:7:38: warning: useless type name in empty declaration

gcc/c/ChangeLog:
PR c/117629
* c-decl.cc (declspecs_add_type): Special-case attempts to use
bool as a typedef name or declaration name.
* c-errors.cc (get_std_for_keyword): New.
(add_note_about_new_keyword): New.
* c-parser.cc (report_bad_enum_name): New, split out from...
(c_parser_enum_specifier): ...here, adding handling for RID_FALSE
and RID_TRUE.
* c-tree.h (add_note_about_new_keyword): New decl.

gcc/testsuite/ChangeLog:
PR c/117629
* gcc.dg/auto-type-2.c: Update expected output with _Bool.
* gcc.dg/c23-bool-errors-1.c: New test.
* gcc.dg/c23-bool-errors-2.c: New test.
* gcc.dg/c23-bool-errors-3.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
gcc/c/c-decl.cc
gcc/c/c-errors.cc
gcc/c/c-parser.cc
gcc/c/c-tree.h
gcc/testsuite/gcc.dg/auto-type-2.c
gcc/testsuite/gcc.dg/c23-bool-errors-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/c23-bool-errors-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/c23-bool-errors-3.c [new file with mode: 0644]

index 44b3b5c3b6ce055f7e9f72cf3e3399a90f751ab8..f60b2a54a17aef49498b1f39084da6531a816e29 100644 (file)
@@ -12493,8 +12493,22 @@ declspecs_add_type (location_t loc, struct c_declspecs *specs,
             "__auto_type".  */
          if (specs->typespec_word != cts_none)
            {
-             error_at (loc,
-                       "two or more data types in declaration specifiers");
+             if (i == RID_BOOL)
+               {
+                 auto_diagnostic_group d;
+                 if (specs->storage_class == csc_typedef)
+                   error_at (loc,
+                             "%qs cannot be defined via %<typedef%>",
+                             IDENTIFIER_POINTER (type));
+                 else
+                   error_at (loc,
+                             "%qs cannot be used here",
+                             IDENTIFIER_POINTER (type));
+                 add_note_about_new_keyword (loc, type);
+               }
+             else
+               error_at (loc,
+                         "two or more data types in declaration specifiers");
              return specs;
            }
          switch (i)
index 4a26eec16c5e0c2e1c814ddf2b7d7b5c169cae52..4682dca56c2c7b07b9b7416beb03aabb07c4a8a6 100644 (file)
@@ -215,3 +215,39 @@ out:
   va_end (ap);
   return warned;
 }
+
+/* Determine in which version of the standard IDENTIFIER
+   became a keyword.  */
+
+static const char *
+get_std_for_keyword (tree identifier)
+{
+  switch (C_RID_CODE (identifier))
+    {
+    default:
+      gcc_unreachable ();
+    case RID_FALSE:
+    case RID_TRUE:
+      return "-std=c23";
+    case RID_BOOL:
+      if (IDENTIFIER_POINTER (identifier)[0] == 'b')
+       /* "bool".  */
+       return "-std=c23";
+      else
+       /* "_Bool".  */
+       return "-std=c99";
+    }
+}
+
+/* Issue a note to the user at LOC that KEYWORD_ID is a keyword
+   in STD_OPTION version of the standard onwards.  */
+
+void
+add_note_about_new_keyword (location_t loc,
+                           tree keyword_id)
+{
+  gcc_assert (TREE_CODE (keyword_id) == IDENTIFIER_NODE);
+  const char *std_option = get_std_for_keyword (keyword_id);
+  inform (loc, "%qs is a keyword with %qs onwards",
+         IDENTIFIER_POINTER (keyword_id), std_option);
+}
index d289af67937ab0cf559067ac5e3685c067b8059b..f72b0f0e39fda2af31abfe6573c1eb99d3a20075 100644 (file)
@@ -3785,6 +3785,47 @@ c_parser_declspecs (c_parser *parser, struct c_declspecs *specs,
     specs->postfix_attrs = c_parser_std_attribute_specifier_sequence (parser);
 }
 
+/* Complain about a non-CPP_NAME within an enumerator list.  */
+
+static void
+report_bad_enum_name (c_parser *parser)
+{
+  if (!parser->error)
+    {
+      c_token *token = c_parser_peek_token (parser);
+      switch (token->type)
+       {
+       default:
+         break;
+       case CPP_CLOSE_BRACE:
+         /* Give a nicer error for "enum {}".  */
+         error_at (token->location,
+                   "empty enum is invalid");
+         parser->error = true;
+         return;
+       case CPP_KEYWORD:
+         /* Give a nicer error for attempts to use "true" and "false"
+            in enums with C23 onwards.  */
+         if (token->keyword == RID_FALSE
+             || token->keyword == RID_TRUE)
+           {
+             auto_diagnostic_group d;
+             error_at (token->location,
+                       "cannot use keyword %qs as enumeration constant",
+                       IDENTIFIER_POINTER (token->value));
+             add_note_about_new_keyword (token->location,
+                                         token->value);
+             parser->error = true;
+             return;
+           }
+         break;
+       }
+    }
+
+  /* Otherwise, a more generic error message.  */
+  c_parser_error (parser, "expected identifier");
+}
+
 /* Parse an enum specifier (C90 6.5.2.2, C99 6.7.2.2, C11 6.7.2.2).
 
    enum-specifier:
@@ -3952,16 +3993,7 @@ c_parser_enum_specifier (c_parser *parser)
          location_t decl_loc, value_loc;
          if (c_parser_next_token_is_not (parser, CPP_NAME))
            {
-             /* Give a nicer error for "enum {}".  */
-             if (c_parser_next_token_is (parser, CPP_CLOSE_BRACE)
-                 && !parser->error)
-               {
-                 error_at (c_parser_peek_token (parser)->location,
-                           "empty enum is invalid");
-                 parser->error = true;
-               }
-             else
-               c_parser_error (parser, "expected identifier");
+             report_bad_enum_name (parser);
              c_parser_skip_until_found (parser, CPP_CLOSE_BRACE, NULL);
              values = error_mark_node;
              break;
index ee18e093f996e243cb18599e840a1dfd01c607e1..743ec5cbae618efbc7896be02876ae6d24a332b6 100644 (file)
@@ -955,6 +955,8 @@ extern bool pedwarn_c11 (location_t, diagnostic_option_id, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
 extern bool pedwarn_c23 (location_t, diagnostic_option_id, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
+extern void add_note_about_new_keyword (location_t loc,
+                                       tree keyword_id);
 
 extern void
 set_c_expr_source_range (c_expr *expr,
index 761671b3c5a46ea419555db882f30cb1a78f13f6..e059912b8cc531209e6f10b1469f7f1f1cd4a92c 100644 (file)
@@ -20,4 +20,5 @@ signed __auto_type e7 = 0; /* { dg-error "__auto_type" } */
 unsigned __auto_type e8 = 0; /* { dg-error "__auto_type" } */
 _Complex __auto_type e9 = 0; /* { dg-error "__auto_type" } */
 int __auto_type e10 = 0; /* { dg-error "two or more data types" } */
-__auto_type _Bool e11 = 0; /* { dg-error "two or more data types" } */
+__auto_type _Bool e11 = 0; /* { dg-error "'_Bool' cannot be used here" } */
+/* { dg-message "'_Bool' is a keyword with '-std=c99' onwards" "" { target *-*-* } .-1 } */
diff --git a/gcc/testsuite/gcc.dg/c23-bool-errors-1.c b/gcc/testsuite/gcc.dg/c23-bool-errors-1.c
new file mode 100644 (file)
index 0000000..e3ea98d
--- /dev/null
@@ -0,0 +1,14 @@
+/* Test error-handling for legacy code that tries to
+   define "bool" with C23.  */
+
+/* { dg-do compile } */
+/* { dg-options "-std=c23" } */
+
+typedef int bool; /* { dg-error "'bool' cannot be defined via 'typedef'" } */
+/* { dg-message "'bool' is a keyword with '-std=c23' onwards" "" { target *-*-* } .-1 } */
+/* { dg-warning "useless type name in empty declaration"  "" { target *-*-* } .-2 } */
+
+/* Also check that we report the correct standard version for "_Bool".  */
+typedef int _Bool; /* { dg-error "'_Bool' cannot be defined via 'typedef'" } */
+/* { dg-message "'_Bool' is a keyword with '-std=c99' onwards" "" { target *-*-* } .-1 } */
+/* { dg-warning "useless type name in empty declaration"  "" { target *-*-* } .-2 } */
diff --git a/gcc/testsuite/gcc.dg/c23-bool-errors-2.c b/gcc/testsuite/gcc.dg/c23-bool-errors-2.c
new file mode 100644 (file)
index 0000000..1a66e3a
--- /dev/null
@@ -0,0 +1,9 @@
+/* Test error-handling for legacy code that tries to
+   use a variable named "bool" with C23.  */
+
+/* { dg-do compile } */
+/* { dg-options "-std=c23" } */
+
+int bool; /* { dg-error "'bool' cannot be used here" } */
+/* { dg-message "'bool' is a keyword with '-std=c23' onwards" "" { target *-*-* } .-1 } */
+/* { dg-warning "useless type name in empty declaration" "" { target *-*-* } .-2 } */
diff --git a/gcc/testsuite/gcc.dg/c23-bool-errors-3.c b/gcc/testsuite/gcc.dg/c23-bool-errors-3.c
new file mode 100644 (file)
index 0000000..51ac5c9
--- /dev/null
@@ -0,0 +1,18 @@
+/* Test error-handling for legacy code that tries to
+   define "false" or "true" within enums with C23.  */
+
+/* { dg-do compile } */
+/* { dg-options "-std=c23" } */
+
+typedef enum { false = 0, true = 1 } _Bool; /* { dg-error "cannot use keyword 'false' as enumeration constant" }
+/* { dg-message "'false' is a keyword with '-std=c23' onwards" "" { target *-*-* } .-1 } */
+/* { dg-error "38: expected ';', identifier or '\\\(' before '_Bool'" "" { target *-*-* } .-2 } */
+/* { dg-warning "38: useless type name in empty declaration" "" { target *-*-* } .-3 } */
+
+typedef enum { true = 1, false = 0 } _Bool; /* { dg-error "cannot use keyword 'true' as enumeration constant" }
+/* { dg-message "'true' is a keyword with '-std=c23' onwards" "" { target *-*-* } .-1 } */
+/* { dg-error "38: expected ';', identifier or '\\\(' before '_Bool'" "" { target *-*-* } .-2 } */
+/* { dg-warning "38: useless type name in empty declaration" "" { target *-*-* } .-3 } */
+
+typedef enum { False = 0, True = 1 } bool; /* { dg-error "expected ';', identifier or '\\(' before 'bool'" }
+/* { dg-warning "38: useless type name in empty declaration" "" { target *-*-* } .-1 } */