From 62094a7626be211c4aae9dff70ffefe242afd32b Mon Sep 17 00:00:00 2001 From: Alex Tumanov <6143578+oleksii-tumanov@users.noreply.github.com> Date: Tue, 13 Oct 2026 03:17:09 -0500 Subject: [PATCH] fix: preserve attributed Objective-C enum members Allow clean enumerator attributes while keeping unsupported preprocessor bodies fail-closed. AI-Assistance: OpenAI Codex (cherry picked from commit 0f59982d5e1c171f916804e85a1e6ac632f6dce4) --- graphify/extractors/objc.py | 2 +- tests/test_objc_enum_declarations.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/graphify/extractors/objc.py b/graphify/extractors/objc.py index 14a0c02ec..e31541bb5 100644 --- a/graphify/extractors/objc.py +++ b/graphify/extractors/objc.py @@ -378,7 +378,7 @@ def extract_objc(path: Path) -> dict: continue # Conditional members need preprocessor handling. Do not emit a # partial enum when a clean body contains unsupported child nodes. - if any(child.type not in {"{", "}", ",", "comment", "enumerator"} + if any(child.type not in {"{", "}", ",", "comment", "enumerator", "attribute_specifier"} for child in body.children): continue diff --git a/tests/test_objc_enum_declarations.py b/tests/test_objc_enum_declarations.py index ae75ea034..169fdf162 100644 --- a/tests/test_objc_enum_declarations.py +++ b/tests/test_objc_enum_declarations.py @@ -232,3 +232,21 @@ def test_objc_macro_enum_unsupported_parts_do_not_emit_partial_enums(tmp_path, d result = extract([source], cache_root=tmp_path) assert {n["label"] for n in result["nodes"]}.isdisjoint({"Broken", "Bad", "Other"}) assert _edge_labels(result, "case_of") == {("Good", "Kept")} + + +@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS"]) +def test_objc_macro_enum_preserves_members_with_attributes(tmp_path, macro): + source = tmp_path / "Attributes.h" + source.write_text( + "#import \n" + f"typedef {macro}(NSInteger, Kind) {{\n" + " First __attribute__((deprecated)),\n" + " Second,\n" + "};\n", + encoding="utf-8", + ) + result = extract([source], cache_root=tmp_path) + assert _edge_labels(result, "case_of") == {("Kind", "First"), ("Kind", "Second")} + nodes = {n["label"]: n for n in result["nodes"]} + assert nodes["First"]["source_location"] == "L3" + assert nodes["Second"]["source_location"] == "L4"