From 1fbbf39334bfd12733e83a115b87c3ae0e3f2e3e Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Wed, 5 Nov 2025 00:20:20 -0800 Subject: [PATCH] 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. --- gobject/glib-mkenums.in | 28 +++++++++--- gobject/tests/mkenums.py | 95 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/gobject/glib-mkenums.in b/gobject/glib-mkenums.in index 7e794e96d..f38871467 100755 --- a/gobject/glib-mkenums.in +++ b/gobject/glib-mkenums.in @@ -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: diff --git a/gobject/tests/mkenums.py b/gobject/tests/mkenums.py index 98004bef8..86dd7b260 100644 --- a/gobject/tests/mkenums.py +++ b/gobject/tests/mkenums.py @@ -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 = """