Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions c_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
"alignas",
"_Atomic(",
)
_CXX_DECLARATION_KEYWORDS = {"using", "namespace", "template", "class"}
_CXX_ACCESS_SPECIFIERS = {"public", "private", "protected"}
_PRIMITIVE_WORDS = {
"void",
"char",
Expand Down Expand Up @@ -213,6 +215,20 @@ def _is_source_key(key: str) -> bool:
return PurePosixPath(key).suffix.lower() == ".c"


def _looks_like_cxx_declaration(text: str) -> bool:
stripped = text.lstrip()
identifier = _IDENTIFIER_RE.match(stripped)
if identifier is None:
return False

word = identifier.group(0)
if word in _CXX_DECLARATION_KEYWORDS:
return True
if word in _CXX_ACCESS_SPECIFIERS:
return stripped[identifier.end() :].lstrip().startswith(":")
return False


class CParser:
"""C parser entrypoint for the currently implemented C subset.

Expand Down Expand Up @@ -1423,6 +1439,72 @@ def _declarator_diagnostic(self, segment: CTopLevelSegment, message: str) -> CDi
unit_name=None,
)

def _union_by_value_names(self, type_: CType) -> set[str]:
if isinstance(type_, CUnion):
return {type_.reference_name}
if isinstance(type_, CTypedef) and type_.type is not None:
return self._union_by_value_names(type_.type)
if isinstance(type_, CFunctionType):
names = set()
names.update(self._union_by_value_names(type_.result_type))
for parameter_type in type_.parameter_types:
names.update(self._union_by_value_names(parameter_type))
return names
if isinstance(type_, CComposedType):
names = set()
protected_by_indirection = False
for component in type_.components:
if isinstance(component, (CPointer, CArray)):
protected_by_indirection = True
continue
if isinstance(component, CFunctionType):
names.update(self._union_by_value_names(component))
protected_by_indirection = False
continue
if isinstance(component, CUnion) and not protected_by_indirection:
names.add(component.reference_name)
protected_by_indirection = False
return names
return set()

def _union_by_value_diagnostics(self, function: CFunction) -> list[CDiagnostic]:
union_names = self._union_by_value_names(function.result_type)
for parameter in function.parameters:
union_names.update(self._union_by_value_names(parameter.type))
if not union_names:
return []

formatted = ", ".join(sorted(union_names))
return [
CDiagnostic(
code="C_UNION_BY_VALUE",
message=(
f"Function {function.name!r} uses union type(s) by value: {formatted}. "
"Use an explicit pointer or defer wrapper policy to the semantic layer."
),
severity="warning",
location=function.source_location,
unit_kind="function",
unit_name=function.name,
)
]

def _append_union_by_value_diagnostics(
self,
function: CFunction,
diagnostics: list[CDiagnostic],
) -> None:
for diagnostic in self._union_by_value_diagnostics(function):
already_present = any(
existing.code == diagnostic.code
and existing.unit_kind == diagnostic.unit_kind
and existing.unit_name == diagnostic.unit_name
and existing.location == diagnostic.location
for existing in diagnostics
)
if not already_present:
diagnostics.append(diagnostic)

def _field_diagnostic(
self,
segment: CTopLevelSegment,
Expand Down Expand Up @@ -1676,6 +1758,7 @@ def _parse_declaration(
or "}" in text
or text.startswith("_Static_assert")
or self._has_unsupported_declaration_marker(text)
or _looks_like_cxx_declaration(text)
):
return [], [], [], []

Expand Down Expand Up @@ -1708,6 +1791,9 @@ def _unsupported_declaration_diagnostic(self, segment: CTopLevelSegment) -> CDia
if self._is_braced_initializer_declaration(segment):
kind = "braced_initializer_declaration"
message = "Braced or designated initializer declarations are not supported yet."
elif _looks_like_cxx_declaration(text):
kind = "cxx_declaration"
message = "C++ declaration syntax is not supported by the C parser."
elif text.startswith("struct "):
kind = "struct_definition"
message = "Struct definitions are not supported yet."
Expand Down Expand Up @@ -1785,6 +1871,11 @@ def _parse_translation_unit(
)
)
continue
if _looks_like_cxx_declaration(segment.text):
unsupported = self._unsupported_declaration_diagnostic(segment)
if unsupported is not None:
diagnostics.append(unsupported)
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
Expand Down Expand Up @@ -1812,6 +1903,7 @@ def _parse_translation_unit(
continue
if function is not None:
functions.append(function)
self._append_union_by_value_diagnostics(function, diagnostics)
continue
unsupported = self._unsupported_declaration_diagnostic(segment)
if unsupported is not None:
Expand All @@ -1830,6 +1922,8 @@ def _parse_translation_unit(
functions.extend(parsed_functions)
typedefs.extend(parsed_typedefs)
variables.extend(parsed_variables)
for function in parsed_functions:
self._append_union_by_value_diagnostics(function, diagnostics)
diagnostics.extend(declarator_diagnostics)
if (
not parsed_functions
Expand Down Expand Up @@ -1877,6 +1971,8 @@ def _build_project(self, parsed_files: dict[str, CFile]) -> CProject:
function.name: function
for function in self._deduplicate_functions(all_functions, project.diagnostics)
}
for function in project.functions.values():
self._append_union_by_value_diagnostics(function, project.diagnostics)
return project

def _index_struct(
Expand Down
13 changes: 8 additions & 5 deletions docs/c_parser/c_parser_architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ Implemented now:
helpers that track braces, parentheses, brackets, literals, and
function-definition end locations.
- `c_parser.preprocessor` records raw `#include` directives, simple object-like
macros, `#undef` directives, conditional/pragma directive provenance, and
unsupported function-like macro diagnostics without expanding macros.
macros, `#undef` directives, conditional/pragma directive provenance
including OpenMP declaration pragmas, and unsupported function-like macro
diagnostics without expanding macros.
- `c_parser.parser` parses variables, typedefs, incomplete `struct`/`union`
tags, basic struct/union/enum definitions, function prototypes, and
function-definition signatures while skipping bodies. Declarator handling
Expand All @@ -46,7 +47,9 @@ Implemented now:
type path and preserve arrays, callback candidates, bit-width text, and
member-level source locations. Supported flexible final struct members set
`CArray.is_flexible=True`; invalid flexible-member placement and union use
produce `C_INVALID_FLEXIBLE_ARRAY_MEMBER` diagnostics.
produce `C_INVALID_FLEXIBLE_ARRAY_MEMBER` diagnostics. Function signatures
that use unions by value produce `C_UNION_BY_VALUE` diagnostics while
pointer-to-union signatures remain parsed normally.
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
Expand All @@ -56,8 +59,8 @@ Implemented now:
unexpanded object-like macros are deferred as macro dependencies rather than
misreported as invalid type sequences. Selected unsupported declaration
forms, including attributes, alignment specifiers,
`_Atomic(type)`, nested aggregate member definitions, and static assertions,
are reported as diagnostics with
`_Atomic(type)`, C++-shaped declarations, nested aggregate member
definitions, and static assertions, are reported as diagnostics with
explicit `unit_kind` values. A declarator must be fully consumed before a
concrete object is returned; unknown suffixes become diagnostics. Primitive
specifier order is normalized, and invalid combinations such as
Expand Down
40 changes: 22 additions & 18 deletions docs/c_parser/c_parser_implementation_checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ now parsed. Declarators use a recursive grammar-style parser for pointer,
array, function, and parenthesized combinations. Declaration types are concrete
`CType` subclasses combined by `CComposedType`; aggregate members are
`CVariable` objects using the same declared-type path. Selected unsupported
extensions are diagnosed, and invalid primitive-specifier combinations raise
`CParseError` without treating unresolved single typedef-name uses as invalid.
extensions and C++-shaped declarations 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`
Expand All @@ -36,7 +37,7 @@ stable.
## Progress Snapshot

- Last updated: 2026-05-24
- Checklist progress: 622/872 checked (71.3%).
- Checklist progress: 630/872 checked (72.2%).
- 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
Expand All @@ -51,16 +52,19 @@ 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. Array and function
parameter declarations preserve their source form in `declared_type` while
their effective `type` applies C parameter-to-pointer adjustment. Raw
conditional directives and macro-shaped declaration dependencies, including
object-like declaration prefixes, are recorded as metadata. `parse_c_project`
diagnostics for invalid placement or union use, and function signatures that
use unions by value produce conservative parser diagnostics. Array and
function parameter declarations preserve their source form in `declared_type`
while their effective `type` applies C parameter-to-pointer adjustment. Raw
conditional directives, pragmas including OpenMP declaration pragmas, and
macro-shaped declaration dependencies, including object-like declaration
prefixes, are recorded as metadata. `parse_c_project`
returns project include/index facts and
resolves basic cross-file typedef and tag references while preserving
unresolved references for later diagnostics. Top-level compatible
redeclarations are merged, matching prototypes plus definitions prefer the
definition while preserving declaration locations, and duplicate/conflicting
definition while preserving declaration locations, C++-shaped declarations
are diagnosed instead of modeled as C objects, and duplicate/conflicting
top-level declarations produce diagnostics.

## Global Rules
Expand Down Expand Up @@ -550,7 +554,7 @@ Scope:
- [x] Decide whether sets serialize as sorted lists.
- [x] Ensure dataclass defaults produce stable JSON.
- [x] Add tests for empty `CFile` serialization.
- [ ] Add tests for each model's minimal JSON shape.
- [x] Add tests for each model's minimal JSON shape.
- [x] Add tests for source-location serialization.
- [x] Add tests that unknown/unresolved metadata is preserved.

Expand Down Expand Up @@ -700,7 +704,7 @@ Scope:
- [x] Decide whether to tokenize fully now or keep logical records until
declarator parsing requires tokens.
- [x] Decide whether system headers are recorded only or optionally searched.
- [ ] Decide whether `#pragma` should become diagnostics or metadata.
- [x] Decide whether `#pragma` should become diagnostics or metadata.
- [ ] Decide whether compiler invocation belongs in Phase 4 or a later
project-resolution phase.

Expand Down Expand Up @@ -797,7 +801,7 @@ Scope:
- [x] Add tests for `struct name`, `union name`, and `enum name` references in
variables and parameters.
- [x] Add tests for multidimensional arrays.
- [ ] Add diagnostics for declarations ignored by the current partial parser.
- [x] Add diagnostics for declarations ignored by the current partial parser.
- [ ] Add structured source facts for declarations that depend on macros.

### Top-Level Redeclaration Tasks
Expand All @@ -822,7 +826,7 @@ Known declaration implementation gaps, with representative syntax:
- preprocessed declarations with line mapping:
`#define API(ret) ret` followed by `API(int) run(void);`

Represented shapes still needing dedicated active regression tests:
Represented shapes with dedicated active regression tests:

- multi-level qualifier placement:
`const int * const * volatile chain;`
Expand Down Expand Up @@ -933,9 +937,9 @@ Scope:

### Phase 6 Risks And Open Questions

- [ ] Decide whether inline functions in headers are definitions or prototypes
- [x] Decide whether inline functions in headers are definitions or prototypes
for wrapper purposes.
- [ ] Decide how to handle attributes in function declarations before full
- [x] Decide how to handle attributes in function declarations before full
extension support exists.

## Phase 7: Structs, Unions, Enums, And Typedefs
Expand Down Expand Up @@ -980,10 +984,10 @@ Scope:
- [x] Parse typedef unions.
- [x] Parse union members with shared declaration backend.
- [x] Retain union member ownership through the containing `CUnion`.
- [ ] Add diagnostics for by-value unions if unsafe.
- [x] Add diagnostics for by-value unions if unsafe.
- [x] Add tests for named unions.
- [x] Add tests for typedef unions.
- [ ] Add tests for union diagnostics.
- [x] Add tests for union diagnostics.

### Enum Tasks

Expand Down Expand Up @@ -1012,7 +1016,7 @@ Scope:
- [x] Add tests for typedef chains.
- [x] Add tests for primitive typedefs.
- [x] Add tests for opaque handle typedefs.
- [ ] Add tests for function pointer typedef diagnostics.
- [x] Add tests for function pointer typedef diagnostics.

### Phase 7 Definition Of Done

Expand Down
24 changes: 14 additions & 10 deletions docs/c_parser/c_parser_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Implemented:
- aggregate member extraction as `CVariable` objects through the declarator
backend, including pointer, array, callback-pointer, flexible-array, and
bit-field source facts with per-member locations
- conservative parser diagnostics for function signatures that use unions by
value, while pointer-to-union signatures remain ordinary parser facts
- inline tag typedef aliases and trailing tag object declarators as separate
concrete models
- simple function prototype extraction
Expand Down Expand Up @@ -167,7 +169,9 @@ Raw-source mode means source normalization plus directive metadata:
- record `#include` directives as structured include dependencies
- record simple object-like `#define` directives as macro metadata
- record `#undef` directives as macro provenance
- record conditional and pragma directives as raw provenance metadata
- record conditional and pragma directives as raw provenance metadata,
including OpenMP declaration pragmas such as `#pragma omp declare simd` and
`#pragma omp declare target`
- record function-like macros as metadata with unsupported/deferred diagnostics
- record function-like wrappers and object-like declaration prefixes as
macro-dependency metadata without claiming they were parsed
Expand Down Expand Up @@ -290,8 +294,9 @@ member in a struct is marked as `CArray(is_flexible=True)`; non-final,
sole-member, and union incomplete-array member forms are retained with
`C_INVALID_FLEXIBLE_ARRAY_MEMBER` error diagnostics.
Selected unsupported forms, such as static assertions,
attributes, alignment specifiers, `_Atomic(type)`, and nested aggregate member
definitions, are reported in `diagnostics` with explicit `unit_kind` values.
attributes, alignment specifiers, `_Atomic(type)`, C++-shaped declarations,
and nested aggregate member definitions, are reported in `diagnostics` with
explicit `unit_kind` values.
Unconsumed declarator suffixes are also diagnosed instead of producing partial
objects. Functions
include `prototype_style`, currently `"prototype"` for
Expand Down Expand Up @@ -506,8 +511,8 @@ Active declaration tests currently cover:
- concrete-type JSON serialization, source locations, and cycle-safe aggregate
references
- diagnostics for selected unsupported attributes, alignment, `_Atomic(type)`,
nested aggregate definitions, K&R definitions, and trailing declarator
extensions
C++-shaped declarations, nested aggregate definitions, K&R definitions, and
trailing declarator extensions
- fatal diagnostics for invalid primitive-specifier combinations while
unresolved single typedef-name uses remain deferred

Expand All @@ -524,18 +529,17 @@ declarations.
| Preprocessed declarations | `#define API(ret) ret` followed by `API(int) run(void);` | Raw mode records macro metadata and does not claim the expanded declaration; preprocessed input with line mapping is not implemented. | Accept compiler-expanded input and map each declaration back through `#line` markers. |
| Additional extension families | `int run(void) __attribute__((visibility("default")));` | Known attribute/alignment/`_Atomic(type)` forms are diagnosed; broader compiler extensions are not modeled. | Add fixture-driven support or a focused diagnostic for each required extension family. |

### Represented But Requiring Stronger Tests
### Represented With Focused Tests

These forms are not absent from the model, but need explicit active regression
tests before they can be treated as stable:
These forms are represented by the current parser and have dedicated active
regression tests:

```c
const int * const * volatile chain;
```

The current parser creates distinct qualified `CPointer` components for
`chain`; dedicated active regression coverage for that multi-level qualifier
shape remains to be added.
`chain`, preserving each qualifier on the exact component it qualifies.

Fixture layout should be separate from Fortran:

Expand Down
13 changes: 13 additions & 0 deletions tests/parser/c/fixtures/stb/stb_connected_components.json
Original file line number Diff line number Diff line change
Expand Up @@ -6811,6 +6811,19 @@
},
"unit_kind": "union_field",
"unit_name": null
},
{
"code": "C_UNION_BY_VALUE",
"message": "Function 'stbcc__clump_find' uses union type(s) by value: union@stb/stb_connected_components.h:229:1. Use an explicit pointer or defer wrapper policy to the semantic layer.",
"severity": "warning",
"location": {
"filename": "stb/stb_connected_components.h",
"line": 321,
"column": 1,
"source_line": "static stbcc__global_clumpid stbcc__clump_find(stbcc_grid *g, stbcc__global_clumpid n)"
},
"unit_kind": "function",
"unit_name": "stbcc__clump_find"
}
]
}
Loading
Loading