From b792818ca0171f8d142c78fe82bc6dc27dfb7121 Mon Sep 17 00:00:00 2001 From: said Date: Sun, 24 May 2026 08:00:06 +0100 Subject: [PATCH 1/3] codex: cleanup unsupported declaration diagnostics --- c_parser/models.py | 1 + c_parser/parser.py | 91 ++++++++++++++++--- .../c/test_c_declarations_and_declarators.py | 21 +++++ tests/parser/c/test_c_lexer_preprocessor.py | 27 ++++++ tests/parser/c/test_c_public_api_skeleton.py | 2 + 5 files changed, 127 insertions(+), 15 deletions(-) diff --git a/c_parser/models.py b/c_parser/models.py index 3c6ec5f1e..4a553c81d 100644 --- a/c_parser/models.py +++ b/c_parser/models.py @@ -478,6 +478,7 @@ class CMacroDependency: name: str context: str = "declaration" source_location: CSourceLocation | None = None + source_text: str = "" @dataclass diff --git a/c_parser/parser.py b/c_parser/parser.py index fe140f259..cc901a62f 100644 --- a/c_parser/parser.py +++ b/c_parser/parser.py @@ -252,21 +252,48 @@ def _macro_dependencies( return dependencies for segment in split_top_level_c_source(source, filename=filename): - text = segment.text.strip() - if not text: - continue - for macro_name in sorted(macro_names): - if re.search(rf"\b{re.escape(macro_name)}\s*\(", text): - dependencies.append( - CMacroDependency( - name=macro_name, - context="declaration", - source_location=self._source_location(segment), - ) - ) - break + dependency = self._segment_macro_dependency(segment, macro_names) + if dependency is not None: + dependencies.append(dependency) return dependencies + def _segment_macro_dependency( + self, + segment: CTopLevelSegment, + macro_names: set[str], + ) -> CMacroDependency | None: + text = segment.text.strip() + if not text: + return None + declaration_text, initializer = top_level_partition(text, "=") + scan_text = declaration_text if initializer is not None else text + for macro_name in sorted(macro_names): + if re.search(rf"\b{re.escape(macro_name)}\s*\(", scan_text): + return CMacroDependency( + name=macro_name, + context="declaration", + source_text=text, + source_location=self._source_location(segment), + ) + return None + + def _macro_dependent_declaration_diagnostic( + self, + segment: CTopLevelSegment, + dependency: CMacroDependency, + ) -> CDiagnostic: + return CDiagnostic( + code="C_MACRO_DEPENDENT_DECLARATION", + message=( + f"Declaration depends on function-like macro {dependency.name!r}; " + "provide preprocessed input to parse it." + ), + severity="warning", + location=dependency.source_location or self._source_location(segment), + unit_kind="macro_dependent_declaration", + unit_name=dependency.name, + ) + def _has_unsupported_declaration_marker(self, text: str) -> bool: return any(marker in text for marker in _UNSUPPORTED_DECLARATION_MARKERS) @@ -1632,6 +1659,18 @@ def _parse_declaration( return self._declarations_from_declarators(spec_text, declarator_list, segment) + def _is_braced_initializer_declaration(self, segment: CTopLevelSegment) -> bool: + text = segment.text.strip() + if not text: + return False + if "{" in text and "}" in text and "=" in text: + return True + if segment.terminator != "block": + return False + + declaration, initializer = top_level_partition(text, "=") + return bool(declaration) and initializer is not None + def _unsupported_declaration_diagnostic(self, segment: CTopLevelSegment) -> CDiagnostic | None: text = segment.text.strip() if not text: @@ -1640,7 +1679,10 @@ def _unsupported_declaration_diagnostic(self, segment: CTopLevelSegment) -> CDia kind = "unsupported_declaration" message = "Unsupported C declaration form." - if text.startswith("struct "): + if self._is_braced_initializer_declaration(segment): + kind = "braced_initializer_declaration" + message = "Braced or designated initializer declarations are not supported yet." + elif text.startswith("struct "): kind = "struct_definition" message = "Struct definitions are not supported yet." elif text.startswith("union "): @@ -1661,6 +1703,9 @@ def _unsupported_declaration_diagnostic(self, segment: CTopLevelSegment) -> CDia elif "_Atomic(" in text: kind = "atomic_type_declaration" message = "_Atomic(type) declarations are not supported yet." + elif "{" in text or "}" in text: + kind = "brace_declaration" + message = "Unsupported declaration containing braces." return CDiagnostic( code="C_UNSUPPORTED_DECLARATION", @@ -1675,6 +1720,8 @@ def _parse_translation_unit( self, source: str, filename: str | None, + *, + function_like_macros: set[str] | None = None, ) -> tuple[ list[CFunction], list[CStruct], @@ -1694,7 +1741,14 @@ def _parse_translation_unit( variables: list[CVariable] = [] diagnostics: list[CDiagnostic] = [] + macro_names = function_like_macros or set() for segment in split_top_level_c_source(source, filename=filename): + macro_dependency = self._segment_macro_dependency(segment, macro_names) + if macro_dependency is not None: + diagnostics.append( + self._macro_dependent_declaration_diagnostic(segment, macro_dependency) + ) + continue tag_definition = self._parse_tag_definition(segment) if tag_definition is not None: aggregate, parsed_functions, parsed_typedefs, parsed_variables, parsed_diagnostics = tag_definition @@ -1709,6 +1763,11 @@ def _parse_translation_unit( variables.extend(parsed_variables) diagnostics.extend(parsed_diagnostics) continue + if self._is_braced_initializer_declaration(segment): + unsupported = self._unsupported_declaration_diagnostic(segment) + if unsupported is not None: + diagnostics.append(unsupported) + continue if segment.terminator != ";": try: function = self._parse_function(segment) @@ -1957,15 +2016,17 @@ def visit_file( parsed.includes = metadata.includes parsed.macros = metadata.macros parsed.raw_directives = metadata.raw_directives + function_like_macro_names = {macro.name for macro in metadata.macros if macro.function_like} parsed.macro_dependencies = self._macro_dependencies( source, filename, - {macro.name for macro in metadata.macros if macro.function_like}, + function_like_macro_names, ) parsed.diagnostics = metadata.diagnostics functions, structs, unions, enums, typedefs, variables, parser_diagnostics = self._parse_translation_unit( source, filename, + function_like_macros=function_like_macro_names, ) parsed.functions = functions parsed.structs = structs diff --git a/tests/parser/c/test_c_declarations_and_declarators.py b/tests/parser/c/test_c_declarations_and_declarators.py index 25914b116..2814d8660 100644 --- a/tests/parser/c/test_c_declarations_and_declarators.py +++ b/tests/parser/c/test_c_declarations_and_declarators.py @@ -509,6 +509,27 @@ def test_unimplemented_declaration_extensions_are_diagnosed_not_partially_modele ] +def test_braced_initializer_declarations_are_diagnosed_not_declarator_failures(): + from c_parser import parse_c_file + + parsed = parse_c_file( + "struct config;\n" + "int values[3] = {1, 2, 3};\n" + "struct config cfg = {.enabled = 1};\n" + "int scalar = 1;\n", + filename="braced_initializers.h", + ) + + assert [variable.name for variable in parsed.variables] == ["scalar"] + assert [ + (diagnostic.code, diagnostic.unit_kind, diagnostic.location.line) + for diagnostic in parsed.diagnostics + ] == [ + ("C_UNSUPPORTED_DECLARATION", "braced_initializer_declaration", 2), + ("C_UNSUPPORTED_DECLARATION", "braced_initializer_declaration", 3), + ] + + def test_unconsumed_declarator_suffixes_are_diagnosed_not_silently_discarded(): from c_parser import parse_c_file diff --git a/tests/parser/c/test_c_lexer_preprocessor.py b/tests/parser/c/test_c_lexer_preprocessor.py index 8237d76e8..8cf12bd2c 100644 --- a/tests/parser/c/test_c_lexer_preprocessor.py +++ b/tests/parser/c/test_c_lexer_preprocessor.py @@ -151,6 +151,7 @@ def test_raw_mode_marks_function_like_macros_as_unsupported_until_expanded(): assert macros["API_DECL"].function_like is True assert parsed.functions == [] assert any(diag.code == "C_UNSUPPORTED_FUNCTION_LIKE_MACRO" for diag in parsed.diagnostics) + assert any(diag.code == "C_MACRO_DEPENDENT_DECLARATION" for diag in parsed.diagnostics) def test_raw_mode_fixture_keeps_macro_shaped_declaration_deferred_until_preprocessing(): @@ -213,7 +214,33 @@ def test_raw_mode_records_macro_dependency_metadata_for_macro_shaped_declaration assert [(item.name, item.context) for item in parsed.macro_dependencies] == [ ("API_DECL", "declaration") ] + assert parsed.macro_dependencies[0].source_text == "API_DECL(int) exported(void)" assert parsed.macro_dependencies[0].source_location.line == 3 + assert [(diag.code, diag.unit_kind, diag.unit_name) for diag in parsed.diagnostics] == [ + ("C_UNSUPPORTED_FUNCTION_LIKE_MACRO", "macro", "API_DECL"), + ("C_MACRO_DEPENDENT_DECLARATION", "macro_dependent_declaration", "API_DECL"), + ] + + +def test_raw_mode_macro_initializers_do_not_hide_parseable_declarations(): + from c_parser import parse_c_file + + parsed = parse_c_file( + """ +#define INIT(value) value +int answer = INIT(42); +""", + filename="macro_initializer.h", + preprocessing="raw", + ) + + assert [variable.name for variable in parsed.variables] == ["answer"] + assert parsed.variables[0].initializer is not None + assert parsed.variables[0].initializer.source_text == "INIT(42)" + assert parsed.macro_dependencies == [] + assert [diagnostic.code for diagnostic in parsed.diagnostics] == [ + "C_UNSUPPORTED_FUNCTION_LIKE_MACRO" + ] @pytest.mark.skip(reason="compiler-preprocessed mode lands after raw metadata collection.") diff --git a/tests/parser/c/test_c_public_api_skeleton.py b/tests/parser/c/test_c_public_api_skeleton.py index abf15f214..0cc8b54e9 100644 --- a/tests/parser/c/test_c_public_api_skeleton.py +++ b/tests/parser/c/test_c_public_api_skeleton.py @@ -191,9 +191,11 @@ def test_model_json_shapes_cover_directive_include_macro_and_diagnostic_fields() assert payload["macros"][0]["name"] == "API" assert payload["macros"][0]["function_like"] is True assert payload["macro_dependencies"][0]["name"] == "API" + assert payload["macro_dependencies"][0]["source_text"] == "API(int) run(void)" assert {diagnostic["code"] for diagnostic in payload["diagnostics"]} >= { "C_UNRESOLVED_INCLUDE", "C_UNSUPPORTED_FUNCTION_LIKE_MACRO", + "C_MACRO_DEPENDENT_DECLARATION", } From e95c5150a9641628cad1b8b1c180e1a9428b4519 Mon Sep 17 00:00:00 2001 From: said Date: Sun, 24 May 2026 08:02:34 +0100 Subject: [PATCH 2/3] codex: update c parser diagnostics checklist --- docs/c_parser/c_parser_implementation_checklist.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/c_parser/c_parser_implementation_checklist.md b/docs/c_parser/c_parser_implementation_checklist.md index 9982edae4..cd90b5b93 100644 --- a/docs/c_parser/c_parser_implementation_checklist.md +++ b/docs/c_parser/c_parser_implementation_checklist.md @@ -804,7 +804,7 @@ Represented shapes still needing dedicated active regression tests: - [x] Shared declaration/declarator parser exists. - [x] It is used by all declaration roles available so far. - [x] Primitive, pointer, array, typedef-name, and tag references have tests. -- [ ] Unsupported declaration-shaped input raises `CParseError` or structured +- [x] Unsupported declaration-shaped input raises `CParseError` or structured diagnostics. ### Phase 5 Risks And Open Questions @@ -1147,9 +1147,9 @@ Scope: - [ ] Report function pointer parameters. - [ ] Report callback typedef parameters. - [ ] Report missing callback `.pyi` policy. -- [ ] Report macro-dependent declarations. -- [ ] Report unsupported attributes. -- [ ] Report unsupported compiler extensions. +- [x] Report macro-dependent declarations. +- [x] Report unsupported attributes. +- [x] Report unsupported compiler extensions. - [ ] Report unsupported bitfields. - [ ] Report pointer ownership ambiguity. - [ ] Report non-const pointer mutability ambiguity. From b1d392e2848757904694d6a7e4832fadb848d8e2 Mon Sep 17 00:00:00 2001 From: said Date: Sun, 24 May 2026 08:04:06 +0100 Subject: [PATCH 3/3] codex: update c parser checklist progress --- docs/c_parser/c_parser_implementation_checklist.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/c_parser/c_parser_implementation_checklist.md b/docs/c_parser/c_parser_implementation_checklist.md index cd90b5b93..b76e24531 100644 --- a/docs/c_parser/c_parser_implementation_checklist.md +++ b/docs/c_parser/c_parser_implementation_checklist.md @@ -35,7 +35,7 @@ stable. ## Progress Snapshot - Last updated: 2026-05-24 -- Checklist progress: 586/856 checked (68.5%). +- Checklist progress: 590/856 checked (68.9%). - Current parser status: partial C parser with raw directive metadata, top-level source splitting, simple declarations/variables/typedefs, prototype-style metadata, K&R diagnostics, simple function signatures, and start/end