mkenums: Handle G_GNUC_FLAG_ENUM annotation

It can appear either after 'enum' or after the closing brace. Also handle
__attribute__((flag_enum)) and, for C++ code, [[gnu::flag_enum]] and
[[clang::flag_enum]] and various other spellings of those. If it is
present, mark the enum as a flags enum, the same as if we detected left
shifts in the values.
This commit is contained in:
Philip Chimento
2025-11-05 00:20:20 -08:00
parent c247e1c20b
commit 1fbbf39334
2 changed files with 117 additions and 6 deletions

View File

@@ -207,9 +207,17 @@ def parse_entries(file, file_name):
else:
continue
m = re.match(r'\s*\}\s*(\w+)', line)
m = re.match(r'''\s*\}\s*
((?:
G_GNUC_FLAG_ENUM|
\[\[(?:.+,)*(?:gnu|clang)::flag_enum(?:,[^\]]+)*\]\]|
__attribute__\(\((?:.+,)*(?:flag_enum|__flag_enum__)(?:,[^)]+)*\)\)
)\s*)?
(\w+)''', line, flags=re.X)
if m:
enumname = m.group(1)
if m.group(1) is not None:
flags = 1
enumname = m.group(2)
enumindex += 1
return 1
@@ -519,7 +527,13 @@ def process_file(curfilename):
if re.match(r'\s*typedef\s+enum.*;', line):
continue
m = re.match(r'''\s*typedef\s+enum\s*[_A-Za-z]*[_A-Za-z0-9]*\s*
m = re.match(r'''\s*typedef\s+enum\s*
((?:
G_GNUC_FLAG_ENUM|
\[\[(?:.+,)*(?:gnu|clang)::flag_enum(?:,[^\]]+)*\]\]|
__attribute__\(\((?:.+,)*(?:flag_enum|__flag_enum__)(?:,[^)]+)*\)\)
)\s*)?
[_A-Za-z]*[_A-Za-z0-9]*\s*
({)?\s*
(?:/\*<
(([^*]|\*(?!/))*)
@@ -527,8 +541,10 @@ def process_file(curfilename):
\s*({)?''', line, flags=re.X)
if m:
groups = m.groups()
if len(groups) >= 2 and groups[1] is not None:
options = parse_trigraph(groups[1])
if len(groups) >= 1 and groups[0] is not None:
flags = 1
if len(groups) >= 3 and groups[2] is not None:
options = parse_trigraph(groups[2])
if 'skip' in options:
continue
enum_prefix = options.get('prefix', None)
@@ -556,7 +572,7 @@ def process_file(curfilename):
print_warning("lowercase_name is deprecated, use underscore_name")
# Didn't have trailing '{' look on next lines
if groups[0] is None and (len(groups) < 4 or groups[3] is None):
if groups[1] is None and (len(groups) < 5 or groups[4] is None):
while True:
line = curfile.readline()
if not line:

View File

@@ -710,6 +710,101 @@ comment: {standard_bottom_comment}
"4",
)
def test_flag_enum_annotation(self):
"""Test use of G_GNUC_FLAG_ENUM"""
possible_spellings = [
"G_GNUC_FLAG_ENUM",
"__attribute__((flag_enum))",
"__attribute__((__flag_enum__))",
"__attribute__((flag_enum,deprecated))",
"__attribute__((deprecated,__flag_enum__))",
"[[gnu::flag_enum]]",
"[[clang::flag_enum]]",
"[[nodiscard,gnu::flag_enum]]",
"[[clang::flag_enum,nodiscard]]",
]
for spelling in possible_spellings:
# Test attribute after closing brace
h_contents = (
"""
typedef enum {
SOME_FLAGS_ONE = (1 << 1),
} %s SomeFlags;
"""
% spelling
)
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"SomeFlags",
"some_flags",
"SOME_FLAGS",
"FLAGS",
"SOME",
"",
"flags",
"Flags",
"FLAGS",
"SOME_FLAGS_ONE",
"one",
"2",
)
# Test attribute after enum keyword, with anonymous enum
h_contents = (
"""
typedef enum %s {
SOME_FLAGS_TWO = (1 << 2),
} SomeFlags;
"""
% spelling
)
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"SomeFlags",
"some_flags",
"SOME_FLAGS",
"FLAGS",
"SOME",
"",
"flags",
"Flags",
"FLAGS",
"SOME_FLAGS_TWO",
"two",
"4",
)
# Test attribute after enum keyword, with named enum
h_contents = (
"""
typedef enum %s _SomeFlags {
SOME_FLAGS_THREE = (1 << 3),
} SomeFlags;
"""
% spelling
)
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"SomeFlags",
"some_flags",
"SOME_FLAGS",
"FLAGS",
"SOME",
"",
"flags",
"Flags",
"FLAGS",
"SOME_FLAGS_THREE",
"three",
"8",
)
def test_enum_symbolic_expression(self):
"""Test use of symbol in value expression."""
h_contents = """