From 8bcf362e4f2d1919f7aa9f6036e9bd6abb4e2bfa Mon Sep 17 00:00:00 2001 From: said Date: Sun, 24 May 2026 05:21:51 +0100 Subject: [PATCH] codex: implement C parameter adjustment --- c_parser/parser.py | 23 ++++++++++- docs/c_parser/c_parser_architecture.md | 25 +++++++----- docs/c_parser/c_parser_cli_workflow.md | 11 ++++- .../c_parser_implementation_checklist.md | 12 ++++-- docs/c_parser/c_parser_reference.md | 22 +++++++--- .../c/test_c_declarations_and_declarators.py | 40 ++++++++++++------- tests/parser/c/test_c_functions.py | 14 +++++++ tests/parser/c/test_c_public_api_skeleton.py | 17 ++++++++ 8 files changed, 127 insertions(+), 37 deletions(-) diff --git a/c_parser/parser.py b/c_parser/parser.py index dc30e037d..41fde5d6b 100644 --- a/c_parser/parser.py +++ b/c_parser/parser.py @@ -625,6 +625,27 @@ def _build_type( ) return type_, function_specifiers + def _adjust_parameter_type(self, declared_type: CType) -> CType: + if isinstance(declared_type, CFunctionType): + return CComposedType( + components=[CPointer(), declared_type], + source_text=declared_type.source_text, + ) + if ( + isinstance(declared_type, CComposedType) + and declared_type.components + and isinstance(declared_type.components[0], CArray) + ): + outer_array = declared_type.components[0] + return CComposedType( + components=[ + CPointer(qualifiers=list(outer_array.qualifiers)), + *declared_type.components[1:], + ], + source_text=declared_type.source_text, + ) + return declared_type + def _parse_parameter(self, text: str) -> CParameter | None: stripped = text.strip() if not stripped or stripped == "void": @@ -638,7 +659,7 @@ def _parse_parameter(self, text: str) -> CParameter | None: ) return CParameter( name=name, - type=type_, + type=self._adjust_parameter_type(type_), declared_type=type_, ) diff --git a/docs/c_parser/c_parser_architecture.md b/docs/c_parser/c_parser_architecture.md index 6d9715b30..d7776026a 100644 --- a/docs/c_parser/c_parser_architecture.md +++ b/docs/c_parser/c_parser_architecture.md @@ -49,7 +49,9 @@ Implemented now: Inline tag definitions followed by aliases or objects produce concrete `CTypedef` or `CVariable` records linked to the aggregate object. Function models expose `result_type` and named `parameters`; their derived - `CFunctionType` is the nameless callable signature. Selected unsupported + `CFunctionType` is the nameless callable signature. Array and function + parameter declarations preserve their written `declared_type` and expose + pointer-adjusted effective `type` values. Selected unsupported declaration forms, including attributes, alignment specifiers, `_Atomic(type)`, nested aggregate member definitions, and static assertions, are reported as diagnostics with @@ -76,7 +78,6 @@ Deferred: - typedef/tag resolution beyond an inline aggregate declaration and callback policy metadata, for example resolving `size_t count(void);` to a prior `typedef unsigned long size_t;` -- parameter adjustment, for example `void process(int values[4]);` - nested aggregate member definitions, braced initializers, compiler attributes, alignment specifiers, and `_Atomic(type)` declarations, for example `struct outer { struct { int x; } inner; };` and @@ -212,9 +213,9 @@ Current and planned responsibilities: - `c_parser/models.py` - Implemented: typed parser models, `CParseError`, compiler-style diagnostic rendering, concrete `CType` composition, and JSON-stable - dataclass serialization. + dataclass serialization, including declared/effective parameter type facts. - Planned: resolved symbol links, richer macro/preprocessing provenance, - parameter-adjustment facts, and project indexes. + and project indexes. - `c_parser/lexer.py` - Implemented: safe comment removal that preserves line mapping, logical record folding for backslash-newline, string/character literal awareness, @@ -237,8 +238,10 @@ Current and planned responsibilities: unspecified empty parameter lists, function definitions preserve start/end locations, K&R-style definitions are rejected with `CParseError`, and invalid primitive-specifier combinations are rejected with `CPARSE003`. - - Planned: symbol resolution, parameter array/function adjustment, and - additional declaration-specifier and extension coverage. + Array and function parameters preserve `declared_type` while effective + `type` uses C parameter adjustment. + - Planned: symbol resolution and additional declaration-specifier and + extension coverage. - `c_parser/project.py` - Placeholder now. - Planned: file discovery for `.c`, `.h`, and possibly `.i`. @@ -250,7 +253,6 @@ Current and planned responsibilities: - Planned: resolve the concrete primitive/tag/typedef types constructed by the parser across declarations and files. - Resolve typedef chains and aggregate references. - - Validate or adjust parameter array/function forms. - Safely fold simple compile-time constant expressions. - `c_parser/cli.py` - Implemented: report formatting and serialization helpers called by @@ -333,8 +335,9 @@ Declaration objects are separate from the type components: function specifiers, `is_variadic`, prototype style, and source/definition locations. Its `type` property builds the corresponding nameless `CFunctionType`. -- `CParameter` has a source name, a `type`, and a reserved `declared_type` for - later C parameter adjustment handling. +- `CParameter` has a source name, written `declared_type`, and effective + `type`; outer array parameters and direct function parameters adjust to + pointer `type` values while their source form is retained. - `CInitializer` preserves initializer source text without claiming evaluation. - `CStruct` and `CUnion` expose `members` and `is_incomplete`; `CEnum` exposes `constants`; `CEnumerator` preserves enumerator name and value text. @@ -380,8 +383,8 @@ reserved for semantic type relationships such as `CVariable.type` and spellings, such as `"const"`. Reused aggregate/typedef objects serialize as references to avoid cycles. -Future parser phases can add symbol links, parameter adjustment, conditional -region metadata, include graphs, and project diagnostics when the corresponding +Future parser phases can add symbol links, conditional region metadata, +include graphs, and project diagnostics when the corresponding behavior lands. Additions should be documented and tested with stable serialization expectations. diff --git a/docs/c_parser/c_parser_cli_workflow.md b/docs/c_parser/c_parser_cli_workflow.md index 6c4a8ccae..ed0a1f384 100644 --- a/docs/c_parser/c_parser_cli_workflow.md +++ b/docs/c_parser/c_parser_cli_workflow.md @@ -6,7 +6,8 @@ macros, `#undef` provenance, metadata diagnostics, variables, typedefs, aggregate declarations, function prototypes, prototype-style metadata, and function-definition signatures with start/end locations. Declarator output can represent parenthesized pointer/array precedence through concrete -`CComposedType` components and nameless `CFunctionType` signatures. +`CComposedType` components and nameless `CFunctionType` signatures; function +parameters expose both declared and C-adjusted effective type facts. This document records the implemented C parse command shape, output schema, and diagnostic contract, plus deferred CLI behavior. @@ -33,7 +34,9 @@ can populate `functions`, `typedefs`, `variables`, `structs`, `unions`, and `enums` in the supported subset. Typedefs, variables, parameters, and aggregate members can include concrete composed types for pointer/array/function forms, including function pointers, functions returning function pointers, and -legal final flexible struct members marked with `is_flexible=True`. Raw +legal final flexible struct members marked with `is_flexible=True`. Array and +function parameters preserve written `declared_type` forms while effective +`type` values use pointer adjustment. Raw `includes`, `macros`, and metadata `diagnostics` can also be populated. The object class distinguishes declarations (`CFunction`, `CVariable`, `CTypedef`, `CStruct`, `CUnion`, or `CEnum`), and incomplete tag @@ -405,6 +408,8 @@ The active CLI/parser tests cover the current partial subset: - flexible array member classification/validation, per-member source locations, and named/unnamed/zero-width bit-field source facts are covered by focused C tests. +- array and function parameter declared/effective type adjustment is covered + by focused C tests. - `--show-vars` and `--print-limit` are rejected in C mode until C-specific display controls exist. - `--semantics` with `--language c` is rejected until C semantic conversion is @@ -444,6 +449,8 @@ Completed order: typedef-name references for later resolution. 14. Added field-level source locations, flexible array member classification and invalid-use diagnostics, plus explicit bit-field regression coverage. +15. Added C array/function parameter adjustment while preserving written + parameter type facts in `declared_type`. Next implementation work should continue with tag/typedef resolution, preprocessed-input line mapping, compiler extension policy, and project diff --git a/docs/c_parser/c_parser_implementation_checklist.md b/docs/c_parser/c_parser_implementation_checklist.md index 1016dceb4..f89538795 100644 --- a/docs/c_parser/c_parser_implementation_checklist.md +++ b/docs/c_parser/c_parser_implementation_checklist.md @@ -15,6 +15,8 @@ extensions are diagnosed, and invalid primitive-specifier combinations raise `CParseError` without treating unresolved single typedef-name uses as invalid. Aggregate members carry their own source locations, and flexible array members are classified and checked for supported struct/union constraints. +Function parameters preserve written array/function forms in `declared_type` +while exposing C-adjusted pointer forms in `type`. This checklist is intentionally detailed so future work can proceed one branch, one checklist item, and one tested capability at a time. The C parser initiative @@ -24,7 +26,7 @@ stable. ## Progress Snapshot - Last updated: 2026-05-24 -- Checklist progress: 516/848 checked (60.8%). +- Checklist progress: 517/849 checked (60.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 @@ -39,7 +41,9 @@ stable. distinguished by their concrete declaration objects rather than a kind field. Struct and union fields now preserve per-member locations; legal final flexible struct members are marked through `CArray.is_flexible`, with error - diagnostics for invalid placement or union use. + diagnostics for invalid placement or union use. Array and function + parameter declarations preserve their source form in `declared_type` while + their effective `type` applies C parameter-to-pointer adjustment. ## Global Rules @@ -729,6 +733,8 @@ Scope: - [x] Implement a helper analogous to `_helper_parse_declaration_line`. - [x] Feed procedure parameters through the same declaration backend. +- [x] Preserve array/function parameter `declared_type` while exposing the + C-adjusted pointer `type`. - [x] Feed function return types through the same declaration backend. - [x] Feed struct/union members through the same declaration backend. - [x] Feed typedefs through the same declaration backend. @@ -752,8 +758,6 @@ Scope: Known declaration implementation gaps, with representative syntax: -- parameter array/function adjustment: - `void process(int values[4], int callback(int));` - braced/designated initializer preservation: `int values[3] = {1, 2, 3};` - nested anonymous aggregate members: diff --git a/docs/c_parser/c_parser_reference.md b/docs/c_parser/c_parser_reference.md index ca53728e7..03f621059 100644 --- a/docs/c_parser/c_parser_reference.md +++ b/docs/c_parser/c_parser_reference.md @@ -73,6 +73,8 @@ Implemented: - recursive declarator extraction for parenthesized pointer/array precedence - nameless `CFunctionType` signatures for function pointer typedefs and parameter source facts +- parameter array/function adjustment that keeps written `declared_type` facts + and exposes effective pointer `type` facts - simple file-scope variable and `typedef` extraction - incomplete `struct name;` and `union name;` extraction as concrete tag types with `is_incomplete=True` @@ -95,8 +97,7 @@ Still deferred: - callback policy metadata beyond parser-side callback candidates - nested aggregate member definitions and broad compiler-extension declarators -- parameter array/function adjustment and braced/designated initializer - preservation +- braced/designated initializer preservation - cross-declaration and cross-file typedef/tag resolution - project include graph and cross-file type resolution - preprocessed-input parsing with `#line`/linemarker source mapping @@ -261,6 +262,17 @@ int add(int a, int b); # CFunction(name="add", result_type=CInt(), parameter int (*compare)(int, int); # CVariable(type=CComposedType([CPointer(), CFunctionType(...)])) ``` +Function parameters preserve both the written type and C's adjusted callable +type: + +```python +void process(int values[4], int callback(int)); +# values.declared_type: CComposedType([CArray(bound="4"), CInt()]) +# values.type: CComposedType([CPointer(), CInt()]) +# callback.declared_type: CFunctionType(...) +# callback.type: CComposedType([CPointer(), CFunctionType(...)]) +``` + Callback-bearing parameters are marked as parser-side callback candidates, without claiming semantic wrappability. Struct and union `members` are `CVariable` objects; optional `bit_width` and `initializer` fields preserve @@ -452,8 +464,9 @@ Active declaration tests currently cover: - all qualifier objects, storage metadata, simple expression initializers, and multiple declarators - pointer/array precedence, multidimensional arrays, parameter VLA/static - metadata, function pointers, callback arrays, and functions returning - function pointers + metadata and pointer adjustment, function-declared callback adjustment, + function pointers, callback arrays, and functions returning function + pointers - functions, variables, typedefs, struct/union members, enums, incomplete tags, inline aggregate aliases, anonymous aggregate typedefs, and recursive struct pointers @@ -474,7 +487,6 @@ declarations. | Capability | C example | Current parser boundary | Needed behavior | | --- | --- | --- | --- | -| Parameter adjustment | `void process(int values[4], int callback(int));` | Preserves the declared array and function parameter types; it does not expose C's adjusted pointer parameter type. | Keep `declared_type`, and expose the adjusted effective type (`int *` and pointer-to-function). | | Braced/designated initializers | `int values[3] = {1, 2, 3};` and `struct point origin = {.x = 1, .y = 2};` | Simple initializer text such as `int answer = 42;` is preserved; braced forms are not reliably emitted as `CVariable` initializer facts. | Parse or preserve balanced initializer source without treating its braces as an aggregate declaration. | | Nested aggregate members | `struct outer { struct { int x; } inner; };` | Produces an unsupported-member diagnostic and does not model `inner`. | Build an anonymous `CStruct`/`CUnion` type used by the member variable. | | Typedef/tag resolution | `typedef unsigned long size_t; size_t count(void);` and `struct state { int id; }; void step(struct state *s);` | Preserves uses as unresolved `CTypedef` or incomplete tag-type objects unless attached inline. | Link uses to declarations across a file/project and diagnose conflicts. | diff --git a/tests/parser/c/test_c_declarations_and_declarators.py b/tests/parser/c/test_c_declarations_and_declarators.py index 9336967f5..ae08db982 100644 --- a/tests/parser/c/test_c_declarations_and_declarators.py +++ b/tests/parser/c/test_c_declarations_and_declarators.py @@ -143,26 +143,38 @@ def test_pointer_qualifiers_belong_to_the_component_they_qualify(): assert dst.components[0].qualifiers == [CRestrict()] -def test_array_components_preserve_bounds_static_minimum_and_qualifiers(): - from c_parser import CArray, CComposedType, CConst, parse_c_file +def test_array_parameters_preserve_declarations_and_expose_adjusted_pointer_types(): + from c_parser import CArray, CComposedType, CConst, CDouble, CInt, CPointer, parse_c_file parsed = parse_c_file( - "void solve(size_t n, double a[static 4], const int shape[2], int work[const *]);\n", + "void solve(size_t n, double a[static 4], const int shape[2], int work[const *], int matrix[3][4]);\n", filename="arrays.h", ) params = {parameter.name: parameter for parameter in parsed.functions[0].parameters} - a = params["a"].type - shape = params["shape"].type - work = params["work"].type - assert isinstance(a, CComposedType) - assert isinstance(a.components[0], CArray) - assert a.components[0].bound == "4" - assert a.components[0].is_static_minimum is True - assert shape.components[0].bound == "2" - assert shape.components[-1].qualifiers == [CConst()] - assert work.components[0].qualifiers == [CConst()] - assert work.components[0].is_variable_length is True + a_declared = params["a"].declared_type + assert isinstance(a_declared, CComposedType) + assert [type(component) for component in a_declared.components] == [CArray, CDouble] + assert a_declared.components[0].bound == "4" + assert a_declared.components[0].is_static_minimum is True + assert [type(component) for component in params["a"].type.components] == [CPointer, CDouble] + + shape_declared = params["shape"].declared_type + assert shape_declared.components[0].bound == "2" + assert shape_declared.components[-1].qualifiers == [CConst()] + assert [type(component) for component in params["shape"].type.components] == [CPointer, CInt] + assert params["shape"].type.components[-1].qualifiers == [CConst()] + + work_declared = params["work"].declared_type + assert work_declared.components[0].qualifiers == [CConst()] + assert work_declared.components[0].is_variable_length is True + assert params["work"].type.components[0].qualifiers == [CConst()] + + matrix_declared = params["matrix"].declared_type + assert [type(component) for component in matrix_declared.components] == [CArray, CArray, CInt] + assert [component.bound for component in matrix_declared.components[:2]] == ["3", "4"] + assert [type(component) for component in params["matrix"].type.components] == [CPointer, CArray, CInt] + assert params["matrix"].type.components[1].bound == "4" def test_multiple_declarators_share_specifiers_but_have_distinct_compositions(): diff --git a/tests/parser/c/test_c_functions.py b/tests/parser/c/test_c_functions.py index 4a7c43979..ea31a4cdf 100644 --- a/tests/parser/c/test_c_functions.py +++ b/tests/parser/c/test_c_functions.py @@ -95,6 +95,20 @@ def test_function_pointer_parameter_is_a_callback_candidate_with_nameless_signat assert len(signature.parameter_types) == 2 +def test_function_parameter_preserves_declaration_and_adjusts_to_callback_pointer(): + from c_parser import CComposedType, CFunctionType, CPointer, parse_c_file + + parsed = parse_c_file("void apply(int callback(int));\n", filename="adjusted_callback.h") + + callback = parsed.functions[0].parameters[0] + assert isinstance(callback.declared_type, CFunctionType) + assert isinstance(callback.type, CComposedType) + assert [type(component) for component in callback.type.components] == [CPointer, CFunctionType] + assert callback.type.components[1] is callback.declared_type + assert callback.callback_candidate is True + assert parsed.functions[0].type.parameter_types[0] is callback.type + + @pytest.mark.skip(reason="function pointer typedef resolution is not implemented yet.") def test_callback_typedef_parameter_links_to_typedef_signature(): from c_parser import CFunctionType, CTypedef, parse_c_file diff --git a/tests/parser/c/test_c_public_api_skeleton.py b/tests/parser/c/test_c_public_api_skeleton.py index 429e89b81..cc998160b 100644 --- a/tests/parser/c/test_c_public_api_skeleton.py +++ b/tests/parser/c/test_c_public_api_skeleton.py @@ -143,6 +143,23 @@ def test_concrete_type_serialization_preserves_semantic_type_fields_and_location assert function["source_location"]["line"] == 2 +def test_parameter_adjustment_serialization_preserves_declared_and_effective_types(): + from c_parser import parse_c_file + + payload = parse_c_file( + "void process(int values[4], int callback(int));\n", + filename="adjustment.h", + ).to_dict() + values, callback = payload["functions"][0]["parameters"] + + assert values["declared_type"]["components"][0]["model"] == "CArray" + assert values["declared_type"]["components"][0]["bound"] == "4" + assert values["type"]["components"][0]["model"] == "CPointer" + assert callback["declared_type"]["model"] == "CFunctionType" + assert callback["type"]["components"][0]["model"] == "CPointer" + assert callback["type"]["components"][1]["model"] == "CFunctionType" + + def test_inline_aggregate_typedef_serialization_uses_references_without_cycles(): from c_parser import parse_c_file