Allow defaults to satisfy required values#63
Conversation
Separate declared requiredness from effective input requirements across validation, namelist evaluation, and generated APIs. Support scalar derived object defaults and broadcast item defaults with deterministic coverage and precedence rules. Update Fortran, f2py/Python, Markdown, templates, documentation, tests, and regenerated derived-type example artifacts.
There was a problem hiding this comment.
Pull request overview
This PR updates nml-tools’ requiredness model so that operational defaults can satisfy schema-declared required (including for supported derived-type defaults), and propagates that distinction through validation, namelist evaluation, Fortran/Python wrapper generation, and Markdown docs.
Changes:
- Introduces shared “declared required vs input required” analysis and uses it across validation/evaluation/codegen/docs.
- Adds supported mapping-valued defaults for derived scalars (
default) and derived arrays (items.defaultbroadcast), including constraint validation and deterministic missing-path checks. - Updates generated Fortran, f2py, Python wrappers, templates, and documentation to reflect effective input requiredness and default precedence.
Reviewed changes
Copilot reviewed 19 out of 21 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/nml_tools/validate.py |
Adds PropertyRequirement + requirement analysis; validates derived object defaults and default coverage policy. |
src/nml_tools/schema.py |
Allows/validates derived-type object default mappings (keys/duplicates/unknowns). |
src/nml_tools/_namelist_eval.py |
Uses effective input-requiredness; applies derived object/item defaults as effective component defaults during evaluation. |
src/nml_tools/codegen_fortran.py |
Shifts requiredness checks and is_set/is_valid semantics to uncovered required components; updates runtime extent checks and default layering. |
src/nml_tools/codegen_f2py.py |
Regroups f2py args by effective requiredness and introduces presence flags for newly-optional values. |
src/nml_tools/codegen_template.py |
Uses effective requirement analysis to drive minimal/filled template emission and derived fallback defaults. |
src/nml_tools/codegen_markdown.py |
Documents both “Declared required” and “Input required”, including derived component default sources. |
README.md |
Documents the operational-default policy and derived default/requiredness behavior. |
tests/test_validate.py |
Adds validation/evaluation coverage for required defaults, derived object defaults, item defaults, and missing-path behavior. |
tests/test_schema.py |
Adds schema resolution tests for use-site vs inherited derived default mappings and default mapping validation. |
tests/test_codegen_template.py |
Tests template precedence for derived object/item defaults and minimal omission rules. |
tests/test_codegen_markdown.py |
Tests docs output for declared vs input required and default source labeling. |
tests/test_codegen_fortran.py |
Tests setter optionality, derived default application, and runtime extent policies in generated Fortran. |
tests/test_codegen_f2py.py |
Tests f2py argument regrouping and presence-flag emission for newly-optional values. |
examples/04_derived_types/schema/run.yml |
Updates example schema to use derived object and item broadcast defaults. |
examples/04_derived_types/README.md |
Updates example narrative and usage to reflect new default/requiredness behavior. |
examples/04_derived_types/f2py/generated/f2py_config.f90 |
Regenerated f2py wrapper showing new presence flags (but currently has an optional-arg call-site issue). |
examples/04_derived_types/fortran/generated/nml_run.f90 |
Regenerated Fortran showing derived default layering and updated is_set/is_valid checks. |
examples/04_derived_types/docs/run.md |
Regenerated docs with declared/input required columns and derived default source details. |
examples/04_derived_types/src/nml_derived_types_example/config_wrappers.py |
Regenerated Python wrapper signature/behavior for newly-optional derived array argument. |
examples/04_derived_types/tests/test_derived_types_example.py |
Extends example end-to-end tests for partial/omitted nested mappings enabled by defaults. |
Comments suppressed due to low confidence (1)
src/nml_tools/codegen_f2py.py:519
- For derived fields,
outer_has_flagis added whenfield.requires_inputis false, and_derived_bridge_assignmentscorrectly gates allocation/init insideif (has__field) then .... Howeverset_call_arguments.append(f"{field.name}={maybe_name}")is unconditional, so the generated wrapper will still pass an unallocated/undefinedmaybe__*actual argument tothis%set(...), making itpresentand potentially causing runtime errors and incorrect semantics (it can override defaults / reset prior values). The wrapper generation needs to omit the keyword argument entirely unless the corresponding presence flag is true.
_derived_bridge_assignments(
field.name,
maybe_name,
leaves,
rank,
field.requires_input,
outer_has_flag,
dim_names,
init_allocates_array=field.runtime_sized_array,
)
)
set_call_arguments.append(f"{field.name}={maybe_name}")
continue
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if not field.requires_input: | ||
| has_base = f"has__{field.name}" | ||
| has_flag = _unique_generated_name(has_base, argument_names_in_use) | ||
| argument_names_in_use.add(has_flag.lower()) | ||
| spec = F2pyArgumentSpec( | ||
| name=field.name, | ||
| title=_one_line(field.title), | ||
| required=field.required, | ||
| required=field.requires_input, | ||
| rank=rank, | ||
| numpy_dtype=_numpy_dtype(type_info), | ||
| dummy_value=_python_dummy_value(type_info), | ||
| doc_type=_python_doc_type(type_info), | ||
| requirement="required" if field.required else "optional", | ||
| requirement="required" if field.requires_input else "optional", | ||
| has_flag=has_flag, | ||
| ) | ||
| if field.required: | ||
| if field.requires_input: | ||
| required_args.append(spec) | ||
| else: | ||
| optional_args.append(spec) |
There was a problem hiding this comment.
@copilot This is intentional Fortran optional-argument forwarding. maybe__ remains unallocated when the presence flag is false, and the corresponding this%set dummy is optional and non-allocatable. Fortran therefore considers that dummy not present despite the keyword appearing in the call. Optional derived fields and runtime-dimension bridges use the same mechanism, and the compiled example tests exercise it across the CI compiler matrix.
b3777c4 to
e10fd27
Compare
Closes #52 #49
This merge request separates schema-declared requiredness from the effective
namelist input requirement. A valid operational default now satisfies
required, including recursively covered required components of one-levelderived values.
It also adds narrow mapping-valued defaults for derived types:
default;items.defaultmay define a broadcast object fallback;Derived storage is initialized in this order:
The implementation keeps parsed/evaluated namelists sparse, does not add a
touched-state model, and preserves the existing one-level intrinsic-component
limit.
Changes
Added shared property-requirement analysis that distinguishes:
Allowed intrinsic scalar and array defaults on properties listed in
required.Added structural and value validation for derived object defaults:
while their selected values overlay component defaults.
Allowed derived
items.defaultmappings as broadcast fallbacks for everyarray element while retaining rejection of derived array-level defaults.
Enforced the conservative derived presence policy:
require input only for the uncovered components;
set after initialization, including the vacuous no-required-component case.
Updated schema-aware namelist evaluation:
Fortran element order.
Updated generated Fortran:
overlays;
missing logical initialization;
is_setandis_validinspect only uncovered requiredcomponents;
is_setcontinues to report effective leaf presence;compatibility.
Tightened runtime intrinsic-array default extent handling:
extent;
larger extents;
items.defaultcontinues to broadcast to any positive extent.Updated f2py and generated Python wrappers:
leaves reported by native validation.
Updated Markdown generation to show both
Declared requiredandInput requiredfor fields and derived components, including component,object, and item default sources.
Updated filled template precedence for derived components to:
Updated minimal templates to omit a derived field only when all components
are default-initialized, not merely when required components are covered.
Documented the operational-default policy and its intentional difference
from annotation-only JSON Schema defaults.
Extended and regenerated the committed derived-types example:
perioduses a partial object fallback;periodsuses a complete broadcast item fallback and becomes optional atthe setter boundary despite remaining declared required;
policy.
Behavior Notes
requiredremains meaningful schema metadata even when a default currentlysatisfies it. Removing that default in a later schema revision restores the
input requirement.
is valid only when component defaults already cover every required component.
declared-required derived field; explicit input must cover the remainder.
because implementing "required if touched" needs per-instance touched state.
vacuously enough set after configuration.
components. Their component-level presence queries still report not set.
place effective required parameters before optional parameters, so positional
Python ordering can change when a default removes an input requirement.
per-element derived array defaults, and touched-state tracking remain out of
scope.
Tests
Added focused coverage for:
is_set/is_validbehavior;
mappings.