Separate Generated Data And Dimension Namespaces - #71
MuellerSeb wants to merge 14 commits into
Conversation
Generate dedicated public data and dimensions companion types while keeping lifecycle state and bindings on the outer namelist object. Reserve errmsg, isolate generated locals, alias PRESENT, and simplify owned arrays to one-based bounds.
Use deterministic internal names for wrapper arguments that collide with generated state or Fortran intrinsics, while preserving natural Python-facing parameter names.
Add a compiled conformance fixture for generated member collisions, flat namelist reads, transactional dimension updates, one-based storage, and arbitrary-bound setter inputs, and run it in CI.
Document the beta API break and one-based storage contract, update manual consumers to use data and dims, and regenerate committed examples and optional-reader fixtures.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved generator and wrapper compilation issues, plus validation and dimension-order defects, remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Separates generated Fortran schema data and runtime dimensions into public companion namespaces while preserving flat namelist syntax and Python-facing APIs.
Changes:
- Adds
%dataand optional%dimscompanion types with one-based storage. - Hardens symbol handling, reader state, intrinsic aliases, and f2py ABI names.
- Updates generators, tests, fixtures, examples, documentation, and CI.
File summaries
| File | Description |
|---|---|
tests/test_schema.py |
Tests schema identifier validation. |
tests/test_enum_support.py |
Updates enum generation coverage. |
tests/test_codegen_fortran.py |
Tests namespaces, collisions, and storage behavior. |
tests/test_codegen_f2py.py |
Tests f2py ABI name handling. |
tests/test_cli_config.py |
Tests dimension default naming. |
tests/fortran_optional_read/out/nml_required.f90 |
Updates generated required reader output. |
tests/fortran_optional_read/out/nml_optional.f90 |
Updates generated optional reader output. |
tests/fortran_optional_read/out/nml_helper.f90 |
Updates generated helper output. |
tests/fortran_optional_read/conformance.f90 |
Migrates optional-reader consumers. |
tests/fortran_namespaces/out/nml_namespaces.f90 |
Adds generated namespace layout. |
tests/fortran_namespaces/out/nml_helper.f90 |
Updates namespace helper output. |
tests/fortran_namespaces/nml-config.toml |
Configures namespace generation. |
tests/fortran_namespaces/namespaces.yml |
Defines namespace test schema. |
tests/fortran_namespaces/input.nml |
Provides namespace test input. |
tests/fortran_namespaces/conformance.f90 |
Exercises namespace conformance. |
tests/fortran_namespaces/CMakeLists.txt |
Builds namespace conformance tests. |
tests/fixtures/01_simple/out/nml_optimization.md |
Updates generated documentation fixture. |
tests/fixtures/01_simple/out/nml_optimization.f90 |
Updates generated module fixture. |
tests/fixtures/01_simple/out/nml_helper.f90 |
Updates generated helper fixture. |
src/nml_tools/templates/python_wrappers.py.j2 |
Preserves Python-facing wrapper names. |
src/nml_tools/templates/nml_helper.f90.j2 |
Exports intrinsic helper aliases. |
src/nml_tools/schema.py |
Validates reserved identifiers. |
src/nml_tools/codegen_f2py.py |
Mangles internal f2py identifiers. |
src/nml_tools/cli.py |
Renames dimension default symbols. |
src/nml_tools/_utils.py |
Supports identifier and dimension validation. |
README.md |
Documents namespace migration and storage behavior. |
examples/04_derived_types/fortran/generated/nml_run.f90 |
Updates generated derived-type module. |
examples/04_derived_types/fortran/generated/nml_helper.f90 |
Updates generated helper output. |
examples/04_derived_types/fortran/config_store.f90 |
Migrates native field access. |
examples/03_references/out/nml_run.f90 |
Updates generated run module. |
examples/03_references/out/nml_report.f90 |
Updates generated report module. |
examples/03_references/out/nml_helper.f90 |
Updates generated helper output. |
examples/02_pybind/fortran/generated/nml_helper.f90 |
Updates generated helper output. |
examples/02_pybind/fortran/generated/nml_config.f90 |
Updates generated configuration module. |
examples/02_pybind/fortran/config_store.f90 |
Migrates native field access. |
examples/01_simple/out/nml_helper.f90 |
Updates generated helper output. |
examples/01_simple/main.f90 |
Migrates native field access. |
.github/workflows/ci.yml |
Updates CI configuration. |
Review details
Suppressed comments (2)
src/nml_tools/codegen_fortran.py:473
runtime_dimensionsis appended only when a shape is encountered, so its order follows schema/property traversal rather than the configured[dimensions]mapping. If the config declaresn_bbeforen_abut the schema first usesn_a, the public*_dims_tlayout and positionalset_dimsargument order no longer match the documented configuration order; register or reorder used dimensions fromruntime_dimension_valuesbefore emitting these APIs.
index += 1
def _register_runtime_dimension(dim_name: str) -> str:
local_name = runtime_dimension_locals.get(dim_name)
if local_name is None:
default_name = _generated_name(dim_name, "dim_default")
if default_name.lower() in static_constants:
src/nml_tools/schema.py:285
- This validation is only reached when
_resolve_noderuns;resolve_mappingreturns the original schema early when it has no references or derived objects requiring normalization. As a result, a plain root schema with a property namederrmsgstill resolves successfully, despite the new policy reserving that name for every schema property (the added test uses a nested object and therefore misses this fast path). Validate properties in the pre-normalization identifier walk, or otherwise apply this check before the early return.
validate_namelist_identifier(name, label=f"property '{name}'")
- Files reviewed: 24/41 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Declare intrinsic procedures in a dedicated helper module so gfortran 10 and newer preserve them for renamed use association, while retaining collision-safe nml__ aliases.
73304e7 to
6cee6f6
Compare
Require an explicit generated helper path for native output, reserve dependency names, remove intrinsic forwarding, and rename internal helper procedures.
Use reserved nml__ wrapper locals so imported types such as status do not collide with operational results.
Explain the direct-intrinsic reservation set and generated helper ownership.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate generator, collision-handling, and CLI configuration issues remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
src/nml_tools/cli.py:992
- This new guard allows configurations with no
mod_path, but the laterlocal_derived_typescheck still unconditionally raises whenhelper_pathis absent. As a result, a docs/template-only schema containing a localx-fortran-typestill fails even though no Fortran source needs the helper. The helper requirement needs to be gated consistently on native Fortran output, not just this initial check.
if helper_path is None and any(
loaded.entry["mod_path"] is not None for loaded in loaded_namelists
):
raise click.ClickException("Fortran module generation requires [helper].path")
src/nml_tools/cli.py:1367
- The same output-mode mismatch remains in
gen-fortran: this new check only requires a helper when a module path is present, while the later local-derived-type validation still rejects a no-mod_pathentry. A configuration that emits no native Fortran module should not require a helper merely because its schema has a local derived type.
f2cmap_path, f2py_c_types = _load_f2py_settings(config, base_dir)
resolver = SchemaResolver()
entries = _iter_namelists(config, base_dir)
if helper_path is None and any(entry["mod_path"] is not None for entry in entries):
raise click.ClickException("Fortran module generation requires [helper].path")
src/nml_tools/codegen_fortran.py:629
- This applies
validate_namelist_identifierto every derived component, so a schema such asvalue%sizeorvalue%presentis rejected even though generated references to those components are qualified (...%size) and cannot shadow the intrinsic. The documented reservation is for identifiers emitted unqualified; validate nested components with the general identifier/errmsgrules instead, otherwise valid derived-type schemas are unnecessarily unusable.
validate_namelist_identifier(
child_display_name,
label=f"property '{child_display_name}'",
)
src/nml_tools/schema.py:501
- The recursive schema validation applies the root-property reservation set to nested derived-type components. Consequently
value%sizeandvalue%presentcannot be resolved even though generated accesses are qualified and the README scopes these intrinsic/helper reservations to names emitted unqualified. Keep the nested-component check to valid identifiers plus the intentionalerrmsg/__rules, or valid derived schemas will fail before code generation.
validate_namelist_identifier(name, label=f"property '{name}'")
- Files reviewed: 29/50 changed files
- Comments generated: 5
- Review effort level: Lite
Reserve errmsg in unqualified generated scopes, protect f2py leaf names, and keep qualified derived components usable.
There was a problem hiding this comment.
🟡 Changes recommended
Two critical generated-Fortran name-collision issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 29/50 changed files
- Comments generated: 2
- Review effort level: Lite
Normalize f2py wrapper reservations and reject imported symbols that conflict with generated procedures.
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved critical helper-symbol and moderate dimension-ordering findings, plus the broad generator/API change, require human review.
Review details
Suppressed comments (1)
src/nml_tools/codegen_fortran.py:461
runtime_dimensionsis appended when a shape is first encountered, so the public*_dims_tcomponent order and the positionalset_dimsdummy order follow schema traversal rather than the[dimensions]configuration order promised by the new layout. If the config declares{b, a}but the schema first usesa, this emitsa, band changes positional structure-constructor/setter semantics; order the used dimensions byruntime_dimension_valuesbefore rendering.
def _register_runtime_dimension(dim_name: str) -> str:
local_name = runtime_dimension_locals.get(dim_name)
if local_name is None:
default_name = _generated_name(dim_name, "dim_default")
if default_name.lower() in static_constants:
- Files reviewed: 29/50 changed files
- Comments generated: 1
- Review effort level: Lite
Reserve helper procedure names and retain configured runtime dimension order in public APIs.
There was a problem hiding this comment.
🟡 Changes recommended
Two critical compile-time namespace collision issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 29/50 changed files
- Comments generated: 2
- Review effort level: Lite
Reject kind and derived-type imports that collide in generated wrapper and module scopes.
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
src/nml_tools/codegen_f2py.py:405
c_intptr_tis used as the kind of every generated wrapper handle, but it is missing from this reserved-name set. A valid schema field or runtime dimension namedc_intptr_tis therefore emitted as a wrapper dummy with the same name; theinteger(c_intptr_t) :: nml__handledeclaration then resolves the kind name to that dummy and the generated wrapper does not compile. Reserve it so it is ABI-mangled likestatusandhandle.
"this",
src/nml_tools/codegen_fortran.py:1547
- Runtime dimensions are not checked against the generated outer type name. A dimension named
nml_run_tbecomes aset_dimsdummy while that function also declaresclass(nml_run_t), so the dummy shadows the type name and the generated module fails to compile. Include the outer type name in the runtime-dimension procedure-scope collision checks.
if runtime_dimension_values:
set_dims_name = f"{module_name}_set_dims"
if set_dims_name.lower() in runtime_dimension_values:
raise ValueError(
f"runtime dimension '{set_dims_name}' conflicts with "
f"generated set_dims procedure '{set_dims_name}'"
- Files reviewed: 29/50 changed files
- Comments generated: 5
- Review effort level: Lite
Protect wrapper ABI names and reject configured imports that shadow generated procedure scopes.
There was a problem hiding this comment.
🟡 Changes recommended
The unresolved critical f2py naming collision can prevent generated wrappers from compiling, with an additional internal naming nit outstanding.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
src/nml_tools/templates/fortran_module.f90.j2:157
- This resolver still exposes
handleandptras unprefixed generated support names, even though this change otherwise reservesnml__*for internal procedure state. Rename these to collision-safe names such asnml__handleandnml__ptr(and update the two uses below) so the generated f2py helper follows the stated naming policy consistently and cannot reintroduce unqualified support names.
subroutine {{ type_prefix }}_resolve_handle(handle, nml__obj, nml__status, errmsg)
integer(c_intptr_t), intent(in) :: handle !< opaque handle to a {{ type_name }} instance
type({{ type_name }}), pointer :: nml__obj !< resolved namelist pointer
integer, intent(out) :: nml__status !< nml-tools status code
character(len=*), intent(out), optional :: errmsg !< error message for non-OK status values
type(c_ptr) :: ptr
- Files reviewed: 29/50 changed files
- Comments generated: 1
- Review effort level: Lite
| "is_valid_wrapper", | ||
| ) | ||
| } | ||
| wrapper_reserved_names.update(wrapper_procedure_names) |
Reject wrapper-procedure type imports and namespace native handle resolution internals.
Closes #65
This merge request separates schema properties, runtime dimensions, and
generated operations into distinct namespaces in generated Fortran types.
Generated namelist objects now use public companion types:
Schemas without runtime dimensions omit the dimensions companion type and
dimscomponent.This is an intentional beta API break for native Fortran consumers. Direct
field access changes from
config%fieldtoconfig%data%field, and runtimedimension access changes from
config%n_itemstoconfig%dims%n_items. Nocompatibility aliases or temporary generation mode are provided.
Changes
<namelist>_data_tand optional<namelist>_dims_tcompanion types.
%dataand configured runtimedimensions below
%dims, while retaining lifecycle state and type-boundprocedures on the outer object.
against generated and imported module symbols.
set,from_file,is_set, andis_valid;data,dims, andis_configured; anderrmsgcase-insensitively for schema properties and runtimedimensions, with a focused diagnostic, so every public procedure can retain
the existing
errmsg=keyword.allocation, setters, derived values, presence checks, shape queries,
constraint validation, and f2py wrappers consistently address
%dataor%dims.nml__*names, including thepassed-object dummy, function results, I/O state, and generated locals.
reservation list for identifiers such as
present,size, andshapethat would shadow generated dependencies.
Fortran and f2py generation; application modules remain supported only for
imported derived types and configured kinds.
from_file(...)into its existing public type-bound wrapper and aprivate module reader helper. The helper owns schema-spelled namelist locals,
preserving flat native namelist syntax for fields named
file,status,nml,iostat, orclose_status.including schema fields and dimensions named
status,handle, or afterFortran intrinsics. Python-facing parameter names and behavior remain
unchanged.
1:size(...)extents;sections; and
1:size(value, dim)destinations.lbound(...)/ubound(...)bookkeeping.<dimension>__dim_default, preventing a same-named schema property defaultfrom colliding at module scope.
outputs, and all committed generated examples for the new component paths.
Behavior Notes
field = ...or
derived%component = ..., never asdata%field = ....is_set(...)andfilled_shape(...)lookup strings, and Python-facing APIs are unchanged.set,set_dims,from_file,is_set,filled_shape, andis_validinterfaces retain their existing behavior and public error model.Status results remain integers; this change does not introduce a status
derived type.
set_dims(...)calls remain transactional. Candidate dimensions arevalidated before either
%dimsor dependent%datastorage is changed.shape-based assignment still produces one-based generated destination
storage.
with non-one lower bounds is unsupported. Generated initialization and
dimension changes restore the supported one-based layout.
Migration
Native Fortran consumers must update direct component access:
Callers with many reads can use
associateto keep access concise:Calls to the existing type-bound procedures and Python wrappers do not require
a corresponding migration.
Tests
Added focused generator coverage for:
derived values, presence, shape, and validation paths;
and imported-symbol collisions;
errmsg;lower/upper-bound bookkeeping; and
Added a compiled Fortran namespace conformance fixture covering:
data,dims, property/dimension overlap,status,file,nml,iostat, andclose_status, with direct-intrinsic names rejected separately;set,set_dims, flatfrom_file, presence and validity queries;bounds.