Skip to content

[C23] More improved type compatibility for enumerations #151199

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

Open
wants to merge 2 commits into
base: release/21.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,14 @@ def note_odr_number_of_bases : Note<
"class has %0 base %plural{1:class|:classes}0">;
def note_odr_enumerator : Note<"enumerator %0 with value %1 here">;
def note_odr_missing_enumerator : Note<"no corresponding enumerator here">;
def note_odr_incompatible_fixed_underlying_type : Note<
"enumeration %0 declared with incompatible fixed underlying types (%1 vs. "
"%2)">;
def note_odr_fixed_underlying_type : Note<
"enumeration %0 has fixed underlying type here">;
def note_odr_missing_fixed_underlying_type : Note<
"enumeration %0 missing fixed underlying type here">;

def err_odr_field_type_inconsistent : Error<
"field %0 declared with incompatible types in different "
"translation units (%1 vs. %2)">;
Expand Down
42 changes: 42 additions & 0 deletions clang/lib/AST/ASTStructuralEquivalence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,48 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
!CheckStructurallyEquivalentAttributes(Context, D1, D2))
return false;

// In C23, if one enumeration has a fixed underlying type, the other shall
// have a compatible fixed underlying type (6.2.7).
if (Context.LangOpts.C23) {
if (D1->isFixed() != D2->isFixed()) {
if (Context.Complain) {
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
<< Context.ToCtx.getTypeDeclType(D2)
<< (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(D1->getLocation(),
D1->isFixed()
? diag::note_odr_fixed_underlying_type
: diag::note_odr_missing_fixed_underlying_type)
<< D1;
Context.Diag2(D2->getLocation(),
D2->isFixed()
? diag::note_odr_fixed_underlying_type
: diag::note_odr_missing_fixed_underlying_type)
<< D2;
}
return false;
}
if (D1->isFixed()) {
assert(D2->isFixed() && "enums expected to have fixed underlying types");
if (!IsStructurallyEquivalent(Context, D1->getIntegerType(),
D2->getIntegerType())) {
if (Context.Complain) {
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
<< Context.ToCtx.getTypeDeclType(D2)
<< (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(D2->getLocation(),
diag::note_odr_incompatible_fixed_underlying_type)
<< D2 << D2->getIntegerType() << D1->getIntegerType();
}
return false;
}
}
}

llvm::SmallVector<const EnumConstantDecl *, 8> D1Enums, D2Enums;
auto CopyEnumerators =
[](auto &&Range, llvm::SmallVectorImpl<const EnumConstantDecl *> &Cont) {
Expand Down
14 changes: 14 additions & 0 deletions clang/test/ASTMerge/enum/Inputs/enum3.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// [C23] missing underlying types
enum E1 : int {
E1Enumerator1
};

enum E2 {
E2Enumerator1
};

// [C23] Incompatible underlying types
enum E3 : long {
E3Enumerator1
};

14 changes: 14 additions & 0 deletions clang/test/ASTMerge/enum/Inputs/enum4.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// [C23] missing underlying types
enum E1 {
E1Enumerator1
};

enum E2 : int {
E2Enumerator1
};

// [C23] Incompatible underlying types
enum E3 : short {
E3Enumerator1
};

16 changes: 16 additions & 0 deletions clang/test/ASTMerge/enum/test2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// RUN: %clang_cc1 -std=c23 -emit-pch -o %t.1.ast %S/Inputs/enum3.c
// RUN: %clang_cc1 -std=c23 -emit-pch -o %t.2.ast %S/Inputs/enum4.c
// RUN: not %clang_cc1 -std=c23 -ast-merge %t.1.ast -ast-merge %t.2.ast -fsyntax-only %s 2>&1 | FileCheck %s

// FIXME: this error should not happen!
// CHECK: error: type 'struct __NSConstantString_tag' has an attribute which currently causes the types to be treated as though they are incompatible
// CHECK: enum3.c:2:6: warning: type 'enum E1' has incompatible definitions in different translation units
// CHECK: enum4.c:2:6: note: enumeration 'E1' missing fixed underlying type here
// CHECK: enum3.c:2:6: note: enumeration 'E1' has fixed underlying type here
// CHECK: enum3.c:6:6: warning: type 'enum E2' has incompatible definitions in different translation units
// CHECK: enum4.c:6:6: note: enumeration 'E2' has fixed underlying type here
// CHECK: enum3.c:6:6: note: enumeration 'E2' missing fixed underlying type here
// CHECK: enum3.c:11:6: warning: type 'enum E3' has incompatible definitions in different translation units
// CHECK: enum3.c:11:6: note: enumeration 'E3' declared with incompatible fixed underlying types ('long' vs. 'short')
// CHECK: 3 warnings and 1 error generated

38 changes: 38 additions & 0 deletions clang/test/C/C23/n3037.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,44 @@ _Static_assert(0 == _Generic(inner_anon_tagged.untagged, struct { int i; } : 1,
enum { E_Untagged1 } nontag_enum; // both-note {{previous definition is here}}
_Static_assert(0 == _Generic(nontag_enum, enum { E_Untagged1 } : 1, default : 0)); // both-error {{redefinition of enumerator 'E_Untagged1'}}

// Test that enumerations with mixed underlying types are properly handled.
enum GH150594_E1 : int { GH150594_Val1 };
enum GH150594_E2 : int { GH150594_Val2 };
enum GH150594_E3 { GH150594_Val3 };
enum GH150594_E4 : int { GH150594_Val4 };
void GH150594(void) {
extern enum GH150594_E1 Fn1(void); // both-note {{previous declaration is here}}
extern enum GH150594_E2 Fn2(void); // c17-note {{previous declaration is here}}
extern enum GH150594_E3 Fn3(void); // both-note {{previous declaration is here}}
extern enum GH150594_E4 Fn4(void); // both-note {{previous declaration is here}}
enum GH150594_E1 { GH150594_Val1 };
enum GH150594_E2 : int { GH150594_Val2 };
enum GH150594_E3 : int { GH150594_Val3 };
enum GH150594_E4 : short { GH150594_Val4 };
extern enum GH150594_E1 Fn1(void); // both-error {{conflicting types for 'Fn1'}}
extern enum GH150594_E2 Fn2(void); // c17-error {{conflicting types for 'Fn2'}}
extern enum GH150594_E3 Fn3(void); // both-error {{conflicting types for 'Fn3'}}
extern enum GH150594_E4 Fn4(void); // both-error {{conflicting types for 'Fn4'}}

// Show that two declarations in the same scope give expected diagnostics.
enum E1 { e1 }; // both-note {{previous declaration is here}}
enum E1 : int { e1 }; // both-error {{enumeration previously declared with nonfixed underlying type}}

enum E2 : int { e2 }; // both-note {{previous declaration is here}}
enum E2 { e2 }; // both-error {{enumeration previously declared with fixed underlying type}}

enum E3 : int { e3 }; // both-note {{previous declaration is here}}
enum E3 : short { e3 }; // both-error {{enumeration redeclared with different underlying type 'short' (was 'int')}}

typedef short foo;
enum E4 : foo { e4 }; // c17-note 2 {{previous definition is here}}
enum E4 : short { e4 }; // c17-error {{redefinition of 'E4'}} \
c17-error {{redefinition of enumerator 'e4'}}

enum E5 : foo { e5 }; // both-note {{previous declaration is here}}
enum E5 : int { e5 }; // both-error {{enumeration redeclared with different underlying type 'int' (was 'foo' (aka 'short'))}}
}

// Test that enumerations are compatible with their underlying type, but still
// diagnose when "same type" is required rather than merely "compatible type".
enum E1 : int { e1 }; // Fixed underlying type
Expand Down
Loading