Skip to content

[C] Add new -Wimplicit-int-enum-cast to -Wc++-compat #137658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2025

Conversation

AaronBallman
Copy link
Collaborator

This introduces a new diagnostic group to diagnose implicit casts from int to an enumeration type. In C, this is valid, but it is not compatible with C++.

Additionally, this moves the "implicit conversion from enum type to different enum type" diagnostic from -Wenum-conversion to a new group -Wimplicit-enum-enum-cast, which is a more accurate home for it. -Wimplicit-enum-enum-cast is also under -Wimplicit-int-enum-cast, as it is the same incompatibility (the enumeration on the right-hand is promoted to int, so it's an int -> enum conversion).

Fixes #37027

This introduces a new diagnostic group to diagnose implicit casts from
int to an enumeration type. In C, this is valid, but it is not
compatible with C++.

Additionally, this moves the "implicit conversion from enum type to
different enum type" diagnostic from `-Wenum-conversion` to a new group
`-Wimplicit-enum-enum-cast`, which is a more accurate home for it.
`-Wimplicit-enum-enum-cast` is also under `-Wimplicit-int-enum-cast`,
as it is the same incompatibility (the enumeration on the right-hand is
promoted to `int`, so it's an int -> enum conversion).

Fixes llvm#37027
@AaronBallman AaronBallman added clang Clang issues not falling into any other category c clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Apr 28, 2025
@AaronBallman AaronBallman changed the title Add new -Wimplicit-int-enum-cast to -Wc++-compat [C] Add new -Wimplicit-int-enum-cast to -Wc++-compat Apr 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 28, 2025

@llvm/pr-subscribers-clang

Author: Aaron Ballman (AaronBallman)

Changes

This introduces a new diagnostic group to diagnose implicit casts from int to an enumeration type. In C, this is valid, but it is not compatible with C++.

Additionally, this moves the "implicit conversion from enum type to different enum type" diagnostic from -Wenum-conversion to a new group -Wimplicit-enum-enum-cast, which is a more accurate home for it. -Wimplicit-enum-enum-cast is also under -Wimplicit-int-enum-cast, as it is the same incompatibility (the enumeration on the right-hand is promoted to int, so it's an int -> enum conversion).

Fixes #37027


Full diff: https://github.com/llvm/llvm-project/pull/137658.diff

6 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+9)
  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+6-1)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4-1)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+7-1)
  • (modified) clang/test/Misc/warning-flags-enabled.c (+5-3)
  • (added) clang/test/Sema/implicit-int-enum-conversion.c (+52)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3105d8b481560..1bcf358974437 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -150,6 +150,15 @@ C Language Changes
 - Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
   diagnoses implicit conversion from ``void *`` to another pointer type as
   being incompatible with C++. (#GH17792)
+- Added ``-Wimplicit-int-enum-cast``, grouped under ``-Wc++-compat``, which
+  diagnoses implicit conversion from integer types to an enumeration type in C,
+  which is not compatible with C++. #GH37027
+- Split "implicit conversion from enum type to different enum type" diagnostic
+  from ``-Wenum-conversion`` into its own diagnostic group,
+  ``-Wimplicit-enum-enum-cast``, which is grouped under both
+  ``-Wenum-conversion`` and ``-Wimplicit-int-enum-cast``. This conversion is an
+  int-to-enum conversion because the enumeration on the right-hand side is
+  promoted to ``int`` before the assignment.
 
 C2y Feature Support
 ^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 31e2cfa7ab485..97ed38d71ed51 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -103,10 +103,12 @@ def AnonEnumEnumConversion : DiagGroup<"anon-enum-enum-conversion",
                                    [DeprecatedAnonEnumEnumConversion]>;
 def EnumEnumConversion : DiagGroup<"enum-enum-conversion",
                                    [DeprecatedEnumEnumConversion]>;
+def ImplicitEnumEnumCast : DiagGroup<"implicit-enum-enum-cast">;
 def EnumFloatConversion : DiagGroup<"enum-float-conversion",
                                     [DeprecatedEnumFloatConversion]>;
 def EnumConversion : DiagGroup<"enum-conversion",
                                [EnumEnumConversion,
+                                ImplicitEnumEnumCast,
                                 EnumFloatConversion,
                                 EnumCompareConditional]>;
 def DeprecatedOFast : DiagGroup<"deprecated-ofast">;
@@ -157,7 +159,10 @@ def : DiagGroup<"c2x-compat", [C23Compat]>;
 def DefaultConstInitUnsafe : DiagGroup<"default-const-init-unsafe">;
 def DefaultConstInit : DiagGroup<"default-const-init", [DefaultConstInitUnsafe]>;
 def ImplicitVoidPtrCast : DiagGroup<"implicit-void-ptr-cast">;
-def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit]>;
+def ImplicitIntToEnumCast : DiagGroup<"implicit-int-enum-cast",
+                                      [ImplicitEnumEnumCast]>;
+def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit,
+                                        ImplicitIntToEnumCast]>;
 
 def ExternCCompat : DiagGroup<"extern-c-compat">;
 def KeywordCompat : DiagGroup<"keyword-compat">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4c96142e28134..2e148e01d6e5e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4310,7 +4310,10 @@ def warn_impcast_string_literal_to_bool : Warning<
   InGroup<StringConversion>, DefaultIgnore;
 def warn_impcast_different_enum_types : Warning<
   "implicit conversion from enumeration type %0 to different enumeration type "
-  "%1">, InGroup<EnumConversion>;
+  "%1">, InGroup<ImplicitEnumEnumCast>;
+def warn_impcast_int_to_enum : Warning<
+  "implicit conversion from %0 to enumeration type %1 is invalid in C++">,
+  InGroup<ImplicitIntToEnumCast>, DefaultIgnore;
 def warn_impcast_bool_to_null_pointer : Warning<
     "initialization of pointer of type %0 to null from a constant boolean "
     "expression">, InGroup<BoolConversion>;
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 2811fd3a04377..332504405eccb 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -12271,10 +12271,16 @@ void Sema::CheckImplicitConversion(Expr *E, QualType T, SourceLocation CC,
     return DiagnoseImpCast(*this, E, T, CC, DiagID);
   }
 
+  // If we're implicitly converting from an integer into an enumeration, that
+  // is valid in C but invalid in C++.
+  QualType SourceType = E->getEnumCoercedType(Context);
+  const BuiltinType *CoercedSourceBT = SourceType->getAs<BuiltinType>();
+  if (CoercedSourceBT && CoercedSourceBT->isInteger() && isa<EnumType>(Target))
+    return DiagnoseImpCast(*this, E, T, CC, diag::warn_impcast_int_to_enum);
+
   // Diagnose conversions between different enumeration types.
   // In C, we pretend that the type of an EnumConstantDecl is its enumeration
   // type, to give us better diagnostics.
-  QualType SourceType = E->getEnumCoercedType(Context);
   Source = Context.getCanonicalType(SourceType).getTypePtr();
 
   if (const EnumType *SourceEnum = Source->getAs<EnumType>())
diff --git a/clang/test/Misc/warning-flags-enabled.c b/clang/test/Misc/warning-flags-enabled.c
index 9f210674b126e..5fea199f3feec 100644
--- a/clang/test/Misc/warning-flags-enabled.c
+++ b/clang/test/Misc/warning-flags-enabled.c
@@ -26,13 +26,15 @@
 // CHECK-NO-LEVELS-NOT: {{^F }}
 // CHECK-NO-LEVELS: warn_objc_root_class_missing [-Wobjc-root-class]
 
-// Test if EnumConversion is a subgroup of -Wconversion.
+// Test if EnumConversion is a subgroup of -Wconversion. Because no diagnostics
+// are grouped directly under -Wenum-conversion, we check for
+// -Wimplicit-enum-enum-cast instead (which is itself under -Wenum-conversion).
 // RUN: diagtool show-enabled --no-levels -Wno-conversion -Wenum-conversion %s | FileCheck --check-prefix CHECK-ENUM-CONVERSION %s
 // RUN: diagtool show-enabled --no-levels %s | FileCheck --check-prefix CHECK-ENUM-CONVERSION %s
 // RUN: diagtool show-enabled --no-levels -Wno-conversion %s | FileCheck --check-prefix CHECK-NO-ENUM-CONVERSION %s
 //
-// CHECK-ENUM-CONVERSION: -Wenum-conversion
-// CHECK-NO-ENUM-CONVERSION-NOT: -Wenum-conversion
+// CHECK-ENUM-CONVERSION: -Wimplicit-enum-enum-cast
+// CHECK-NO-ENUM-CONVERSION-NOT: -Wimplicit-enum-enum-cast
 
 // Test if -Wshift-op-parentheses is a subgroup of -Wparentheses
 // RUN: diagtool show-enabled --no-levels -Wno-parentheses -Wshift-op-parentheses %s | FileCheck --check-prefix CHECK-SHIFT-OP-PARENTHESES %s
diff --git a/clang/test/Sema/implicit-int-enum-conversion.c b/clang/test/Sema/implicit-int-enum-conversion.c
new file mode 100644
index 0000000000000..13afb5d297aba
--- /dev/null
+++ b/clang/test/Sema/implicit-int-enum-conversion.c
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wimplicit-int-enum-cast %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx -x c++ %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good -Wno-implicit-enum-enum-cast %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good -Wc++-compat -Wno-implicit-enum-enum-cast -Wno-implicit-int-enum-cast %s
+// good-no-diagnostics
+
+enum E1 {
+  E1_Zero,
+  E1_One
+};
+
+enum E2 {
+  E2_Zero
+};
+
+struct S {
+  enum E1 e;
+} s = { 12 }; // expected-warning {{implicit conversion from 'int' to enumeration type 'enum E1' is invalid in C++}} \
+                 cxx-error {{cannot initialize a member subobject of type 'enum E1' with an rvalue of type 'int'}}
+
+enum E1 foo(void) {
+  int x;
+  enum E1 e = 12; // expected-warning {{implicit conversion from 'int' to enumeration type 'enum E1' is invalid in C++}} \
+                     cxx-error {{cannot initialize a variable of type 'enum E1' with an rvalue of type 'int'}}
+
+  // Enum to integer is fine.
+  x = e;
+
+  // Integer to enum is not fine.
+  e = x;    // expected-warning {{implicit conversion from 'int' to enumeration type 'enum E1' is invalid in C++}} \
+               cxx-error {{assigning to 'enum E1' from incompatible type 'int'}}
+  return x; // expected-warning {{implicit conversion from 'int' to enumeration type 'enum E1' is invalid in C++}} \
+               cxx-error {{cannot initialize return object of type 'enum E1' with an lvalue of type 'int'}}
+}
+
+// Returning with the correct types is fine.
+enum E1 bar(void) {
+  return E1_Zero;
+}
+
+// Enum to different-enum conversion is also a C++ incompatibility, but is
+// handled via a more general diagnostic, -Wimplicit-enum-enum-cast, which is
+// on by default.
+enum E1 quux(void) {
+  enum E1 e1 = E2_Zero; // expected-warning {{implicit conversion from enumeration type 'enum E2' to different enumeration type 'enum E1'}} \
+                           cxx-error {{cannot initialize a variable of type 'enum E1' with an rvalue of type 'E2'}}
+  e1 = E2_Zero;         // expected-warning {{implicit conversion from enumeration type 'enum E2' to different enumeration type 'enum E1'}}   \
+                           cxx-error {{assigning to 'enum E1' from incompatible type 'E2'}}
+  return E2_Zero;       // expected-warning {{implicit conversion from enumeration type 'enum E2' to different enumeration type 'enum E1'}} \
+                           cxx-error {{cannot initialize return object of type 'enum E1' with an rvalue of type 'E2'}}
+}

@AaronBallman AaronBallman merged commit df267d7 into llvm:main Apr 29, 2025
12 checks passed
@AaronBallman AaronBallman deleted the aballman-gh37027 branch April 29, 2025 11:06
@asmok-g
Copy link

asmok-g commented May 6, 2025

@AaronBallman If you try to compile the following c program after this patch

void free ();
typedef enum {REG_EESCAPE} reg_errcode_t;
typedef struct {int *stack;} compile_stack_type;
reg_errcode_t byte_regex_compile () {
  compile_stack_type compile_stack;
  return (free (compile_stack.stack), REG_EESCAPE);                                                                                                                                
}

cmd: clang -Wimplicit-int-enum-cast -c pre.i

It will give an incorrect diagnostic:

pre.i:6:37: warning: implicit conversion from 'int' to enumeration type 'reg_errcode_t' is invalid in C++ [-Wimplicit-int-enum-cast]
    6 |   return (free (compile_stack.stack), REG_EESCAPE);                                                                                                                                
      |   ~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
1 warning generated.

@AaronBallman
Copy link
Collaborator Author

AaronBallman commented May 6, 2025

There is an implicit enum conversion happening (paren expression results in an int due to promotions, and that needs to be converted to the struct type). However, in C++ the enumerator has the type of the enumeration, not int, so the comma results in the type of the right-most operand, which is the enumeration type so there's not an implicit conversion in that case. Thanks for letting me know, I'll investigate a fix.

@AaronBallman
Copy link
Collaborator Author

Ooof, this may not be easy to work around. In C, we're in a context where we know we have an implicit conversion. But we have no way of knowing that we would not have the implicit conversion in C++. We could probably handle comma expressions as a special case, but I suspect that will just move the issue to another place. Still investigating whether there's a good way to salvage this.

IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This introduces a new diagnostic group to diagnose implicit casts from
int to an enumeration type. In C, this is valid, but it is not
compatible with C++.

Additionally, this moves the "implicit conversion from enum type to
different enum type" diagnostic from `-Wenum-conversion` to a new group
`-Wimplicit-enum-enum-cast`, which is a more accurate home for it.
`-Wimplicit-enum-enum-cast` is also under `-Wimplicit-int-enum-cast`, as
it is the same incompatibility (the enumeration on the right-hand is
promoted to `int`, so it's an int -> enum conversion).

Fixes llvm#37027
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This introduces a new diagnostic group to diagnose implicit casts from
int to an enumeration type. In C, this is valid, but it is not
compatible with C++.

Additionally, this moves the "implicit conversion from enum type to
different enum type" diagnostic from `-Wenum-conversion` to a new group
`-Wimplicit-enum-enum-cast`, which is a more accurate home for it.
`-Wimplicit-enum-enum-cast` is also under `-Wimplicit-int-enum-cast`, as
it is the same incompatibility (the enumeration on the right-hand is
promoted to `int`, so it's an int -> enum conversion).

Fixes llvm#37027
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This introduces a new diagnostic group to diagnose implicit casts from
int to an enumeration type. In C, this is valid, but it is not
compatible with C++.

Additionally, this moves the "implicit conversion from enum type to
different enum type" diagnostic from `-Wenum-conversion` to a new group
`-Wimplicit-enum-enum-cast`, which is a more accurate home for it.
`-Wimplicit-enum-enum-cast` is also under `-Wimplicit-int-enum-cast`, as
it is the same incompatibility (the enumeration on the right-hand is
promoted to `int`, so it's an int -> enum conversion).

Fixes llvm#37027
AaronBallman added a commit to AaronBallman/llvm-project that referenced this pull request May 6, 2025
In C++, the type of an enumerator is the type of the enumeration,
whereas in C, the type of the enumerator is 'int'. The type of a comma
operator is the type of the right-hand operand, which means you can get
an implicit conversion with this code in C but not in C++:

  enum E { Zero };
  enum E foo() {
    return ((void)0, Zero);
  }

We were previously incorrectly diagnosing this code as being
incompatible with C++ because the type of the paren expression would be
'int' there, whereas in C++ the type is 'E'.

So now we handle the comma operator with special logic when analyzing
implicit conversions in C. When analyzing the left-hand operand of a
comma operator, we do not need to check for that operand causing an
implicit conversion for the entire comma expression. So we only check
for that case with the right-hand operand.

This addresses a concern brought up post-commit:
llvm#137658 (comment)
@AaronBallman
Copy link
Collaborator Author

@AaronBallman If you try to compile the following c program after this patch

void free ();
typedef enum {REG_EESCAPE} reg_errcode_t;
typedef struct {int *stack;} compile_stack_type;
reg_errcode_t byte_regex_compile () {
  compile_stack_type compile_stack;
  return (free (compile_stack.stack), REG_EESCAPE);                                                                                                                                
}

cmd: clang -Wimplicit-int-enum-cast -c pre.i

It will give an incorrect diagnostic:

pre.i:6:37: warning: implicit conversion from 'int' to enumeration type 'reg_errcode_t' is invalid in C++ [-Wimplicit-int-enum-cast]
    6 |   return (free (compile_stack.stack), REG_EESCAPE);                                                                                                                                
      |   ~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
1 warning generated.

#138752 should address this issue, thank you for bringing it up!

@asmok-g
Copy link

asmok-g commented May 7, 2025

@AaronBallman Thanks a lot for the prompt fix!

@AaronBallman
Copy link
Collaborator Author

@AaronBallman Thanks a lot for the prompt fix!

Thank you for promptly letting me know about the problem!

AaronBallman added a commit that referenced this pull request May 7, 2025
In C++, the type of an enumerator is the type of the enumeration,
whereas in C, the type of the enumerator is 'int'. The type of a comma
operator is the type of the right-hand operand, which means you can get
an implicit conversion with this code in C but not in C++:
```
  enum E { Zero };
  enum E foo() {
    return ((void)0, Zero);
  }
```
We were previously incorrectly diagnosing this code as being
incompatible with C++ because the type of the paren expression would be
'int' there, whereas in C++ the type is 'E'.

So now we handle the comma operator with special logic when analyzing
implicit conversions in C. When analyzing the left-hand operand of a
comma operator, we do not need to check for that operand causing an
implicit conversion for the entire comma expression. So we only check
for that case with the right-hand operand.

This addresses a concern brought up post-commit:
#137658 (comment)
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request May 7, 2025
…ns (#138752)

In C++, the type of an enumerator is the type of the enumeration,
whereas in C, the type of the enumerator is 'int'. The type of a comma
operator is the type of the right-hand operand, which means you can get
an implicit conversion with this code in C but not in C++:
```
  enum E { Zero };
  enum E foo() {
    return ((void)0, Zero);
  }
```
We were previously incorrectly diagnosing this code as being
incompatible with C++ because the type of the paren expression would be
'int' there, whereas in C++ the type is 'E'.

So now we handle the comma operator with special logic when analyzing
implicit conversions in C. When analyzing the left-hand operand of a
comma operator, we do not need to check for that operand causing an
implicit conversion for the entire comma expression. So we only check
for that case with the right-hand operand.

This addresses a concern brought up post-commit:
llvm/llvm-project#137658 (comment)
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
This introduces a new diagnostic group to diagnose implicit casts from
int to an enumeration type. In C, this is valid, but it is not
compatible with C++.

Additionally, this moves the "implicit conversion from enum type to
different enum type" diagnostic from `-Wenum-conversion` to a new group
`-Wimplicit-enum-enum-cast`, which is a more accurate home for it.
`-Wimplicit-enum-enum-cast` is also under `-Wimplicit-int-enum-cast`, as
it is the same incompatibility (the enumeration on the right-hand is
promoted to `int`, so it's an int -> enum conversion).

Fixes llvm#37027
Ankur-0429 pushed a commit to Ankur-0429/llvm-project that referenced this pull request May 9, 2025
This introduces a new diagnostic group to diagnose implicit casts from
int to an enumeration type. In C, this is valid, but it is not
compatible with C++.

Additionally, this moves the "implicit conversion from enum type to
different enum type" diagnostic from `-Wenum-conversion` to a new group
`-Wimplicit-enum-enum-cast`, which is a more accurate home for it.
`-Wimplicit-enum-enum-cast` is also under `-Wimplicit-int-enum-cast`, as
it is the same incompatibility (the enumeration on the right-hand is
promoted to `int`, so it's an int -> enum conversion).

Fixes llvm#37027
@asmok-g
Copy link

asmok-g commented May 9, 2025

@AaronBallman I met another case, giving the same error even after the fix. Sorry I only noticed it now:

const char *p;
const char *pend;
...
return (free (compile_stack.stack), p == pend ? REG_EBRACE : REG_BADBR));

Both REG_EBRACE, REG_BADBR are reg_errcode_t

@AaronBallman
Copy link
Collaborator Author

@AaronBallman I met another case, giving the same error even after the fix. Sorry I only noticed it now:

const char *p;
const char *pend;
...
return (free (compile_stack.stack), p == pend ? REG_EBRACE : REG_BADBR));

Both REG_EBRACE, REG_BADBR are reg_errcode_t

Thank you! I can confirm the issue happens; I spent a few hours looking at it this morning and didn't have a solution, but ran out of time I could spend on it. Do you mind filing an issue so we don't lose track of it?

@asmok-g
Copy link

asmok-g commented May 12, 2025

Created an issue: #139536

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

-Wc++-compat should warn on implicit conversions from int to enum
4 participants