feat(objc): extract NS_CLOSED_ENUM declarations as enums (#4401)

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit c0d1001005)
This commit is contained in:
Jason Daniel authored and safishamsi committed 2026-10-23 16:11:20 +01:00
1 parent fedc4e82ed
commit 8a29ab62cb
2 files changed
+7 -6

No files matched your search

+4 -2
View File
@@ -295,7 +295,9 @@ def extract_objc(path: Path) -> dict:
accepted = []
for macro in descendants(root, "macro_type_specifier"):
macro_name = macro.child_by_field_name("name")
if macro_name is None or _read(macro_name) not in ("NS_ENUM", "NS_OPTIONS"):
if macro_name is None or _read(macro_name) not in (
"NS_ENUM", "NS_OPTIONS", "NS_CLOSED_ENUM"
):
continue
# The closest preceding structural token in an enclosing node must
@@ -622,7 +624,7 @@ def extract_objc(path: Path) -> dict:
# `typedef enum { ... } Name;` / `typedef enum Tag { ... } Name;`.
# The enum body sits on a child enum_specifier that carries no usable
# name; the name callers reference is the typedef alias (the trailing
# type_identifier). NS_ENUM/NS_OPTIONS are handled separately through
# type_identifier). NS_ENUM/NS_OPTIONS/NS_CLOSED_ENUM are handled separately through
# their structural macro headers and isolated enum body fragments.
enum_spec = next((c for c in node.children if c.type == "enum_specifier"), None)
if enum_spec is not None and any(
+3 -4
View File
@@ -6,7 +6,7 @@ definitions were dropped entirely — the type and all of its enumerators were
invisible. Every other language with enums emits a type node plus a `case_of`
edge per member (Java #1719, C++ #3939, Swift); ObjC now matches.
NS_ENUM and NS_OPTIONS typedefs use the same graph representation.
NS_ENUM, NS_OPTIONS and NS_CLOSED_ENUM typedefs use the same graph representation.
"""
from __future__ import annotations
@@ -86,7 +86,7 @@ def test_objc_anonymous_and_bodyless_enums_mint_no_phantom(tmp_path):
@pytest.mark.parametrize("suffix", [".h", ".m", ".mm"])
@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS"])
@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS", "NS_CLOSED_ENUM"])
def test_objc_macro_enum_types_and_members_survive_cache(tmp_path, suffix, macro):
source = tmp_path / ("Window" + suffix)
source.write_text(
@@ -164,7 +164,6 @@ def test_objc_macro_enum_preserves_original_utf8_member_lines(tmp_path, newline)
"typedef OTHER_ENUM(NSInteger, Broken) { Bad };",
"typedef MY_NS_ENUM(NSInteger, Broken) { Bad };",
"typedef CF_ENUM(NSInteger, Broken) { Bad };",
"typedef NS_CLOSED_ENUM(NSInteger, Broken) { Bad };",
"NS_ENUM(NSInteger, Broken) { Bad };",
"typedef NS_ENUM(NSInteger, ) { Bad };",
"typedef NS_ENUM(NSInteger, Broken, Extra) { Bad };",
@@ -234,7 +233,7 @@ def test_objc_macro_enum_unsupported_parts_do_not_emit_partial_enums(tmp_path, d
assert _edge_labels(result, "case_of") == {("Good", "Kept")}
@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS"])
@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS", "NS_CLOSED_ENUM"])
def test_objc_macro_enum_preserves_members_with_attributes(tmp_path, macro):
source = tmp_path / "Attributes.h"
source.write_text(