From 479ab07d4d91b06667ca58b1e78f7773e74d52ac Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 12:34:30 +0100 Subject: [PATCH 01/22] seperate python surface generation from binding.py and implement the dispatch in native C --- CHANGELOG.md | 6 + docs/developer/feature-to-code-map.md | 2 +- docs/developer/source-map.md | 4 +- .../internal-architecture/pipeline-map.md | 2 +- .../wrapper-generation-pipeline.md | 10 + prik/codegen/__init__.py | 4 + prik/codegen/c/binding.py | 1201 ++-- prik/codegen/c/naming.py | 140 + prik/codegen/c/python_surface.py | 539 ++ prik/codegen/generator.py | 12 + prik/codegen/nodes.py | 26 +- prik/codegen/overloads.py | 27 + prik/codegen/plan.py | 6 +- prik/codegen/planner.py | 1 + prik/codegen/printers/source_printers.py | 17 + .../codegen/test_overload_dispatch_plan.py | 57 +- .../end_to_end/test_generic_interfaces.py | 5 +- .../fixtures/refactoring_goldens/binding.c | 4316 +++++++++++ .../fixtures/refactoring_goldens/bridge.f90 | 2322 ++++++ .../refactoring_goldens/contract.pyi.golden | 136 + .../native/refactoring_goldens.f90 | 174 + .../fixtures/refactoring_goldens/parser.json | 6391 +++++++++++++++++ .../codegen/test_refactoring_goldens.py | 120 + 23 files changed, 14825 insertions(+), 693 deletions(-) create mode 100644 prik/codegen/c/naming.py create mode 100644 prik/codegen/c/python_surface.py create mode 100644 prik/codegen/overloads.py create mode 100644 tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/binding.c create mode 100644 tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/bridge.f90 create mode 100644 tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/contract.pyi.golden create mode 100644 tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/native/refactoring_goldens.f90 create mode 100644 tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/parser.json create mode 100644 tests/fortran/infrastructure/codegen/test_refactoring_goldens.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e661ffa8..ddbaeefce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ release tags add a leading `v` to the package version. - Added Zenodo version and concept DOI links to the citation metadata, README, and About page. +### Changed + +- Moved exact overload selection from generated Python predicate chains to + generated C dispatchers with planned candidate IDs and direct switch-based + calls to the selected existing wrapper. + ## 0.2.1 — 2026-08-11 ### Added diff --git a/docs/developer/feature-to-code-map.md b/docs/developer/feature-to-code-map.md index 9f7fd66b9..944595f5d 100644 --- a/docs/developer/feature-to-code-map.md +++ b/docs/developer/feature-to-code-map.md @@ -32,7 +32,7 @@ before documentation may call the behavior supported. | C parse output | `docs/developer/c-parser-reference.md`, `docs/user/examples/recipes/inspect-c-api.md` | `prik/parsers/c/parser.py`, `models.py`, `lexer.py`, `type_resolver.py` | `tests/c/parsing/test_c_declarations_and_declarators.py`, `tests/c/parsing/test_c_fixture_suite.py` | Parser facts and diagnostics match fixtures | | Semantic IR | `docs/user/reference/semantic-ir.md` | `prik/semantics/models.py`, `fortran2ir.py`, `c2ir.py` | `tests/fortran/semantic_ir/semantics/`, `tests/c/semantics/conversion/` | Source facts lower without losing wrapper-relevant meaning | | Generated Fortran bridge | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/fortran/bridge.py`, `prik/codegen/printers/source_printers.py` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | Generated bridge compiles and preserves native calling contract | -| Generated CPython binding | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/c/binding.py`, `prik/codegen/printers/source_printers.py` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | Extension imports, validates Python inputs, and returns documented values | +| Generated CPython binding | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/codegen/printers/source_printers.py` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | Extension imports, validates Python inputs, dispatches overloads in C, and installs the derived-class Python facade | | Public API exports | `README.md`, `docs/user/reference/python-api.md` | `prik/__init__.py` | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py`, C public API tests | Import paths are intentional and documented | PRIK_C_DOCS_END --> diff --git a/docs/developer/source-map.md b/docs/developer/source-map.md index 76c864557..710b6a813 100644 --- a/docs/developer/source-map.md +++ b/docs/developer/source-map.md @@ -64,7 +64,7 @@ PRIK_C_DOCS_END --> | C parser facts and diagnostics | `prik/parsers/c/parser.py` | `docs/developer/c-parser-reference.md`, `docs/user/examples/recipes/inspect-c-api.md` | `tests/c/fixtures/parser/`, `tests/c/semantics/conversion/` | | Semantic IR shape and cross-stage metadata | `prik/semantics/models.py`, `prik/semantics/metadata.py`, `prik/semantics/fortran2ir.py`, `prik/semantics/c2ir.py` | `docs/user/reference/semantic-ir.md` | `tests/fortran/semantic_ir/semantics/`, `tests/c/semantics/conversion/` | | Generated Fortran bridge | `prik/codegen/fortran/bridge.py`, `prik/codegen/printers/source_printers.py` | `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` generated artifact assertions | -| Generated CPython binding and Python-visible runtime behavior | `prik/codegen/c/binding.py`, `prik/codegen/printers/source_printers.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/reference/python-api.md` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | +| Generated CPython binding and Python-visible runtime behavior | `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/codegen/c/naming.py`, `prik/codegen/printers/source_printers.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/reference/python-api.md` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | PRIK_C_DOCS_END --> ## Package Map @@ -119,6 +119,8 @@ update this table, the package README files, and the mechanical checks in | `prik/codegen/generator.py` | Ordered direct bridge, binding, header, and source generation. | | `prik/codegen/fortran/bridge.py` | Direct Fortran bridge lowering from typed plans. | | `prik/codegen/c/binding.py` | Direct Python-extension binding lowering from typed plans. | +| `prik/codegen/c/python_surface.py` | Executable derived-class facade and thin class-overload forwarding source. | +| `prik/codegen/c/naming.py` | Shared symbols referenced by generated C and the embedded Python facade. | | `prik/codegen/printers/source_printers.py` | Native binding, header, and Fortran source printing. | | `prik/codegen/printers/pyi_printer.py` | Semantic `.pyi` printing. | | `prik/compiling/objects.py` | Native compile object model. | diff --git a/docs/maintainer/internal-architecture/pipeline-map.md b/docs/maintainer/internal-architecture/pipeline-map.md index 120c3551b..384e78c2d 100644 --- a/docs/maintainer/internal-architecture/pipeline-map.md +++ b/docs/maintainer/internal-architecture/pipeline-map.md @@ -45,7 +45,7 @@ PRIK_C_DOCS_END --> | Semantic IR | `prik/semantics/fortran2ir.py` | parser project and target facts | `SemanticModule` objects | semantic Fortran tests | | Semantic policy completion | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py` | full semantic modules with signatures and `.pyi` overrides | semantic modules annotated with every ownership, transfer, destruction, mutability, storage, accessor, and projection decision needed by wrapper generation | ownership-policy tests | | Wrapper planning | `prik/codegen/planner.py`, `prik/codegen/plan.py` | policy-completed semantic modules | typed wrapper plans consuming completed decisions without a separate support-analysis traversal | `tests/fortran/infrastructure/codegen/`, wrapper tests | -| Direct bridge and binding lowering | `prik/codegen/fortran/bridge.py`, `prik/codegen/c/binding.py`, `prik/codegen/generator.py` | validated typed wrapper plans | Fortran, C, and header syntax nodes | `tests/fortran/infrastructure/codegen/`, wrapper tests | +| Direct bridge and binding lowering | `prik/codegen/fortran/bridge.py`, `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/codegen/generator.py` | validated typed wrapper plans | Fortran, C, header syntax nodes, and the embedded derived-class facade | `tests/fortran/infrastructure/codegen/`, wrapper tests | | Wrapper and semantic-contract printing | `prik/codegen/printers/` | wrapper syntax nodes or semantic IR | wrapper source files or semantic `.pyi` text | printer, generated-contract, and wrapper artifact tests | | Compile and link | `prik/compiling/`, `prik/pipeline/build.py` | dependency-batched native objects, generated bridge and binding objects, compiler-process limit, and ordered link inputs | shared library | wrapper runtime and build-mode tests | diff --git a/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md b/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md index 5d4bb331f..7668493db 100644 --- a/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md +++ b/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md @@ -121,6 +121,16 @@ owning plan nodes. C method-table emission and generated Python class assembly only attach that text; neither backend infers signatures, ownership, mutation, nullability, or exception behavior while rendering source. +`OverloadPlan` stores candidates, exact argument-match records, receiver +conventions, and one unique integer candidate ID per overload set. The C +binding binds the supplied call shape, evaluates those completed predicates in +candidate order, and then switches on the selected ID to call the existing +candidate wrapper. This preserves first-match behavior for overlapping +optional domains without speculative native calls. Module generics are +installed directly in the C method table. `PythonSurfaceEmitter` owns only the +executable derived-class facade; overloaded methods and constructors emitted +there are thin receiver-forwarding descriptors over private C dispatchers. + `NativeCallSlotPlan` and `LifecycleActionPlan` are subordinate transfer details. Native slots stay indexed on `FunctionPlan` because native ABI order can interleave argument slots, result slots, literals, and helpers. Lifecycle diff --git a/prik/codegen/__init__.py b/prik/codegen/__init__.py index 78a1fb74b..0e245c52b 100644 --- a/prik/codegen/__init__.py +++ b/prik/codegen/__init__.py @@ -9,6 +9,7 @@ BackendScalarType, CAllowThreadsBegin, CAllowThreadsEnd, + CCase, CDeclaration, CExpressionStatement, CFunction, @@ -25,6 +26,7 @@ CModulePropertySupport, CParameter, CReturn, + CSwitch, CodeExpression, FortranAllocate, FortranAssignment, @@ -104,6 +106,7 @@ "CAllowThreadsBegin", "CAllowThreadsEnd", "CBindingGenerator", + "CCase", "CDeclaration", "CExpressionStatement", "CFunction", @@ -121,6 +124,7 @@ "CParameter", "CReturn", "CSourcePrinter", + "CSwitch", "ClassVisitor", "CodeExpression", "DatatypeFamily", diff --git a/prik/codegen/c/binding.py b/prik/codegen/c/binding.py index 0fefd394e..48e78bfb6 100644 --- a/prik/codegen/c/binding.py +++ b/prik/codegen/c/binding.py @@ -26,8 +26,6 @@ CallbackResultAction, CallbackTransferAction, ClassConstructorKind, - ClassMethodKind, - OverloadMatchKind, DerivedActualAccess, DerivedCallAction, DerivedDummyCategory, @@ -45,16 +43,19 @@ NativeArrayOperation, NativeDescriptorHandoffABI, OptionalMode, + OverloadMatchKind, PythonExceptionKind, TransformationAction, WritebackPhase, - overload_builtin_scalar_family, ) +from prik.codegen.c.naming import CBindingNames +from prik.codegen.c.python_surface import PythonSurfaceContext, PythonSurfaceEmitter from prik.types.numpy import is_boolean_semantic_type_name from prik.codegen.nodes import ( CAllowThreadsBegin, CAllowThreadsEnd, CBreak, + CCase, CComment, CDeclaration, CExpressionStatement, @@ -75,17 +76,16 @@ CParameter, CReturn, CStructDefinition, + CSwitch, CodeExpression, ) from prik.codegen.naming import NativeSymbolNames +from prik.codegen.overloads import OverloadPlanQueries from prik.codegen.plan import ( ArrayHandoffPlan, ArgumentTransferPlan, CallbackHandoffPlan, CallbackTransferPlan, - ClassMethodPlan, - OverloadArgumentMatchPlan, - OverloadPlan, ClassSurfacePlan, DatatypeFamily, DerivedFieldPlan, @@ -100,6 +100,8 @@ NativeArrayActualPlan, NativeArrayHandlePlan, NativeCallSlotPlan, + OverloadArgumentMatchPlan, + OverloadPlan, ResultPlan, ) from prik.codegen.primitive_scalar_types import PrimitiveScalarTypeRegistry @@ -145,6 +147,15 @@ class _CFunctionContext: role_values: dict[str, str] +@dataclass(frozen=True) +class _COverloadDispatch: + """Describe one namespace-installed C overload dispatcher.""" + + overload: OverloadPlan + receiver: bool + public: bool + + class CBindingGenerator(ClassVisitor): """Build the CPython C half of a wrapper from validated binding-plan views. @@ -237,8 +248,8 @@ def binding_module(self, plan: ModulePlan) -> CModule: then assembles module support, runtime helpers, wrappers, and module initialization in emitted dependency order. """ - # Stage 1: cache names that the generated class-property surface shares. - self._class_python_names = { + # Stage 1: complete the immutable name index consumed by Python-surface emission. + class_python_names = { surface.type_identity: surface.python_names[0] for namespace in plan.namespaces for surface in namespace.classes @@ -265,6 +276,7 @@ def binding_module(self, plan: ModulePlan) -> CModule: *self._derived_handle_operation_functions(plan), *self._native_array_operation_functions(plan), *functions, + *self._overload_dispatch_functions(plan, class_python_names), self._module_init(plan, needs_native_support), ), ) @@ -1965,7 +1977,7 @@ def _derived_origin_needs_guard(self, variable: ModuleVariablePlan) -> bool: @staticmethod def _derived_origin_symbol(variable: ModuleVariablePlan) -> str: """Return the binding-local derived origin symbol derived from the supplied completed binding records; this helper preserves completed policy.""" - return NativeSymbolNames.compact(variable.owner_path, variable.symbol_name) + return CBindingNames.derived_origin_symbol(variable) def _derived_origin_bridge_name(self, variable: ModuleVariablePlan, operation: str) -> str: """Return the binding-local derived origin bridge name derived from the supplied completed binding records; this helper preserves completed policy.""" @@ -1989,7 +2001,7 @@ def _derived_origin_poisoned_name(self, variable: ModuleVariablePlan) -> str: def _derived_origin_capsule_method_name(self, variable: ModuleVariablePlan) -> str: """Return the binding-local derived origin capsule method name derived from the supplied completed binding records; this helper preserves completed policy.""" - return f"_prik_origin_{self._derived_origin_symbol(variable)}_native_ops" + return CBindingNames.derived_origin_capsule_method(variable) def _module_declarations( self, @@ -2031,6 +2043,7 @@ def _module_declarations( *self._default_native_array_bridge_prototypes(plan), *self._derived_field_bridge_prototypes(plan), *self._derived_private_method_prototypes(plan), + *self._overload_dispatch_prototypes(plan), *self._derived_handle_operation_declarations(plan), *self._derived_module_owner_declarations(plan), *self._module_variable_declarations(plan), @@ -2038,6 +2051,19 @@ def _module_declarations( *self._namespace_declarations(plan), ) + def _overload_dispatch_prototypes(self, plan: ModulePlan) -> tuple[CFunctionPrototype, ...]: + """Declare namespace dispatchers before generated method tables use them.""" + return tuple( + CFunctionPrototype( + CBindingNames.overload_dispatch_function(dispatch.overload), + "PyObject *", + self._binding_parameters(), + "static", + ) + for namespace in plan.namespaces + for dispatch in self._namespace_overload_dispatches(namespace) + ) + def _module_variable_declarations(self, plan: ModulePlan) -> tuple[CFunctionPrototype, ...]: """Return bridge and binding helper declarations for module state.""" variables = self._variables(plan) @@ -2222,9 +2248,9 @@ def _class_constructor_prototypes(self, plan: ModulePlan) -> tuple[CFunctionProt for surface in namespace.classes if surface.constructor.kind is not ClassConstructorKind.ABSENT for prototype in ( - CFunctionPrototype(self._class_create_bridge_name(surface), "void *"), + CFunctionPrototype(CBindingNames.class_create_bridge(surface), "void *"), CFunctionPrototype( - self._class_create_method_name(surface), + CBindingNames.class_create_method(surface), "PyObject *", (CParameter("self", "PyObject *"), CParameter("args", "PyObject *")), "static", @@ -2254,7 +2280,7 @@ def _class_constructor_function( result = "result" destroy = self._derived_destroy_bridge_name(derived.backend_symbol) return CFunction( - self._class_create_method_name(surface), + CBindingNames.class_create_method(surface), "PyObject *", parameters=(CParameter("self", "PyObject *"), CParameter("args", "PyObject *")), storage="static", @@ -2263,7 +2289,7 @@ def _class_constructor_function( CodeExpression('!PyArg_ParseTuple(args, "")'), body=(CReturn(CodeExpression("NULL")),), ), - CDeclaration(address, "void *", CodeExpression(f"{self._class_create_bridge_name(surface)}()")), + CDeclaration(address, "void *", CodeExpression(f"{CBindingNames.class_create_bridge(surface)}()")), CIf( CodeExpression(f"{address} == NULL"), body=( @@ -2289,7 +2315,7 @@ def _class_constructor_function( CDeclaration( helper, "PyObject *", - CodeExpression(f'PyObject_GetAttrString(self, "{self._class_wrap_helper_name(surface)}")'), + CodeExpression(f'PyObject_GetAttrString(self, "{CBindingNames.class_wrap_helper(surface)}")'), ), CIf( CodeExpression(f"{helper} == NULL"), @@ -10933,21 +10959,21 @@ def _allocatable_holder_presence_bridge_name(type_name: str) -> str: @staticmethod def _allocatable_holder_presence_method_name(type_name: str) -> str: """Return the binding-local allocatable holder presence method name derived from the supplied local lowering values; this helper preserves completed policy.""" - return f"_prik_{type_name.casefold()}_allocatable_holder_require_present" + return CBindingNames.allocatable_holder_presence_method(type_name) @staticmethod def _pointer_holder_presence_method_name(type_name: str) -> str: """Return the binding-local pointer holder presence method name derived from the supplied local lowering values; this helper preserves completed policy.""" - return f"_prik_{type_name.casefold()}_pointer_holder_require_present" + return CBindingNames.pointer_holder_presence_method(type_name) @staticmethod def _derived_field_symbol(derived: DerivedTypePlan, field: DerivedFieldPlan) -> str: """Return the binding-local derived field symbol derived from the supplied completed binding records; this helper preserves completed policy.""" - return f"{derived.backend_symbol}_{field.name}".casefold() + return CBindingNames.derived_field_symbol(derived, field) def _derived_field_method_name(self, derived: DerivedTypePlan, field: DerivedFieldPlan, action: str) -> str: """Return the binding-local derived field method name derived from the supplied completed binding records; this helper preserves completed policy.""" - return f"_prik_field_{self._derived_field_symbol(derived, field)}_{action}" + return CBindingNames.derived_field_method(derived, field, action) def _derived_field_bridge_name(self, derived: DerivedTypePlan, field: DerivedFieldPlan, action: str) -> str: """Return the binding-local derived field bridge name derived from the supplied completed binding records; this helper preserves completed policy.""" @@ -10969,7 +10995,7 @@ def _allocatable_holder_field_method_name( action: str, ) -> str: """Return the binding-local allocatable holder field method name derived from the supplied completed binding records; this helper preserves completed policy.""" - return f"_prik_allocatable_holder_field_{self._derived_field_symbol(derived, field)}_{action}" + return CBindingNames.allocatable_holder_field_method(derived, field, action) def _pointer_holder_field_bridge_name( self, @@ -10987,17 +11013,17 @@ def _pointer_holder_field_method_name( action: str, ) -> str: """Return the binding-local pointer holder field method name derived from the supplied completed binding records; this helper preserves completed policy.""" - return f"_prik_pointer_holder_field_{self._derived_field_symbol(derived, field)}_{action}" + return CBindingNames.pointer_holder_field_method(derived, field, action) @staticmethod def _allocatable_holder_ops_name(type_name: str) -> str: """Return the binding-local allocatable holder ops name derived from the supplied local lowering values; this helper preserves completed policy.""" - return f"_prik_ops_{type_name.casefold()}_allocatable_holder" + return CBindingNames.allocatable_holder_ops(type_name) @staticmethod def _pointer_holder_ops_name(type_name: str) -> str: """Return the binding-local pointer holder ops name derived from the supplied local lowering values; this helper preserves completed policy.""" - return f"_prik_ops_{type_name.casefold()}_pointer_holder" + return CBindingNames.pointer_holder_ops(type_name) def _derived_field_descriptor_callback_name( self, @@ -11044,7 +11070,7 @@ def _derived_handle_actual_callback_name( @staticmethod def _module_member_symbol(variable: ModuleVariablePlan, member: DerivedMemberPathPlan) -> str: """Return the binding-local module member symbol derived from the supplied completed binding records; this helper preserves completed policy.""" - return "_".join((variable.symbol_name, *member.path)).casefold() + return CBindingNames.module_member_symbol(variable, member) def _module_member_method_name( self, @@ -11053,7 +11079,7 @@ def _module_member_method_name( action: str, ) -> str: """Return the binding-local module member method name derived from the supplied completed binding records; this helper preserves completed policy.""" - return f"_prik_module_field_{self._module_member_symbol(variable, member)}_{action}" + return CBindingNames.module_member_method(variable, member, action) def _module_member_bridge_name( self, @@ -11109,8 +11135,7 @@ def _module_member_handle_actual_callback_name( @staticmethod def _module_member_ops_name(variable: ModuleVariablePlan, prefix: tuple[str, ...]) -> str: """Return the binding-local module member ops name derived from the supplied completed binding records; this helper preserves completed policy.""" - suffix = "_".join((variable.symbol_name, *prefix)).casefold() - return f"_prik_ops_{suffix}" + return CBindingNames.module_member_ops(variable, prefix) def _derived_member_proxy_variables(self, plan: ModulePlan) -> tuple[ModuleVariablePlan, ...]: """Return plain derived module objects with typed member operations.""" @@ -11155,10 +11180,11 @@ def _method_table(self, module: ModulePlan, namespace: NamespacePlan) -> CMethod ) for function in namespace.functions ), + *self._overload_method_entries(namespace), *( CMethodDefEntry( - self._class_create_method_name(surface), - self._class_create_method_name(surface), + CBindingNames.class_create_method(surface), + CBindingNames.class_create_method(surface), "METH_VARARGS", "", ) @@ -11169,6 +11195,438 @@ def _method_table(self, module: ModulePlan, namespace: NamespacePlan) -> CMethod ), ) + def _overload_method_entries(self, namespace: NamespacePlan) -> tuple[CMethodDefEntry, ...]: + """Install public module dispatchers and private class dispatchers.""" + return tuple( + CMethodDefEntry( + dispatch.overload.python_name + if dispatch.public + else CBindingNames.overload_dispatch_method(dispatch.overload), + CBindingNames.overload_dispatch_function(dispatch.overload), + "METH_VARARGS | METH_KEYWORDS", + dispatch.overload.docstring if dispatch.public else "", + ) + for dispatch in self._namespace_overload_dispatches(namespace) + ) + + @staticmethod + def _namespace_overload_dispatches(namespace: NamespacePlan) -> tuple[_COverloadDispatch, ...]: + """Return every distinct overload surface installed in one namespace.""" + dispatches = [_COverloadDispatch(overload, receiver=False, public=True) for overload in namespace.overloads] + seen = {id(overload) for overload in namespace.overloads} + for surface in namespace.classes: + constructor = surface.constructor.overload + if constructor is not None and id(constructor) not in seen: + dispatches.append(_COverloadDispatch(constructor, receiver=True, public=False)) + seen.add(id(constructor)) + for overload in surface.overloads: + if id(overload) in seen: + continue + receiver = bool(overload.candidate_passed_objects and overload.candidate_passed_objects[0]) + dispatches.append(_COverloadDispatch(overload, receiver=receiver, public=False)) + seen.add(id(overload)) + return tuple(dispatches) + + def _overload_dispatch_functions( + self, + plan: ModulePlan, + class_python_names: dict[tuple[str, str], str], + ) -> tuple[CFunction, ...]: + """Lower every completed overload surface into one C dispatcher.""" + return tuple( + self._overload_dispatch_function(dispatch, class_python_names) + for namespace in plan.namespaces + for dispatch in self._namespace_overload_dispatches(namespace) + ) + + def _overload_dispatch_function( + self, + dispatch: _COverloadDispatch, + class_python_names: dict[tuple[str, str], str], + ) -> CFunction: + """Classify one call, assign a candidate ID, and switch to its wrapper.""" + overload = dispatch.overload + positional_offset = 1 if dispatch.receiver else 0 + body = [ + CDeclaration("nargs", "Py_ssize_t", CodeExpression("PyTuple_GET_SIZE(args)")), + ] + if dispatch.receiver: + body.extend(self._overload_receiver_nodes(overload)) + else: + body.append(CDeclaration("user_nargs", "Py_ssize_t", CodeExpression("nargs"))) + body.append(CDeclaration("candidate_id", "int", CodeExpression("-1"))) + body.extend(self._overload_special_case_nodes(overload, dispatch.receiver)) + body.extend( + CIf( + CodeExpression( + "candidate_id < 0 && (" + + self._overload_candidate_condition( + matches, + positional_offset=positional_offset, + class_python_names=class_python_names, + ) + + ")" + ), + body=(CExpressionStatement(CodeExpression(f"candidate_id = {candidate_id}")),), + ) + for candidate_id, matches in zip( + overload.candidate_ids, + overload.candidate_matches, + strict=True, + ) + ) + cases = tuple( + self._overload_candidate_case( + dispatch, + candidate_id, + candidate, + matches, + positional_offset=positional_offset, + ) + for candidate_id, candidate, matches in zip( + overload.candidate_ids, + overload.candidates, + overload.candidate_matches, + strict=True, + ) + ) + body.append( + CSwitch( + CodeExpression("candidate_id"), + cases=(*cases, self._overload_default_case(overload)), + ) + ) + return CFunction( + CBindingNames.overload_dispatch_function(overload), + "PyObject *", + parameters=self._binding_parameters(), + body=tuple(body), + storage="static", + ) + + def _overload_receiver_nodes(self, overload: OverloadPlan) -> tuple[CIf | CDeclaration, ...]: + """Extract the class receiver inserted by the generated Python method.""" + message = self._c_string_literal(f"no matching overload for {overload.python_name}") + return ( + CIf( + CodeExpression("nargs < 1"), + body=( + CExpressionStatement(CodeExpression(f"PyErr_SetString(PyExc_TypeError, {message})")), + CReturn(CodeExpression("NULL")), + ), + ), + CDeclaration("receiver", "PyObject *", CodeExpression("PyTuple_GET_ITEM(args, 0)")), + CDeclaration("user_nargs", "Py_ssize_t", CodeExpression("nargs - 1")), + ) + + def _overload_special_case_nodes( + self, + overload: OverloadPlan, + has_receiver: bool, + ) -> tuple[CIf, ...]: + """Preserve planned early errors and reflected identity behavior.""" + nodes = [] + if overload.unsupported_extra_argument_message is not None: + message = self._c_string_literal(overload.unsupported_extra_argument_message) + nodes.append( + CIf( + CodeExpression("user_nargs > 1"), + body=( + CExpressionStatement(CodeExpression(f"PyErr_SetString(PyExc_TypeError, {message})")), + CReturn(CodeExpression("NULL")), + ), + ) + ) + if overload.identity_receiver_shortcut and has_receiver: + nodes.append( + CIf( + CodeExpression( + "user_nargs == 1 && (kwargs == NULL || PyDict_Size(kwargs) == 0) " + "&& PyTuple_GET_ITEM(args, 1) == receiver" + ), + body=( + CExpressionStatement(CodeExpression("Py_INCREF(receiver)")), + CReturn(CodeExpression("receiver")), + ), + ) + ) + return tuple(nodes) + + def _overload_candidate_condition( + self, + matches: tuple[OverloadArgumentMatchPlan, ...], + *, + positional_offset: int, + class_python_names: dict[tuple[str, str], str], + ) -> str: + """Return one ordered candidate predicate over borrowed call arguments.""" + shape = self._overload_call_shape_condition(matches) + predicates = tuple( + self._overload_argument_condition( + match, + self._overload_argument_value_expression(match, index, positional_offset), + class_python_names, + ) + for index, match in enumerate(matches) + ) + return " && ".join((shape, *predicates)) + + def _overload_call_shape_condition(self, matches: tuple[OverloadArgumentMatchPlan, ...]) -> str: + """Validate keyword membership and positional-keyword exclusivity.""" + keyword_hits = ( + " + ".join( + f"(PyDict_GetItemString(kwargs, {self._c_string_literal(match.python_name)}) != NULL)" + for match in matches + ) + or "0" + ) + duplicates = ( + " && ".join( + "(user_nargs <= " + f"{index} || PyDict_GetItemString(kwargs, {self._c_string_literal(match.python_name)}) == NULL)" + for index, match in enumerate(matches) + ) + or "1" + ) + keyword_shape = f"PyDict_Size(kwargs) == ({keyword_hits}) && {duplicates}" + return f"user_nargs <= {len(matches)} && (kwargs == NULL || ({keyword_shape}))" + + def _overload_argument_value_expression( + self, + match: OverloadArgumentMatchPlan, + index: int, + positional_offset: int, + ) -> str: + """Return a borrowed value from its candidate-specific canonical position.""" + name = self._c_string_literal(match.python_name) + return ( + f"(user_nargs > {index} ? PyTuple_GET_ITEM(args, {index + positional_offset}) " + f": (kwargs != NULL ? PyDict_GetItemString(kwargs, {name}) : NULL))" + ) + + def _overload_argument_condition( + self, + match: OverloadArgumentMatchPlan, + value: str, + class_python_names: dict[tuple[str, str], str], + ) -> str: + """Wrap one exact C predicate with its required or optional presence rule.""" + predicate = self._overload_required_argument_condition(match, value, class_python_names) + if match.optional: + return f"({value} == NULL || ({predicate}))" + return f"({value} != NULL && ({predicate}))" + + def _overload_required_argument_condition( + self, + match: OverloadArgumentMatchPlan, + value: str, + class_python_names: dict[tuple[str, str], str], + ) -> str: + """Return the C-API predicate for one completed overload match kind.""" + if match.kind is OverloadMatchKind.DERIVED: + if match.derived_type_identity is None: + raise ValueError(f"Derived overload argument {match.python_name!r} has no type identity") + class_name = self._c_string_literal(class_python_names[match.derived_type_identity]) + expected = f"PyDict_GetItemString(PyModule_GetDict(self), {class_name})" + return f"{expected} != NULL && (PyObject *)Py_TYPE({value}) == {expected}" + if match.kind is OverloadMatchKind.NUMPY_ARRAY: + numpy_type = PrimitiveScalarTypeRegistry.type_for(match.semantic_type_name).numpy_type_macro + return ( + f"PyArray_Check({value}) && PyArray_NDIM((PyArrayObject *){value}) == {match.rank} " + f"&& PyArray_TYPE((PyArrayObject *){value}) == {numpy_type}" + ) + if match.kind is OverloadMatchKind.STRING: + return f"PyUnicode_Check({value})" + if match.kind is OverloadMatchKind.NUMPY_SCALAR: + predicate = f"PyArray_IsScalar({value}, {self._overload_numpy_scalar_kind(match.semantic_type_name)})" + if match.accept_builtin_scalar: + predicate = f"({predicate} || {self._overload_builtin_scalar_condition(match, value)})" + return predicate + raise ValueError(f"Unsupported overload match kind: {match.kind.value}") + + @staticmethod + def _overload_numpy_scalar_kind(semantic_type_name: str) -> str: + """Return the NumPy scalar macro suffix used by exact C dispatch.""" + if is_boolean_semantic_type_name(semantic_type_name): + return "Bool" + kinds = { + "Int8": "Int8", + "Int16": "Int16", + "Int32": "Int", + "Int64": "Int64", + "Float32": "Float", + "Float64": "Double", + "Complex64": "CFloat", + "Complex128": "CDouble", + } + try: + return kinds[semantic_type_name] + except KeyError as exc: + raise ValueError(f"Unsupported NumPy overload scalar {semantic_type_name!r}") from exc + + @staticmethod + def _overload_builtin_scalar_condition(match: OverloadArgumentMatchPlan, value: str) -> str: + """Return the exact builtin predicate allowed for reflected dispatch.""" + if is_boolean_semantic_type_name(match.semantic_type_name): + return f"PyBool_Check({value})" + if match.semantic_type_name.startswith("Int"): + return f"PyLong_CheckExact({value})" + if match.semantic_type_name.startswith("Float"): + return f"PyFloat_CheckExact({value})" + if match.semantic_type_name.startswith("Complex"): + return f"PyComplex_CheckExact({value})" + raise ValueError(f"Unsupported reflected overload scalar {match.semantic_type_name!r}") + + def _overload_candidate_case( + self, + dispatch: _COverloadDispatch, + candidate_id: int, + candidate: FunctionPlan, + matches: tuple[OverloadArgumentMatchPlan, ...], + *, + positional_offset: int, + ) -> CCase: + """Build one switch leaf that calls the selected existing C wrapper.""" + body = [ + CDeclaration("candidate_kwargs", "PyObject *", CodeExpression("PyDict_New()")), + CIf(CodeExpression("candidate_kwargs == NULL"), body=(CReturn(CodeExpression("NULL")),)), + ] + for index, match in enumerate(matches): + body.extend( + self._overload_candidate_keyword_nodes( + match, + index, + positional_offset=positional_offset, + ) + ) + if dispatch.receiver: + receiver_name = OverloadPlanQueries.receiver_name(candidate) + body.extend(self._overload_set_keyword_nodes(receiver_name, "receiver")) + body.extend( + ( + CDeclaration("candidate_args", "PyObject *", CodeExpression("PyTuple_New(0)")), + CIf( + CodeExpression("candidate_args == NULL"), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(candidate_kwargs)")), + CReturn(CodeExpression("NULL")), + ), + ), + CDeclaration( + "candidate_result", + "PyObject *", + CodeExpression(f"{self._binding_function_name(candidate)}(self, candidate_args, candidate_kwargs)"), + ), + CExpressionStatement(CodeExpression("Py_DECREF(candidate_args)")), + CExpressionStatement(CodeExpression("Py_DECREF(candidate_kwargs)")), + CReturn(CodeExpression("candidate_result")), + ) + ) + return CCase(CodeExpression(str(candidate_id)), body=tuple(body)) + + def _overload_candidate_keyword_nodes( + self, + match: OverloadArgumentMatchPlan, + index: int, + *, + positional_offset: int, + ) -> tuple[CDeclaration | CIf | CExpressionStatement, ...]: + """Copy one matched borrowed argument into the selected candidate call.""" + value_name = f"candidate_value_{index}" + nodes = [ + CDeclaration( + value_name, + "PyObject *", + CodeExpression(self._overload_argument_value_expression(match, index, positional_offset)), + ) + ] + coerced_name = None + if match.accept_builtin_scalar: + coerced_name = f"candidate_coerced_{index}" + nodes.append(CDeclaration(coerced_name, "PyObject *", CodeExpression("NULL"))) + nodes.append(self._overload_builtin_coercion_node(match, value_name, coerced_name)) + set_nodes = self._overload_set_keyword_nodes(match.python_name, value_name, coerced_name=coerced_name) + if match.optional: + nodes.append(CIf(CodeExpression(f"{value_name} != NULL"), body=set_nodes)) + else: + nodes.extend(set_nodes) + return tuple(nodes) + + def _overload_builtin_coercion_node( + self, + match: OverloadArgumentMatchPlan, + value_name: str, + coerced_name: str, + ) -> CIf: + """Convert one accepted builtin to the exact NumPy scalar expected downstream.""" + builtin = self._overload_builtin_scalar_condition(match, value_name) + numpy_type = PrimitiveScalarTypeRegistry.type_for(match.semantic_type_name).numpy_type_macro + type_name = f"candidate_scalar_type_{coerced_name.rsplit('_', 1)[-1]}" + return CIf( + CodeExpression(f"{value_name} != NULL && {builtin}"), + body=( + CDeclaration( + type_name, + "PyObject *", + CodeExpression(f"(PyObject *)PyArray_TypeObjectFromType({numpy_type})"), + ), + CIf( + CodeExpression(f"{type_name} == NULL"), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(candidate_kwargs)")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement( + CodeExpression(f"{coerced_name} = PyObject_CallOneArg({type_name}, {value_name})") + ), + CIf( + CodeExpression(f"{coerced_name} == NULL"), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(candidate_kwargs)")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement(CodeExpression(f"{value_name} = {coerced_name}")), + ), + ) + + def _overload_set_keyword_nodes( + self, + name: str, + value_name: str, + *, + coerced_name: str | None = None, + ) -> tuple[CIf | CExpressionStatement, ...]: + """Set one candidate keyword and release any temporary scalar conversion.""" + cleanup = ( + *((CExpressionStatement(CodeExpression(f"Py_XDECREF({coerced_name})")),) if coerced_name else ()), + CExpressionStatement(CodeExpression("Py_DECREF(candidate_kwargs)")), + CReturn(CodeExpression("NULL")), + ) + nodes = [ + CIf( + CodeExpression( + f"PyDict_SetItemString(candidate_kwargs, {self._c_string_literal(name)}, {value_name}) < 0" + ), + body=cleanup, + ) + ] + if coerced_name is not None: + nodes.append(CExpressionStatement(CodeExpression(f"Py_XDECREF({coerced_name})"))) + return tuple(nodes) + + def _overload_default_case(self, overload: OverloadPlan) -> CCase: + """Raise the stable public error when no planned candidate matches.""" + message = self._c_string_literal(f"no matching overload for {overload.python_name}") + return CCase( + None, + body=( + CExpressionStatement(CodeExpression(f"PyErr_SetString(PyExc_TypeError, {message})")), + CReturn(CodeExpression("NULL")), + ), + ) + def _derived_private_method_entries(self, namespace: NamespacePlan) -> tuple[CMethodDefEntry, ...]: """Expose private field callables used by generated Python properties.""" names = ( @@ -11328,7 +11786,11 @@ def _module_def(self, module: ModulePlan, namespace: NamespacePlan) -> CModuleDe f"{owner}_{symbol}_methods", ) - def _module_init(self, plan: ModulePlan, needs_native_support: bool) -> CFunction: + def _module_init( + self, + plan: ModulePlan, + needs_native_support: bool, + ) -> CFunction: """Return module init from the supplied completed binding records; this helper preserves the selected binding behavior.""" module_name = plan.binding.owner_path root_namespace = self._namespace(plan, ()) @@ -11344,7 +11806,11 @@ def _module_init(self, plan: ModulePlan, needs_native_support: bool) -> CFunctio CodeExpression(f"PyModule_Create(&{module_name}_{self._namespace_symbol(root_namespace)}_module)"), ), CExpressionStatement(CodeExpression("if (mod == NULL) return NULL")), - *self._namespace_configuration_nodes(plan, root_namespace, "mod"), + *self._namespace_configuration_nodes( + plan, + root_namespace, + "mod", + ), *(node for namespace in child_namespaces for node in self._child_namespace_nodes(plan, namespace)), *( node @@ -11383,7 +11849,11 @@ def _child_namespace_nodes( f"{{ Py_DECREF({object_name}); Py_DECREF(mod); return NULL; }}" ) ), - *self._namespace_configuration_nodes(module, namespace, object_name), + *self._namespace_configuration_nodes( + module, + namespace, + object_name, + ), ) def _child_namespace_import_registration_nodes( @@ -11424,7 +11894,10 @@ def _namespace_configuration_nodes( ) return ( *property_nodes, - *self._namespace_python_initializer_nodes(namespace, object_name), + *self._namespace_python_initializer_nodes( + namespace, + object_name, + ), *self._module_native_array_owner_nodes(namespace, object_name), *self._derived_module_owner_nodes(namespace, object_name), *self._module_initializer_nodes(namespace), @@ -11438,9 +11911,16 @@ def _namespace_python_initializer_nodes( ) -> tuple[CDeclaration | CExpressionStatement | CIf, ...]: """Install exact overload dispatch plus generated opaque wrapper types.""" has_proxy = any(variable.derived is not None for variable in namespace.variables) - if not namespace.derived_types and not has_proxy and not namespace.overloads: + if not namespace.derived_types and not has_proxy: return () - source = self._namespace_python_source(namespace) + context = PythonSurfaceContext( + allocatable_holder_identities=self._namespace_allocatable_holder_identities(namespace), + pointer_holder_identities=self._namespace_pointer_holder_identities(namespace), + nullable_module_proxy_owner_paths=frozenset( + variable.owner_path for variable in namespace.variables if self._nullable_derived_module_proxy(variable) + ), + ) + source = PythonSurfaceEmitter(context).emit(namespace) literal = self._c_string_literal(source) result_name = f"{self._namespace_symbol(namespace)}_python_setup" dictionary = f"{self._namespace_symbol(namespace)}_python_dict" @@ -11456,655 +11936,6 @@ def _namespace_python_initializer_nodes( CExpressionStatement(CodeExpression(f"Py_DECREF({result_name})")), ) - def _namespace_python_source(self, namespace: NamespacePlan) -> str: - """Return overloads, opaque classes, and typed member operation maps.""" - surfaces = {surface.type_identity: surface for surface in namespace.classes} - class_names = { - surface.type_identity: surface.python_names[0] for surface in namespace.classes if surface.python_names - } - ops_names = {derived.type_identity: self._direct_type_ops_name(derived) for derived in namespace.derived_types} - sections = [ - "_prik_unset = object()", - "import numpy as _prik_numpy", - *(self._module_overload_python_source(overload) for overload in namespace.overloads), - *( - self._derived_type_python_source( - derived, - surfaces.get(derived.type_identity), - class_names, - ops_names, - ) - for derived in namespace.derived_types - ), - ] - sections.extend(self._holder_ops_python_sources(namespace)) - sections.extend(self._module_proxy_ops_python_sources(namespace)) - return "\n\n".join(section for section in sections if section) - - def _holder_ops_python_sources(self, namespace: NamespacePlan) -> tuple[str, ...]: - """Render allocatable and pointer holder operation maps by completed identity.""" - allocatable = self._namespace_allocatable_holder_identities(namespace) - pointer = self._namespace_pointer_holder_identities(namespace) - return ( - *( - self._allocatable_holder_ops_python_source(derived) - for derived in namespace.derived_types - if derived.type_identity in allocatable - ), - *( - self._pointer_holder_ops_python_source(derived) - for derived in namespace.derived_types - if derived.type_identity in pointer - ), - ) - - def _module_proxy_ops_python_sources(self, namespace: NamespacePlan) -> tuple[str, ...]: - """Render persistent module-derived operation maps in declaration order.""" - return tuple( - self._module_proxy_ops_python_source(variable) - for variable in namespace.variables - if variable.derived is not None - ) - - def _derived_type_python_source( - self, - derived: DerivedTypePlan, - surface: ClassSurfacePlan | None, - class_names: dict[tuple[str, str], str], - ops_names: dict[tuple[str, str], str], - ) -> str: - """Return one opaque wrapper assembled from its completed class surface.""" - name = derived.python_names[0] - ops_name = self._direct_type_ops_name(derived) - base = self._class_base_name(surface, class_names) - base_ops = ops_names[surface.base_identities[0]] if surface is not None and surface.base_identities else None - slots = "()" if base else "('_prik_capsule', '_prik_owner', '_prik_ops', '_prik_origin')" - own_ops = self._direct_type_ops_literal(derived) - combined_ops = f"{{**{base_ops}, **{own_ops}}}" if base_ops is not None else own_ops - lines = [ - f"{ops_name} = {combined_ops}", - f"class {name}{f'({base})' if base else ''}:", - f" {surface.docstring!r}" if surface is not None else f" {name!r}", - f" __slots__ = {slots}", - ] - lines.extend(self._class_constructor_python_lines(surface)) - lines.extend(self._derived_class_member_python_lines(derived, surface)) - lines.extend(self._class_wrap_helper_python_lines(surface, name, ops_name)) - return "\n".join(lines) - - def _derived_class_member_python_lines( - self, - derived: DerivedTypePlan, - surface: ClassSurfacePlan | None, - ) -> tuple[str, ...]: - """Render fields, public methods, and overload descriptors for one class.""" - methods = () if surface is None else tuple(method for method in surface.methods if method.public) - overloads = () if surface is None else surface.overloads - return ( - *self._derived_property_python_source_lines(derived.fields), - *self._class_method_python_source_lines(methods), - *self._class_overload_python_source_lines(overloads), - ) - - def _derived_property_python_source_lines(self, fields: tuple[DerivedFieldPlan, ...]) -> tuple[str, ...]: - """Flatten field descriptors while preserving declaration order.""" - return tuple(line for field in fields for line in self._derived_property_python_lines(field)) - - def _class_method_python_source_lines(self, methods: tuple[ClassMethodPlan, ...]) -> tuple[str, ...]: - """Flatten public method descriptors while preserving plan order.""" - return tuple(line for method in methods for line in self._class_method_python_lines(method)) - - def _class_overload_python_source_lines(self, overloads: tuple[OverloadPlan, ...]) -> tuple[str, ...]: - """Flatten overload descriptors while preserving plan order.""" - return tuple(line for overload in overloads for line in self._class_overload_python_lines(overload)) - - def _class_wrap_helper_python_lines( - self, - surface: ClassSurfacePlan | None, - name: str, - ops_name: str, - ) -> tuple[str, ...]: - """Render the sole helper that attaches existing opaque native storage.""" - return ( - f"def {self._class_wrap_helper_name(surface, fallback=name)}(capsule, owner=None, ops=None, origin='direct'):", - f" value = object.__new__({name})", - " value._prik_capsule = capsule", - " value._prik_owner = owner", - f" value._prik_ops = {ops_name} if ops is None else ops", - " value._prik_origin = origin", - " return value", - ) - - def _class_constructor_python_lines(self, surface: ClassSurfacePlan | None) -> tuple[str, ...]: - """Render one constructor selected entirely by the class plan.""" - if surface is None or surface.constructor.kind is ClassConstructorKind.ABSENT: - return self._absent_constructor_python_lines(surface) - handlers = { - ClassConstructorKind.DEFAULT_FIELDS: self._default_constructor_python_lines, - ClassConstructorKind.BOUND_PROCEDURE: self._bound_constructor_python_lines, - ClassConstructorKind.OVERLOAD_SET: self._overloaded_constructor_python_lines, - } - handler = handlers.get(surface.constructor.kind) - if handler is None: - raise ValueError(f"Unsupported completed constructor kind: {surface.constructor.kind.value}") - return handler(surface) - - @staticmethod - def _absent_constructor_python_lines(surface: ClassSurfacePlan | None) -> tuple[str, ...]: - """Render one explicit rejection for a nonconstructible wrapper class.""" - message = ( - surface.constructor.rejection_message - if surface is not None and surface.constructor.rejection_message - else "native wrapper construction is disabled" - ) - return ( - " def __new__(cls, *args, **kwargs):", - f" {surface.constructor.docstring!r}" if surface is not None else " 'Construction disabled.'", - f" raise TypeError({message!r})", - ) - - def _default_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[str, ...]: - """Allocate one owner, then apply only explicitly supplied field values.""" - fields = surface.constructor.fields - parameters = ", ".join(f"{field.name}=_prik_unset" for field in fields) - signature = f", *, {parameters}" if parameters else "" - lines = [ - " def __new__(cls, *args, **kwargs):", - f" return {self._class_create_method_name(surface)}()", - f" def __init__(self{signature}):", - f" {surface.constructor.docstring!r}", - ] - if not fields: - lines.append(" pass") - for field in fields: - lines.extend( - ( - f" if {field.name} is not _prik_unset:", - f" self.{field.name} = {field.name}", - ) - ) - return tuple(lines) - - def _bound_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[str, ...]: - """Call one validated target after allocating the persistent owner.""" - target = surface.constructor.target - if target is None: - raise ValueError(f"Bound constructor {surface.owner_path!r} has no target function") - parameters = self._callable_public_arguments(target) - lines = [ - " def __new__(cls, *args, **kwargs):", - f" return {self._class_create_method_name(surface)}()", - f" def __init__(self{self._python_parameter_suffix(parameters)}):", - f" {surface.constructor.docstring!r}", - " _prik_arguments = {'self': self}", - ] - lines.extend(self._optional_keyword_collection_lines(parameters, indent=" ")) - lines.append(f" {target.binding.python_name}(**_prik_arguments)") - return tuple(lines) - - def _overloaded_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[str, ...]: - """Dispatch one completed constructor overload after owner allocation.""" - overload = surface.constructor.overload - if overload is None: - raise ValueError(f"Overloaded constructor {surface.owner_path!r} has no overload plan") - return ( - " def __new__(cls, *args, **kwargs):", - f" return {self._class_create_method_name(surface)}()", - *self._class_overload_python_lines( - overload, - constructor=True, - docstring=surface.constructor.docstring, - ), - ) - - def _class_method_python_lines(self, method: ClassMethodPlan) -> tuple[str, ...]: - """Render a readable Python descriptor over one ordinary function plan.""" - arguments = tuple(sorted(method.function.arguments, key=lambda argument: argument.python_position)) - passed = next( - (argument for argument in arguments if argument.native_position == method.passed_object_position), - None, - ) - public = tuple(argument for argument in arguments if argument is not passed) - parameter_names = tuple(argument.binding.python_name for argument in public) - call_names = tuple("self" if argument is passed else argument.binding.python_name for argument in arguments) - lines = [] - if method.kind is ClassMethodKind.STATIC: - lines.append(" @staticmethod") - signature = ", ".join(parameter_names) - else: - signature = ", ".join(("self", *parameter_names)) - lines.extend( - ( - f" def {method.python_name}({signature}):", - f" {method.docstring!r}", - f" return {method.function.binding.python_name}({', '.join(call_names)})", - ) - ) - return tuple(lines) - - def _class_overload_python_lines( - self, - overload: OverloadPlan, - *, - constructor: bool = False, - docstring: str | None = None, - ) -> tuple[str, ...]: - """Render deterministic exact-type selection without trial candidate calls.""" - passed_object = True if constructor else overload.candidate_passed_objects[0] - method_name = "__init__" if constructor else overload.python_name - signature = "self, *args, **kwargs" if passed_object else "*args, **kwargs" - return self._overload_python_lines( - overload, - method_name=method_name, - signature=signature, - indent=" ", - receiver_object="self" if passed_object or constructor else None, - static=not passed_object, - docstring=docstring or overload.docstring, - ) - - def _module_overload_python_source(self, overload: OverloadPlan) -> str: - """Render one namespace generic through the shared exact-match path.""" - return "\n".join( - self._overload_python_lines( - overload, - method_name=overload.python_name, - signature="*args, **kwargs", - indent="", - receiver_object=None, - static=False, - docstring=overload.docstring, - ) - ) - - def _overload_python_lines( - self, - overload: OverloadPlan, - *, - method_name: str, - signature: str, - indent: str, - receiver_object: str | None, - static: bool, - docstring: str, - ) -> tuple[str, ...]: - """Render one deterministic overload dispatcher at any namespace depth.""" - self._require_overload_complete(overload) - body_indent = f"{indent} " - lines = [ - *((f"{indent}@staticmethod",) if static else ()), - f"{indent}def {method_name}({signature}):", - f"{body_indent}{docstring!r}", - ] - if overload.unsupported_extra_argument_message is not None: - lines.extend( - ( - f"{body_indent}if len(args) > 1:", - f"{body_indent} raise TypeError({overload.unsupported_extra_argument_message!r})", - ) - ) - if overload.identity_receiver_shortcut and receiver_object is not None: - lines.extend( - ( - f"{body_indent}if len(args) == 1 and not kwargs and args[0] is {receiver_object}:", - f"{body_indent} return {receiver_object}", - ) - ) - for candidate, matches, candidate_passed in zip( - overload.candidates, - overload.candidate_matches, - overload.candidate_passed_objects, - strict=True, - ): - candidate_receiver = ( - self._overload_receiver_name(candidate) if candidate_passed or receiver_object is not None else None - ) - lines.extend( - self._overload_candidate_python_lines( - candidate, - matches, - receiver_name=candidate_receiver, - receiver_object=receiver_object, - indent=body_indent, - ) - ) - lines.append(f"{body_indent}raise TypeError('no matching overload for {overload.python_name}')") - return tuple(lines) - - @staticmethod - def _require_overload_complete(overload: OverloadPlan) -> None: - """Reject incomplete editable overload plans before Python source assembly.""" - if not overload.candidates: - raise ValueError(f"Overload {overload.owner_path!r} has no candidates") - if not (len(overload.candidates) == len(overload.candidate_matches) == len(overload.candidate_passed_objects)): - raise ValueError(f"Overload {overload.owner_path!r} has incomplete candidate metadata") - - def _overload_candidate_python_lines( - self, - candidate: FunctionPlan, - matches: tuple, - *, - receiver_name: str | None, - receiver_object: str | None, - indent: str, - ) -> tuple[str, ...]: - """Render one exact predicate and its single non-speculative call leaf.""" - names = tuple(match.python_name for match in matches) - condition = " and ".join(self._overload_dictionary_argument_predicate(item) for item in matches) or "True" - receiver_line = ( - (f"{indent} _prik_arguments[{receiver_name!r}] = {receiver_object}",) - if receiver_name is not None and receiver_object is not None - else () - ) - coercion_lines = tuple( - line - for match in matches - if match.accept_builtin_scalar - for line in self._overload_builtin_coercion_lines(match, f"{indent} ") - ) - return ( - f"{indent}_prik_names = {names!r}", - f"{indent}if (", - f"{indent} len(args) <= len(_prik_names)", - f"{indent} and all(_prik_name in _prik_names for _prik_name in kwargs)", - f"{indent} and not any(_prik_name in kwargs for _prik_name in _prik_names[:len(args)])", - f"{indent}):", - f"{indent} _prik_arguments = dict(zip(_prik_names, args))", - f"{indent} _prik_arguments.update(kwargs)", - f"{indent} if {condition}:", - *coercion_lines, - *receiver_line, - f"{indent} return {candidate.binding.python_name}(**_prik_arguments)", - ) - - def _overload_builtin_coercion_lines( - self, - match: OverloadArgumentMatchPlan, - indent: str, - ) -> tuple[str, ...]: - """Restore the NumPy scalar type lost before reflected dispatch.""" - name = match.python_name - assignment = ( - f"_prik_arguments[{name!r}] = _prik_numpy.{self._numpy_scalar_type_name(match.semantic_type_name)}(" - f"_prik_arguments[{name!r}])" - ) - if match.optional: - return (f"{indent}if {name!r} in _prik_arguments:", f"{indent} {assignment}") - return (f"{indent}{assignment}",) - - @staticmethod - def _overload_receiver_name(candidate: FunctionPlan) -> str: - """Return the completed Python argument that receives the class instance.""" - call = candidate.class_call - if call is None or call.passed_object_position is None: - raise ValueError(f"Overload candidate {candidate.owner_path!r} has no completed receiver position") - receiver = next( - ( - argument - for argument in candidate.arguments - if argument.native_position == call.passed_object_position and argument.python_visible - ), - None, - ) - if receiver is None: - raise ValueError(f"Overload candidate {candidate.owner_path!r} has no visible receiver argument") - return receiver.binding.python_name - - @staticmethod - def _callable_public_arguments(function: FunctionPlan) -> tuple[ArgumentTransferPlan, ...]: - """Return ordered user parameters, excluding the class passed object.""" - return tuple( - argument - for argument in sorted(function.arguments, key=lambda item: item.python_position) - if argument.binding.python_name != "self" - ) - - @staticmethod - def _python_parameter_suffix(arguments: tuple[ArgumentTransferPlan, ...]) -> str: - """Return the binding-local python parameter suffix derived from the supplied local lowering values; this helper preserves completed policy.""" - if not arguments: - return "" - rendered = ", ".join( - argument.binding.python_name - + ( - "=_prik_unset" - if argument.binding.optional_mode not in {OptionalMode.REQUIRED, OptionalMode.REQUIRED_DESCRIPTOR} - else "" - ) - for argument in arguments - ) - return f", {rendered}" - - @staticmethod - def _optional_keyword_collection_lines( - arguments: tuple[ArgumentTransferPlan, ...], - *, - indent: str, - ) -> tuple[str, ...]: - """Build optional keyword collection lines from the supplied local lowering values; emitted nodes only project completed binding actions.""" - lines = [] - for argument in arguments: - name = argument.binding.python_name - if argument.binding.optional_mode in {OptionalMode.REQUIRED, OptionalMode.REQUIRED_DESCRIPTOR}: - lines.append(f"{indent}_prik_arguments['{name}'] = {name}") - else: - lines.extend( - ( - f"{indent}if {name} is not _prik_unset:", - f"{indent} _prik_arguments['{name}'] = {name}", - ) - ) - return tuple(lines) - - def _overload_dictionary_argument_predicate( - self, - argument: OverloadArgumentMatchPlan, - ) -> str: - """Match one normalized candidate argument without invoking its target.""" - name = argument.python_name - predicate = self._required_overload_argument_predicate( - argument, - f"_prik_arguments[{name!r}]", - ) - if argument.optional: - return f"({name!r} not in _prik_arguments or ({predicate}))" - return f"({name!r} in _prik_arguments and ({predicate}))" - - def _required_overload_argument_predicate( - self, - argument: OverloadArgumentMatchPlan, - name: str, - ) -> str: - """Dispatch one typed match record into a small source leaf.""" - if argument.kind is OverloadMatchKind.DERIVED: - if argument.derived_type_identity is None: - raise ValueError(f"Derived overload argument {name!r} has no type identity") - return f"type({name}) is {self._class_python_names[argument.derived_type_identity]}" - if argument.kind is OverloadMatchKind.NUMPY_ARRAY: - return self._numpy_array_overload_predicate(argument, name) - if argument.kind is OverloadMatchKind.STRING: - return f"isinstance({name}, str)" - if argument.kind is OverloadMatchKind.NUMPY_SCALAR: - numpy_type = self._numpy_scalar_type_name(argument.semantic_type_name) - predicate = f"type({name}) is _prik_numpy.{numpy_type}" - if argument.accept_builtin_scalar: - builtin = self._builtin_scalar_type_name(argument.semantic_type_name) - predicate = f"({predicate} or type({name}) is {builtin})" - return predicate - raise ValueError(f"Unsupported class overload match kind: {argument.kind.value}") - - def _numpy_array_overload_predicate( - self, - argument: OverloadArgumentMatchPlan, - name: str, - ) -> str: - """Render one exact NumPy array rank and dtype predicate.""" - dtype = self._numpy_scalar_type_name(argument.semantic_type_name) - return ( - f"isinstance({name}, _prik_numpy.ndarray) and {name}.ndim == {argument.rank} " - f"and {name}.dtype == _prik_numpy.dtype(_prik_numpy.{dtype})" - ) - - @staticmethod - def _numpy_scalar_type_name(semantic_type_name: str) -> str: - """Map a completed semantic scalar to its NumPy runtime spelling.""" - if is_boolean_semantic_type_name(semantic_type_name): - return "bool_" - numpy_types = { - "Int8": "int8", - "Int16": "int16", - "Int32": "int32", - "Int64": "int64", - "Float32": "float32", - "Float64": "float64", - "Complex64": "complex64", - "Complex128": "complex128", - } - try: - return numpy_types[semantic_type_name] - except KeyError as exc: - raise ValueError(f"Unsupported NumPy overload scalar {semantic_type_name!r}") from exc - - @staticmethod - def _builtin_scalar_type_name(semantic_type_name: str) -> str: - """Return the Python scalar produced before reflected NumPy dispatch.""" - return overload_builtin_scalar_family(semantic_type_name) - - @staticmethod - def _class_base_name( - surface: ClassSurfacePlan | None, - class_names: dict[tuple[str, str], str], - ) -> str | None: - """Return the binding-local class base name derived from the supplied local lowering values; this helper preserves completed policy.""" - if surface is None or not surface.base_identities: - return None - return class_names[surface.base_identities[0]] - - @staticmethod - def _class_create_bridge_name(surface: ClassSurfacePlan) -> str: - """Return the binding-local class create bridge name derived from the supplied local lowering values; this helper preserves completed policy.""" - return f"bind_c_prik_create_{surface.type_identity[1].casefold()}" - - @staticmethod - def _class_create_method_name(surface: ClassSurfacePlan) -> str: - """Return the binding-local class create method name derived from the supplied local lowering values; this helper preserves completed policy.""" - return f"_prik_create_{surface.type_identity[1].casefold()}" - - @staticmethod - def _class_wrap_helper_name( - surface: ClassSurfacePlan | None, - *, - fallback: str | None = None, - ) -> str: - """Return the binding-local class wrap helper name derived from the supplied local lowering values; this helper preserves completed policy.""" - name = surface.python_names[0] if surface is not None else fallback - if name is None: - raise ValueError("Class wrapper helper requires a Python type name") - return f"_prik_wrap_{name}" - - @staticmethod - def _derived_property_python_lines(field: DerivedFieldPlan) -> tuple[str, ...]: - """Build derived property python lines from the supplied completed binding records; emitted nodes only project completed binding actions.""" - lines = [ - " @property", - f" def {field.name}(self):", - f" {field.docstring!r}", - " present = self._prik_ops.get('_present')", - " if present is not None:", - " present(self)", - f" return self._prik_ops['{field.name}_get'](self)", - ] - if field.setter_action is SetterAction.WRITE_THROUGH: - lines.extend( - ( - f" @{field.name}.setter", - f" def {field.name}(self, value):", - " present = self._prik_ops.get('_present')", - " if present is not None:", - " present(self)", - f" self._prik_ops['{field.name}_set'](self, value)", - ) - ) - elif field.setter_action is SetterAction.REJECT_REPLACEMENT: - lines.extend( - ( - f" @{field.name}.setter", - f" def {field.name}(self, value):", - f" raise AttributeError('field {field.name} does not support replacement assignment')", - ) - ) - return tuple(lines) - - def _direct_type_ops_literal(self, derived: DerivedTypePlan) -> str: - """Return the binding-local direct type ops literal derived from the supplied local lowering values; this helper preserves completed policy.""" - entries = [] - for field in derived.fields: - entries.append(f"'{field.name}_get': {self._derived_field_method_name(derived, field, 'get')}") - if field.setter_action is SetterAction.WRITE_THROUGH: - entries.append(f"'{field.name}_set': {self._derived_field_method_name(derived, field, 'set')}") - return "{" + ", ".join(entries) + "}" - - def _allocatable_holder_ops_python_source(self, derived: DerivedTypePlan) -> str: - """Build allocatable holder ops python source from the supplied local lowering values; emitted nodes only project completed binding actions.""" - entries = [f"'_present': {self._allocatable_holder_presence_method_name(derived.backend_symbol)}"] - for field in derived.fields: - entries.append(f"'{field.name}_get': {self._allocatable_holder_field_method_name(derived, field, 'get')}") - if field.setter_action is SetterAction.WRITE_THROUGH: - entries.append( - f"'{field.name}_set': {self._allocatable_holder_field_method_name(derived, field, 'set')}" - ) - return f"{self._allocatable_holder_ops_name(derived.backend_symbol)} = {{{', '.join(entries)}}}" - - def _pointer_holder_ops_python_source(self, derived: DerivedTypePlan) -> str: - """Return pointer holder ops python source from the supplied local lowering values; this helper preserves the selected binding behavior.""" - entries = [f"'_present': {self._pointer_holder_presence_method_name(derived.backend_symbol)}"] - for field in derived.fields: - entries.append(f"'{field.name}_get': {self._pointer_holder_field_method_name(derived, field, 'get')}") - if field.setter_action is SetterAction.WRITE_THROUGH: - entries.append(f"'{field.name}_set': {self._pointer_holder_field_method_name(derived, field, 'set')}") - return f"{self._pointer_holder_ops_name(derived.backend_symbol)} = {{{', '.join(entries)}}}" - - @staticmethod - def _direct_type_ops_name(derived: DerivedTypePlan) -> str: - """Return the binding-local direct type ops name derived from the supplied local lowering values; this helper preserves completed policy.""" - return f"_prik_ops_{derived.type_name.casefold()}" - - def _module_proxy_ops_python_source(self, variable: ModuleVariablePlan) -> str: - """Return one operation dictionary per reachable plain-module object path.""" - if variable.derived is None: - return "" - if variable.derived.access is ModuleObjectAccessMechanism.DIRECT_ADDRESS: - direct = f"_prik_ops_{variable.derived.handoff.type_name.casefold()}" - native_ops = self._derived_origin_capsule_method_name(variable) - return f"{self._module_member_ops_name(variable, ())} = dict({direct}, _native_ops={native_ops}())" - grouped: dict[tuple[str, ...], list[DerivedMemberPathPlan]] = {} - for member in variable.derived.member_paths: - grouped.setdefault(member.path[:-1], []).append(member) - return "\n".join( - f"{self._module_member_ops_name(variable, prefix)} = " - f"{self._module_proxy_ops_literal(variable, prefix, members)}" - for prefix, members in grouped.items() - ) - - def _module_proxy_ops_literal( - self, - variable: ModuleVariablePlan, - prefix: tuple[str, ...], - members: list[DerivedMemberPathPlan], - ) -> str: - """Return module proxy ops literal from the supplied completed binding records; this helper preserves the selected binding behavior.""" - entries = [] - if not prefix: - entries.append(f"'_native_ops': {self._derived_origin_capsule_method_name(variable)}()") - if self._nullable_derived_module_proxy(variable): - entries.append(f"'_present': {self._module_derived_presence_method_name(variable)}") - for member in members: - field = member.field - entries.append(f"'{field.name}_get': {self._module_member_method_name(variable, member, 'get')}") - if field.setter_action is SetterAction.WRITE_THROUGH: - entries.append(f"'{field.name}_set': {self._module_member_method_name(variable, member, 'set')}") - return "{" + ", ".join(entries) + "}" - @staticmethod def _c_string_literal(value: str) -> str: """Escape generated Python helper source as one C string literal.""" @@ -12368,7 +12199,7 @@ def _module_derived_presence_bridge_name(plan: ModuleVariablePlan) -> str: @staticmethod def _module_derived_presence_method_name(plan: ModuleVariablePlan) -> str: """Return the binding-local module derived presence method name derived from the supplied completed binding records; this helper preserves completed policy.""" - return f"_prik_module_{plan.symbol_name.casefold()}_require_present" + return CBindingNames.module_derived_presence_method(plan) def _functions(self, plan: ModulePlan) -> tuple[FunctionPlan, ...]: """Build functions from the supplied completed binding records; emitted nodes only project completed binding actions.""" diff --git a/prik/codegen/c/naming.py b/prik/codegen/c/naming.py new file mode 100644 index 000000000..557f0a301 --- /dev/null +++ b/prik/codegen/c/naming.py @@ -0,0 +1,140 @@ +"""Shared symbol spelling for the C binding and its Python surface.""" + +from __future__ import annotations + +from prik.codegen.naming import NativeSymbolNames +from prik.codegen.plan import ( + ClassSurfacePlan, + DerivedFieldPlan, + DerivedMemberPathPlan, + DerivedTypePlan, + ModuleVariablePlan, + OverloadPlan, +) + + +class CBindingNames: + """Own symbols referenced by both generated C and embedded Python source.""" + + @staticmethod + def derived_origin_symbol(variable: ModuleVariablePlan) -> str: + """Return the compact native-origin symbol for one module variable.""" + return NativeSymbolNames.compact(variable.owner_path, variable.symbol_name) + + @classmethod + def derived_origin_capsule_method(cls, variable: ModuleVariablePlan) -> str: + """Return the private Python callable exposing native-origin operations.""" + return f"_prik_origin_{cls.derived_origin_symbol(variable)}_native_ops" + + @staticmethod + def derived_field_symbol(derived: DerivedTypePlan, field: DerivedFieldPlan) -> str: + """Return the shared symbol fragment for one derived field.""" + return f"{derived.backend_symbol}_{field.name}".casefold() + + @classmethod + def derived_field_method(cls, derived: DerivedTypePlan, field: DerivedFieldPlan, action: str) -> str: + """Return the private Python callable for one direct field operation.""" + return f"_prik_field_{cls.derived_field_symbol(derived, field)}_{action}" + + @classmethod + def allocatable_holder_field_method( + cls, + derived: DerivedTypePlan, + field: DerivedFieldPlan, + action: str, + ) -> str: + """Return the private callable for one allocatable-holder field operation.""" + return f"_prik_allocatable_holder_field_{cls.derived_field_symbol(derived, field)}_{action}" + + @classmethod + def pointer_holder_field_method( + cls, + derived: DerivedTypePlan, + field: DerivedFieldPlan, + action: str, + ) -> str: + """Return the private callable for one pointer-holder field operation.""" + return f"_prik_pointer_holder_field_{cls.derived_field_symbol(derived, field)}_{action}" + + @staticmethod + def allocatable_holder_presence_method(type_name: str) -> str: + """Return the allocatable-holder presence guard exposed to Python.""" + return f"_prik_{type_name.casefold()}_allocatable_holder_require_present" + + @staticmethod + def pointer_holder_presence_method(type_name: str) -> str: + """Return the pointer-holder presence guard exposed to Python.""" + return f"_prik_{type_name.casefold()}_pointer_holder_require_present" + + @staticmethod + def allocatable_holder_ops(type_name: str) -> str: + """Return the Python operation-map name for an allocatable holder.""" + return f"_prik_ops_{type_name.casefold()}_allocatable_holder" + + @staticmethod + def pointer_holder_ops(type_name: str) -> str: + """Return the Python operation-map name for a pointer holder.""" + return f"_prik_ops_{type_name.casefold()}_pointer_holder" + + @staticmethod + def module_member_symbol(variable: ModuleVariablePlan, member: DerivedMemberPathPlan) -> str: + """Return the shared symbol fragment for one module-derived member.""" + return "_".join((variable.symbol_name, *member.path)).casefold() + + @classmethod + def module_member_method( + cls, + variable: ModuleVariablePlan, + member: DerivedMemberPathPlan, + action: str, + ) -> str: + """Return the private Python callable for one module-member operation.""" + return f"_prik_module_field_{cls.module_member_symbol(variable, member)}_{action}" + + @staticmethod + def module_member_ops(variable: ModuleVariablePlan, prefix: tuple[str, ...]) -> str: + """Return the operation-map name for one reachable module-object path.""" + suffix = "_".join((variable.symbol_name, *prefix)).casefold() + return f"_prik_ops_{suffix}" + + @staticmethod + def module_derived_presence_method(variable: ModuleVariablePlan) -> str: + """Return the presence guard for a nullable module-derived object.""" + return f"_prik_module_{variable.symbol_name.casefold()}_require_present" + + @staticmethod + def class_create_method(surface: ClassSurfacePlan) -> str: + """Return the private C constructor callable installed in the namespace.""" + return f"_prik_create_{surface.type_identity[1].casefold()}" + + @staticmethod + def class_create_bridge(surface: ClassSurfacePlan) -> str: + """Return the Fortran bridge symbol allocating one class owner.""" + return f"bind_c_prik_create_{surface.type_identity[1].casefold()}" + + @staticmethod + def class_wrap_helper( + surface: ClassSurfacePlan | None, + *, + fallback: str | None = None, + ) -> str: + """Return the Python helper attaching existing native storage.""" + name = surface.python_names[0] if surface is not None else fallback + if name is None: + raise ValueError("Class wrapper helper requires a Python type name") + return f"_prik_wrap_{name}" + + @staticmethod + def overload_dispatch_symbol(overload: OverloadPlan) -> str: + """Return one compact symbol unique to the planned overload surface.""" + return NativeSymbolNames.compact(overload.owner_path, overload.python_name, limit=38) + + @classmethod + def overload_dispatch_method(cls, overload: OverloadPlan) -> str: + """Return the private namespace callable used by class forwarding methods.""" + return f"_prik_dispatch_{cls.overload_dispatch_symbol(overload)}" + + @classmethod + def overload_dispatch_function(cls, overload: OverloadPlan) -> str: + """Return the generated C function implementing overload selection.""" + return f"wrap_{cls.overload_dispatch_method(overload)}" diff --git a/prik/codegen/c/python_surface.py b/prik/codegen/c/python_surface.py new file mode 100644 index 000000000..d6124d838 --- /dev/null +++ b/prik/codegen/c/python_surface.py @@ -0,0 +1,539 @@ +"""Emit the executable Python facade embedded in a generated C extension.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from prik.codegen.c.naming import CBindingNames +from prik.codegen.plan import ( + ArgumentTransferPlan, + ClassMethodPlan, + ClassSurfacePlan, + DerivedFieldPlan, + DerivedMemberPathPlan, + DerivedTypePlan, + FunctionPlan, + ModuleVariablePlan, + NamespacePlan, + OverloadPlan, +) +from prik.codegen.visitor import ClassVisitor +from prik.semantics.ownership import SetterAction +from prik.semantics.wrapper_policy import ( + ClassConstructorKind, + ClassMethodKind, + ModuleObjectAccessMechanism, + OptionalMode, +) + + +@dataclass(frozen=True) +class PythonSurfaceContext: + """Store namespace facts already selected by planning and C orchestration.""" + + allocatable_holder_identities: frozenset[tuple[str, str]] + pointer_holder_identities: frozenset[tuple[str, str]] + nullable_module_proxy_owner_paths: frozenset[str] + + +class PythonSurfaceEmitter(ClassVisitor): + """Render derived classes and their thin overload forwarders as Python source.""" + + def __init__(self, context: PythonSurfaceContext) -> None: + self._context = context + + def emit(self, namespace: NamespacePlan) -> str: + """Return overloads, opaque classes, and typed member operation maps.""" + return self.visit(namespace) + + def _visit_NamespacePlan(self, namespace: NamespacePlan) -> str: + """Render one planned namespace as executable Python source.""" + surfaces = self._class_surfaces(namespace) + class_names = self._class_names(namespace) + ops_names = self._direct_ops_names(namespace) + sections = [ + "_prik_unset = object()", + *( + self._derived_type_python_source( + derived, + surfaces.get(derived.type_identity), + class_names, + ops_names, + ) + for derived in namespace.derived_types + ), + ] + sections.extend(self._holder_ops_python_sources(namespace)) + sections.extend(self._module_proxy_ops_python_sources(namespace)) + return "\n\n".join(section for section in sections if section) + + @staticmethod + def _class_surfaces(namespace: NamespacePlan) -> dict[tuple[str, str], ClassSurfacePlan]: + """Index planned class surfaces by completed type identity.""" + return {surface.type_identity: surface for surface in namespace.classes} + + @staticmethod + def _class_names(namespace: NamespacePlan) -> dict[tuple[str, str], str]: + """Index visible class names needed for inheritance rendering.""" + return {surface.type_identity: surface.python_names[0] for surface in namespace.classes if surface.python_names} + + def _direct_ops_names(self, namespace: NamespacePlan) -> dict[tuple[str, str], str]: + """Index operation dictionaries inherited by generated subclasses.""" + return {derived.type_identity: self._direct_type_ops_name(derived) for derived in namespace.derived_types} + + def _holder_ops_python_sources(self, namespace: NamespacePlan) -> tuple[str, ...]: + """Render allocatable and pointer holder operation maps by completed identity.""" + return ( + *( + self._allocatable_holder_ops_python_source(derived) + for derived in namespace.derived_types + if derived.type_identity in self._context.allocatable_holder_identities + ), + *( + self._pointer_holder_ops_python_source(derived) + for derived in namespace.derived_types + if derived.type_identity in self._context.pointer_holder_identities + ), + ) + + def _module_proxy_ops_python_sources(self, namespace: NamespacePlan) -> tuple[str, ...]: + """Render persistent module-derived operation maps in declaration order.""" + return tuple( + self._module_proxy_ops_python_source(variable) + for variable in namespace.variables + if variable.derived is not None + ) + + def _derived_type_python_source( + self, + derived: DerivedTypePlan, + surface: ClassSurfacePlan | None, + class_names: dict[tuple[str, str], str], + ops_names: dict[tuple[str, str], str], + ) -> str: + """Return one opaque wrapper assembled from its completed class surface.""" + name = derived.python_names[0] + ops_name = self._direct_type_ops_name(derived) + base = self._class_base_name(surface, class_names) + base_ops = self._class_base_ops_name(surface, ops_names) + slots = self._class_slots(base) + own_ops = self._direct_type_ops_literal(derived) + combined_ops = self._combined_ops_literal(base_ops, own_ops) + lines = [ + f"{ops_name} = {combined_ops}", + f"class {name}{f'({base})' if base else ''}:", + self._class_docstring_line(surface, name), + f" __slots__ = {slots}", + ] + lines.extend(self._class_constructor_python_lines(surface)) + lines.extend(self._derived_class_member_python_lines(derived, surface)) + lines.extend(self._class_wrap_helper_python_lines(surface, name, ops_name)) + return "\n".join(lines) + + @staticmethod + def _class_base_ops_name( + surface: ClassSurfacePlan | None, + ops_names: dict[tuple[str, str], str], + ) -> str | None: + """Return the inherited operation-map name, when one is planned.""" + if surface is None or not surface.base_identities: + return None + return ops_names[surface.base_identities[0]] + + @staticmethod + def _class_slots(base: str | None) -> str: + """Store native wrapper state only on the root generated class.""" + return "()" if base else "('_prik_capsule', '_prik_owner', '_prik_ops', '_prik_origin')" + + @staticmethod + def _combined_ops_literal(base_ops: str | None, own_ops: str) -> str: + """Merge inherited and directly declared operation dictionaries.""" + return f"{{**{base_ops}, **{own_ops}}}" if base_ops is not None else own_ops + + @staticmethod + def _class_docstring_line(surface: ClassSurfacePlan | None, name: str) -> str: + """Return the class-body docstring line from its completed surface.""" + return f" {surface.docstring!r}" if surface is not None else f" {name!r}" + + def _derived_class_member_python_lines( + self, + derived: DerivedTypePlan, + surface: ClassSurfacePlan | None, + ) -> tuple[str, ...]: + """Render fields, public methods, and overload descriptors for one class.""" + methods = () if surface is None else tuple(method for method in surface.methods if method.public) + overloads = () if surface is None else surface.overloads + return ( + *self._derived_property_python_source_lines(derived.fields), + *self._class_method_python_source_lines(methods), + *self._class_overload_python_source_lines(overloads), + ) + + def _derived_property_python_source_lines(self, fields: tuple[DerivedFieldPlan, ...]) -> tuple[str, ...]: + """Flatten field descriptors while preserving declaration order.""" + return tuple(line for field in fields for line in self._derived_property_python_lines(field)) + + def _class_method_python_source_lines(self, methods: tuple[ClassMethodPlan, ...]) -> tuple[str, ...]: + """Flatten public method descriptors while preserving plan order.""" + return tuple(line for method in methods for line in self._class_method_python_lines(method)) + + def _class_overload_python_source_lines(self, overloads: tuple[OverloadPlan, ...]) -> tuple[str, ...]: + """Flatten overload descriptors while preserving plan order.""" + return tuple(line for overload in overloads for line in self._class_overload_python_lines(overload)) + + def _class_wrap_helper_python_lines( + self, + surface: ClassSurfacePlan | None, + name: str, + ops_name: str, + ) -> tuple[str, ...]: + """Render the sole helper that attaches existing opaque native storage.""" + return ( + f"def {CBindingNames.class_wrap_helper(surface, fallback=name)}(capsule, owner=None, ops=None, origin='direct'):", + f" value = object.__new__({name})", + " value._prik_capsule = capsule", + " value._prik_owner = owner", + f" value._prik_ops = {ops_name} if ops is None else ops", + " value._prik_origin = origin", + " return value", + ) + + def _class_constructor_python_lines(self, surface: ClassSurfacePlan | None) -> tuple[str, ...]: + """Render one constructor selected entirely by the class plan.""" + if surface is None or surface.constructor.kind is ClassConstructorKind.ABSENT: + return self._absent_constructor_python_lines(surface) + handlers = { + ClassConstructorKind.DEFAULT_FIELDS: self._default_constructor_python_lines, + ClassConstructorKind.BOUND_PROCEDURE: self._bound_constructor_python_lines, + ClassConstructorKind.OVERLOAD_SET: self._overloaded_constructor_python_lines, + } + handler = handlers.get(surface.constructor.kind) + if handler is None: + raise ValueError(f"Unsupported completed constructor kind: {surface.constructor.kind.value}") + return handler(surface) + + @staticmethod + def _absent_constructor_python_lines(surface: ClassSurfacePlan | None) -> tuple[str, ...]: + """Render one explicit rejection for a nonconstructible wrapper class.""" + message = ( + surface.constructor.rejection_message + if surface is not None and surface.constructor.rejection_message + else "native wrapper construction is disabled" + ) + return ( + " def __new__(cls, *args, **kwargs):", + f" {surface.constructor.docstring!r}" if surface is not None else " 'Construction disabled.'", + f" raise TypeError({message!r})", + ) + + def _default_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[str, ...]: + """Allocate one owner, then apply only explicitly supplied field values.""" + fields = surface.constructor.fields + parameters = ", ".join(f"{field.name}=_prik_unset" for field in fields) + signature = f", *, {parameters}" if parameters else "" + lines = [ + " def __new__(cls, *args, **kwargs):", + f" return {CBindingNames.class_create_method(surface)}()", + f" def __init__(self{signature}):", + f" {surface.constructor.docstring!r}", + ] + if not fields: + lines.append(" pass") + for field in fields: + lines.extend( + ( + f" if {field.name} is not _prik_unset:", + f" self.{field.name} = {field.name}", + ) + ) + return tuple(lines) + + def _bound_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[str, ...]: + """Call one validated target after allocating the persistent owner.""" + target = surface.constructor.target + if target is None: + raise ValueError(f"Bound constructor {surface.owner_path!r} has no target function") + parameters = self._callable_public_arguments(target) + lines = [ + " def __new__(cls, *args, **kwargs):", + f" return {CBindingNames.class_create_method(surface)}()", + f" def __init__(self{self._python_parameter_suffix(parameters)}):", + f" {surface.constructor.docstring!r}", + " _prik_arguments = {'self': self}", + ] + lines.extend(self._optional_keyword_collection_lines(parameters, indent=" ")) + lines.append(f" {target.binding.python_name}(**_prik_arguments)") + return tuple(lines) + + def _overloaded_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[str, ...]: + """Dispatch one completed constructor overload after owner allocation.""" + overload = surface.constructor.overload + if overload is None: + raise ValueError(f"Overloaded constructor {surface.owner_path!r} has no overload plan") + return ( + " def __new__(cls, *args, **kwargs):", + f" return {CBindingNames.class_create_method(surface)}()", + *self._class_overload_python_lines( + overload, + constructor=True, + docstring=surface.constructor.docstring, + ), + ) + + def _class_method_python_lines(self, method: ClassMethodPlan) -> tuple[str, ...]: + """Render a readable Python descriptor over one ordinary function plan.""" + arguments = self._ordered_method_arguments(method) + passed = self._passed_method_argument(method, arguments) + signature = self._class_method_signature(method, arguments, passed) + call_names = self._class_method_call_names(arguments, passed) + return ( + *self._class_method_decorators(method), + f" def {method.python_name}({signature}):", + f" {method.docstring!r}", + f" return {method.function.binding.python_name}({', '.join(call_names)})", + ) + + @staticmethod + def _ordered_method_arguments(method: ClassMethodPlan) -> tuple[ArgumentTransferPlan, ...]: + """Return class-call arguments in their completed Python order.""" + return tuple(sorted(method.function.arguments, key=lambda argument: argument.python_position)) + + @staticmethod + def _passed_method_argument( + method: ClassMethodPlan, + arguments: tuple[ArgumentTransferPlan, ...], + ) -> ArgumentTransferPlan | None: + """Return the argument occupied by the planned class receiver.""" + return next( + (argument for argument in arguments if argument.native_position == method.passed_object_position), + None, + ) + + @staticmethod + def _class_method_signature( + method: ClassMethodPlan, + arguments: tuple[ArgumentTransferPlan, ...], + passed: ArgumentTransferPlan | None, + ) -> str: + """Render one static or instance method signature.""" + public_names = tuple(argument.binding.python_name for argument in arguments if argument is not passed) + names = public_names if method.kind is ClassMethodKind.STATIC else ("self", *public_names) + return ", ".join(names) + + @staticmethod + def _class_method_call_names( + arguments: tuple[ArgumentTransferPlan, ...], + passed: ArgumentTransferPlan | None, + ) -> tuple[str, ...]: + """Render candidate call arguments with the receiver restored.""" + return tuple("self" if argument is passed else argument.binding.python_name for argument in arguments) + + @staticmethod + def _class_method_decorators(method: ClassMethodPlan) -> tuple[str, ...]: + """Return the Python descriptor decorators for one planned method.""" + return (" @staticmethod",) if method.kind is ClassMethodKind.STATIC else () + + def _class_overload_python_lines( + self, + overload: OverloadPlan, + *, + constructor: bool = False, + docstring: str | None = None, + ) -> tuple[str, ...]: + """Render one thin class descriptor over a namespace-installed C dispatcher.""" + passed_object = self._overload_has_receiver(overload, constructor) + return ( + *self._overload_decorators(passed_object), + f" def {self._overload_method_name(overload, constructor)}({self._overload_signature(passed_object)}):", + f" {docstring or overload.docstring!r}", + f" return {CBindingNames.overload_dispatch_method(overload)}(" + f"{self._overload_receiver_prefix(passed_object)}*args, **kwargs)", + ) + + @staticmethod + def _overload_has_receiver(overload: OverloadPlan, constructor: bool) -> bool: + """Return the completed receiver convention for one class overload.""" + return constructor or overload.candidate_passed_objects[0] + + @staticmethod + def _overload_decorators(passed_object: bool) -> tuple[str, ...]: + """Render the static-method marker when no receiver is planned.""" + return () if passed_object else (" @staticmethod",) + + @staticmethod + def _overload_method_name(overload: OverloadPlan, constructor: bool) -> str: + """Return the Python descriptor name for a method or constructor.""" + return "__init__" if constructor else overload.python_name + + @staticmethod + def _overload_signature(passed_object: bool) -> str: + """Return the variadic descriptor signature with its receiver convention.""" + return "self, *args, **kwargs" if passed_object else "*args, **kwargs" + + @staticmethod + def _overload_receiver_prefix(passed_object: bool) -> str: + """Return the receiver prefix forwarded to the private C dispatcher.""" + return "self, " if passed_object else "" + + @staticmethod + def _callable_public_arguments(function: FunctionPlan) -> tuple[ArgumentTransferPlan, ...]: + """Return ordered user parameters, excluding the class passed object.""" + return tuple( + argument + for argument in sorted(function.arguments, key=lambda item: item.python_position) + if argument.binding.python_name != "self" + ) + + @staticmethod + def _python_parameter_suffix(arguments: tuple[ArgumentTransferPlan, ...]) -> str: + """Return one rendered Python constructor parameter suffix.""" + if not arguments: + return "" + rendered = ", ".join( + argument.binding.python_name + + ( + "=_prik_unset" + if argument.binding.optional_mode not in {OptionalMode.REQUIRED, OptionalMode.REQUIRED_DESCRIPTOR} + else "" + ) + for argument in arguments + ) + return f", {rendered}" + + @staticmethod + def _optional_keyword_collection_lines( + arguments: tuple[ArgumentTransferPlan, ...], + *, + indent: str, + ) -> tuple[str, ...]: + """Build optional keyword collection lines from completed binding plans.""" + lines = [] + for argument in arguments: + name = argument.binding.python_name + if argument.binding.optional_mode in {OptionalMode.REQUIRED, OptionalMode.REQUIRED_DESCRIPTOR}: + lines.append(f"{indent}_prik_arguments['{name}'] = {name}") + else: + lines.extend( + ( + f"{indent}if {name} is not _prik_unset:", + f"{indent} _prik_arguments['{name}'] = {name}", + ) + ) + return tuple(lines) + + @staticmethod + def _class_base_name( + surface: ClassSurfacePlan | None, + class_names: dict[tuple[str, str], str], + ) -> str | None: + """Return the planned Python base-class name.""" + if surface is None or not surface.base_identities: + return None + return class_names[surface.base_identities[0]] + + @staticmethod + def _derived_property_python_lines(field: DerivedFieldPlan) -> tuple[str, ...]: + """Build a property from completed getter and setter actions.""" + lines = [ + " @property", + f" def {field.name}(self):", + f" {field.docstring!r}", + " present = self._prik_ops.get('_present')", + " if present is not None:", + " present(self)", + f" return self._prik_ops['{field.name}_get'](self)", + ] + if field.setter_action is SetterAction.WRITE_THROUGH: + lines.extend( + ( + f" @{field.name}.setter", + f" def {field.name}(self, value):", + " present = self._prik_ops.get('_present')", + " if present is not None:", + " present(self)", + f" self._prik_ops['{field.name}_set'](self, value)", + ) + ) + elif field.setter_action is SetterAction.REJECT_REPLACEMENT: + lines.extend( + ( + f" @{field.name}.setter", + f" def {field.name}(self, value):", + f" raise AttributeError('field {field.name} does not support replacement assignment')", + ) + ) + return tuple(lines) + + def _direct_type_ops_literal(self, derived: DerivedTypePlan) -> str: + """Return the operation dictionary for directly owned native storage.""" + entries = [] + for field in derived.fields: + entries.append(f"'{field.name}_get': {CBindingNames.derived_field_method(derived, field, 'get')}") + if field.setter_action is SetterAction.WRITE_THROUGH: + entries.append(f"'{field.name}_set': {CBindingNames.derived_field_method(derived, field, 'set')}") + return "{" + ", ".join(entries) + "}" + + def _allocatable_holder_ops_python_source(self, derived: DerivedTypePlan) -> str: + """Build the operation dictionary for allocatable-holder storage.""" + entries = [f"'_present': {CBindingNames.allocatable_holder_presence_method(derived.backend_symbol)}"] + for field in derived.fields: + entries.append( + f"'{field.name}_get': {CBindingNames.allocatable_holder_field_method(derived, field, 'get')}" + ) + if field.setter_action is SetterAction.WRITE_THROUGH: + entries.append( + f"'{field.name}_set': {CBindingNames.allocatable_holder_field_method(derived, field, 'set')}" + ) + return f"{CBindingNames.allocatable_holder_ops(derived.backend_symbol)} = {{{', '.join(entries)}}}" + + def _pointer_holder_ops_python_source(self, derived: DerivedTypePlan) -> str: + """Build the operation dictionary for pointer-holder storage.""" + entries = [f"'_present': {CBindingNames.pointer_holder_presence_method(derived.backend_symbol)}"] + for field in derived.fields: + entries.append(f"'{field.name}_get': {CBindingNames.pointer_holder_field_method(derived, field, 'get')}") + if field.setter_action is SetterAction.WRITE_THROUGH: + entries.append( + f"'{field.name}_set': {CBindingNames.pointer_holder_field_method(derived, field, 'set')}" + ) + return f"{CBindingNames.pointer_holder_ops(derived.backend_symbol)} = {{{', '.join(entries)}}}" + + @staticmethod + def _direct_type_ops_name(derived: DerivedTypePlan) -> str: + """Return the Python operation-map name for direct storage.""" + return f"_prik_ops_{derived.type_name.casefold()}" + + def _module_proxy_ops_python_source(self, variable: ModuleVariablePlan) -> str: + """Return one operation dictionary per reachable plain-module object path.""" + if variable.derived is None: + return "" + if variable.derived.access is ModuleObjectAccessMechanism.DIRECT_ADDRESS: + direct = f"_prik_ops_{variable.derived.handoff.type_name.casefold()}" + native_ops = CBindingNames.derived_origin_capsule_method(variable) + return f"{CBindingNames.module_member_ops(variable, ())} = dict({direct}, _native_ops={native_ops}())" + grouped: dict[tuple[str, ...], list[DerivedMemberPathPlan]] = {} + for member in variable.derived.member_paths: + grouped.setdefault(member.path[:-1], []).append(member) + return "\n".join( + f"{CBindingNames.module_member_ops(variable, prefix)} = " + f"{self._module_proxy_ops_literal(variable, prefix, members)}" + for prefix, members in grouped.items() + ) + + def _module_proxy_ops_literal( + self, + variable: ModuleVariablePlan, + prefix: tuple[str, ...], + members: list[DerivedMemberPathPlan], + ) -> str: + """Return one completed module-proxy operation dictionary.""" + entries = [] + if not prefix: + entries.append(f"'_native_ops': {CBindingNames.derived_origin_capsule_method(variable)}()") + if variable.owner_path in self._context.nullable_module_proxy_owner_paths: + entries.append(f"'_present': {CBindingNames.module_derived_presence_method(variable)}") + for member in members: + field = member.field + entries.append(f"'{field.name}_get': {CBindingNames.module_member_method(variable, member, 'get')}") + if field.setter_action is SetterAction.WRITE_THROUGH: + entries.append(f"'{field.name}_set': {CBindingNames.module_member_method(variable, member, 'set')}") + return "{" + ", ".join(entries) + "}" diff --git a/prik/codegen/generator.py b/prik/codegen/generator.py index e02839754..d122ed47b 100644 --- a/prik/codegen/generator.py +++ b/prik/codegen/generator.py @@ -444,11 +444,23 @@ def _overload_cardinality_diagnostics( diagnostics.append(self._diagnostic(overload.owner_path, "empty-overload", overload.python_name)) expected = len(overload.candidates) for actual, code in ( + (len(overload.candidate_ids), "incomplete-overload-candidate-ids"), (len(overload.candidate_matches), "incomplete-overload-match-plan"), (len(overload.candidate_passed_objects), "incomplete-overload-call-plan"), ): if actual != expected: diagnostics.append(self._diagnostic(overload.owner_path, code, (expected, actual))) + if len(set(overload.candidate_ids)) != len(overload.candidate_ids): + diagnostics.append( + self._diagnostic(overload.owner_path, "duplicate-overload-candidate-id", overload.python_name) + ) + if any( + type(candidate_id) is not int or not 0 <= candidate_id <= 2_147_483_647 + for candidate_id in overload.candidate_ids + ): + diagnostics.append( + self._diagnostic(overload.owner_path, "invalid-overload-candidate-id", overload.candidate_ids) + ) return tuple(diagnostics) def _overload_signature_diagnostics( diff --git a/prik/codegen/nodes.py b/prik/codegen/nodes.py index 428808058..11fe4b65d 100644 --- a/prik/codegen/nodes.py +++ b/prik/codegen/nodes.py @@ -186,6 +186,22 @@ class CBreak(StageRecord): """C break statement.""" +@dataclass +class CCase(StageRecord): + """One value or default branch in a generated C switch.""" + + value: CodeExpression | None + body: tuple[CDeclaration | CExpressionStatement | CIf | CFor | CBreak | CReturn, ...] = () + + +@dataclass +class CSwitch(StageRecord): + """C switch statement used for planned integer dispatch keys.""" + + expression: CodeExpression + cases: tuple[CCase, ...] = () + + @dataclass class CReturn(StageRecord): """C return statement.""" @@ -201,7 +217,15 @@ class CFunction(StageRecord): return_type: str parameters: tuple[CParameter, ...] = () body: tuple[ - CDeclaration | CExpressionStatement | CAllowThreadsBegin | CAllowThreadsEnd | CIf | CFor | CBreak | CReturn, + CDeclaration + | CExpressionStatement + | CAllowThreadsBegin + | CAllowThreadsEnd + | CIf + | CFor + | CBreak + | CSwitch + | CReturn, ..., ] = () storage: str | None = None diff --git a/prik/codegen/overloads.py b/prik/codegen/overloads.py new file mode 100644 index 000000000..f882ff913 --- /dev/null +++ b/prik/codegen/overloads.py @@ -0,0 +1,27 @@ +"""Small shared queries over completed overload plans.""" + +from __future__ import annotations + +from prik.codegen.plan import FunctionPlan + + +class OverloadPlanQueries: + """Answer shared structural questions over completed overload candidates.""" + + @staticmethod + def receiver_name(candidate: FunctionPlan) -> str: + """Return the visible candidate argument receiving a class instance.""" + call = candidate.class_call + if call is None or call.passed_object_position is None: + raise ValueError(f"Overload candidate {candidate.owner_path!r} has no completed receiver position") + receiver = next( + ( + argument + for argument in candidate.arguments + if argument.native_position == call.passed_object_position and argument.python_visible + ), + None, + ) + if receiver is None: + raise ValueError(f"Overload candidate {candidate.owner_path!r} has no visible receiver argument") + return receiver.binding.python_name diff --git a/prik/codegen/plan.py b/prik/codegen/plan.py index 5d98b20c9..091b1e03a 100644 --- a/prik/codegen/plan.py +++ b/prik/codegen/plan.py @@ -309,14 +309,16 @@ class OverloadArgumentMatchPlan(StageRecord): class OverloadPlan(StageRecord): """Describe an exact-match overload and the function candidates it owns. - Candidate and match tuples remain parallel in planner order. Class and - namespace surfaces consume this record to emit one deterministic dispatch. + Candidate IDs, match tuples, and receiver flags remain parallel in planner + order. Class and namespace surfaces consume this record to emit one + deterministic dispatch. """ owner_path: str python_name: str kind: str candidates: tuple[FunctionPlan, ...] + candidate_ids: tuple[int, ...] candidate_matches: tuple[tuple[OverloadArgumentMatchPlan, ...], ...] candidate_passed_objects: tuple[bool, ...] unsupported_extra_argument_message: str | None = None diff --git a/prik/codegen/planner.py b/prik/codegen/planner.py index ac766ca09..d6fffff58 100644 --- a/prik/codegen/planner.py +++ b/prik/codegen/planner.py @@ -649,6 +649,7 @@ def _overload_plan( python_name=policy.python_name, kind=policy.kind, candidates=candidates, + candidate_ids=tuple(range(len(candidates))), candidate_matches=tuple( tuple( OverloadArgumentMatchPlan( diff --git a/prik/codegen/printers/source_printers.py b/prik/codegen/printers/source_printers.py index ecb53f099..1d9aab02a 100644 --- a/prik/codegen/printers/source_printers.py +++ b/prik/codegen/printers/source_printers.py @@ -17,6 +17,7 @@ CExpressionStatement, CFor, CBreak, + CCase, CFunction, CFunctionPointerType, CFunctionPrototype, @@ -33,6 +34,7 @@ CParameter, CReturn, CStructDefinition, + CSwitch, FortranAllocate, FortranAssignment, FortranCall, @@ -334,6 +336,21 @@ def _visit_CBreak(self, _node: CBreak) -> str: """Render one C loop-break statement.""" return "break;" + def _visit_CCase(self, node: CCase) -> str: + """Render one switch case with an explicit terminating branch body.""" + label = "default: {" if node.value is None else f"case {node.value.text}: {{" + lines = [label] + lines.extend(self._indented(self.visit(statement)) for statement in node.body) + lines.append("}") + return "\n".join(lines) + + def _visit_CSwitch(self, node: CSwitch) -> str: + """Render one integer-key switch and its ordered cases.""" + lines = [f"switch ({node.expression.text}) {{"] + lines.extend(self._indented(self.visit(case)) for case in node.cases) + lines.append("}") + return "\n".join(lines) + def _visit_CReturn(self, node: CReturn) -> str: """Render one C return with or without the node expression.""" if node.expression is None: diff --git a/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py b/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py index 574ed3ee0..29ece7423 100644 --- a/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py +++ b/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py @@ -7,7 +7,8 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.semantics.policy_completion import complete_semantic_policies from prik.semantics.wrapper_policy import OverloadMatchKind -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.codegen import CBindingGenerator, WrapperCodeGenerator, WrapperPlanner +from prik.codegen.c.naming import CBindingNames def _plan(): @@ -41,6 +42,38 @@ def test_plan_records_one_exact_numpy_scalar_predicate_per_candidate(): "Float64", ] assert [matches[0].rank for matches in overload.candidate_matches] == [0, 0] + assert overload.candidate_ids == (0, 1) + + +def test_binding_lowers_public_overload_to_candidate_id_switch(): + plan = _plan() + overload = plan.namespaces[0].overloads[0] + + artifacts = WrapperCodeGenerator().generate(plan) + c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") + dispatcher = CBindingNames.overload_dispatch_function(overload) + + assert f'{{"convert", (PyCFunction){dispatcher}, METH_VARARGS | METH_KEYWORDS' in c_source + assert "int candidate_id = -1;" in c_source + assert "switch (candidate_id)" in c_source + assert "case 0: {" in c_source + assert "case 1: {" in c_source + assert "wrap__prik_overload_convert_0(self, candidate_args, candidate_kwargs)" in c_source + assert "wrap__prik_overload_convert_1(self, candidate_args, candidate_kwargs)" in c_source + assert "PyRun_String" not in c_source + + +def test_binding_uses_numpy_bool_scalar_predicate_for_storage_specific_logicals(): + assert { + name: CBindingGenerator._overload_numpy_scalar_kind(name) + for name in ("Bool", "Bool8", "Bool16", "Bool32", "Bool64") + } == { + "Bool": "Bool", + "Bool8": "Bool", + "Bool16": "Bool", + "Bool32": "Bool", + "Bool64": "Bool", + } def test_generator_rejects_ambiguous_edited_overload_plan_before_emission(): @@ -57,6 +90,28 @@ def test_generator_rejects_ambiguous_edited_overload_plan_before_emission(): WrapperCodeGenerator().generate(invalid) +def test_generator_rejects_duplicate_candidate_ids_before_emission(): + plan = _plan() + namespace = plan.namespaces[0] + overload = namespace.overloads[0] + duplicate_ids = replace(overload, candidate_ids=(0, 0)) + invalid = replace(plan, namespaces=(replace(namespace, overloads=(duplicate_ids,)),)) + + with pytest.raises(ValueError, match="duplicate-overload-candidate-id"): + WrapperCodeGenerator().generate(invalid) + + +def test_generator_rejects_candidate_id_reserved_for_no_match(): + plan = _plan() + namespace = plan.namespaces[0] + overload = namespace.overloads[0] + invalid_ids = replace(overload, candidate_ids=(-1, 1)) + invalid = replace(plan, namespaces=(replace(namespace, overloads=(invalid_ids,)),)) + + with pytest.raises(ValueError, match="invalid-overload-candidate-id"): + WrapperCodeGenerator().generate(invalid) + + def test_generic_candidate_with_array_of_derived_values_is_blocked_before_lowering(): module = parse_pyi_text( """ diff --git a/tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py b/tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py index 29c44aca5..e4bf5409d 100644 --- a/tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py +++ b/tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py @@ -84,13 +84,14 @@ def test_fortran_generic_interfaces_dispatch_in_generated_c_extension( assert module.convert(np.int32(4)) == np.int32(14) assert module.convert(np.float64(4.0)) == np.float64(4.5) + assert module.convert(value=np.int32(5)) == np.int32(15) assert module.convert(np.complex128(2.0 + 3.0j)) == np.complex128(3.0 + 2.0j) assert module.summarize(np.float64(2.5)) == np.float64(2.5) assert module.summarize(np.array([1.0, 2.0, 3.0], dtype=np.float64)) == np.float64(6.0) value = module.accumulator() value.add(np.int32(2)) - value.add(np.float64(0.5)) + value.add(value=np.float64(0.5)) assert value.total == np.float64(2.5) assert module.inspect(value) == np.float64(2.5) @@ -100,6 +101,8 @@ def test_fortran_generic_interfaces_dispatch_in_generated_c_extension( with pytest.raises(TypeError): module.convert("not numeric") + with pytest.raises(TypeError, match="no matching overload for convert"): + module.convert(np.int32(1), value=np.int32(2)) with pytest.raises(TypeError): value.add(np.complex128(1.0 + 0.0j)) diff --git a/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/binding.c b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/binding.c new file mode 100644 index 000000000..e50b35d0c --- /dev/null +++ b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/binding.c @@ -0,0 +1,4316 @@ +#define PRIK_BINDING_IMPORT_ARRAY 1 + +#define PRIK_BINDING_NATIVE_ARRAY_ACTUAL 1 + +#include + +#include + +#include + +#include + +#include + +#include + +#include + +#include + +#include "binding_support/prik_binding.h" + +#include "refactoring_goldens_wrapper.h" + +typedef struct prik_callback_context_callback_83b3d1d9 { + PyObject * callable; + PyObject * module; + unsigned long thread_id; + struct prik_callback_context_callback_83b3d1d9 * previous; + PyObject * last_result; +} prik_callback_context_callback_83b3d1d9; + +static _Thread_local prik_callback_context_callback_83b3d1d9 * prik_callback_current_callback_83b3d1d9 = NULL; + +typedef int (*prik_derived_consumer_fn)(void *, void *); + +typedef int (*prik_derived_scoped_fn)(prik_derived_consumer_fn, void *); + +typedef int (*prik_derived_checkout_fn)(void **); + +typedef int (*prik_derived_restore_fn)(void *); + +typedef int (*prik_derived_present_fn)(void); + +typedef void * (*prik_derived_address_fn)(void); + +typedef struct prik_derived_origin_ops { + const char * type_symbol; + prik_derived_present_fn present; + prik_derived_address_fn address; + prik_derived_scoped_fn scoped; + prik_derived_checkout_fn checkout; + prik_derived_restore_fn restore; +} prik_derived_origin_ops; + +typedef struct prik_derived_call_case { + const char * origin; + int access; + const char * capsule_name; + int uses_ops; + int requires_present; + const char * failure_kind; + const char * failure_message; +} prik_derived_call_case; + +typedef struct prik_derived_alias_entry { + void * identity; + int writable; + const char * argument_name; +} prik_derived_alias_entry; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_summarize_item[] = {{"direct", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"allocatable_holder", 3, "prik.derived.vector.allocatable_holder", 0, 1, NULL, NULL}, {"pointer_holder", 4, "prik.derived.vector.pointer_holder", 0, 1, NULL, NULL}, {"module_proxy", 2, NULL, 1, 0, NULL, NULL}, {"module_target", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"module_allocatable", 2, NULL, 1, 1, NULL, NULL}, {"module_allocatable_target", 1, NULL, 1, 1, NULL, NULL}, {"module_pointer", 2, NULL, 1, 1, NULL, NULL}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_reset_allocatable_item_value[] = {{"direct", 0, "prik.derived.holder_item", 0, 0, "allocatable-derived-actual-required", "requires allocatable storage"}, {"allocatable_holder", 3, "prik.derived.holder_item.allocatable_holder", 0, 0, NULL, NULL}, {"pointer_holder", 0, "prik.derived.holder_item.pointer_holder", 0, 0, "allocatable-derived-actual-required", "requires allocatable storage"}, {"module_proxy", 0, NULL, 1, 0, "allocatable-derived-actual-required", "requires allocatable storage"}, {"module_target", 0, "prik.derived.holder_item", 0, 0, "allocatable-derived-actual-required", "requires allocatable storage"}, {"module_allocatable", 5, NULL, 1, 0, NULL, NULL}, {"module_allocatable_target", 5, NULL, 1, 0, NULL, NULL}, {"module_pointer", 0, NULL, 1, 0, "allocatable-derived-actual-required", "requires allocatable storage"}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_shift_pointer_item_value[] = {{"direct", 0, "prik.derived.holder_item", 0, 0, "pointer-derived-actual-required", "projected pointer association writeback requires pointer storage"}, {"allocatable_holder", 0, "prik.derived.holder_item.allocatable_holder", 0, 0, "pointer-derived-actual-required", "projected pointer association writeback requires pointer storage"}, {"pointer_holder", 4, "prik.derived.holder_item.pointer_holder", 0, 0, NULL, NULL}, {"module_proxy", 0, NULL, 1, 0, "pointer-derived-actual-required", "projected pointer association writeback requires pointer storage"}, {"module_target", 0, "prik.derived.holder_item", 0, 0, "pointer-derived-actual-required", "projected pointer association writeback requires pointer storage"}, {"module_allocatable", 0, NULL, 1, 0, "pointer-derived-actual-required", "projected pointer association writeback requires pointer storage"}, {"module_allocatable_target", 0, NULL, 1, 0, "pointer-derived-actual-required", "projected pointer association writeback requires pointer storage"}, {"module_pointer", 6, NULL, 1, 0, NULL, NULL}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_vector___method___scale_self[] = {{"direct", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"allocatable_holder", 3, "prik.derived.vector.allocatable_holder", 0, 1, NULL, NULL}, {"pointer_holder", 4, "prik.derived.vector.pointer_holder", 0, 1, NULL, NULL}, {"module_proxy", 2, NULL, 1, 0, NULL, NULL}, {"module_target", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"module_allocatable", 2, NULL, 1, 1, NULL, NULL}, {"module_allocatable_target", 1, NULL, 1, 1, NULL, NULL}, {"module_pointer", 2, NULL, 1, 1, NULL, NULL}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_vector___method___shift_owner[] = {{"direct", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"allocatable_holder", 3, "prik.derived.vector.allocatable_holder", 0, 1, NULL, NULL}, {"pointer_holder", 4, "prik.derived.vector.pointer_holder", 0, 1, NULL, NULL}, {"module_proxy", 2, NULL, 1, 0, NULL, NULL}, {"module_target", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"module_allocatable", 2, NULL, 1, 1, NULL, NULL}, {"module_allocatable_target", 1, NULL, 1, 1, NULL, NULL}, {"module_pointer", 2, NULL, 1, 1, NULL, NULL}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_vector___method___magnitude_self[] = {{"direct", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"allocatable_holder", 3, "prik.derived.vector.allocatable_holder", 0, 1, NULL, NULL}, {"pointer_holder", 4, "prik.derived.vector.pointer_holder", 0, 1, NULL, NULL}, {"module_proxy", 2, NULL, 1, 0, NULL, NULL}, {"module_target", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"module_allocatable", 2, NULL, 1, 1, NULL, NULL}, {"module_allocatable_target", 1, NULL, 1, 1, NULL, NULL}, {"module_pointer", 2, NULL, 1, 1, NULL, NULL}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_vector___method___replace_samples_self[] = {{"direct", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"allocatable_holder", 3, "prik.derived.vector.allocatable_holder", 0, 1, NULL, NULL}, {"pointer_holder", 4, "prik.derived.vector.pointer_holder", 0, 1, NULL, NULL}, {"module_proxy", 2, NULL, 1, 0, NULL, NULL}, {"module_target", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"module_allocatable", 2, NULL, 1, 1, NULL, NULL}, {"module_allocatable_target", 1, NULL, 1, 1, NULL, NULL}, {"module_pointer", 2, NULL, 1, 1, NULL, NULL}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_vector___add___add_vectors_left[] = {{"direct", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"allocatable_holder", 3, "prik.derived.vector.allocatable_holder", 0, 1, NULL, NULL}, {"pointer_holder", 4, "prik.derived.vector.pointer_holder", 0, 1, NULL, NULL}, {"module_proxy", 2, NULL, 1, 0, NULL, NULL}, {"module_target", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"module_allocatable", 2, NULL, 1, 1, NULL, NULL}, {"module_allocatable_target", 1, NULL, 1, 1, NULL, NULL}, {"module_pointer", 2, NULL, 1, 1, NULL, NULL}}; + +static const prik_derived_call_case prik_derived_cases_refactoring_goldens_vector___add___add_vectors_right[] = {{"direct", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"allocatable_holder", 3, "prik.derived.vector.allocatable_holder", 0, 1, NULL, NULL}, {"pointer_holder", 4, "prik.derived.vector.pointer_holder", 0, 1, NULL, NULL}, {"module_proxy", 2, NULL, 1, 0, NULL, NULL}, {"module_target", 1, "prik.derived.vector", 0, 0, NULL, NULL}, {"module_allocatable", 2, NULL, 1, 1, NULL, NULL}, {"module_allocatable_target", 1, NULL, 1, 1, NULL, NULL}, {"module_pointer", 2, NULL, 1, 1, NULL, NULL}}; + +bool bind_c_prik_origin_active_vector_26504a12_present(void); + +int bind_c_prik_origin_active_vector_26504a12_scoped(prik_derived_consumer_fn consumer, void * context); + +int bind_c_prik_origin_active_vector_26504a12_checkout(void ** holder); + +int bind_c_prik_origin_active_vector_26504a12_restore(void * holder); + +static int prik_origin_active_vector_26504a12_present(void); + +static int prik_origin_active_vector_26504a12_scoped(prik_derived_consumer_fn consumer, void * context); + +static int prik_origin_active_vector_26504a12_checkout(void ** holder); + +static int prik_origin_active_vector_26504a12_restore(void * holder); + +static PyObject * _prik_origin_active_vector_26504a12_native_ops(PyObject * self, PyObject * args); + +static atomic_bool prik_origin_active_vector_26504a12_active = false; + +static atomic_bool prik_origin_active_vector_26504a12_poisoned = false; + +static prik_derived_origin_ops prik_origin_active_vector_26504a12_ops = {"vector", prik_origin_active_vector_26504a12_present, NULL, prik_origin_active_vector_26504a12_scoped, prik_origin_active_vector_26504a12_checkout, prik_origin_active_vector_26504a12_restore}; + +bool bind_c_prik_origin_selected_vector_d2fd3c9d_present(void); + +int bind_c_prik_origin_selected_vector_d2fd3c9d_scoped(prik_derived_consumer_fn consumer, void * context); + +int bind_c_prik_origin_selected_vector_d2fd3c9d_checkout(void ** holder); + +int bind_c_prik_origin_selected_vector_d2fd3c9d_restore(void * holder); + +static int prik_origin_selected_vector_d2fd3c9d_present(void); + +static int prik_origin_selected_vector_d2fd3c9d_scoped(prik_derived_consumer_fn consumer, void * context); + +static int prik_origin_selected_vector_d2fd3c9d_checkout(void ** holder); + +static int prik_origin_selected_vector_d2fd3c9d_restore(void * holder); + +static PyObject * _prik_origin_selected_vector_d2fd3c9d_native_ops(PyObject * self, PyObject * args); + +static atomic_bool prik_origin_selected_vector_d2fd3c9d_active = false; + +static atomic_bool prik_origin_selected_vector_d2fd3c9d_poisoned = false; + +static prik_derived_origin_ops prik_origin_selected_vector_d2fd3c9d_ops = {"vector", prik_origin_selected_vector_d2fd3c9d_present, NULL, prik_origin_selected_vector_d2fd3c9d_scoped, prik_origin_selected_vector_d2fd3c9d_checkout, prik_origin_selected_vector_d2fd3c9d_restore}; + +int32_t bind_c_summarize(int32_t required, void * scale, void * values, int values_dense_actual, int64_t values_extent_0, int64_t values_upper_bound_0, int64_t values_stride_0, const char * label, int64_t label_length, void * item, int item_access, void * item_identity, prik_derived_scoped_fn item_scoped, prik_derived_checkout_fn item_checkout, prik_derived_restore_fn item_restore, int * item_status); + +void bind_c_make_values(int32_t count, double fill_value, CFI_cdesc_t * result); + +double bind_c_apply_callback(double value); + +void bind_c_split_value(double value, double * doubled, int32_t * status); + +void bind_c_reset_allocatable_item(void * value, int value_access, void * value_identity, prik_derived_scoped_fn value_scoped, prik_derived_checkout_fn value_checkout, prik_derived_restore_fn value_restore, int * value_status, void ** value_output, int * value_output_present); + +void bind_c_shift_pointer_item(void * value, int value_access, void * value_identity, prik_derived_scoped_fn value_scoped, prik_derived_checkout_fn value_checkout, prik_derived_restore_fn value_restore, int * value_status, void ** value_output, int * value_output_present, double amount); + +void bind_c__prik_class_vector_scale(void * self, int self_access, void * self_identity, int self_polymorphic, prik_derived_scoped_fn self_scoped, prik_derived_checkout_fn self_checkout, prik_derived_restore_fn self_restore, int * self_status, double factor); + +void bind_c__prik_class_vector_shift(double dx, void * owner, int owner_access, void * owner_identity, int owner_polymorphic, prik_derived_scoped_fn owner_scoped, prik_derived_checkout_fn owner_checkout, prik_derived_restore_fn owner_restore, int * owner_status, double dy); + +double bind_c__prik_class_vector_magnitude(void * self, int self_access, void * self_identity, int self_polymorphic, prik_derived_scoped_fn self_scoped, prik_derived_checkout_fn self_checkout, prik_derived_restore_fn self_restore, int * self_status); + +void bind_c__prik_class_vector_replace_samples(void * self, int self_access, void * self_identity, int self_polymorphic, prik_derived_scoped_fn self_scoped, prik_derived_checkout_fn self_checkout, prik_derived_restore_fn self_restore, int * self_status, void * values, int values_dense_actual, int64_t values_extent_0, int64_t values_upper_bound_0, int64_t values_stride_0); + +void * bind_c__prik_class_vector___add___0(void * left, int left_access, void * left_identity, int left_polymorphic, prik_derived_scoped_fn left_scoped, prik_derived_checkout_fn left_checkout, prik_derived_restore_fn left_restore, int * left_status, void * right, int right_access, void * right_identity, prik_derived_scoped_fn right_scoped, prik_derived_checkout_fn right_checkout, prik_derived_restore_fn right_restore, int * right_status); + +double bind_c__prik_overload_convert_0(int32_t value); + +int32_t bind_c__prik_overload_convert_1(double value); + +void * bind_c_prik_create_holder_item(void); + +static PyObject * _prik_create_holder_item(PyObject * self, PyObject * args); + +void * bind_c_prik_create_vector(void); + +static PyObject * _prik_create_vector(PyObject * self, PyObject * args); + +void bind_c_prik_destroy_holder_item(void * address); + +void bind_c_prik_destroy_vector(void * address); + +void bind_c_prik_destroy_holder_item_allocatable_holder(void * address); + +void bind_c_prik_destroy_holder_item_pointer_holder(void * address); + +bool bind_c_prik_holder_item_allocatable_holder_present(void * address); + +bool bind_c_prik_holder_item_pointer_holder_present(void * address); + +bool bind_c_owned_result_5531b6b6_allocated(CFI_cdesc_t * result); + +void bind_c_owned_result_5531b6b6_deallocate(CFI_cdesc_t * result); + +void bind_c_owned_result_5531b6b6_destroy(CFI_cdesc_t * result); + +void bind_c_owned_result_5531b6b6_shape(CFI_cdesc_t * result, int64_t * extent_0); + +int32_t bind_c_prik_field_holder_item_code_get(void * owner); + +void bind_c_prik_field_holder_item_code_set(void * owner, int32_t value); + +double bind_c_prik_field_holder_item_weight_get(void * owner); + +void bind_c_prik_field_holder_item_weight_set(void * owner, double value); + +double bind_c_prik_field_vector_x_get(void * owner); + +void bind_c_prik_field_vector_x_set(void * owner, double value); + +double bind_c_prik_field_vector_y_get(void * owner); + +void bind_c_prik_field_vector_y_set(void * owner, double value); + +bool bind_c_prik_field_handle_vector_samples_allocated(void * owner); + +void bind_c_prik_field_handle_vector_samples_deallocate(void * owner); + +void bind_c_prik_field_handle_vector_samples_descriptor(void * owner, void (*callback)(CFI_cdesc_t *, void *), void * context); + +void bind_c_prik_field_handle_vector_samples_resize(void * owner, int64_t extent_0); + +void bind_c_prik_field_handle_vector_samples_shape(void * owner, int64_t * extent_0); + +double bind_c_prik_module_field_active_vector_x_get(void); + +void bind_c_prik_module_field_active_vector_x_set(double value); + +double bind_c_prik_module_field_active_vector_y_get(void); + +void bind_c_prik_module_field_active_vector_y_set(double value); + +bool bind_c_prik_module_field_handle_active_vector_samples_allocated(void); + +void bind_c_prik_module_field_handle_active_vector_samples_deallocate(void); + +void bind_c_prik_module_field_handle_active_vector_samples_descriptor(void (*callback)(CFI_cdesc_t *, void *), void * context); + +void bind_c_prik_module_field_handle_active_vector_samples_resize(int64_t extent_0); + +void bind_c_prik_module_field_handle_active_vector_samples_shape(int64_t * extent_0); + +double bind_c_prik_module_field_selected_vector_x_get(void); + +void bind_c_prik_module_field_selected_vector_x_set(double value); + +double bind_c_prik_module_field_selected_vector_y_get(void); + +void bind_c_prik_module_field_selected_vector_y_set(double value); + +bool bind_c_prik_module_field_handle_selected_vector_samples_allocated(void); + +void bind_c_prik_module_field_handle_selected_vector_samples_deallocate(void); + +void bind_c_prik_module_field_handle_selected_vector_samples_descriptor(void (*callback)(CFI_cdesc_t *, void *), void * context); + +void bind_c_prik_module_field_handle_selected_vector_samples_resize(int64_t extent_0); + +void bind_c_prik_module_field_handle_selected_vector_samples_shape(int64_t * extent_0); + +int32_t bind_c_prik_allocatable_holder_field_holder_item_code_get(void * owner); + +void bind_c_prik_allocatable_holder_field_holder_item_code_set(void * owner, int32_t value); + +double bind_c_prik_allocatable_holder_field_holder_item_weight_get(void * owner); + +void bind_c_prik_allocatable_holder_field_holder_item_weight_set(void * owner, double value); + +int32_t bind_c_prik_pointer_holder_field_holder_item_code_get(void * owner_address); + +void bind_c_prik_pointer_holder_field_holder_item_code_set(void * owner_address, int32_t value); + +double bind_c_prik_pointer_holder_field_holder_item_weight_get(void * owner_address); + +void bind_c_prik_pointer_holder_field_holder_item_weight_set(void * owner_address, double value); + +static PyObject * _prik_field_holder_item_code_get(PyObject * self, PyObject * args); + +static PyObject * _prik_field_holder_item_code_set(PyObject * self, PyObject * args); + +static PyObject * _prik_field_holder_item_weight_get(PyObject * self, PyObject * args); + +static PyObject * _prik_field_holder_item_weight_set(PyObject * self, PyObject * args); + +static PyObject * _prik_field_vector_x_get(PyObject * self, PyObject * args); + +static PyObject * _prik_field_vector_x_set(PyObject * self, PyObject * args); + +static PyObject * _prik_field_vector_y_get(PyObject * self, PyObject * args); + +static PyObject * _prik_field_vector_y_set(PyObject * self, PyObject * args); + +static PyObject * _prik_field_vector_samples_get(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_active_vector_x_get(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_active_vector_x_set(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_active_vector_y_get(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_active_vector_y_set(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_active_vector_samples_get(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_selected_vector_x_get(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_selected_vector_x_set(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_selected_vector_y_get(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_selected_vector_y_set(PyObject * self, PyObject * args); + +static PyObject * _prik_module_field_selected_vector_samples_get(PyObject * self, PyObject * args); + +static PyObject * _prik_holder_item_allocatable_holder_require_present(PyObject * self, PyObject * args); + +static PyObject * _prik_allocatable_holder_field_holder_item_code_get(PyObject * self, PyObject * args); + +static PyObject * _prik_allocatable_holder_field_holder_item_code_set(PyObject * self, PyObject * args); + +static PyObject * _prik_allocatable_holder_field_holder_item_weight_get(PyObject * self, PyObject * args); + +static PyObject * _prik_allocatable_holder_field_holder_item_weight_set(PyObject * self, PyObject * args); + +static PyObject * _prik_holder_item_pointer_holder_require_present(PyObject * self, PyObject * args); + +static PyObject * _prik_pointer_holder_field_holder_item_code_get(PyObject * self, PyObject * args); + +static PyObject * _prik_pointer_holder_field_holder_item_code_set(PyObject * self, PyObject * args); + +static PyObject * _prik_pointer_holder_field_holder_item_weight_get(PyObject * self, PyObject * args); + +static PyObject * _prik_pointer_holder_field_holder_item_weight_set(PyObject * self, PyObject * args); + +static PyObject * _prik_module_active_vector_require_present(PyObject * self, PyObject * args); + +static PyObject * _prik_module_selected_vector_require_present(PyObject * self, PyObject * args); + +static PyObject * wrap__prik_dispatch_convert_d27e6413(PyObject * self, PyObject * args, PyObject * kwargs); + +static PyObject * wrap__prik_dispatch_add_eeb3bbc5(PyObject * self, PyObject * args, PyObject * kwargs); + +static void prik_field_handle_vector_samples_descriptor_callback(CFI_cdesc_t * descriptor, void * context); + +static void prik_field_handle_vector_samples_actual_callback(CFI_cdesc_t * descriptor, void * context); + +static PyObject * prik_field_handle_vector_samples_aligned(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_aligned_def = {"prik_field_handle_vector_samples_aligned", (PyCFunction)prik_field_handle_vector_samples_aligned, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_allocated(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_allocated_def = {"prik_field_handle_vector_samples_allocated", (PyCFunction)prik_field_handle_vector_samples_allocated, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_array_actual(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_array_actual_def = {"prik_field_handle_vector_samples_array_actual", (PyCFunction)prik_field_handle_vector_samples_array_actual, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_deallocate(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_deallocate_def = {"prik_field_handle_vector_samples_deallocate", (PyCFunction)prik_field_handle_vector_samples_deallocate, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_descriptor(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_descriptor_def = {"prik_field_handle_vector_samples_descriptor", (PyCFunction)prik_field_handle_vector_samples_descriptor, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_layout(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_layout_def = {"prik_field_handle_vector_samples_layout", (PyCFunction)prik_field_handle_vector_samples_layout, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_native_byte_order(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_native_byte_order_def = {"prik_field_handle_vector_samples_native_byte_order", (PyCFunction)prik_field_handle_vector_samples_native_byte_order, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_resize(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_resize_def = {"prik_field_handle_vector_samples_resize", (PyCFunction)prik_field_handle_vector_samples_resize, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_shape(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_shape_def = {"prik_field_handle_vector_samples_shape", (PyCFunction)prik_field_handle_vector_samples_shape, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_to_numpy(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_to_numpy_def = {"prik_field_handle_vector_samples_to_numpy", (PyCFunction)prik_field_handle_vector_samples_to_numpy, METH_VARARGS, ""}; + +static PyObject * prik_field_handle_vector_samples_writeable(PyObject * self, PyObject * args); + +static PyMethodDef prik_field_handle_vector_samples_writeable_def = {"prik_field_handle_vector_samples_writeable", (PyCFunction)prik_field_handle_vector_samples_writeable, METH_VARARGS, ""}; + +static void prik_module_field_handle_active_vector_samples_descriptor_callback(CFI_cdesc_t * descriptor, void * context); + +static void prik_module_field_handle_active_vector_samples_actual_callback(CFI_cdesc_t * descriptor, void * context); + +static PyObject * prik_module_field_handle_active_vector_samples_aligned(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_aligned_def = {"prik_module_field_handle_active_vector_samples_aligned", (PyCFunction)prik_module_field_handle_active_vector_samples_aligned, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_allocated(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_allocated_def = {"prik_module_field_handle_active_vector_samples_allocated", (PyCFunction)prik_module_field_handle_active_vector_samples_allocated, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_array_actual(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_array_actual_def = {"prik_module_field_handle_active_vector_samples_array_actual", (PyCFunction)prik_module_field_handle_active_vector_samples_array_actual, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_deallocate(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_deallocate_def = {"prik_module_field_handle_active_vector_samples_deallocate", (PyCFunction)prik_module_field_handle_active_vector_samples_deallocate, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_descriptor(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_descriptor_def = {"prik_module_field_handle_active_vector_samples_descriptor", (PyCFunction)prik_module_field_handle_active_vector_samples_descriptor, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_layout(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_layout_def = {"prik_module_field_handle_active_vector_samples_layout", (PyCFunction)prik_module_field_handle_active_vector_samples_layout, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_native_byte_order(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_native_byte_order_def = {"prik_module_field_handle_active_vector_samples_native_byte_order", (PyCFunction)prik_module_field_handle_active_vector_samples_native_byte_order, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_resize(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_resize_def = {"prik_module_field_handle_active_vector_samples_resize", (PyCFunction)prik_module_field_handle_active_vector_samples_resize, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_shape(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_shape_def = {"prik_module_field_handle_active_vector_samples_shape", (PyCFunction)prik_module_field_handle_active_vector_samples_shape, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_to_numpy(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_to_numpy_def = {"prik_module_field_handle_active_vector_samples_to_numpy", (PyCFunction)prik_module_field_handle_active_vector_samples_to_numpy, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_active_vector_samples_writeable(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_active_vector_samples_writeable_def = {"prik_module_field_handle_active_vector_samples_writeable", (PyCFunction)prik_module_field_handle_active_vector_samples_writeable, METH_VARARGS, ""}; + +static void prik_module_field_handle_selected_vector_samples_descriptor_callback(CFI_cdesc_t * descriptor, void * context); + +static void prik_module_field_handle_selected_vector_samples_actual_callback(CFI_cdesc_t * descriptor, void * context); + +static PyObject * prik_module_field_handle_selected_vector_samples_aligned(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_aligned_def = {"prik_module_field_handle_selected_vector_samples_aligned", (PyCFunction)prik_module_field_handle_selected_vector_samples_aligned, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_allocated(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_allocated_def = {"prik_module_field_handle_selected_vector_samples_allocated", (PyCFunction)prik_module_field_handle_selected_vector_samples_allocated, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_array_actual(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_array_actual_def = {"prik_module_field_handle_selected_vector_samples_array_actual", (PyCFunction)prik_module_field_handle_selected_vector_samples_array_actual, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_deallocate(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_deallocate_def = {"prik_module_field_handle_selected_vector_samples_deallocate", (PyCFunction)prik_module_field_handle_selected_vector_samples_deallocate, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_descriptor(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_descriptor_def = {"prik_module_field_handle_selected_vector_samples_descriptor", (PyCFunction)prik_module_field_handle_selected_vector_samples_descriptor, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_layout(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_layout_def = {"prik_module_field_handle_selected_vector_samples_layout", (PyCFunction)prik_module_field_handle_selected_vector_samples_layout, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_native_byte_order(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_native_byte_order_def = {"prik_module_field_handle_selected_vector_samples_native_byte_order", (PyCFunction)prik_module_field_handle_selected_vector_samples_native_byte_order, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_resize(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_resize_def = {"prik_module_field_handle_selected_vector_samples_resize", (PyCFunction)prik_module_field_handle_selected_vector_samples_resize, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_shape(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_shape_def = {"prik_module_field_handle_selected_vector_samples_shape", (PyCFunction)prik_module_field_handle_selected_vector_samples_shape, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_to_numpy(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_to_numpy_def = {"prik_module_field_handle_selected_vector_samples_to_numpy", (PyCFunction)prik_module_field_handle_selected_vector_samples_to_numpy, METH_VARARGS, ""}; + +static PyObject * prik_module_field_handle_selected_vector_samples_writeable(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_field_handle_selected_vector_samples_writeable_def = {"prik_module_field_handle_selected_vector_samples_writeable", (PyCFunction)prik_module_field_handle_selected_vector_samples_writeable, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_active_vector_derived_owner = NULL; + +static PyObject * prik_module_refactoring_goldens_selected_vector_derived_owner = NULL; + +int32_t bind_c_get_counter(void); + +void bind_c_set_counter(int32_t value); + +bool bind_c_workspace_allocated(void); + +void bind_c_workspace_array_actual(void (*callback)(CFI_cdesc_t *, void *), void * context); + +void bind_c_workspace_deallocate(void); + +void bind_c_workspace_descriptor(void (*callback)(CFI_cdesc_t *, void *), void * context); + +void bind_c_workspace_resize(int64_t extent_0); + +void bind_c_workspace_shape(int64_t * extent_0); + +void * bind_c_selected_array_actual(void); + +void bind_c_selected_associate(CFI_cdesc_t * source); + +bool bind_c_selected_associated(void); + +bool bind_c_selected_contiguous(void); + +void bind_c_selected_descriptor(CFI_cdesc_t * descriptor); + +void bind_c_selected_nullify(void); + +void bind_c_selected_shape(int64_t * extent_0); + +bool bind_c_prik_module_active_vector_present(void); + +bool bind_c_prik_module_selected_vector_present(void); + +static PyObject * module_get_counter(void); + +static int module_set_counter(PyObject * value_obj); + +static PyObject * module_get_workspace(void); + +static PyObject * module_get_selected(void); + +static PyObject * module_get_active_vector(void); + +static PyObject * module_get_selected_vector(void); + +static PyObject * prik_module_refactoring_goldens_workspace_handle = NULL; + +static PyObject * prik_module_refactoring_goldens_workspace_owner = NULL; + +static PyObject * prik_module_refactoring_goldens_workspace_aligned(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_aligned_def = {"prik_module_refactoring_goldens_workspace_aligned", (PyCFunction)prik_module_refactoring_goldens_workspace_aligned, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_allocated(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_allocated_def = {"prik_module_refactoring_goldens_workspace_allocated", (PyCFunction)prik_module_refactoring_goldens_workspace_allocated, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_array_actual(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_array_actual_def = {"prik_module_refactoring_goldens_workspace_array_actual", (PyCFunction)prik_module_refactoring_goldens_workspace_array_actual, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_deallocate(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_deallocate_def = {"prik_module_refactoring_goldens_workspace_deallocate", (PyCFunction)prik_module_refactoring_goldens_workspace_deallocate, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_descriptor(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_descriptor_def = {"prik_module_refactoring_goldens_workspace_descriptor", (PyCFunction)prik_module_refactoring_goldens_workspace_descriptor, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_layout(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_layout_def = {"prik_module_refactoring_goldens_workspace_layout", (PyCFunction)prik_module_refactoring_goldens_workspace_layout, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_native_byte_order(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_native_byte_order_def = {"prik_module_refactoring_goldens_workspace_native_byte_order", (PyCFunction)prik_module_refactoring_goldens_workspace_native_byte_order, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_resize(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_resize_def = {"prik_module_refactoring_goldens_workspace_resize", (PyCFunction)prik_module_refactoring_goldens_workspace_resize, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_shape(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_shape_def = {"prik_module_refactoring_goldens_workspace_shape", (PyCFunction)prik_module_refactoring_goldens_workspace_shape, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_to_numpy(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_to_numpy_def = {"prik_module_refactoring_goldens_workspace_to_numpy", (PyCFunction)prik_module_refactoring_goldens_workspace_to_numpy, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_workspace_writeable(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_workspace_writeable_def = {"prik_module_refactoring_goldens_workspace_writeable", (PyCFunction)prik_module_refactoring_goldens_workspace_writeable, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_handle = NULL; + +static PyObject * prik_module_refactoring_goldens_selected_owner = NULL; + +static PyObject * prik_module_refactoring_goldens_selected_aligned(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_aligned_def = {"prik_module_refactoring_goldens_selected_aligned", (PyCFunction)prik_module_refactoring_goldens_selected_aligned, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_array_actual(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_array_actual_def = {"prik_module_refactoring_goldens_selected_array_actual", (PyCFunction)prik_module_refactoring_goldens_selected_array_actual, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_associate(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_associate_def = {"prik_module_refactoring_goldens_selected_associate", (PyCFunction)prik_module_refactoring_goldens_selected_associate, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_associated(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_associated_def = {"prik_module_refactoring_goldens_selected_associated", (PyCFunction)prik_module_refactoring_goldens_selected_associated, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_contiguous(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_contiguous_def = {"prik_module_refactoring_goldens_selected_contiguous", (PyCFunction)prik_module_refactoring_goldens_selected_contiguous, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_descriptor(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_descriptor_def = {"prik_module_refactoring_goldens_selected_descriptor", (PyCFunction)prik_module_refactoring_goldens_selected_descriptor, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_layout(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_layout_def = {"prik_module_refactoring_goldens_selected_layout", (PyCFunction)prik_module_refactoring_goldens_selected_layout, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_native_byte_order(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_native_byte_order_def = {"prik_module_refactoring_goldens_selected_native_byte_order", (PyCFunction)prik_module_refactoring_goldens_selected_native_byte_order, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_nullify(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_nullify_def = {"prik_module_refactoring_goldens_selected_nullify", (PyCFunction)prik_module_refactoring_goldens_selected_nullify, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_shape(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_shape_def = {"prik_module_refactoring_goldens_selected_shape", (PyCFunction)prik_module_refactoring_goldens_selected_shape, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_to_numpy(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_to_numpy_def = {"prik_module_refactoring_goldens_selected_to_numpy", (PyCFunction)prik_module_refactoring_goldens_selected_to_numpy, METH_VARARGS, ""}; + +static PyObject * prik_module_refactoring_goldens_selected_writeable(PyObject * self, PyObject * args); + +static PyMethodDef prik_module_refactoring_goldens_selected_writeable_def = {"prik_module_refactoring_goldens_selected_writeable", (PyCFunction)prik_module_refactoring_goldens_selected_writeable, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_aligned(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_aligned_def = {"prik_owned_refactoring_goldens_make_values_return_aligned", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_aligned, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_allocated(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_allocated_def = {"prik_owned_refactoring_goldens_make_values_return_allocated", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_allocated, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_array_actual(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_array_actual_def = {"prik_owned_refactoring_goldens_make_values_return_array_actual", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_array_actual, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_deallocate(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_deallocate_def = {"prik_owned_refactoring_goldens_make_values_return_deallocate", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_deallocate, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_descriptor(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_descriptor_def = {"prik_owned_refactoring_goldens_make_values_return_descriptor", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_descriptor, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_destroy(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_destroy_def = {"prik_owned_refactoring_goldens_make_values_return_destroy", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_destroy, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_layout(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_layout_def = {"prik_owned_refactoring_goldens_make_values_return_layout", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_layout, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_native_byte_order(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_native_byte_order_def = {"prik_owned_refactoring_goldens_make_values_return_native_byte_order", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_native_byte_order, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_resize(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_resize_def = {"prik_owned_refactoring_goldens_make_values_return_resize", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_resize, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_shape(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_shape_def = {"prik_owned_refactoring_goldens_make_values_return_shape", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_shape, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_to_numpy(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_to_numpy_def = {"prik_owned_refactoring_goldens_make_values_return_to_numpy", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_to_numpy, METH_VARARGS, ""}; + +static PyObject * prik_owned_refactoring_goldens_make_values_return_writeable(PyObject * self, PyObject * args); + +static PyMethodDef prik_owned_refactoring_goldens_make_values_return_writeable_def = {"prik_owned_refactoring_goldens_make_values_return_writeable", (PyCFunction)prik_owned_refactoring_goldens_make_values_return_writeable, METH_VARARGS, ""}; + +static PyMethodDef refactoring_goldens_root_methods[] = { + {"summarize", (PyCFunction)wrap_summarize, METH_VARARGS | METH_KEYWORDS, "summarize(required, scale=..., values=..., label=..., item=...) -> int32\n\nParameters\n----------\nrequired : int32\nscale : int32 or None\n May be omitted or passed as None.\nvalues : ndarray[float64] or None\n Rank: 1\n Shape: (::Strided)\n May be omitted or passed as None.\n Ownership: Caller-owned.\nlabel : str or None\n May be omitted or passed as None.\nitem : vector or None\n May be omitted or passed as None.\n Ownership: Wrapper-owned.\n\nReturns\n-------\nresult : int32\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nValueError\n If rank, shape, layout, or descriptor state violates the contract.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"make_values", (PyCFunction)wrap_make_values, METH_VARARGS | METH_KEYWORDS, "make_values(count, fill_value) -> AllocatableArray[float64] | None\n\nParameters\n----------\ncount : int32\nfill_value : float64\n\nReturns\n-------\nresult : AllocatableArray[float64] or None\n Rank: 1\n Descriptor ownership: owned.\n Unallocated state remains inside the returned handle.\n Ownership: Wrapper-owned.\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nValueError\n If rank, shape, layout, or descriptor state violates the contract."}, + {"apply_callback", (PyCFunction)wrap_apply_callback, METH_VARARGS | METH_KEYWORDS, "apply_callback(callback, value) -> float64\n\nParameters\n----------\ncallback : scalar_callback\nvalue : float64\n\nReturns\n-------\nresult : float64\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype."}, + {"split_value", (PyCFunction)wrap_split_value, METH_VARARGS | METH_KEYWORDS, "split_value(value) -> tuple[float64, int32]\n\nParameters\n----------\nvalue : float64\n\nReturns\n-------\ndoubled : float64\nstatus : int32\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype."}, + {"reset_allocatable_item", (PyCFunction)wrap_reset_allocatable_item, METH_VARARGS | METH_KEYWORDS, "reset_allocatable_item(value) -> holder_item\n\nParameters\n----------\nvalue : holder_item or None\n Pass None for an unallocated or unassociated required descriptor.\n Native code may update this value; the updated value is returned.\n Ownership: Wrapper-owned.\n\nReturns\n-------\nvalue : holder_item\n Ownership: Wrapper-owned.\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"shift_pointer_item", (PyCFunction)wrap_shift_pointer_item, METH_VARARGS | METH_KEYWORDS, "shift_pointer_item(value, amount) -> holder_item\n\nParameters\n----------\nvalue : holder_item or None\n Pass None for an unallocated or unassociated required descriptor.\n Native code may update this value; the updated value is returned.\n Ownership: Wrapper-owned.\namount : float64\n\nReturns\n-------\nvalue : holder_item\n Ownership: Wrapper-owned.\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"_prik_class_vector_scale", (PyCFunction)wrap__prik_class_vector_scale, METH_VARARGS | METH_KEYWORDS, "_prik_class_vector_scale(self, factor) -> None\n\nParameters\n----------\nself : vector\n Native code may update the supplied storage in place.\n Ownership: Wrapper-owned.\nfactor : float64\n\nReturns\n-------\nNone\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"_prik_class_vector_shift", (PyCFunction)wrap__prik_class_vector_shift, METH_VARARGS | METH_KEYWORDS, "_prik_class_vector_shift(dx, owner, dy) -> None\n\nParameters\n----------\ndx : float64\nowner : vector\n Native code may update the supplied storage in place.\n Ownership: Wrapper-owned.\ndy : float64\n\nReturns\n-------\nNone\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"_prik_class_vector_magnitude", (PyCFunction)wrap__prik_class_vector_magnitude, METH_VARARGS | METH_KEYWORDS, "_prik_class_vector_magnitude(self) -> float64\n\nParameters\n----------\nself : vector\n Ownership: Wrapper-owned.\n\nReturns\n-------\nresult : float64\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"_prik_class_vector_replace_samples", (PyCFunction)wrap__prik_class_vector_replace_samples, METH_VARARGS | METH_KEYWORDS, "_prik_class_vector_replace_samples(self, values) -> None\n\nParameters\n----------\nself : vector\n Native code may update the supplied storage in place.\n Ownership: Wrapper-owned.\nvalues : ndarray[float64]\n Rank: 1\n Shape: (::Strided)\n Ownership: Caller-owned.\n\nReturns\n-------\nNone\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nValueError\n If rank, shape, layout, or descriptor state violates the contract.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"_prik_class_vector___add___0", (PyCFunction)wrap__prik_class_vector___add___0, METH_VARARGS | METH_KEYWORDS, "_prik_class_vector___add___0(left, right) -> vector\n\nParameters\n----------\nleft : vector\n Ownership: Wrapper-owned.\nright : vector\n Ownership: Wrapper-owned.\n\nReturns\n-------\nresult : vector\n Ownership: Wrapper-owned.\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype.\nRuntimeError\n If a derived-object transaction cannot be acquired or restored."}, + {"_prik_overload_convert_0", (PyCFunction)wrap__prik_overload_convert_0, METH_VARARGS | METH_KEYWORDS, "_prik_overload_convert_0(value) -> float64\n\nParameters\n----------\nvalue : int32\n\nReturns\n-------\nresult : float64\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype."}, + {"_prik_overload_convert_1", (PyCFunction)wrap__prik_overload_convert_1, METH_VARARGS | METH_KEYWORDS, "_prik_overload_convert_1(value) -> int32\n\nParameters\n----------\nvalue : float64\n\nReturns\n-------\nresult : int32\n\nRaises\n------\nTypeError\n If an argument has an incompatible Python type or dtype."}, + {"convert", (PyCFunction)wrap__prik_dispatch_convert_d27e6413, METH_VARARGS | METH_KEYWORDS, "convert(*args, **kwargs)\n\nSupported Signatures\n--------------------\nconvert(value: int32) -> float64\nconvert(value: float64) -> int32\n\nRaises\n------\nTypeError\n If no supported signature matches the supplied arguments."}, + {"_prik_dispatch_add_eeb3bbc5", (PyCFunction)wrap__prik_dispatch_add_eeb3bbc5, METH_VARARGS | METH_KEYWORDS, ""}, + {"_prik_create_holder_item", (PyCFunction)_prik_create_holder_item, METH_VARARGS, ""}, + {"_prik_create_vector", (PyCFunction)_prik_create_vector, METH_VARARGS, ""}, + {"_prik_field_holder_item_code_get", (PyCFunction)_prik_field_holder_item_code_get, METH_VARARGS, ""}, + {"_prik_field_holder_item_code_set", (PyCFunction)_prik_field_holder_item_code_set, METH_VARARGS, ""}, + {"_prik_field_holder_item_weight_get", (PyCFunction)_prik_field_holder_item_weight_get, METH_VARARGS, ""}, + {"_prik_field_holder_item_weight_set", (PyCFunction)_prik_field_holder_item_weight_set, METH_VARARGS, ""}, + {"_prik_field_vector_x_get", (PyCFunction)_prik_field_vector_x_get, METH_VARARGS, ""}, + {"_prik_field_vector_x_set", (PyCFunction)_prik_field_vector_x_set, METH_VARARGS, ""}, + {"_prik_field_vector_y_get", (PyCFunction)_prik_field_vector_y_get, METH_VARARGS, ""}, + {"_prik_field_vector_y_set", (PyCFunction)_prik_field_vector_y_set, METH_VARARGS, ""}, + {"_prik_field_vector_samples_get", (PyCFunction)_prik_field_vector_samples_get, METH_VARARGS, ""}, + {"_prik_module_field_active_vector_x_get", (PyCFunction)_prik_module_field_active_vector_x_get, METH_VARARGS, ""}, + {"_prik_module_field_active_vector_x_set", (PyCFunction)_prik_module_field_active_vector_x_set, METH_VARARGS, ""}, + {"_prik_module_field_active_vector_y_get", (PyCFunction)_prik_module_field_active_vector_y_get, METH_VARARGS, ""}, + {"_prik_module_field_active_vector_y_set", (PyCFunction)_prik_module_field_active_vector_y_set, METH_VARARGS, ""}, + {"_prik_module_field_active_vector_samples_get", (PyCFunction)_prik_module_field_active_vector_samples_get, METH_VARARGS, ""}, + {"_prik_module_field_selected_vector_x_get", (PyCFunction)_prik_module_field_selected_vector_x_get, METH_VARARGS, ""}, + {"_prik_module_field_selected_vector_x_set", (PyCFunction)_prik_module_field_selected_vector_x_set, METH_VARARGS, ""}, + {"_prik_module_field_selected_vector_y_get", (PyCFunction)_prik_module_field_selected_vector_y_get, METH_VARARGS, ""}, + {"_prik_module_field_selected_vector_y_set", (PyCFunction)_prik_module_field_selected_vector_y_set, METH_VARARGS, ""}, + {"_prik_module_field_selected_vector_samples_get", (PyCFunction)_prik_module_field_selected_vector_samples_get, METH_VARARGS, ""}, + {"_prik_allocatable_holder_field_holder_item_code_get", (PyCFunction)_prik_allocatable_holder_field_holder_item_code_get, METH_VARARGS, ""}, + {"_prik_allocatable_holder_field_holder_item_code_set", (PyCFunction)_prik_allocatable_holder_field_holder_item_code_set, METH_VARARGS, ""}, + {"_prik_allocatable_holder_field_holder_item_weight_get", (PyCFunction)_prik_allocatable_holder_field_holder_item_weight_get, METH_VARARGS, ""}, + {"_prik_allocatable_holder_field_holder_item_weight_set", (PyCFunction)_prik_allocatable_holder_field_holder_item_weight_set, METH_VARARGS, ""}, + {"_prik_holder_item_allocatable_holder_require_present", (PyCFunction)_prik_holder_item_allocatable_holder_require_present, METH_VARARGS, ""}, + {"_prik_pointer_holder_field_holder_item_code_get", (PyCFunction)_prik_pointer_holder_field_holder_item_code_get, METH_VARARGS, ""}, + {"_prik_pointer_holder_field_holder_item_code_set", (PyCFunction)_prik_pointer_holder_field_holder_item_code_set, METH_VARARGS, ""}, + {"_prik_pointer_holder_field_holder_item_weight_get", (PyCFunction)_prik_pointer_holder_field_holder_item_weight_get, METH_VARARGS, ""}, + {"_prik_pointer_holder_field_holder_item_weight_set", (PyCFunction)_prik_pointer_holder_field_holder_item_weight_set, METH_VARARGS, ""}, + {"_prik_holder_item_pointer_holder_require_present", (PyCFunction)_prik_holder_item_pointer_holder_require_present, METH_VARARGS, ""}, + {"_prik_module_active_vector_require_present", (PyCFunction)_prik_module_active_vector_require_present, METH_VARARGS, ""}, + {"_prik_module_selected_vector_require_present", (PyCFunction)_prik_module_selected_vector_require_present, METH_VARARGS, ""}, + {"_prik_origin_active_vector_26504a12_native_ops", (PyCFunction)_prik_origin_active_vector_26504a12_native_ops, METH_VARARGS, ""}, + {"_prik_origin_selected_vector_d2fd3c9d_native_ops", (PyCFunction)_prik_origin_selected_vector_d2fd3c9d_native_ops, METH_VARARGS, ""}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef refactoring_goldens_root_module = { + PyModuleDef_HEAD_INIT, + "refactoring_goldens", + "refactoring_goldens\n\nModule Attributes\n-----------------\ndefault_count : int32\n Read-only constant.\ncounter : int32\nworkspace : AllocatableArray[float64]\n Persistent allocatable descriptor handle.\n Replacement assignment is not supported.\nselected : PointerArray[float64]\n Persistent pointer descriptor handle.\n Replacement assignment is not supported.\nactive_vector : vector\n Live native module object.\n Replacement assignment is not supported.\nselected_vector : vector\n Live native module object.\n Replacement assignment is not supported.\n\nFunctions\n---------\nsummarize(required, scale=..., values=..., label=..., item=...) -> int32\nmake_values(count, fill_value) -> AllocatableArray[float64] | None\napply_callback(callback, value) -> float64\nsplit_value(value) -> tuple[float64, int32]\nreset_allocatable_item(value) -> holder_item\nshift_pointer_item(value, amount) -> holder_item\nconvert(*args, **kwargs)\n\nClasses\n-------\nholder_item\nvector", + 0, + refactoring_goldens_root_methods, +}; + +static PyObject *refactoring_goldens_root_module_property_setup_getattro(PyObject *self, PyObject *name) +{ + if (PyUnicode_Check(name)) { + { + int comparison = PyUnicode_CompareWithASCIIString(name, "counter"); + if (comparison == -1 && PyErr_Occurred()) return NULL; + if (comparison == 0) return module_get_counter(); + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "workspace"); + if (comparison == -1 && PyErr_Occurred()) return NULL; + if (comparison == 0) return module_get_workspace(); + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "selected"); + if (comparison == -1 && PyErr_Occurred()) return NULL; + if (comparison == 0) return module_get_selected(); + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "active_vector"); + if (comparison == -1 && PyErr_Occurred()) return NULL; + if (comparison == 0) return module_get_active_vector(); + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "selected_vector"); + if (comparison == -1 && PyErr_Occurred()) return NULL; + if (comparison == 0) return module_get_selected_vector(); + } + } + return PyModule_Type.tp_getattro(self, name); +} + +static int refactoring_goldens_root_module_property_setup_setattro(PyObject *self, PyObject *name, PyObject *value) +{ + if (PyUnicode_Check(name)) { + { + int comparison = PyUnicode_CompareWithASCIIString(name, "counter"); + if (comparison == -1 && PyErr_Occurred()) return -1; + if (comparison == 0) { + if (value == NULL) { + PyErr_SetString(PyExc_AttributeError, "module variable counter cannot be deleted"); + return -1; + } + return module_set_counter(value); + } + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "workspace"); + if (comparison == -1 && PyErr_Occurred()) return -1; + if (comparison == 0) { + PyErr_SetString(PyExc_AttributeError, "module variable workspace is read-only"); + return -1; + } + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "selected"); + if (comparison == -1 && PyErr_Occurred()) return -1; + if (comparison == 0) { + PyErr_SetString(PyExc_AttributeError, "module variable selected is read-only"); + return -1; + } + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "active_vector"); + if (comparison == -1 && PyErr_Occurred()) return -1; + if (comparison == 0) { + PyErr_SetString(PyExc_AttributeError, "module variable active_vector is read-only"); + return -1; + } + } + { + int comparison = PyUnicode_CompareWithASCIIString(name, "selected_vector"); + if (comparison == -1 && PyErr_Occurred()) return -1; + if (comparison == 0) { + PyErr_SetString(PyExc_AttributeError, "module variable selected_vector is read-only"); + return -1; + } + } + } + return PyModule_Type.tp_setattro(self, name, value); +} + +static PyType_Slot refactoring_goldens_root_module_property_setup_slots[] = { + {Py_tp_getattro, (void *)refactoring_goldens_root_module_property_setup_getattro}, + {Py_tp_setattro, (void *)refactoring_goldens_root_module_property_setup_setattro}, + {0, NULL} +}; +static PyType_Spec refactoring_goldens_root_module_property_setup_spec = { + "refactoring_goldens.__prik_module_type", + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + refactoring_goldens_root_module_property_setup_slots +}; +static int refactoring_goldens_root_module_property_setup(PyObject *module) +{ + PyObject *bases = PyTuple_Pack(1, (PyObject *)&PyModule_Type); + if (bases == NULL) return -1; + PyObject *module_type = PyType_FromSpecWithBases(&refactoring_goldens_root_module_property_setup_spec, bases); + Py_DECREF(bases); + if (module_type == NULL) return -1; + int status = PyObject_SetAttrString(module, "__class__", module_type); + Py_DECREF(module_type); + return status; +} + +void * prik_malloc(size_t size) { + const char * fail_alloc = getenv("PRIK_WRAPPER_FAIL_ALLOC"); + if (fail_alloc != NULL && fail_alloc[0] != '\0' && fail_alloc[0] != '0') { + return NULL; + } + return malloc(size == 0 ? 1 : size); +} + +static void prik_callback_abort_callback_83b3d1d9(const char * message) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, message); + } + PyErr_PrintEx(0); + abort(); +} + +double prik_callback_trampoline_callback_83b3d1d9(void * value_data) { + prik_callback_context_callback_83b3d1d9 * callback_context = prik_callback_current_callback_83b3d1d9; + if (callback_context == NULL || callback_context->thread_id != PyThread_get_thread_ident()) { + PyGILState_Ensure(); + PyErr_SetString(PyExc_RuntimeError, "callback invoked outside its entering Python thread"); + prik_callback_abort_callback_83b3d1d9("callback thread violation"); + } + PyGILState_STATE callback_gil = PyGILState_Ensure(); + PyObject * callback_args = PyTuple_New(1); + if (callback_args == NULL) { + prik_callback_abort_callback_83b3d1d9("failed to allocate callback arguments"); + } + PyObject * callback_arg_0 = prik_float64_to_numpy(value_data); + if (callback_arg_0 == NULL) { + prik_callback_abort_callback_83b3d1d9("failed to convert callback argument"); + } + PyTuple_SET_ITEM(callback_args, 0, callback_arg_0); + PyObject * callback_result = PyObject_CallObject(callback_context->callable, callback_args); + Py_DECREF(callback_args); + if (callback_result == NULL) { + prik_callback_abort_callback_83b3d1d9("Python callback raised an exception"); + } + double callback_value; + if (prik_float64_unpack(callback_result, &callback_value) < 0) { + prik_callback_abort_callback_83b3d1d9("invalid callback return value"); + } + Py_DECREF(callback_result); + PyGILState_Release(callback_gil); + return callback_value; +} + +static int prik_extract_derived_argument(PyObject * object, const char * type_name, const char * type_symbol, const char * direct_capsule_name, const char * argument_name, const prik_derived_call_case * cases, size_t case_count, void ** carrier, int * access, prik_derived_origin_ops ** ops) { + *carrier = NULL; + *access = 0; + *ops = NULL; + PyObject * origin_object = PyObject_GetAttrString(object, "_prik_origin"); + if (origin_object == NULL) { + PyErr_Clear(); + PyErr_Format(PyExc_TypeError, "Expected exact wrapper type %s for argument %s", type_name, argument_name); + return -1; + } + const char * origin = PyUnicode_AsUTF8(origin_object); + if (origin == NULL) { + Py_DECREF(origin_object); + return -1; + } + const prik_derived_call_case * selected = NULL; + for (size_t index = 0; index < case_count; ++index) { + if (strcmp(origin, cases[index].origin) == 0) { + selected = &cases[index]; + break; + } + } + if (selected == NULL) { + Py_DECREF(origin_object); + PyErr_Format(PyExc_TypeError, "Unknown native origin %s for argument %s", origin, argument_name); + return -1; + } + if (selected->access == 0) { + PyErr_Format(PyExc_TypeError, "%s: %s", selected->failure_kind, selected->failure_message); + Py_DECREF(origin_object); + return -1; + } + if (selected->uses_ops) { + PyObject * operation_map = PyObject_GetAttrString(object, "_prik_ops"); + if (operation_map == NULL) { + Py_DECREF(origin_object); + return -1; + } + PyObject * ops_capsule = PyDict_GetItemString(operation_map, "_native_ops"); + if (ops_capsule == NULL) { + Py_DECREF(operation_map); + Py_DECREF(origin_object); + PyErr_Format(PyExc_TypeError, "module origin for argument %s has no native operations", argument_name); + return -1; + } + *ops = (prik_derived_origin_ops *)PyCapsule_GetPointer(ops_capsule, "prik.derived_origin_ops"); + Py_DECREF(operation_map); + if (*ops == NULL) { + Py_DECREF(origin_object); + return -1; + } + if (selected->access == 1) { + if ((*ops)->address == NULL) { + Py_DECREF(origin_object); + PyErr_Format(PyExc_RuntimeError, "module origin for argument %s has no address operation", argument_name); + return -1; + } + *carrier = (*ops)->address(); + } + } else { + const char * capsule_name = selected->access == 1 ? direct_capsule_name : selected->capsule_name; + PyObject * carrier_capsule = PyObject_GetAttrString(object, "_prik_capsule"); + if (carrier_capsule == NULL) { + Py_DECREF(origin_object); + return -1; + } + if (!PyCapsule_IsValid(carrier_capsule, capsule_name)) { + Py_DECREF(carrier_capsule); + Py_DECREF(origin_object); + PyErr_Format(PyExc_TypeError, "Expected exact wrapper type %s for argument %s", type_name, argument_name); + return -1; + } + *carrier = PyCapsule_GetPointer(carrier_capsule, capsule_name); + Py_DECREF(carrier_capsule); + if (*carrier == NULL) { + Py_DECREF(origin_object); + return -1; + } + } + if (*ops != NULL && ((*ops)->type_symbol == NULL || strcmp((*ops)->type_symbol, type_symbol) != 0)) { + Py_DECREF(origin_object); + PyErr_Format(PyExc_TypeError, "Expected exact wrapper type %s for argument %s", type_name, argument_name); + return -1; + } + if (selected->requires_present && *ops != NULL && (*ops)->present != NULL && !(*ops)->present()) { + Py_DECREF(origin_object); + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", argument_name); + return -1; + } + if (selected->requires_present && selected->access == 1 && *carrier == NULL) { + Py_DECREF(origin_object); + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", argument_name); + return -1; + } + *access = selected->access; + Py_DECREF(origin_object); + return 0; +} + +static int prik_validate_derived_aliases(const prik_derived_alias_entry * entries, size_t count) { + for (size_t left = 0; left < count; ++left) { + if (entries[left].identity != NULL) { + for (size_t right = left + 1; right < count; ++right) { + if (entries[left].identity == entries[right].identity && (entries[left].writable || entries[right].writable)) { + PyErr_Format(PyExc_TypeError, "derived origin is repeated in writable arguments %s and %s", entries[left].argument_name, entries[right].argument_name); + return -1; + } + } + } + } + return 0; +} + +static int prik_origin_active_vector_26504a12_present(void) { + return bind_c_prik_origin_active_vector_26504a12_present() ? 1 : 0; +} + +static int prik_origin_active_vector_26504a12_scoped(prik_derived_consumer_fn consumer, void * context) { + const char * prik_derived_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_ORIGIN"); + if (prik_derived_fault != NULL && strcmp(prik_derived_fault, "scoped:before:active_vector") == 0) { + return 7; + } + if (atomic_load(&prik_origin_active_vector_26504a12_poisoned)) { + return 3; + } + bool expected = false; + if (!atomic_compare_exchange_strong(&prik_origin_active_vector_26504a12_active, &expected, true)) { + return 2; + } + int status = bind_c_prik_origin_active_vector_26504a12_scoped(consumer, context); + if (status == 0 && prik_derived_fault != NULL && strcmp(prik_derived_fault, "scoped:after:active_vector") == 0) { + status = 7; + } + atomic_store(&prik_origin_active_vector_26504a12_active, false); + return status; +} + +static int prik_origin_active_vector_26504a12_checkout(void ** holder) { + const char * prik_derived_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_ORIGIN"); + if (prik_derived_fault != NULL && strcmp(prik_derived_fault, "checkout:before:active_vector") == 0) { + return 7; + } + if (atomic_load(&prik_origin_active_vector_26504a12_poisoned)) { + return 3; + } + bool expected = false; + if (!atomic_compare_exchange_strong(&prik_origin_active_vector_26504a12_active, &expected, true)) { + return 2; + } + int status = bind_c_prik_origin_active_vector_26504a12_checkout(holder); + if (status != 0) { + atomic_store(&prik_origin_active_vector_26504a12_active, false); + } + return status; +} + +static int prik_origin_active_vector_26504a12_restore(void * holder) { + const char * prik_derived_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_ORIGIN"); + if (!atomic_load(&prik_origin_active_vector_26504a12_active)) { + return 6; + } + int status = bind_c_prik_origin_active_vector_26504a12_restore(holder); + if (status == 0 && prik_derived_fault != NULL && strcmp(prik_derived_fault, "restore:after:active_vector") == 0) { + status = 7; + } + if (status != 0) { + atomic_store(&prik_origin_active_vector_26504a12_poisoned, true); + } + atomic_store(&prik_origin_active_vector_26504a12_active, false); + return status; +} + +static PyObject * _prik_origin_active_vector_26504a12_native_ops(PyObject * self, PyObject * args) { + return PyCapsule_New((void *)&prik_origin_active_vector_26504a12_ops, "prik.derived_origin_ops", NULL); +} + +static int prik_origin_selected_vector_d2fd3c9d_present(void) { + return bind_c_prik_origin_selected_vector_d2fd3c9d_present() ? 1 : 0; +} + +static int prik_origin_selected_vector_d2fd3c9d_scoped(prik_derived_consumer_fn consumer, void * context) { + const char * prik_derived_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_ORIGIN"); + if (prik_derived_fault != NULL && strcmp(prik_derived_fault, "scoped:before:selected_vector") == 0) { + return 7; + } + if (atomic_load(&prik_origin_selected_vector_d2fd3c9d_poisoned)) { + return 3; + } + bool expected = false; + if (!atomic_compare_exchange_strong(&prik_origin_selected_vector_d2fd3c9d_active, &expected, true)) { + return 2; + } + int status = bind_c_prik_origin_selected_vector_d2fd3c9d_scoped(consumer, context); + if (status == 0 && prik_derived_fault != NULL && strcmp(prik_derived_fault, "scoped:after:selected_vector") == 0) { + status = 7; + } + atomic_store(&prik_origin_selected_vector_d2fd3c9d_active, false); + return status; +} + +static int prik_origin_selected_vector_d2fd3c9d_checkout(void ** holder) { + const char * prik_derived_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_ORIGIN"); + if (prik_derived_fault != NULL && strcmp(prik_derived_fault, "checkout:before:selected_vector") == 0) { + return 7; + } + if (atomic_load(&prik_origin_selected_vector_d2fd3c9d_poisoned)) { + return 3; + } + bool expected = false; + if (!atomic_compare_exchange_strong(&prik_origin_selected_vector_d2fd3c9d_active, &expected, true)) { + return 2; + } + int status = bind_c_prik_origin_selected_vector_d2fd3c9d_checkout(holder); + if (status != 0) { + atomic_store(&prik_origin_selected_vector_d2fd3c9d_active, false); + } + return status; +} + +static int prik_origin_selected_vector_d2fd3c9d_restore(void * holder) { + const char * prik_derived_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_ORIGIN"); + if (!atomic_load(&prik_origin_selected_vector_d2fd3c9d_active)) { + return 6; + } + int status = bind_c_prik_origin_selected_vector_d2fd3c9d_restore(holder); + if (status == 0 && prik_derived_fault != NULL && strcmp(prik_derived_fault, "restore:after:selected_vector") == 0) { + status = 7; + } + if (status != 0) { + atomic_store(&prik_origin_selected_vector_d2fd3c9d_poisoned, true); + } + atomic_store(&prik_origin_selected_vector_d2fd3c9d_active, false); + return status; +} + +static PyObject * _prik_origin_selected_vector_d2fd3c9d_native_ops(PyObject * self, PyObject * args) { + return PyCapsule_New((void *)&prik_origin_selected_vector_d2fd3c9d_ops, "prik.derived_origin_ops", NULL); +} + +static void prik_destroy_holder_item_capsule(PyObject * capsule) { + void * address = PyCapsule_GetPointer(capsule, "prik.derived.holder_item"); + if (address != NULL) { + bind_c_prik_destroy_holder_item(address); + } else { + PyErr_Clear(); + } +} + +static void prik_destroy_vector_capsule(PyObject * capsule) { + void * address = PyCapsule_GetPointer(capsule, "prik.derived.vector"); + if (address != NULL) { + bind_c_prik_destroy_vector(address); + } else { + PyErr_Clear(); + } +} + +static void prik_destroy_holder_item_allocatable_holder_capsule(PyObject * capsule) { + void * address = PyCapsule_GetPointer(capsule, "prik.derived.holder_item.allocatable_holder"); + if (address != NULL) { + bind_c_prik_destroy_holder_item_allocatable_holder(address); + } else { + PyErr_Clear(); + } +} + +static void prik_destroy_holder_item_pointer_holder_capsule(PyObject * capsule) { + void * address = PyCapsule_GetPointer(capsule, "prik.derived.holder_item.pointer_holder"); + if (address != NULL) { + bind_c_prik_destroy_holder_item_pointer_holder(address); + } else { + PyErr_Clear(); + } +} + +static PyObject * _prik_create_holder_item(PyObject * self, PyObject * args) { + if (!PyArg_ParseTuple(args, "")) { + return NULL; + } + void * address = bind_c_prik_create_holder_item(); + if (address == NULL) { + PyErr_NoMemory(); + return NULL; + } + PyObject * capsule = PyCapsule_New(address, "prik.derived.holder_item", prik_destroy_holder_item_capsule); + if (capsule == NULL) { + bind_c_prik_destroy_holder_item(address); + return NULL; + } + PyObject * wrapper_helper = PyObject_GetAttrString(self, "_prik_wrap_holder_item"); + if (wrapper_helper == NULL) { + Py_DECREF(capsule); + return NULL; + } + PyObject * result = PyObject_CallFunctionObjArgs(wrapper_helper, capsule, NULL); + Py_DECREF(wrapper_helper); + Py_DECREF(capsule); + return result; +} + +static PyObject * _prik_create_vector(PyObject * self, PyObject * args) { + if (!PyArg_ParseTuple(args, "")) { + return NULL; + } + void * address = bind_c_prik_create_vector(); + if (address == NULL) { + PyErr_NoMemory(); + return NULL; + } + PyObject * capsule = PyCapsule_New(address, "prik.derived.vector", prik_destroy_vector_capsule); + if (capsule == NULL) { + bind_c_prik_destroy_vector(address); + return NULL; + } + PyObject * wrapper_helper = PyObject_GetAttrString(self, "_prik_wrap_vector"); + if (wrapper_helper == NULL) { + Py_DECREF(capsule); + return NULL; + } + PyObject * result = PyObject_CallFunctionObjArgs(wrapper_helper, capsule, NULL); + Py_DECREF(wrapper_helper); + Py_DECREF(capsule); + return result; +} + +static PyObject * _prik_field_holder_item_code_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + int32_t value = bind_c_prik_field_holder_item_code_get(owner_address); + return prik_int32_to_numpy(&value); +} + +static PyObject * _prik_field_holder_item_code_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + int32_t value; + if (prik_int32_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.int32 for field code. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_field_holder_item_code_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_field_holder_item_weight_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value = bind_c_prik_field_holder_item_weight_get(owner_address); + return prik_float64_to_numpy(&value); +} + +static PyObject * _prik_field_holder_item_weight_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field weight. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_field_holder_item_weight_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_field_vector_x_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value = bind_c_prik_field_vector_x_get(owner_address); + return prik_float64_to_numpy(&value); +} + +static PyObject * _prik_field_vector_x_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field x. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_field_vector_x_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_field_vector_y_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value = bind_c_prik_field_vector_y_get(owner_address); + return prik_float64_to_numpy(&value); +} + +static PyObject * _prik_field_vector_y_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field y. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_field_vector_y_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_field_vector_samples_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * refactoring_goldens_vector_samples_ops = PyDict_New(); + PyObject * refactoring_goldens_vector_samples_operation = NULL; + PyObject * refactoring_goldens_vector_samples_runtime = NULL; + PyObject * refactoring_goldens_vector_samples_helper = NULL; + PyObject * refactoring_goldens_vector_samples_handle = NULL; + if (refactoring_goldens_vector_samples_ops == NULL) { + return NULL; + } + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_aligned_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "aligned", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_allocated_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "allocated", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_array_actual_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "array_actual", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_deallocate_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "deallocate", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_descriptor_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "descriptor", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_layout_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "layout", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_native_byte_order_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "native_byte_order", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_resize_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "resize", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_shape_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "shape", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_to_numpy_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "to_numpy", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_field_handle_vector_samples_writeable_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "writeable", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_runtime = PyImport_ImportModule("prik.runtime.handles"); + if (refactoring_goldens_vector_samples_runtime == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + refactoring_goldens_vector_samples_helper = PyObject_GetAttrString(refactoring_goldens_vector_samples_runtime, "_native_array_handle_from_generated_ops"); + Py_DECREF(refactoring_goldens_vector_samples_runtime); + if (refactoring_goldens_vector_samples_helper == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + refactoring_goldens_vector_samples_handle = PyObject_CallFunction(refactoring_goldens_vector_samples_helper, "ssiOOssO", "allocatable", "float64", 1, refactoring_goldens_vector_samples_ops, owner_obj, "borrowed", "borrowed_view", Py_None); + Py_DECREF(refactoring_goldens_vector_samples_helper); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return refactoring_goldens_vector_samples_handle; +} + +static PyObject * _prik_module_field_active_vector_x_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + double value = bind_c_prik_module_field_active_vector_x_get(); + return prik_float64_to_numpy(&value); +} + +static PyObject * _prik_module_field_active_vector_x_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field x. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_module_field_active_vector_x_set(value); + Py_RETURN_NONE; +} + +static PyObject * _prik_module_field_active_vector_y_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + double value = bind_c_prik_module_field_active_vector_y_get(); + return prik_float64_to_numpy(&value); +} + +static PyObject * _prik_module_field_active_vector_y_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field y. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_module_field_active_vector_y_set(value); + Py_RETURN_NONE; +} + +static PyObject * _prik_module_field_active_vector_samples_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * refactoring_goldens_vector_samples_ops = PyDict_New(); + PyObject * refactoring_goldens_vector_samples_operation = NULL; + PyObject * refactoring_goldens_vector_samples_runtime = NULL; + PyObject * refactoring_goldens_vector_samples_helper = NULL; + PyObject * refactoring_goldens_vector_samples_handle = NULL; + if (refactoring_goldens_vector_samples_ops == NULL) { + return NULL; + } + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_aligned_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "aligned", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_allocated_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "allocated", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_array_actual_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "array_actual", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_deallocate_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "deallocate", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_descriptor_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "descriptor", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_layout_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "layout", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_native_byte_order_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "native_byte_order", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_resize_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "resize", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_shape_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "shape", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_to_numpy_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "to_numpy", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_active_vector_samples_writeable_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "writeable", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_runtime = PyImport_ImportModule("prik.runtime.handles"); + if (refactoring_goldens_vector_samples_runtime == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + refactoring_goldens_vector_samples_helper = PyObject_GetAttrString(refactoring_goldens_vector_samples_runtime, "_native_array_handle_from_generated_ops"); + Py_DECREF(refactoring_goldens_vector_samples_runtime); + if (refactoring_goldens_vector_samples_helper == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + refactoring_goldens_vector_samples_handle = PyObject_CallFunction(refactoring_goldens_vector_samples_helper, "ssiOOssO", "allocatable", "float64", 1, refactoring_goldens_vector_samples_ops, owner_obj, "borrowed", "borrowed_view", Py_None); + Py_DECREF(refactoring_goldens_vector_samples_helper); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return refactoring_goldens_vector_samples_handle; +} + +static PyObject * _prik_module_field_selected_vector_x_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + double value = bind_c_prik_module_field_selected_vector_x_get(); + return prik_float64_to_numpy(&value); +} + +static PyObject * _prik_module_field_selected_vector_x_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field x. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_module_field_selected_vector_x_set(value); + Py_RETURN_NONE; +} + +static PyObject * _prik_module_field_selected_vector_y_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + double value = bind_c_prik_module_field_selected_vector_y_get(); + return prik_float64_to_numpy(&value); +} + +static PyObject * _prik_module_field_selected_vector_y_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field y. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_module_field_selected_vector_y_set(value); + Py_RETURN_NONE; +} + +static PyObject * _prik_module_field_selected_vector_samples_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * refactoring_goldens_vector_samples_ops = PyDict_New(); + PyObject * refactoring_goldens_vector_samples_operation = NULL; + PyObject * refactoring_goldens_vector_samples_runtime = NULL; + PyObject * refactoring_goldens_vector_samples_helper = NULL; + PyObject * refactoring_goldens_vector_samples_handle = NULL; + if (refactoring_goldens_vector_samples_ops == NULL) { + return NULL; + } + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_aligned_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "aligned", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_allocated_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "allocated", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_array_actual_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "array_actual", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_deallocate_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "deallocate", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_descriptor_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "descriptor", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_layout_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "layout", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_native_byte_order_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "native_byte_order", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_resize_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "resize", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_shape_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "shape", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_to_numpy_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "to_numpy", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_operation = PyCFunction_NewEx(&prik_module_field_handle_selected_vector_samples_writeable_def, owner_obj, NULL); + if (refactoring_goldens_vector_samples_operation == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + if (PyDict_SetItemString(refactoring_goldens_vector_samples_ops, "writeable", refactoring_goldens_vector_samples_operation) < 0) { + Py_DECREF(refactoring_goldens_vector_samples_operation); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + Py_DECREF(refactoring_goldens_vector_samples_operation); + refactoring_goldens_vector_samples_runtime = PyImport_ImportModule("prik.runtime.handles"); + if (refactoring_goldens_vector_samples_runtime == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + refactoring_goldens_vector_samples_helper = PyObject_GetAttrString(refactoring_goldens_vector_samples_runtime, "_native_array_handle_from_generated_ops"); + Py_DECREF(refactoring_goldens_vector_samples_runtime); + if (refactoring_goldens_vector_samples_helper == NULL) { + Py_DECREF(refactoring_goldens_vector_samples_ops); + return NULL; + } + refactoring_goldens_vector_samples_handle = PyObject_CallFunction(refactoring_goldens_vector_samples_helper, "ssiOOssO", "allocatable", "float64", 1, refactoring_goldens_vector_samples_ops, owner_obj, "borrowed", "borrowed_view", Py_None); + Py_DECREF(refactoring_goldens_vector_samples_helper); + Py_DECREF(refactoring_goldens_vector_samples_ops); + return refactoring_goldens_vector_samples_handle; +} + +static PyObject * _prik_holder_item_allocatable_holder_require_present(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.allocatable_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + if (!bind_c_prik_holder_item_allocatable_holder_present(owner_address)) { + PyErr_SetString(PyExc_ReferenceError, "allocatable derived object is unallocated"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * _prik_allocatable_holder_field_holder_item_code_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.allocatable_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + int32_t value = bind_c_prik_allocatable_holder_field_holder_item_code_get(owner_address); + return prik_int32_to_python(&value); +} + +static PyObject * _prik_allocatable_holder_field_holder_item_code_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.allocatable_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + int32_t value; + if (prik_int32_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.int32 for field code. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_allocatable_holder_field_holder_item_code_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_allocatable_holder_field_holder_item_weight_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.allocatable_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value = bind_c_prik_allocatable_holder_field_holder_item_weight_get(owner_address); + return prik_float64_to_python(&value); +} + +static PyObject * _prik_allocatable_holder_field_holder_item_weight_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.allocatable_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field weight. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_allocatable_holder_field_holder_item_weight_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_holder_item_pointer_holder_require_present(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.pointer_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + if (!bind_c_prik_holder_item_pointer_holder_present(owner_address)) { + PyErr_SetString(PyExc_ReferenceError, "pointer derived object is disassociated"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * _prik_pointer_holder_field_holder_item_code_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.pointer_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + int32_t value = bind_c_prik_pointer_holder_field_holder_item_code_get(owner_address); + return prik_int32_to_python(&value); +} + +static PyObject * _prik_pointer_holder_field_holder_item_code_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.pointer_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + int32_t value; + if (prik_int32_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.int32 for field code. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_pointer_holder_field_holder_item_code_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_pointer_holder_field_holder_item_weight_get(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.pointer_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value = bind_c_prik_pointer_holder_field_holder_item_weight_get(owner_address); + return prik_float64_to_python(&value); +} + +static PyObject * _prik_pointer_holder_field_holder_item_weight_set(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * value_obj; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &value_obj)) return NULL; + PyObject * owner_capsule = PyObject_GetAttrString(owner_obj, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.holder_item.pointer_holder"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + double value; + if (prik_float64_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected numpy.float64 for field weight. Received ", Py_TYPE(value_obj)->tp_name); } return NULL; }; + bind_c_prik_pointer_holder_field_holder_item_weight_set(owner_address, value); + Py_RETURN_NONE; +} + +static PyObject * _prik_module_active_vector_require_present(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + if (!bind_c_prik_module_active_vector_present()) { + PyErr_SetString(PyExc_ReferenceError, "module object active_vector is not currently present"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * _prik_module_selected_vector_require_present(PyObject * self, PyObject * args) { + PyObject * owner_obj; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + if (!bind_c_prik_module_selected_vector_present()) { + PyErr_SetString(PyExc_ReferenceError, "module object selected_vector is not currently present"); + return NULL; + } + Py_RETURN_NONE; +} + +static void prik_field_handle_vector_samples_descriptor_callback(CFI_cdesc_t * descriptor, void * context) { + *(PyObject **)context = NULL; + PyObject * dimensions = PyList_New(1); + if (dimensions == NULL) { + return; + } + PyObject * dimension_0 = Py_BuildValue("{sL,sL,sL}", "lower_bound", (long long)descriptor->dim[0].lower_bound, "extent", (long long)descriptor->dim[0].extent, "sm", (long long)descriptor->dim[0].sm); + if (dimension_0 == NULL) { + Py_DECREF(dimensions); + return; + } + PyList_SET_ITEM(dimensions, 0, dimension_0); + PyObject * descriptor_record = Py_BuildValue("{sK,sK,si,sO}", "base_addr", (unsigned long long)(uintptr_t)descriptor->base_addr, "elem_len", (unsigned long long)descriptor->elem_len, "rank", (int)descriptor->rank, "dim", dimensions); + Py_DECREF(dimensions); + *(PyObject **)context = descriptor_record; + return; +} + +static void prik_field_handle_vector_samples_actual_callback(CFI_cdesc_t * descriptor, void * context) { + *(void **)context = descriptor->base_addr; + return; +} + +static PyObject * prik_field_handle_vector_samples_aligned(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_field_handle_vector_samples_allocated(PyObject * self, PyObject * args) { + PyObject * owner_capsule = PyObject_GetAttrString(self, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + return PyBool_FromLong(bind_c_prik_field_handle_vector_samples_allocated(owner_address)); +} + +static PyObject * prik_field_handle_vector_samples_array_actual(PyObject * self, PyObject * args) { + PyObject * owner_capsule = PyObject_GetAttrString(self, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + void * base_addr = NULL; + bind_c_prik_field_handle_vector_samples_descriptor(owner_address, prik_field_handle_vector_samples_actual_callback, &base_addr); + return PyLong_FromVoidPtr(base_addr); +} + +static PyObject * prik_field_handle_vector_samples_deallocate(PyObject * self, PyObject * args) { + PyObject * owner_capsule = PyObject_GetAttrString(self, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + bind_c_prik_field_handle_vector_samples_deallocate(owner_address); + Py_RETURN_NONE; +} + +static PyObject * prik_field_handle_vector_samples_descriptor(PyObject * self, PyObject * args) { + PyObject * owner_capsule = PyObject_GetAttrString(self, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + PyObject * descriptor_record = NULL; + bind_c_prik_field_handle_vector_samples_descriptor(owner_address, prik_field_handle_vector_samples_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_field_handle_vector_samples_layout(PyObject * self, PyObject * args) { + return PyUnicode_FromString("F"); +} + +static PyObject * prik_field_handle_vector_samples_native_byte_order(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_field_handle_vector_samples_resize(PyObject * self, PyObject * args) { + PyObject * owner_capsule = PyObject_GetAttrString(self, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + PyObject * extent_0_obj; + int64_t extent_0 = 0; + if (!PyArg_ParseTuple(args, "O", &extent_0_obj)) return NULL; + extent_0 = (int64_t)PyLong_AsLongLong(extent_0_obj); if (PyErr_Occurred()) return NULL; + bind_c_prik_field_handle_vector_samples_resize(owner_address, extent_0); + Py_RETURN_NONE; +} + +static PyObject * prik_field_handle_vector_samples_shape(PyObject * self, PyObject * args) { + PyObject * owner_capsule = PyObject_GetAttrString(self, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + int64_t extent_0 = 0; + bind_c_prik_field_handle_vector_samples_shape(owner_address, &extent_0); + PyObject * shape = PyTuple_New(1); + if (shape == NULL) { + return NULL; + } + PyTuple_SET_ITEM(shape, 0, PyLong_FromLongLong((long long)extent_0)); + if (PyErr_Occurred()) { + Py_DECREF(shape); + return NULL; + } + return shape; +} + +static PyObject * prik_field_handle_vector_samples_to_numpy(PyObject * self, PyObject * args) { + PyObject * owner_capsule = PyObject_GetAttrString(self, "_prik_capsule"); + if (owner_capsule == NULL) { + return NULL; + } + if (owner_capsule == Py_None) { + Py_DECREF(owner_capsule); + PyErr_SetString(PyExc_ReferenceError, "module proxy has no whole-object address"); + return NULL; + } + void * owner_address = PyCapsule_GetPointer(owner_capsule, "prik.derived.vector"); + Py_DECREF(owner_capsule); + if (owner_address == NULL) { + return NULL; + } + PyObject * descriptor_record = NULL; + bind_c_prik_field_handle_vector_samples_descriptor(owner_address, prik_field_handle_vector_samples_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_field_handle_vector_samples_writeable(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static void prik_module_field_handle_active_vector_samples_descriptor_callback(CFI_cdesc_t * descriptor, void * context) { + *(PyObject **)context = NULL; + PyObject * dimensions = PyList_New(1); + if (dimensions == NULL) { + return; + } + PyObject * dimension_0 = Py_BuildValue("{sL,sL,sL}", "lower_bound", (long long)descriptor->dim[0].lower_bound, "extent", (long long)descriptor->dim[0].extent, "sm", (long long)descriptor->dim[0].sm); + if (dimension_0 == NULL) { + Py_DECREF(dimensions); + return; + } + PyList_SET_ITEM(dimensions, 0, dimension_0); + PyObject * descriptor_record = Py_BuildValue("{sK,sK,si,sO}", "base_addr", (unsigned long long)(uintptr_t)descriptor->base_addr, "elem_len", (unsigned long long)descriptor->elem_len, "rank", (int)descriptor->rank, "dim", dimensions); + Py_DECREF(dimensions); + *(PyObject **)context = descriptor_record; + return; +} + +static void prik_module_field_handle_active_vector_samples_actual_callback(CFI_cdesc_t * descriptor, void * context) { + *(void **)context = descriptor->base_addr; + return; +} + +static PyObject * prik_module_field_handle_active_vector_samples_aligned(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_field_handle_active_vector_samples_allocated(PyObject * self, PyObject * args) { + return PyBool_FromLong(bind_c_prik_module_field_handle_active_vector_samples_allocated()); +} + +static PyObject * prik_module_field_handle_active_vector_samples_array_actual(PyObject * self, PyObject * args) { + void * base_addr = NULL; + bind_c_prik_module_field_handle_active_vector_samples_descriptor(prik_module_field_handle_active_vector_samples_actual_callback, &base_addr); + return PyLong_FromVoidPtr(base_addr); +} + +static PyObject * prik_module_field_handle_active_vector_samples_deallocate(PyObject * self, PyObject * args) { + bind_c_prik_module_field_handle_active_vector_samples_deallocate(); + Py_RETURN_NONE; +} + +static PyObject * prik_module_field_handle_active_vector_samples_descriptor(PyObject * self, PyObject * args) { + PyObject * descriptor_record = NULL; + bind_c_prik_module_field_handle_active_vector_samples_descriptor(prik_module_field_handle_active_vector_samples_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_module_field_handle_active_vector_samples_layout(PyObject * self, PyObject * args) { + return PyUnicode_FromString("F"); +} + +static PyObject * prik_module_field_handle_active_vector_samples_native_byte_order(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_field_handle_active_vector_samples_resize(PyObject * self, PyObject * args) { + PyObject * extent_0_obj; + int64_t extent_0 = 0; + if (!PyArg_ParseTuple(args, "O", &extent_0_obj)) return NULL; + extent_0 = (int64_t)PyLong_AsLongLong(extent_0_obj); if (PyErr_Occurred()) return NULL; + bind_c_prik_module_field_handle_active_vector_samples_resize(extent_0); + Py_RETURN_NONE; +} + +static PyObject * prik_module_field_handle_active_vector_samples_shape(PyObject * self, PyObject * args) { + int64_t extent_0 = 0; + bind_c_prik_module_field_handle_active_vector_samples_shape(&extent_0); + PyObject * shape = PyTuple_New(1); + if (shape == NULL) { + return NULL; + } + PyTuple_SET_ITEM(shape, 0, PyLong_FromLongLong((long long)extent_0)); + if (PyErr_Occurred()) { + Py_DECREF(shape); + return NULL; + } + return shape; +} + +static PyObject * prik_module_field_handle_active_vector_samples_to_numpy(PyObject * self, PyObject * args) { + PyObject * descriptor_record = NULL; + bind_c_prik_module_field_handle_active_vector_samples_descriptor(prik_module_field_handle_active_vector_samples_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_module_field_handle_active_vector_samples_writeable(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static void prik_module_field_handle_selected_vector_samples_descriptor_callback(CFI_cdesc_t * descriptor, void * context) { + *(PyObject **)context = NULL; + PyObject * dimensions = PyList_New(1); + if (dimensions == NULL) { + return; + } + PyObject * dimension_0 = Py_BuildValue("{sL,sL,sL}", "lower_bound", (long long)descriptor->dim[0].lower_bound, "extent", (long long)descriptor->dim[0].extent, "sm", (long long)descriptor->dim[0].sm); + if (dimension_0 == NULL) { + Py_DECREF(dimensions); + return; + } + PyList_SET_ITEM(dimensions, 0, dimension_0); + PyObject * descriptor_record = Py_BuildValue("{sK,sK,si,sO}", "base_addr", (unsigned long long)(uintptr_t)descriptor->base_addr, "elem_len", (unsigned long long)descriptor->elem_len, "rank", (int)descriptor->rank, "dim", dimensions); + Py_DECREF(dimensions); + *(PyObject **)context = descriptor_record; + return; +} + +static void prik_module_field_handle_selected_vector_samples_actual_callback(CFI_cdesc_t * descriptor, void * context) { + *(void **)context = descriptor->base_addr; + return; +} + +static PyObject * prik_module_field_handle_selected_vector_samples_aligned(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_field_handle_selected_vector_samples_allocated(PyObject * self, PyObject * args) { + return PyBool_FromLong(bind_c_prik_module_field_handle_selected_vector_samples_allocated()); +} + +static PyObject * prik_module_field_handle_selected_vector_samples_array_actual(PyObject * self, PyObject * args) { + void * base_addr = NULL; + bind_c_prik_module_field_handle_selected_vector_samples_descriptor(prik_module_field_handle_selected_vector_samples_actual_callback, &base_addr); + return PyLong_FromVoidPtr(base_addr); +} + +static PyObject * prik_module_field_handle_selected_vector_samples_deallocate(PyObject * self, PyObject * args) { + bind_c_prik_module_field_handle_selected_vector_samples_deallocate(); + Py_RETURN_NONE; +} + +static PyObject * prik_module_field_handle_selected_vector_samples_descriptor(PyObject * self, PyObject * args) { + PyObject * descriptor_record = NULL; + bind_c_prik_module_field_handle_selected_vector_samples_descriptor(prik_module_field_handle_selected_vector_samples_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_module_field_handle_selected_vector_samples_layout(PyObject * self, PyObject * args) { + return PyUnicode_FromString("F"); +} + +static PyObject * prik_module_field_handle_selected_vector_samples_native_byte_order(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_field_handle_selected_vector_samples_resize(PyObject * self, PyObject * args) { + PyObject * extent_0_obj; + int64_t extent_0 = 0; + if (!PyArg_ParseTuple(args, "O", &extent_0_obj)) return NULL; + extent_0 = (int64_t)PyLong_AsLongLong(extent_0_obj); if (PyErr_Occurred()) return NULL; + bind_c_prik_module_field_handle_selected_vector_samples_resize(extent_0); + Py_RETURN_NONE; +} + +static PyObject * prik_module_field_handle_selected_vector_samples_shape(PyObject * self, PyObject * args) { + int64_t extent_0 = 0; + bind_c_prik_module_field_handle_selected_vector_samples_shape(&extent_0); + PyObject * shape = PyTuple_New(1); + if (shape == NULL) { + return NULL; + } + PyTuple_SET_ITEM(shape, 0, PyLong_FromLongLong((long long)extent_0)); + if (PyErr_Occurred()) { + Py_DECREF(shape); + return NULL; + } + return shape; +} + +static PyObject * prik_module_field_handle_selected_vector_samples_to_numpy(PyObject * self, PyObject * args) { + PyObject * descriptor_record = NULL; + bind_c_prik_module_field_handle_selected_vector_samples_descriptor(prik_module_field_handle_selected_vector_samples_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_module_field_handle_selected_vector_samples_writeable(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static void prik_release_native_handle_refactoring_goldens_make_values_return(void * storage) { + CFI_cdesc_t * owner_descriptor = (CFI_cdesc_t *)storage; + if (owner_descriptor == NULL) { + return; + } + bind_c_owned_result_5531b6b6_destroy(owner_descriptor); +} + +static void prik_module_refactoring_goldens_workspace_descriptor_callback(CFI_cdesc_t * descriptor, void * context) { + *(PyObject **)context = NULL; + PyObject * dimensions = PyList_New(1); + if (dimensions == NULL) { + return; + } + PyObject * dimension_0 = Py_BuildValue("{sL,sL,sL}", "lower_bound", (long long)descriptor->dim[0].lower_bound, "extent", (long long)descriptor->dim[0].extent, "sm", (long long)descriptor->dim[0].sm); + if (dimension_0 == NULL) { + Py_DECREF(dimensions); + return; + } + PyList_SET_ITEM(dimensions, 0, dimension_0); + PyObject * descriptor_record = Py_BuildValue("{sK,sK,si,sO}", "base_addr", (unsigned long long)(uintptr_t)descriptor->base_addr, "elem_len", (unsigned long long)descriptor->elem_len, "rank", (int)descriptor->rank, "dim", dimensions); + Py_DECREF(dimensions); + *(PyObject **)context = descriptor_record; + return; +} + +static void prik_module_refactoring_goldens_workspace_array_actual_callback(CFI_cdesc_t * descriptor, void * context) { + *(void **)context = descriptor->base_addr; + return; +} + +static PyObject * prik_module_refactoring_goldens_workspace_aligned(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_refactoring_goldens_workspace_allocated(PyObject * self, PyObject * args) { + return PyBool_FromLong(bind_c_workspace_allocated()); +} + +static PyObject * prik_module_refactoring_goldens_workspace_array_actual(PyObject * self, PyObject * args) { + void * base_addr = NULL; + bind_c_workspace_array_actual(prik_module_refactoring_goldens_workspace_array_actual_callback, &base_addr); + return PyLong_FromVoidPtr(base_addr); +} + +static PyObject * prik_module_refactoring_goldens_workspace_deallocate(PyObject * self, PyObject * args) { + bind_c_workspace_deallocate(); + Py_RETURN_NONE; +} + +static PyObject * prik_module_refactoring_goldens_workspace_descriptor(PyObject * self, PyObject * args) { + PyObject * descriptor_record = NULL; + bind_c_workspace_descriptor(prik_module_refactoring_goldens_workspace_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_module_refactoring_goldens_workspace_layout(PyObject * self, PyObject * args) { + return PyUnicode_FromString("F"); +} + +static PyObject * prik_module_refactoring_goldens_workspace_native_byte_order(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_refactoring_goldens_workspace_resize(PyObject * self, PyObject * args) { + PyObject * extent_0_obj; + int64_t extent_0 = 0; + if (!PyArg_ParseTuple(args, "O", &extent_0_obj)) return NULL; + extent_0 = (int64_t)PyLong_AsLongLong(extent_0_obj); if (PyErr_Occurred()) return NULL; + bind_c_workspace_resize(extent_0); + Py_RETURN_NONE; +} + +static PyObject * prik_module_refactoring_goldens_workspace_shape(PyObject * self, PyObject * args) { + int64_t extent_0 = 0; + bind_c_workspace_shape(&extent_0); + PyObject * shape = PyTuple_New(1); + if (shape == NULL) { + return NULL; + } + PyTuple_SET_ITEM(shape, 0, PyLong_FromLongLong((long long)extent_0)); + if (PyErr_Occurred()) { Py_DECREF(shape); return NULL; }; + return shape; +} + +static PyObject * prik_module_refactoring_goldens_workspace_to_numpy(PyObject * self, PyObject * args) { + PyObject * descriptor_record = NULL; + bind_c_workspace_descriptor(prik_module_refactoring_goldens_workspace_descriptor_callback, &descriptor_record); + return descriptor_record; +} + +static PyObject * prik_module_refactoring_goldens_workspace_writeable(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_refactoring_goldens_selected_aligned(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_refactoring_goldens_selected_array_actual(PyObject * self, PyObject * args) { + return PyLong_FromVoidPtr(bind_c_selected_array_actual()); +} + +static PyObject * prik_module_refactoring_goldens_selected_associate(PyObject * self, PyObject * args) { + PyObject * source_packed; + if (!PyArg_ParseTuple(args, "O", &source_packed)) return NULL; + PyObject * source_item = NULL; + CFI_CDESC_T(1) source_storage; + CFI_cdesc_t * source_descriptor = NULL; + void * source_base_addr = NULL; + size_t source_elem_len = 0; + CFI_rank_t source_descriptor_rank = 0; + CFI_index_t source_extents[1]; + CFI_index_t source_lower_bound_0 = 0; + CFI_index_t source_extent_0 = 0; + CFI_index_t source_stride_multiplier_0 = 0; + int source_establish_status = CFI_SUCCESS; + if (!PyTuple_Check(source_packed) || PyTuple_GET_SIZE(source_packed) != 6) { PyErr_SetString(PyExc_TypeError, "pointer association requires 6 descriptor facts"); return NULL; }; + source_item = PyTuple_GET_ITEM(source_packed, 0); + source_base_addr = (void *)PyLong_AsVoidPtr(source_item); + if (source_base_addr == NULL && PyErr_Occurred()) return NULL; + source_item = PyTuple_GET_ITEM(source_packed, 1); + source_elem_len = (size_t)PyLong_AsUnsignedLongLong(source_item); + if (PyErr_Occurred()) return NULL; + source_item = PyTuple_GET_ITEM(source_packed, 2); + source_descriptor_rank = PyLong_AsLongLong(source_item); + if (PyErr_Occurred()) return NULL; + source_item = PyTuple_GET_ITEM(source_packed, 3); + source_lower_bound_0 = PyLong_AsLongLong(source_item); + if (PyErr_Occurred()) return NULL; + source_item = PyTuple_GET_ITEM(source_packed, 4); + source_extent_0 = PyLong_AsLongLong(source_item); + if (PyErr_Occurred()) return NULL; + source_item = PyTuple_GET_ITEM(source_packed, 5); + source_stride_multiplier_0 = PyLong_AsLongLong(source_item); + if (PyErr_Occurred()) return NULL; + source_extents[0] = source_extent_0; + if (source_descriptor_rank != 1) { PyErr_Format(PyExc_ValueError, "pointer association source rank %d does not match destination rank 1", (int)source_descriptor_rank); return NULL; }; + source_establish_status = CFI_establish((CFI_cdesc_t *)&source_storage, source_base_addr, CFI_attribute_pointer, CFI_type_double, source_elem_len, 1, source_extents); + if (source_establish_status != CFI_SUCCESS) { PyErr_SetString(PyExc_RuntimeError, "failed to establish pointer association source"); return NULL; }; + ((CFI_cdesc_t *)&source_storage)->dim[0].lower_bound = source_lower_bound_0; + ((CFI_cdesc_t *)&source_storage)->dim[0].extent = source_extent_0; + ((CFI_cdesc_t *)&source_storage)->dim[0].sm = source_stride_multiplier_0; + source_descriptor = (CFI_cdesc_t *)&source_storage; + bind_c_selected_associate(source_descriptor); + Py_RETURN_NONE; +} + +static PyObject * prik_module_refactoring_goldens_selected_associated(PyObject * self, PyObject * args) { + return PyBool_FromLong(bind_c_selected_associated()); +} + +static PyObject * prik_module_refactoring_goldens_selected_contiguous(PyObject * self, PyObject * args) { + return PyBool_FromLong(bind_c_selected_contiguous()); +} + +static PyObject * prik_module_refactoring_goldens_selected_descriptor(PyObject * self, PyObject * args) { + CFI_CDESC_T(1) descriptor_storage; + CFI_cdesc_t * descriptor = (CFI_cdesc_t *)&descriptor_storage; + int status = CFI_SUCCESS; + status = CFI_establish(descriptor, NULL, CFI_attribute_pointer, CFI_type_double, sizeof(double), 1, NULL); + if (status != CFI_SUCCESS) { + PyErr_SetString(PyExc_RuntimeError, "failed to establish pointer descriptor reader"); + return NULL; + } + bind_c_selected_descriptor(descriptor); + PyObject * dimensions = PyList_New(1); + if (dimensions == NULL) { + return NULL; + } + PyObject * dimension_0 = Py_BuildValue("{sL,sL,sL}", "lower_bound", (long long)descriptor->dim[0].lower_bound, "extent", (long long)descriptor->dim[0].extent, "sm", (long long)descriptor->dim[0].sm); + if (dimension_0 == NULL) { + Py_DECREF(dimensions); + return NULL; + } + PyList_SET_ITEM(dimensions, 0, dimension_0); + PyObject * descriptor_record = Py_BuildValue("{sK,sK,si,sO}", "base_addr", (unsigned long long)(uintptr_t)descriptor->base_addr, "elem_len", (unsigned long long)descriptor->elem_len, "rank", (int)descriptor->rank, "dim", dimensions); + Py_DECREF(dimensions); + return descriptor_record; +} + +static PyObject * prik_module_refactoring_goldens_selected_layout(PyObject * self, PyObject * args) { + return PyUnicode_FromString("F"); +} + +static PyObject * prik_module_refactoring_goldens_selected_native_byte_order(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_module_refactoring_goldens_selected_nullify(PyObject * self, PyObject * args) { + bind_c_selected_nullify(); + Py_RETURN_NONE; +} + +static PyObject * prik_module_refactoring_goldens_selected_shape(PyObject * self, PyObject * args) { + int64_t extent_0 = 0; + bind_c_selected_shape(&extent_0); + PyObject * shape = PyTuple_New(1); + if (shape == NULL) { + return NULL; + } + PyTuple_SET_ITEM(shape, 0, PyLong_FromLongLong((long long)extent_0)); + if (PyErr_Occurred()) { Py_DECREF(shape); return NULL; }; + return shape; +} + +static PyObject * prik_module_refactoring_goldens_selected_to_numpy(PyObject * self, PyObject * args) { + CFI_CDESC_T(1) descriptor_storage; + CFI_cdesc_t * descriptor = (CFI_cdesc_t *)&descriptor_storage; + int status = CFI_SUCCESS; + status = CFI_establish(descriptor, NULL, CFI_attribute_pointer, CFI_type_double, sizeof(double), 1, NULL); + if (status != CFI_SUCCESS) { + PyErr_SetString(PyExc_RuntimeError, "failed to establish pointer descriptor reader"); + return NULL; + } + bind_c_selected_descriptor(descriptor); + PyObject * dimensions = PyList_New(1); + if (dimensions == NULL) { + return NULL; + } + PyObject * dimension_0 = Py_BuildValue("{sL,sL,sL}", "lower_bound", (long long)descriptor->dim[0].lower_bound, "extent", (long long)descriptor->dim[0].extent, "sm", (long long)descriptor->dim[0].sm); + if (dimension_0 == NULL) { + Py_DECREF(dimensions); + return NULL; + } + PyList_SET_ITEM(dimensions, 0, dimension_0); + PyObject * descriptor_record = Py_BuildValue("{sK,sK,si,sO}", "base_addr", (unsigned long long)(uintptr_t)descriptor->base_addr, "elem_len", (unsigned long long)descriptor->elem_len, "rank", (int)descriptor->rank, "dim", dimensions); + Py_DECREF(dimensions); + return descriptor_record; +} + +static PyObject * prik_module_refactoring_goldens_selected_writeable(PyObject * self, PyObject * args) { + return PyBool_FromLong(1); +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_aligned(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + return PyBool_FromLong(1); +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_allocated(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + CFI_cdesc_t * owner_descriptor = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + owner_descriptor = (CFI_cdesc_t *)owner_handle->descriptor; + return PyBool_FromLong(bind_c_owned_result_5531b6b6_allocated(owner_descriptor)); +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_array_actual(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + CFI_cdesc_t * owner_descriptor = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + owner_descriptor = (CFI_cdesc_t *)owner_handle->descriptor; + return PyLong_FromVoidPtr(owner_descriptor->base_addr); +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_deallocate(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + CFI_cdesc_t * owner_descriptor = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + owner_descriptor = (CFI_cdesc_t *)owner_handle->descriptor; + bind_c_owned_result_5531b6b6_deallocate(owner_descriptor); + Py_RETURN_NONE; +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_descriptor(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + Py_INCREF(owner_obj); + return owner_obj; +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_destroy(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + prik_native_array_handle_release(owner_handle); + Py_RETURN_NONE; +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_layout(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + return PyUnicode_FromString("F"); +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_native_byte_order(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + return PyBool_FromLong(1); +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_resize(PyObject * self, PyObject * args) { + PyObject * owner_obj; + PyObject * extent_0_obj; + prik_native_array_handle * owner_handle = NULL; + CFI_cdesc_t * owner_descriptor = NULL; + CFI_index_t lower_bounds[1]; + CFI_index_t upper_bounds[1]; + int status = CFI_SUCCESS; + if (!PyArg_ParseTuple(args, "OO", &owner_obj, &extent_0_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + owner_descriptor = (CFI_cdesc_t *)owner_handle->descriptor; + upper_bounds[0] = (CFI_index_t)PyLong_AsLongLong(extent_0_obj) - 1; + if (PyErr_Occurred()) return NULL; + lower_bounds[0] = 0; + bind_c_owned_result_5531b6b6_deallocate(owner_descriptor); + status = CFI_allocate(owner_descriptor, lower_bounds, upper_bounds, owner_descriptor->elem_len); + if (status != CFI_SUCCESS) { PyErr_SetString(PyExc_RuntimeError, "failed to resize owned native array"); return NULL; }; + Py_RETURN_NONE; +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_shape(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + CFI_cdesc_t * owner_descriptor = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + owner_descriptor = (CFI_cdesc_t *)owner_handle->descriptor; + int64_t extent_0 = 0; + bind_c_owned_result_5531b6b6_shape(owner_descriptor, &extent_0); + return Py_BuildValue("(L)", extent_0); +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_to_numpy(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + CFI_cdesc_t * owner_descriptor = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + owner_descriptor = (CFI_cdesc_t *)owner_handle->descriptor; + PyObject * dimensions = PyList_New(1); + if (dimensions == NULL) { + return NULL; + } + PyObject * dimension_0 = Py_BuildValue("{sL,sL,sL}", "lower_bound", (long long)owner_descriptor->dim[0].lower_bound, "extent", (long long)owner_descriptor->dim[0].extent, "sm", (long long)owner_descriptor->dim[0].sm); + if (dimension_0 == NULL) { + Py_DECREF(dimensions); + return NULL; + } + PyList_SET_ITEM(dimensions, 0, dimension_0); + PyObject * descriptor_record = Py_BuildValue("{sK,sK,si,sO}", "base_addr", (unsigned long long)(uintptr_t)owner_descriptor->base_addr, "elem_len", (unsigned long long)owner_descriptor->elem_len, "rank", (int)owner_descriptor->rank, "dim", dimensions); + Py_DECREF(dimensions); + return descriptor_record; +} + +static PyObject * prik_owned_refactoring_goldens_make_values_return_writeable(PyObject * self, PyObject * args) { + PyObject * owner_obj; + prik_native_array_handle * owner_handle = NULL; + if (!PyArg_ParseTuple(args, "O", &owner_obj)) return NULL; + owner_handle = prik_native_array_handle_from_capsule(owner_obj, PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1))); + if (owner_handle == NULL) return NULL; + return PyBool_FromLong(1); +} + +static PyObject * wrap_summarize(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"required", "scale", "values", "label", "item", NULL}; + PyObject * bound_required_obj; + int32_t bound_required; + PyObject * bound_scale_obj = Py_None; + int32_t bound_scale; + void * bound_scale_nullable = NULL; + PyObject * bound_values_obj = Py_None; + void * bound_values = NULL; + int64_t bound_values_extent_0 = 0; + int64_t bound_values_upper_bound_0 = 0; + int64_t bound_values_stride_0 = 1; + int bound_values_dense_actual = 0; + PyObject * bound_label_obj = Py_None; + Py_ssize_t bound_label_length = 0; + const char * bound_label = NULL; + PyObject * bound_item_obj = Py_None; + void * bound_item = NULL; + int bound_item_derived_access = 0; + prik_derived_origin_ops * bound_item_derived_ops = NULL; + void * bound_item_derived_identity = NULL; + int bound_item_derived_status = 0; + int32_t result; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOOO", kwlist, &bound_required_obj, &bound_scale_obj, &bound_values_obj, &bound_label_obj, &bound_item_obj)) return NULL; + if (prik_int32_unpack_exact(bound_required_obj, &bound_required) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.int32 for argument required. Received ", Py_TYPE(bound_required_obj)->tp_name); } return NULL; }; + if (bound_scale_obj != Py_None) { + if (prik_int32_unpack_exact(bound_scale_obj, &bound_scale) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.int32 for argument scale. Received ", Py_TYPE(bound_scale_obj)->tp_name); } return NULL; }; + bound_scale_nullable = &bound_scale; + } + if (bound_values_obj != Py_None) { + if (prik_array_validate(bound_values_obj, NPY_FLOAT64, 1, 1, PRIK_ARRAY_LAYOUT_POSITIVE_STRIDED_F, 0, 0, "numpy.float64", "values") < 0) return NULL; + bound_values = PyArray_DATA((PyArrayObject *)bound_values_obj); + bound_values_dense_actual = PyArray_IS_F_CONTIGUOUS((PyArrayObject *)bound_values_obj); + bound_values_extent_0 = (int64_t)PyArray_DIM((PyArrayObject *)bound_values_obj, 0); + if (!bound_values_dense_actual) { + bound_values_stride_0 = PyArray_SIZE((PyArrayObject *)bound_values_obj) == 0 ? 1 : (PyArray_STRIDE((PyArrayObject *)bound_values_obj, 0) / PyArray_ITEMSIZE((PyArrayObject *)bound_values_obj)) / (1); + bound_values_upper_bound_0 = bound_values_extent_0 == 0 ? -1 : (bound_values_extent_0 - 1) * bound_values_stride_0; + bound_values_extent_0 = bound_values_upper_bound_0 + 1; + } + } + if (bound_label_obj != Py_None) { + if (!PyUnicode_Check(bound_label_obj)) { PyErr_Format(PyExc_TypeError, "Expected an argument of type str for argument label. Received ", Py_TYPE(bound_label_obj)->tp_name); return NULL; }; + bound_label = PyUnicode_AsUTF8AndSize(bound_label_obj, &bound_label_length); + if (bound_label == NULL) return NULL; + if ((Py_ssize_t)strlen(bound_label) != bound_label_length) { PyErr_SetString(PyExc_TypeError, "Argument label cannot contain embedded NUL"); return NULL; }; + } + if (bound_item_obj != Py_None) { + int bound_item_extract_status = prik_extract_derived_argument(bound_item_obj, "vector", "vector", "prik.derived.vector", "item", prik_derived_cases_refactoring_goldens_summarize_item, sizeof(prik_derived_cases_refactoring_goldens_summarize_item) / sizeof(prik_derived_cases_refactoring_goldens_summarize_item[0]), &bound_item, &bound_item_derived_access, &bound_item_derived_ops); + if (bound_item_extract_status < 0) { + return NULL; + } + } + bound_item_derived_identity = bound_item_derived_ops != NULL ? (void *)bound_item_derived_ops : bound_item; + result = bind_c_summarize(bound_required, bound_scale_nullable, bound_values, bound_values_dense_actual, bound_values_extent_0, bound_values_upper_bound_0, bound_values_stride_0, bound_label, (int64_t)bound_label_length, bound_item, bound_item_derived_access, bound_item_derived_identity, bound_item_derived_ops != NULL ? bound_item_derived_ops->scoped : NULL, bound_item_derived_ops != NULL ? bound_item_derived_ops->checkout : NULL, bound_item_derived_ops != NULL ? bound_item_derived_ops->restore : NULL, &bound_item_derived_status); + if (bound_item_derived_status != 0) { + if (bound_item_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "item"); + } else { + if (bound_item_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "item", bound_item_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + PyObject * result_obj = prik_int32_to_python(&result); + if (result_obj == NULL) { + return NULL; + } + return result_obj; +} + +static PyObject * wrap_make_values(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"count", "fill_value", NULL}; + PyObject * bound_count_obj; + int32_t bound_count; + PyObject * bound_fill_value_obj; + double bound_fill_value; + CFI_cdesc_t * result = NULL; + int result_owner_status = CFI_SUCCESS; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &bound_count_obj, &bound_fill_value_obj)) return NULL; + if (prik_int32_unpack_exact(bound_count_obj, &bound_count) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.int32 for argument count. Received ", Py_TYPE(bound_count_obj)->tp_name); } return NULL; }; + if (prik_float64_unpack_exact(bound_fill_value_obj, &bound_fill_value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument fill_value. Received ", Py_TYPE(bound_fill_value_obj)->tp_name); } return NULL; }; + result = (CFI_cdesc_t *)calloc(1, sizeof(CFI_CDESC_T(1))); + if (result == NULL) { + PyErr_NoMemory(); + return NULL; + } + result_owner_status = CFI_establish(result, NULL, CFI_attribute_allocatable, CFI_type_double, sizeof(double), 1, NULL); + if (result_owner_status != CFI_SUCCESS) { + free(result); + result = NULL; + PyErr_SetString(PyExc_RuntimeError, "failed to establish owned native array descriptor storage"); + return NULL; + } + bind_c_make_values(bound_count, bound_fill_value, result); + PyObject * result_handle_runtime = NULL; + PyObject * result_handle_helper = NULL; + PyObject * result_handle_ops = NULL; + PyObject * result_handle_owner = NULL; + PyObject * result_handle_operation = NULL; + PyObject * result_obj = NULL; + result_handle_ops = PyDict_New(); + if (result_handle_ops == NULL) { + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_aligned_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "aligned", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_allocated_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "allocated", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_array_actual_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "array_actual", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_deallocate_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "deallocate", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_descriptor_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "descriptor", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_destroy_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "destroy", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_layout_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "layout", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_native_byte_order_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "native_byte_order", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_resize_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "resize", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_shape_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "shape", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_to_numpy_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "to_numpy", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_operation = PyCFunction_NewEx(&prik_owned_refactoring_goldens_make_values_return_writeable_def, NULL, NULL); + if (result_handle_operation == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + if (PyDict_SetItemString(result_handle_ops, "writeable", result_handle_operation) < 0) { + Py_DECREF(result_handle_operation); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + Py_DECREF(result_handle_operation); + result_handle_owner = prik_native_array_handle_capsule_new(PRIK_NATIVE_ARRAY_KIND_ALLOCATABLE, 1, CFI_type_double, sizeof(double), sizeof(CFI_CDESC_T(1)), result, prik_release_native_handle_refactoring_goldens_make_values_return); + if (result_handle_owner == NULL) { + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + result = NULL; + result_handle_runtime = PyImport_ImportModule("prik.runtime.handles"); + if (result_handle_runtime == NULL) { + Py_DECREF(result_handle_owner); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + result_handle_helper = PyObject_GetAttrString(result_handle_runtime, "_native_array_handle_from_generated_ops"); + Py_DECREF(result_handle_runtime); + if (result_handle_helper == NULL) { + Py_DECREF(result_handle_owner); + Py_DECREF(result_handle_ops); + if (result != NULL) { if (result->base_addr != NULL) (void)CFI_deallocate(result); free(result); result = NULL; }; + return NULL; + } + result_obj = PyObject_CallFunction(result_handle_helper, "ssiOOssO", "allocatable", "float64", 1, result_handle_ops, result_handle_owner, "owned", "borrowed_view", Py_None); + Py_DECREF(result_handle_helper); + Py_DECREF(result_handle_owner); + Py_DECREF(result_handle_ops); + if (result_obj == NULL) { + return NULL; + } + return result_obj; +} + +static PyObject * wrap_apply_callback(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"callback", "value", NULL}; + PyObject * bound_callback_obj; + PyObject * bound_value_obj; + double bound_value; + prik_callback_context_callback_83b3d1d9 callback_callback_context; + double result; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &bound_callback_obj, &bound_value_obj)) return NULL; + if (!PyCallable_Check(bound_callback_obj)) { + PyErr_SetString(PyExc_TypeError, "argument callback must be callable"); + return NULL; + } + if (prik_float64_unpack_exact(bound_value_obj, &bound_value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument value. Received ", Py_TYPE(bound_value_obj)->tp_name); } return NULL; }; + callback_callback_context.callable = bound_callback_obj; + callback_callback_context.module = self; + callback_callback_context.thread_id = PyThread_get_thread_ident(); + callback_callback_context.previous = prik_callback_current_callback_83b3d1d9; + callback_callback_context.last_result = NULL; + Py_INCREF(bound_callback_obj); + Py_INCREF(self); + prik_callback_current_callback_83b3d1d9 = &callback_callback_context; + result = bind_c_apply_callback(bound_value); + prik_callback_current_callback_83b3d1d9 = callback_callback_context.previous; + Py_XDECREF(callback_callback_context.last_result); + Py_DECREF(callback_callback_context.module); + Py_DECREF(callback_callback_context.callable); + PyObject * result_obj = prik_float64_to_python(&result); + if (result_obj == NULL) { + return NULL; + } + return result_obj; +} + +static PyObject * wrap_split_value(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"value", NULL}; + PyObject * bound_value_obj; + double bound_value; + double doubled; + int32_t status; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &bound_value_obj)) return NULL; + if (prik_float64_unpack_exact(bound_value_obj, &bound_value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument value. Received ", Py_TYPE(bound_value_obj)->tp_name); } return NULL; }; + bind_c_split_value(bound_value, &doubled, &status); + PyObject * result_0_obj = prik_float64_to_python(&doubled); + if (result_0_obj == NULL) { + return NULL; + } + PyObject * result_1_obj = prik_int32_to_python(&status); + if (result_1_obj == NULL) { + Py_DECREF(result_0_obj); + return NULL; + } + PyObject * result_obj = PyTuple_New(2); + if (result_obj == NULL) { + Py_DECREF(result_0_obj); + Py_DECREF(result_1_obj); + return NULL; + } + PyTuple_SET_ITEM(result_obj, 0, result_0_obj); + PyTuple_SET_ITEM(result_obj, 1, result_1_obj); + return result_obj; +} + +static PyObject * wrap_reset_allocatable_item(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"value", NULL}; + PyObject * bound_value_obj; + void * bound_value = NULL; + int bound_value_derived_access = 0; + prik_derived_origin_ops * bound_value_derived_ops = NULL; + void * bound_value_derived_identity = NULL; + int bound_value_derived_status = 0; + int bound_value_descriptor_output_present = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &bound_value_obj)) return NULL; + if (bound_value_obj != Py_None) { + int bound_value_extract_status = prik_extract_derived_argument(bound_value_obj, "holder_item", "holder_item", "prik.derived.holder_item", "value", prik_derived_cases_refactoring_goldens_reset_allocatable_item_value, sizeof(prik_derived_cases_refactoring_goldens_reset_allocatable_item_value) / sizeof(prik_derived_cases_refactoring_goldens_reset_allocatable_item_value[0]), &bound_value, &bound_value_derived_access, &bound_value_derived_ops); + if (bound_value_extract_status < 0) { + return NULL; + } + } else { + bound_value_derived_access = 3; + } + bound_value_derived_identity = bound_value_derived_ops != NULL ? (void *)bound_value_derived_ops : bound_value; + bind_c_reset_allocatable_item(bound_value, bound_value_derived_access, bound_value_derived_identity, bound_value_derived_ops != NULL ? bound_value_derived_ops->scoped : NULL, bound_value_derived_ops != NULL ? bound_value_derived_ops->checkout : NULL, bound_value_derived_ops != NULL ? bound_value_derived_ops->restore : NULL, &bound_value_derived_status, &bound_value, &bound_value_descriptor_output_present); + if (bound_value_derived_status != 0) { + if (bound_value_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "value"); + } else { + if (bound_value_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "value", bound_value_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + PyObject * result_obj = NULL; + if (bound_value_obj != Py_None) { + Py_INCREF(bound_value_obj); + result_obj = bound_value_obj; + } else { + if (!bound_value_descriptor_output_present) { + if (bound_value != NULL) { + bind_c_prik_destroy_holder_item_allocatable_holder(bound_value); + } + Py_INCREF(Py_None); + result_obj = Py_None; + } else { + PyObject * result_obj_capsule = PyCapsule_New(bound_value, "prik.derived.holder_item.allocatable_holder", prik_destroy_holder_item_allocatable_holder_capsule); + if (result_obj_capsule == NULL) { + bind_c_prik_destroy_holder_item_allocatable_holder(bound_value); + return NULL; + } + PyObject * result_obj_helper = PyObject_GetAttrString(self, "_prik_wrap_holder_item"); + if (result_obj_helper == NULL) { + Py_DECREF(result_obj_capsule); + return NULL; + } + PyObject * result_obj_ops = PyObject_GetAttrString(self, "_prik_ops_holder_item_allocatable_holder"); + if (result_obj_ops == NULL) { + Py_DECREF(result_obj_helper); + Py_DECREF(result_obj_capsule); + return NULL; + } + result_obj = PyObject_CallFunction(result_obj_helper, "OOOs", result_obj_capsule, Py_None, result_obj_ops, "allocatable_holder"); + Py_DECREF(result_obj_ops); + Py_DECREF(result_obj_helper); + Py_DECREF(result_obj_capsule); + if (result_obj == NULL) { + return NULL; + } + } + } + return result_obj; +} + +static PyObject * wrap_shift_pointer_item(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"value", "amount", NULL}; + PyObject * bound_value_obj; + void * bound_value = NULL; + int bound_value_derived_access = 0; + prik_derived_origin_ops * bound_value_derived_ops = NULL; + void * bound_value_derived_identity = NULL; + int bound_value_derived_status = 0; + int bound_value_descriptor_output_present = 0; + PyObject * bound_amount_obj; + double bound_amount; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &bound_value_obj, &bound_amount_obj)) return NULL; + if (bound_value_obj != Py_None) { + int bound_value_extract_status = prik_extract_derived_argument(bound_value_obj, "holder_item", "holder_item", "prik.derived.holder_item", "value", prik_derived_cases_refactoring_goldens_shift_pointer_item_value, sizeof(prik_derived_cases_refactoring_goldens_shift_pointer_item_value) / sizeof(prik_derived_cases_refactoring_goldens_shift_pointer_item_value[0]), &bound_value, &bound_value_derived_access, &bound_value_derived_ops); + if (bound_value_extract_status < 0) { + return NULL; + } + } else { + bound_value_derived_access = 4; + } + bound_value_derived_identity = bound_value_derived_ops != NULL ? (void *)bound_value_derived_ops : bound_value; + if (prik_float64_unpack_exact(bound_amount_obj, &bound_amount) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument amount. Received ", Py_TYPE(bound_amount_obj)->tp_name); } return NULL; }; + bind_c_shift_pointer_item(bound_value, bound_value_derived_access, bound_value_derived_identity, bound_value_derived_ops != NULL ? bound_value_derived_ops->scoped : NULL, bound_value_derived_ops != NULL ? bound_value_derived_ops->checkout : NULL, bound_value_derived_ops != NULL ? bound_value_derived_ops->restore : NULL, &bound_value_derived_status, &bound_value, &bound_value_descriptor_output_present, bound_amount); + if (bound_value_derived_status != 0) { + if (bound_value_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "value"); + } else { + if (bound_value_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "value", bound_value_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + PyObject * result_obj = NULL; + if (bound_value_obj != Py_None) { + Py_INCREF(bound_value_obj); + result_obj = bound_value_obj; + } else { + if (!bound_value_descriptor_output_present) { + if (bound_value != NULL) { + bind_c_prik_destroy_holder_item_pointer_holder(bound_value); + } + Py_INCREF(Py_None); + result_obj = Py_None; + } else { + PyObject * result_obj_capsule = PyCapsule_New(bound_value, "prik.derived.holder_item.pointer_holder", prik_destroy_holder_item_pointer_holder_capsule); + if (result_obj_capsule == NULL) { + bind_c_prik_destroy_holder_item_pointer_holder(bound_value); + return NULL; + } + PyObject * result_obj_helper = PyObject_GetAttrString(self, "_prik_wrap_holder_item"); + if (result_obj_helper == NULL) { + Py_DECREF(result_obj_capsule); + return NULL; + } + PyObject * result_obj_ops = PyObject_GetAttrString(self, "_prik_ops_holder_item_pointer_holder"); + if (result_obj_ops == NULL) { + Py_DECREF(result_obj_helper); + Py_DECREF(result_obj_capsule); + return NULL; + } + result_obj = PyObject_CallFunction(result_obj_helper, "OOOs", result_obj_capsule, self, result_obj_ops, "pointer_holder"); + Py_DECREF(result_obj_ops); + Py_DECREF(result_obj_helper); + Py_DECREF(result_obj_capsule); + if (result_obj == NULL) { + return NULL; + } + } + } + return result_obj; +} + +static PyObject * wrap__prik_class_vector_scale(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"self", "factor", NULL}; + PyObject * bound_self_obj; + void * bound_self = NULL; + int bound_self_derived_access = 0; + prik_derived_origin_ops * bound_self_derived_ops = NULL; + void * bound_self_derived_identity = NULL; + int bound_self_derived_status = 0; + int bound_self_polymorphic = 0; + const char * bound_self_polymorphic_type_name = NULL; + const char * bound_self_polymorphic_type_symbol = NULL; + const char * bound_self_polymorphic_capsule_name = NULL; + PyObject * bound_self_polymorphic_expected = NULL; + PyObject * bound_factor_obj; + double bound_factor; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &bound_self_obj, &bound_factor_obj)) return NULL; + if (bound_self_obj != Py_None) { + bound_self_polymorphic_expected = PyObject_GetAttrString(self, "vector"); + if (bound_self_polymorphic_expected == NULL) { + return NULL; + } + if (Py_TYPE(bound_self_obj) == (PyTypeObject *)bound_self_polymorphic_expected) { + bound_self_polymorphic = 1; + bound_self_polymorphic_type_name = "vector"; + bound_self_polymorphic_type_symbol = "vector"; + bound_self_polymorphic_capsule_name = "prik.derived.vector"; + } + Py_DECREF(bound_self_polymorphic_expected); + if (bound_self_polymorphic == 0) { + PyErr_Format(PyExc_TypeError, "argument self requires exact polymorphic wrapper type: vector"); + return NULL; + } + int bound_self_extract_status = prik_extract_derived_argument(bound_self_obj, bound_self_polymorphic_type_name, bound_self_polymorphic_type_symbol, bound_self_polymorphic_capsule_name, "self", prik_derived_cases_refactoring_goldens_vector___method___scale_self, sizeof(prik_derived_cases_refactoring_goldens_vector___method___scale_self) / sizeof(prik_derived_cases_refactoring_goldens_vector___method___scale_self[0]), &bound_self, &bound_self_derived_access, &bound_self_derived_ops); + if (bound_self_extract_status < 0) { + return NULL; + } + } else { + PyErr_Format(PyExc_TypeError, "argument self requires a derived wrapper"); + return NULL; + } + bound_self_derived_identity = bound_self_derived_ops != NULL ? (void *)bound_self_derived_ops : bound_self; + if (prik_float64_unpack_exact(bound_factor_obj, &bound_factor) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument factor. Received ", Py_TYPE(bound_factor_obj)->tp_name); } return NULL; }; + bind_c__prik_class_vector_scale(bound_self, bound_self_derived_access, bound_self_derived_identity, bound_self_polymorphic, bound_self_derived_ops != NULL ? bound_self_derived_ops->scoped : NULL, bound_self_derived_ops != NULL ? bound_self_derived_ops->checkout : NULL, bound_self_derived_ops != NULL ? bound_self_derived_ops->restore : NULL, &bound_self_derived_status, bound_factor); + if (bound_self_derived_status != 0) { + if (bound_self_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "self"); + } else { + if (bound_self_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "self", bound_self_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * wrap__prik_class_vector_shift(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"dx", "owner", "dy", NULL}; + PyObject * bound_dx_obj; + double bound_dx; + PyObject * bound_owner_obj; + void * bound_owner = NULL; + int bound_owner_derived_access = 0; + prik_derived_origin_ops * bound_owner_derived_ops = NULL; + void * bound_owner_derived_identity = NULL; + int bound_owner_derived_status = 0; + int bound_owner_polymorphic = 0; + const char * bound_owner_polymorphic_type_name = NULL; + const char * bound_owner_polymorphic_type_symbol = NULL; + const char * bound_owner_polymorphic_capsule_name = NULL; + PyObject * bound_owner_polymorphic_expected = NULL; + PyObject * bound_dy_obj; + double bound_dy; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOO", kwlist, &bound_dx_obj, &bound_owner_obj, &bound_dy_obj)) return NULL; + if (prik_float64_unpack_exact(bound_dx_obj, &bound_dx) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument dx. Received ", Py_TYPE(bound_dx_obj)->tp_name); } return NULL; }; + if (bound_owner_obj != Py_None) { + bound_owner_polymorphic_expected = PyObject_GetAttrString(self, "vector"); + if (bound_owner_polymorphic_expected == NULL) { + return NULL; + } + if (Py_TYPE(bound_owner_obj) == (PyTypeObject *)bound_owner_polymorphic_expected) { + bound_owner_polymorphic = 1; + bound_owner_polymorphic_type_name = "vector"; + bound_owner_polymorphic_type_symbol = "vector"; + bound_owner_polymorphic_capsule_name = "prik.derived.vector"; + } + Py_DECREF(bound_owner_polymorphic_expected); + if (bound_owner_polymorphic == 0) { + PyErr_Format(PyExc_TypeError, "argument owner requires exact polymorphic wrapper type: vector"); + return NULL; + } + int bound_owner_extract_status = prik_extract_derived_argument(bound_owner_obj, bound_owner_polymorphic_type_name, bound_owner_polymorphic_type_symbol, bound_owner_polymorphic_capsule_name, "owner", prik_derived_cases_refactoring_goldens_vector___method___shift_owner, sizeof(prik_derived_cases_refactoring_goldens_vector___method___shift_owner) / sizeof(prik_derived_cases_refactoring_goldens_vector___method___shift_owner[0]), &bound_owner, &bound_owner_derived_access, &bound_owner_derived_ops); + if (bound_owner_extract_status < 0) { + return NULL; + } + } else { + PyErr_Format(PyExc_TypeError, "argument owner requires a derived wrapper"); + return NULL; + } + bound_owner_derived_identity = bound_owner_derived_ops != NULL ? (void *)bound_owner_derived_ops : bound_owner; + if (prik_float64_unpack_exact(bound_dy_obj, &bound_dy) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument dy. Received ", Py_TYPE(bound_dy_obj)->tp_name); } return NULL; }; + bind_c__prik_class_vector_shift(bound_dx, bound_owner, bound_owner_derived_access, bound_owner_derived_identity, bound_owner_polymorphic, bound_owner_derived_ops != NULL ? bound_owner_derived_ops->scoped : NULL, bound_owner_derived_ops != NULL ? bound_owner_derived_ops->checkout : NULL, bound_owner_derived_ops != NULL ? bound_owner_derived_ops->restore : NULL, &bound_owner_derived_status, bound_dy); + if (bound_owner_derived_status != 0) { + if (bound_owner_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "owner"); + } else { + if (bound_owner_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "owner", bound_owner_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * wrap__prik_class_vector_magnitude(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"self", NULL}; + PyObject * bound_self_obj; + void * bound_self = NULL; + int bound_self_derived_access = 0; + prik_derived_origin_ops * bound_self_derived_ops = NULL; + void * bound_self_derived_identity = NULL; + int bound_self_derived_status = 0; + int bound_self_polymorphic = 0; + const char * bound_self_polymorphic_type_name = NULL; + const char * bound_self_polymorphic_type_symbol = NULL; + const char * bound_self_polymorphic_capsule_name = NULL; + PyObject * bound_self_polymorphic_expected = NULL; + double result; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &bound_self_obj)) return NULL; + if (bound_self_obj != Py_None) { + bound_self_polymorphic_expected = PyObject_GetAttrString(self, "vector"); + if (bound_self_polymorphic_expected == NULL) { + return NULL; + } + if (Py_TYPE(bound_self_obj) == (PyTypeObject *)bound_self_polymorphic_expected) { + bound_self_polymorphic = 1; + bound_self_polymorphic_type_name = "vector"; + bound_self_polymorphic_type_symbol = "vector"; + bound_self_polymorphic_capsule_name = "prik.derived.vector"; + } + Py_DECREF(bound_self_polymorphic_expected); + if (bound_self_polymorphic == 0) { + PyErr_Format(PyExc_TypeError, "argument self requires exact polymorphic wrapper type: vector"); + return NULL; + } + int bound_self_extract_status = prik_extract_derived_argument(bound_self_obj, bound_self_polymorphic_type_name, bound_self_polymorphic_type_symbol, bound_self_polymorphic_capsule_name, "self", prik_derived_cases_refactoring_goldens_vector___method___magnitude_self, sizeof(prik_derived_cases_refactoring_goldens_vector___method___magnitude_self) / sizeof(prik_derived_cases_refactoring_goldens_vector___method___magnitude_self[0]), &bound_self, &bound_self_derived_access, &bound_self_derived_ops); + if (bound_self_extract_status < 0) { + return NULL; + } + } else { + PyErr_Format(PyExc_TypeError, "argument self requires a derived wrapper"); + return NULL; + } + bound_self_derived_identity = bound_self_derived_ops != NULL ? (void *)bound_self_derived_ops : bound_self; + result = bind_c__prik_class_vector_magnitude(bound_self, bound_self_derived_access, bound_self_derived_identity, bound_self_polymorphic, bound_self_derived_ops != NULL ? bound_self_derived_ops->scoped : NULL, bound_self_derived_ops != NULL ? bound_self_derived_ops->checkout : NULL, bound_self_derived_ops != NULL ? bound_self_derived_ops->restore : NULL, &bound_self_derived_status); + if (bound_self_derived_status != 0) { + if (bound_self_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "self"); + } else { + if (bound_self_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "self", bound_self_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + PyObject * result_obj = prik_float64_to_python(&result); + if (result_obj == NULL) { + return NULL; + } + return result_obj; +} + +static PyObject * wrap__prik_class_vector_replace_samples(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"self", "values", NULL}; + PyObject * bound_self_obj; + void * bound_self = NULL; + int bound_self_derived_access = 0; + prik_derived_origin_ops * bound_self_derived_ops = NULL; + void * bound_self_derived_identity = NULL; + int bound_self_derived_status = 0; + int bound_self_polymorphic = 0; + const char * bound_self_polymorphic_type_name = NULL; + const char * bound_self_polymorphic_type_symbol = NULL; + const char * bound_self_polymorphic_capsule_name = NULL; + PyObject * bound_self_polymorphic_expected = NULL; + PyObject * bound_values_obj; + void * bound_values = NULL; + int64_t bound_values_extent_0 = 0; + int64_t bound_values_upper_bound_0 = 0; + int64_t bound_values_stride_0 = 1; + int bound_values_dense_actual = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &bound_self_obj, &bound_values_obj)) return NULL; + if (bound_self_obj != Py_None) { + bound_self_polymorphic_expected = PyObject_GetAttrString(self, "vector"); + if (bound_self_polymorphic_expected == NULL) { + return NULL; + } + if (Py_TYPE(bound_self_obj) == (PyTypeObject *)bound_self_polymorphic_expected) { + bound_self_polymorphic = 1; + bound_self_polymorphic_type_name = "vector"; + bound_self_polymorphic_type_symbol = "vector"; + bound_self_polymorphic_capsule_name = "prik.derived.vector"; + } + Py_DECREF(bound_self_polymorphic_expected); + if (bound_self_polymorphic == 0) { + PyErr_Format(PyExc_TypeError, "argument self requires exact polymorphic wrapper type: vector"); + return NULL; + } + int bound_self_extract_status = prik_extract_derived_argument(bound_self_obj, bound_self_polymorphic_type_name, bound_self_polymorphic_type_symbol, bound_self_polymorphic_capsule_name, "self", prik_derived_cases_refactoring_goldens_vector___method___replace_samples_self, sizeof(prik_derived_cases_refactoring_goldens_vector___method___replace_samples_self) / sizeof(prik_derived_cases_refactoring_goldens_vector___method___replace_samples_self[0]), &bound_self, &bound_self_derived_access, &bound_self_derived_ops); + if (bound_self_extract_status < 0) { + return NULL; + } + } else { + PyErr_Format(PyExc_TypeError, "argument self requires a derived wrapper"); + return NULL; + } + bound_self_derived_identity = bound_self_derived_ops != NULL ? (void *)bound_self_derived_ops : bound_self; + if (PyArray_Check(bound_values_obj)) { + if (prik_array_validate(bound_values_obj, NPY_FLOAT64, 1, 1, PRIK_ARRAY_LAYOUT_POSITIVE_STRIDED_F, 0, 0, "numpy.float64", "values") < 0) return NULL; + bound_values = PyArray_DATA((PyArrayObject *)bound_values_obj); + bound_values_dense_actual = PyArray_IS_F_CONTIGUOUS((PyArrayObject *)bound_values_obj); + bound_values_extent_0 = (int64_t)PyArray_DIM((PyArrayObject *)bound_values_obj, 0); + if (!bound_values_dense_actual) { + bound_values_stride_0 = PyArray_SIZE((PyArrayObject *)bound_values_obj) == 0 ? 1 : (PyArray_STRIDE((PyArrayObject *)bound_values_obj, 0) / PyArray_ITEMSIZE((PyArrayObject *)bound_values_obj)) / (1); + bound_values_upper_bound_0 = bound_values_extent_0 == 0 ? -1 : (bound_values_extent_0 - 1) * bound_values_stride_0; + bound_values_extent_0 = bound_values_upper_bound_0 + 1; + } + } else { + PyObject * bound_values_shape = NULL; + prik_array_actual bound_values_actual; + bound_values_shape = PyTuple_New(1); + if (bound_values_shape == NULL) return NULL; + Py_INCREF(Py_None); + PyTuple_SET_ITEM(bound_values_shape, 0, Py_None); + if (PyTuple_GET_ITEM(bound_values_shape, 0) == NULL) { Py_DECREF(bound_values_shape); return NULL; }; + if (prik_array_actual_unpack(bound_values_obj, "float64", 1, bound_values_shape, NULL, 0, 1, 1, 0, 0, 1, 0, 0, -1, &bound_values_actual) < 0) { Py_DECREF(bound_values_shape); return NULL; }; + Py_DECREF(bound_values_shape); + bound_values = bound_values_actual.data; + bound_values_extent_0 = bound_values_actual.extents[0]; + bound_values_upper_bound_0 = bound_values_actual.upper_bounds[0]; + bound_values_stride_0 = bound_values_actual.strides[0]; + } + bind_c__prik_class_vector_replace_samples(bound_self, bound_self_derived_access, bound_self_derived_identity, bound_self_polymorphic, bound_self_derived_ops != NULL ? bound_self_derived_ops->scoped : NULL, bound_self_derived_ops != NULL ? bound_self_derived_ops->checkout : NULL, bound_self_derived_ops != NULL ? bound_self_derived_ops->restore : NULL, &bound_self_derived_status, bound_values, bound_values_dense_actual, bound_values_extent_0, bound_values_upper_bound_0, bound_values_stride_0); + if (bound_self_derived_status != 0) { + if (bound_self_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "self"); + } else { + if (bound_self_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "self", bound_self_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * wrap__prik_class_vector___add___0(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"left", "right", NULL}; + PyObject * bound_left_obj; + void * bound_left = NULL; + int bound_left_derived_access = 0; + prik_derived_origin_ops * bound_left_derived_ops = NULL; + void * bound_left_derived_identity = NULL; + int bound_left_derived_status = 0; + int bound_left_polymorphic = 0; + const char * bound_left_polymorphic_type_name = NULL; + const char * bound_left_polymorphic_type_symbol = NULL; + const char * bound_left_polymorphic_capsule_name = NULL; + PyObject * bound_left_polymorphic_expected = NULL; + PyObject * bound_right_obj; + void * bound_right = NULL; + int bound_right_derived_access = 0; + prik_derived_origin_ops * bound_right_derived_ops = NULL; + void * bound_right_derived_identity = NULL; + int bound_right_derived_status = 0; + prik_derived_alias_entry prik_derived_aliases[2]; + void * result = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &bound_left_obj, &bound_right_obj)) return NULL; + if (bound_left_obj != Py_None) { + bound_left_polymorphic_expected = PyObject_GetAttrString(self, "vector"); + if (bound_left_polymorphic_expected == NULL) { + return NULL; + } + if (Py_TYPE(bound_left_obj) == (PyTypeObject *)bound_left_polymorphic_expected) { + bound_left_polymorphic = 1; + bound_left_polymorphic_type_name = "vector"; + bound_left_polymorphic_type_symbol = "vector"; + bound_left_polymorphic_capsule_name = "prik.derived.vector"; + } + Py_DECREF(bound_left_polymorphic_expected); + if (bound_left_polymorphic == 0) { + PyErr_Format(PyExc_TypeError, "argument left requires exact polymorphic wrapper type: vector"); + return NULL; + } + int bound_left_extract_status = prik_extract_derived_argument(bound_left_obj, bound_left_polymorphic_type_name, bound_left_polymorphic_type_symbol, bound_left_polymorphic_capsule_name, "left", prik_derived_cases_refactoring_goldens_vector___add___add_vectors_left, sizeof(prik_derived_cases_refactoring_goldens_vector___add___add_vectors_left) / sizeof(prik_derived_cases_refactoring_goldens_vector___add___add_vectors_left[0]), &bound_left, &bound_left_derived_access, &bound_left_derived_ops); + if (bound_left_extract_status < 0) { + return NULL; + } + } else { + PyErr_Format(PyExc_TypeError, "argument left requires a derived wrapper"); + return NULL; + } + bound_left_derived_identity = bound_left_derived_ops != NULL ? (void *)bound_left_derived_ops : bound_left; + if (bound_right_obj != Py_None) { + int bound_right_extract_status = prik_extract_derived_argument(bound_right_obj, "vector", "vector", "prik.derived.vector", "right", prik_derived_cases_refactoring_goldens_vector___add___add_vectors_right, sizeof(prik_derived_cases_refactoring_goldens_vector___add___add_vectors_right) / sizeof(prik_derived_cases_refactoring_goldens_vector___add___add_vectors_right[0]), &bound_right, &bound_right_derived_access, &bound_right_derived_ops); + if (bound_right_extract_status < 0) { + return NULL; + } + } else { + PyErr_Format(PyExc_TypeError, "argument right requires a derived wrapper"); + return NULL; + } + bound_right_derived_identity = bound_right_derived_ops != NULL ? (void *)bound_right_derived_ops : bound_right; + prik_derived_aliases[0].identity = bound_left_derived_identity; + prik_derived_aliases[0].writable = 0; + prik_derived_aliases[0].argument_name = "left"; + prik_derived_aliases[1].identity = bound_right_derived_identity; + prik_derived_aliases[1].writable = 0; + prik_derived_aliases[1].argument_name = "right"; + if (prik_validate_derived_aliases(prik_derived_aliases, 2) < 0) { + return NULL; + } + result = bind_c__prik_class_vector___add___0(bound_left, bound_left_derived_access, bound_left_derived_identity, bound_left_polymorphic, bound_left_derived_ops != NULL ? bound_left_derived_ops->scoped : NULL, bound_left_derived_ops != NULL ? bound_left_derived_ops->checkout : NULL, bound_left_derived_ops != NULL ? bound_left_derived_ops->restore : NULL, &bound_left_derived_status, bound_right, bound_right_derived_access, bound_right_derived_identity, bound_right_derived_ops != NULL ? bound_right_derived_ops->scoped : NULL, bound_right_derived_ops != NULL ? bound_right_derived_ops->checkout : NULL, bound_right_derived_ops != NULL ? bound_right_derived_ops->restore : NULL, &bound_right_derived_status); + if (bound_left_derived_status != 0) { + if (bound_left_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "left"); + } else { + if (bound_left_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "left", bound_left_derived_status); + } + } + return NULL; + } + if (bound_right_derived_status != 0) { + if (bound_right_derived_status == 1) { + PyErr_Format(PyExc_ValueError, "derived payload for argument %s is not present", "right"); + } else { + if (bound_right_derived_status == 4) { + PyErr_NoMemory(); + } else { + PyErr_Format(PyExc_RuntimeError, "derived origin failure for argument %s (status %d)", "right", bound_right_derived_status); + } + } + return NULL; + } + const char * prik_derived_after_native_fault = getenv("PRIK_WRAPPER_FAIL_DERIVED_AFTER_NATIVE"); + if (prik_derived_after_native_fault != NULL && prik_derived_after_native_fault[0] != '\0' && prik_derived_after_native_fault[0] != '0') { + if (result != NULL) { bind_c_prik_destroy_vector(result); result = NULL; }; + PyErr_SetString(PyExc_RuntimeError, "injected derived failure after native return"); + return NULL; + } + if (result == NULL) { + if (result != NULL) { bind_c_prik_destroy_vector(result); result = NULL; }; + PyErr_NoMemory(); + return NULL; + } + if (result == NULL) { + PyErr_NoMemory(); + return NULL; + } + PyObject * result_obj_capsule = PyCapsule_New(result, "prik.derived.vector", prik_destroy_vector_capsule); + if (result_obj_capsule == NULL) { + bind_c_prik_destroy_vector(result); + return NULL; + } + PyObject * result_obj_helper = PyObject_GetAttrString(self, "_prik_wrap_vector"); + if (result_obj_helper == NULL) { + Py_DECREF(result_obj_capsule); + return NULL; + } + PyObject * result_obj = PyObject_CallFunctionObjArgs(result_obj_helper, result_obj_capsule, NULL); + Py_DECREF(result_obj_helper); + Py_DECREF(result_obj_capsule); + if (result_obj == NULL) { + return NULL; + } + return result_obj; +} + +static PyObject * wrap__prik_overload_convert_0(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"value", NULL}; + PyObject * bound_value_obj; + int32_t bound_value; + double result; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &bound_value_obj)) return NULL; + if (prik_int32_unpack_exact(bound_value_obj, &bound_value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.int32 for argument value. Received ", Py_TYPE(bound_value_obj)->tp_name); } return NULL; }; + result = bind_c__prik_overload_convert_0(bound_value); + PyObject * result_obj = prik_float64_to_python(&result); + if (result_obj == NULL) { + return NULL; + } + return result_obj; +} + +static PyObject * wrap__prik_overload_convert_1(PyObject * self, PyObject * args, PyObject * kwargs) { + static char * kwlist[] = {"value", NULL}; + PyObject * bound_value_obj; + double bound_value; + int32_t result; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &bound_value_obj)) return NULL; + if (prik_float64_unpack_exact(bound_value_obj, &bound_value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.float64 for argument value. Received ", Py_TYPE(bound_value_obj)->tp_name); } return NULL; }; + result = bind_c__prik_overload_convert_1(bound_value); + PyObject * result_obj = prik_int32_to_python(&result); + if (result_obj == NULL) { + return NULL; + } + return result_obj; +} + +static PyObject * module_get_counter(void) { + int32_t value = bind_c_get_counter(); + PyObject * result = prik_int32_to_numpy(&value); + return result; +} + +static int module_set_counter(PyObject * value_obj) { + int32_t value; + if (prik_int32_unpack_exact(value_obj, &value) < 0) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_TypeError, "Expected an argument of type numpy.int32 for module variable counter. Received ", Py_TYPE(value_obj)->tp_name); } return -1; }; + bind_c_set_counter(value); + return 0; +} + +static PyObject * module_get_workspace(void) { + if (prik_module_refactoring_goldens_workspace_handle != NULL) { + Py_INCREF(prik_module_refactoring_goldens_workspace_handle); + return prik_module_refactoring_goldens_workspace_handle; + } + PyObject * prik_module_refactoring_goldens_workspace_handle_build_ops = PyDict_New(); + PyObject * prik_module_refactoring_goldens_workspace_handle_build_operation = NULL; + PyObject * prik_module_refactoring_goldens_workspace_handle_build_runtime = NULL; + PyObject * prik_module_refactoring_goldens_workspace_handle_build_helper = NULL; + if (prik_module_refactoring_goldens_workspace_handle_build_ops == NULL) { + return NULL; + } + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_aligned_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "aligned", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_allocated_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "allocated", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_array_actual_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "array_actual", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_deallocate_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "deallocate", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_descriptor_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "descriptor", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_layout_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "layout", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_native_byte_order_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "native_byte_order", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_resize_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "resize", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_shape_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "shape", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_to_numpy_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "to_numpy", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_workspace_writeable_def, NULL, NULL); + if (prik_module_refactoring_goldens_workspace_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_workspace_handle_build_ops, "writeable", prik_module_refactoring_goldens_workspace_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_operation); + prik_module_refactoring_goldens_workspace_handle_build_runtime = PyImport_ImportModule("prik.runtime.handles"); + if (prik_module_refactoring_goldens_workspace_handle_build_runtime == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + prik_module_refactoring_goldens_workspace_handle_build_helper = PyObject_GetAttrString(prik_module_refactoring_goldens_workspace_handle_build_runtime, "_native_array_handle_from_generated_ops"); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_runtime); + if (prik_module_refactoring_goldens_workspace_handle_build_helper == NULL) { + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + return NULL; + } + prik_module_refactoring_goldens_workspace_handle = PyObject_CallFunction(prik_module_refactoring_goldens_workspace_handle_build_helper, "ssiOOssO", "allocatable", "float64", 1, prik_module_refactoring_goldens_workspace_handle_build_ops, prik_module_refactoring_goldens_workspace_owner != NULL ? prik_module_refactoring_goldens_workspace_owner : Py_None, "borrowed", "descriptor_view", Py_None); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_helper); + Py_DECREF(prik_module_refactoring_goldens_workspace_handle_build_ops); + if (prik_module_refactoring_goldens_workspace_handle == NULL) { + return NULL; + } + Py_INCREF(prik_module_refactoring_goldens_workspace_handle); + return prik_module_refactoring_goldens_workspace_handle; +} + +static PyObject * module_get_selected(void) { + if (prik_module_refactoring_goldens_selected_handle != NULL) { + Py_INCREF(prik_module_refactoring_goldens_selected_handle); + return prik_module_refactoring_goldens_selected_handle; + } + PyObject * prik_module_refactoring_goldens_selected_handle_build_ops = PyDict_New(); + PyObject * prik_module_refactoring_goldens_selected_handle_build_operation = NULL; + PyObject * prik_module_refactoring_goldens_selected_handle_build_runtime = NULL; + PyObject * prik_module_refactoring_goldens_selected_handle_build_helper = NULL; + if (prik_module_refactoring_goldens_selected_handle_build_ops == NULL) { + return NULL; + } + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_aligned_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "aligned", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_array_actual_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "array_actual", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_associate_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "associate", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_associated_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "associated", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_contiguous_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "contiguous", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_descriptor_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "descriptor", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_layout_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "layout", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_native_byte_order_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "native_byte_order", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_nullify_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "nullify", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_shape_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "shape", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_to_numpy_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "to_numpy", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_operation = PyCFunction_NewEx(&prik_module_refactoring_goldens_selected_writeable_def, NULL, NULL); + if (prik_module_refactoring_goldens_selected_handle_build_operation == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + if (PyDict_SetItemString(prik_module_refactoring_goldens_selected_handle_build_ops, "writeable", prik_module_refactoring_goldens_selected_handle_build_operation) < 0) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_operation); + prik_module_refactoring_goldens_selected_handle_build_runtime = PyImport_ImportModule("prik.runtime.handles"); + if (prik_module_refactoring_goldens_selected_handle_build_runtime == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + prik_module_refactoring_goldens_selected_handle_build_helper = PyObject_GetAttrString(prik_module_refactoring_goldens_selected_handle_build_runtime, "_native_array_handle_from_generated_ops"); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_runtime); + if (prik_module_refactoring_goldens_selected_handle_build_helper == NULL) { + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + return NULL; + } + prik_module_refactoring_goldens_selected_handle = PyObject_CallFunction(prik_module_refactoring_goldens_selected_handle_build_helper, "ssiOOssO", "pointer", "float64", 1, prik_module_refactoring_goldens_selected_handle_build_ops, prik_module_refactoring_goldens_selected_owner != NULL ? prik_module_refactoring_goldens_selected_owner : Py_None, "borrowed", "unsupported", Py_None); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_helper); + Py_DECREF(prik_module_refactoring_goldens_selected_handle_build_ops); + if (prik_module_refactoring_goldens_selected_handle == NULL) { + return NULL; + } + Py_INCREF(prik_module_refactoring_goldens_selected_handle); + return prik_module_refactoring_goldens_selected_handle; +} + +static PyObject * module_get_active_vector(void) { + PyObject * capsule = Py_None; + PyObject * helper = PyObject_GetAttrString(prik_module_refactoring_goldens_active_vector_derived_owner, "_prik_wrap_vector"); + if (helper == NULL) { + return NULL; + } + PyObject * ops = PyObject_GetAttrString(prik_module_refactoring_goldens_active_vector_derived_owner, "_prik_ops_active_vector"); + if (ops == NULL) { + Py_DECREF(helper); + return NULL; + } + PyObject * result = PyObject_CallFunction(helper, "OOOs", capsule, prik_module_refactoring_goldens_active_vector_derived_owner, ops, "module_allocatable"); + Py_DECREF(helper); + Py_DECREF(ops); + return result; +} + +static PyObject * module_get_selected_vector(void) { + PyObject * capsule = Py_None; + PyObject * helper = PyObject_GetAttrString(prik_module_refactoring_goldens_selected_vector_derived_owner, "_prik_wrap_vector"); + if (helper == NULL) { + return NULL; + } + PyObject * ops = PyObject_GetAttrString(prik_module_refactoring_goldens_selected_vector_derived_owner, "_prik_ops_selected_vector"); + if (ops == NULL) { + Py_DECREF(helper); + return NULL; + } + PyObject * result = PyObject_CallFunction(helper, "OOOs", capsule, prik_module_refactoring_goldens_selected_vector_derived_owner, ops, "module_pointer"); + Py_DECREF(helper); + Py_DECREF(ops); + return result; +} + +static PyObject * wrap__prik_dispatch_convert_d27e6413(PyObject * self, PyObject * args, PyObject * kwargs) { + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t user_nargs = nargs; + int candidate_id = -1; + if (candidate_id < 0 && (user_nargs <= 1 && (kwargs == NULL || (PyDict_Size(kwargs) == ((PyDict_GetItemString(kwargs, "value") != NULL)) && (user_nargs <= 0 || PyDict_GetItemString(kwargs, "value") == NULL))) && ((user_nargs > 0 ? PyTuple_GET_ITEM(args, 0) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "value") : NULL)) != NULL && (PyArray_IsScalar((user_nargs > 0 ? PyTuple_GET_ITEM(args, 0) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "value") : NULL)), Int))))) { + candidate_id = 0; + } + if (candidate_id < 0 && (user_nargs <= 1 && (kwargs == NULL || (PyDict_Size(kwargs) == ((PyDict_GetItemString(kwargs, "value") != NULL)) && (user_nargs <= 0 || PyDict_GetItemString(kwargs, "value") == NULL))) && ((user_nargs > 0 ? PyTuple_GET_ITEM(args, 0) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "value") : NULL)) != NULL && (PyArray_IsScalar((user_nargs > 0 ? PyTuple_GET_ITEM(args, 0) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "value") : NULL)), Double))))) { + candidate_id = 1; + } + switch (candidate_id) { + case 0: { + PyObject * candidate_kwargs = PyDict_New(); + if (candidate_kwargs == NULL) { + return NULL; + } + PyObject * candidate_value_0 = (user_nargs > 0 ? PyTuple_GET_ITEM(args, 0) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "value") : NULL)); + if (PyDict_SetItemString(candidate_kwargs, "value", candidate_value_0) < 0) { + Py_DECREF(candidate_kwargs); + return NULL; + } + PyObject * candidate_args = PyTuple_New(0); + if (candidate_args == NULL) { + Py_DECREF(candidate_kwargs); + return NULL; + } + PyObject * candidate_result = wrap__prik_overload_convert_0(self, candidate_args, candidate_kwargs); + Py_DECREF(candidate_args); + Py_DECREF(candidate_kwargs); + return candidate_result; + } + case 1: { + PyObject * candidate_kwargs = PyDict_New(); + if (candidate_kwargs == NULL) { + return NULL; + } + PyObject * candidate_value_0 = (user_nargs > 0 ? PyTuple_GET_ITEM(args, 0) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "value") : NULL)); + if (PyDict_SetItemString(candidate_kwargs, "value", candidate_value_0) < 0) { + Py_DECREF(candidate_kwargs); + return NULL; + } + PyObject * candidate_args = PyTuple_New(0); + if (candidate_args == NULL) { + Py_DECREF(candidate_kwargs); + return NULL; + } + PyObject * candidate_result = wrap__prik_overload_convert_1(self, candidate_args, candidate_kwargs); + Py_DECREF(candidate_args); + Py_DECREF(candidate_kwargs); + return candidate_result; + } + default: { + PyErr_SetString(PyExc_TypeError, "no matching overload for convert"); + return NULL; + } + } +} + +static PyObject * wrap__prik_dispatch_add_eeb3bbc5(PyObject * self, PyObject * args, PyObject * kwargs) { + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + if (nargs < 1) { + PyErr_SetString(PyExc_TypeError, "no matching overload for __add__"); + return NULL; + } + PyObject * receiver = PyTuple_GET_ITEM(args, 0); + Py_ssize_t user_nargs = nargs - 1; + int candidate_id = -1; + if (candidate_id < 0 && (user_nargs <= 1 && (kwargs == NULL || (PyDict_Size(kwargs) == ((PyDict_GetItemString(kwargs, "right") != NULL)) && (user_nargs <= 0 || PyDict_GetItemString(kwargs, "right") == NULL))) && ((user_nargs > 0 ? PyTuple_GET_ITEM(args, 1) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "right") : NULL)) != NULL && (PyDict_GetItemString(PyModule_GetDict(self), "vector") != NULL && (PyObject *)Py_TYPE((user_nargs > 0 ? PyTuple_GET_ITEM(args, 1) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "right") : NULL))) == PyDict_GetItemString(PyModule_GetDict(self), "vector"))))) { + candidate_id = 0; + } + switch (candidate_id) { + case 0: { + PyObject * candidate_kwargs = PyDict_New(); + if (candidate_kwargs == NULL) { + return NULL; + } + PyObject * candidate_value_0 = (user_nargs > 0 ? PyTuple_GET_ITEM(args, 1) : (kwargs != NULL ? PyDict_GetItemString(kwargs, "right") : NULL)); + if (PyDict_SetItemString(candidate_kwargs, "right", candidate_value_0) < 0) { + Py_DECREF(candidate_kwargs); + return NULL; + } + if (PyDict_SetItemString(candidate_kwargs, "left", receiver) < 0) { + Py_DECREF(candidate_kwargs); + return NULL; + } + PyObject * candidate_args = PyTuple_New(0); + if (candidate_args == NULL) { + Py_DECREF(candidate_kwargs); + return NULL; + } + PyObject * candidate_result = wrap__prik_class_vector___add___0(self, candidate_args, candidate_kwargs); + Py_DECREF(candidate_args); + Py_DECREF(candidate_kwargs); + return candidate_result; + } + default: { + PyErr_SetString(PyExc_TypeError, "no matching overload for __add__"); + return NULL; + } + } +} + +PyMODINIT_FUNC PyInit_refactoring_goldens(void) { + import_array(); + PyObject * mod = PyModule_Create(&refactoring_goldens_root_module); + if (mod == NULL) return NULL; + if (refactoring_goldens_root_module_property_setup(mod) < 0) { Py_DECREF(mod); return NULL; }; + PyObject * root_python_dict = PyModule_GetDict(mod); + if (root_python_dict == NULL) { + return NULL; + } + PyObject * root_python_setup = PyRun_String("_prik_unset = object()\n\n_prik_ops_holder_item = {'code_get': _prik_field_holder_item_code_get, 'code_set': _prik_field_holder_item_code_set, 'weight_get': _prik_field_holder_item_weight_get, 'weight_set': _prik_field_holder_item_weight_set}\nclass holder_item:\n 'holder_item\\n\\nOpaque wrapper for native type holder_item.\\n\\nConstructor\\n-----------\\nholder_item(*, code=0, weight=0) -> holder_item\\n\\nFields\\n------\\ncode : int32\\nweight : float64'\n __slots__ = ('_prik_capsule', '_prik_owner', '_prik_ops', '_prik_origin')\n def __new__(cls, *args, **kwargs):\n return _prik_create_holder_item()\n def __init__(self, *, code=_prik_unset, weight=_prik_unset):\n 'holder_item(*, code=0, weight=0) -> holder_item\\n\\nParameters\\n----------\\ncode : int32\\nweight : float64\\n\\nReturns\\n-------\\nholder_item\\n New wrapper-owned native instance.\\n\\nRaises\\n------\\nTypeError\\n If the supplied arguments do not satisfy the constructor contract.'\n if code is not _prik_unset:\n self.code = code\n if weight is not _prik_unset:\n self.weight = weight\n @property\n def code(self):\n 'code : int32\\n Assignment writes through to native storage.'\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n return self._prik_ops['code_get'](self)\n @code.setter\n def code(self, value):\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n self._prik_ops['code_set'](self, value)\n @property\n def weight(self):\n 'weight : float64\\n Assignment writes through to native storage.'\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n return self._prik_ops['weight_get'](self)\n @weight.setter\n def weight(self, value):\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n self._prik_ops['weight_set'](self, value)\ndef _prik_wrap_holder_item(capsule, owner=None, ops=None, origin='direct'):\n value = object.__new__(holder_item)\n value._prik_capsule = capsule\n value._prik_owner = owner\n value._prik_ops = _prik_ops_holder_item if ops is None else ops\n value._prik_origin = origin\n return value\n\n_prik_ops_vector = {'x_get': _prik_field_vector_x_get, 'x_set': _prik_field_vector_x_set, 'y_get': _prik_field_vector_y_get, 'y_set': _prik_field_vector_y_set, 'samples_get': _prik_field_vector_samples_get}\nclass vector:\n 'vector\\n\\nOpaque wrapper for native type vector.\\n\\nConstructor\\n-----------\\nvector(*, x=0, y=0) -> vector\\n\\nFields\\n------\\nx : float64\\ny : float64\\nsamples : AllocatableArray[float64]\\n\\nMethods\\n-------\\nscale(factor) -> None\\nshift(dx, dy) -> None\\nmagnitude() -> float64\\nreplace_samples(values) -> None\\n__add__(*args, **kwargs)'\n __slots__ = ('_prik_capsule', '_prik_owner', '_prik_ops', '_prik_origin')\n def __new__(cls, *args, **kwargs):\n return _prik_create_vector()\n def __init__(self, *, x=_prik_unset, y=_prik_unset):\n 'vector(*, x=0, y=0) -> vector\\n\\nParameters\\n----------\\nx : float64\\ny : float64\\n\\nReturns\\n-------\\nvector\\n New wrapper-owned native instance.\\n\\nRaises\\n------\\nTypeError\\n If the supplied arguments do not satisfy the constructor contract.'\n if x is not _prik_unset:\n self.x = x\n if y is not _prik_unset:\n self.y = y\n @property\n def x(self):\n 'x : float64\\n Assignment writes through to native storage.'\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n return self._prik_ops['x_get'](self)\n @x.setter\n def x(self, value):\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n self._prik_ops['x_set'](self, value)\n @property\n def y(self):\n 'y : float64\\n Assignment writes through to native storage.'\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n return self._prik_ops['y_get'](self)\n @y.setter\n def y(self, value):\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n self._prik_ops['y_set'](self, value)\n @property\n def samples(self):\n 'samples : AllocatableArray[float64]\\n Rank: 1\\n Live allocatable array descriptor handle.\\n The parent wrapper retains the descriptor owner.\\n Replacement assignment is not supported.'\n present = self._prik_ops.get('_present')\n if present is not None:\n present(self)\n return self._prik_ops['samples_get'](self)\n @samples.setter\n def samples(self, value):\n raise AttributeError('field samples does not support replacement assignment')\n def scale(self, factor):\n 'scale(factor) -> None\\n\\nParameters\\n----------\\nfactor : float64\\n\\nReturns\\n-------\\nNone\\n\\nRaises\\n------\\nTypeError\\n If an argument has an incompatible Python type or dtype.\\n\\nNotes\\n-----\\nUpdates the wrapped native instance in place.'\n return _prik_class_vector_scale(self, factor)\n def shift(self, dx, dy):\n 'shift(dx, dy) -> None\\n\\nParameters\\n----------\\ndx : float64\\ndy : float64\\n\\nReturns\\n-------\\nNone\\n\\nRaises\\n------\\nTypeError\\n If an argument has an incompatible Python type or dtype.\\n\\nNotes\\n-----\\nUpdates the wrapped native instance in place.'\n return _prik_class_vector_shift(dx, self, dy)\n def magnitude(self):\n 'magnitude() -> float64\\n\\nReturns\\n-------\\nresult : float64\\n\\nRaises\\n------\\nTypeError\\n If an argument has an incompatible Python type or dtype.'\n return _prik_class_vector_magnitude(self)\n def replace_samples(self, values):\n 'replace_samples(values) -> None\\n\\nParameters\\n----------\\nvalues : ndarray[float64]\\n Rank: 1\\n Shape: (::Strided)\\n Ownership: Caller-owned.\\n\\nReturns\\n-------\\nNone\\n\\nRaises\\n------\\nTypeError\\n If an argument has an incompatible Python type or dtype.\\nValueError\\n If rank, shape, layout, or descriptor state violates the contract.\\n\\nNotes\\n-----\\nUpdates the wrapped native instance in place.'\n return _prik_class_vector_replace_samples(self, values)\n def __add__(self, *args, **kwargs):\n '__add__(*args, **kwargs)\\n\\nSupported Signatures\\n--------------------\\n__add__(right: vector) -> vector\\n\\nRaises\\n------\\nTypeError\\n If no supported signature matches the supplied arguments.\\n\\nNotes\\n-----\\nDispatches to a native operation on the wrapped instance.'\n return _prik_dispatch_add_eeb3bbc5(self, *args, **kwargs)\ndef _prik_wrap_vector(capsule, owner=None, ops=None, origin='direct'):\n value = object.__new__(vector)\n value._prik_capsule = capsule\n value._prik_owner = owner\n value._prik_ops = _prik_ops_vector if ops is None else ops\n value._prik_origin = origin\n return value\n\n_prik_ops_holder_item_allocatable_holder = {'_present': _prik_holder_item_allocatable_holder_require_present, 'code_get': _prik_allocatable_holder_field_holder_item_code_get, 'code_set': _prik_allocatable_holder_field_holder_item_code_set, 'weight_get': _prik_allocatable_holder_field_holder_item_weight_get, 'weight_set': _prik_allocatable_holder_field_holder_item_weight_set}\n\n_prik_ops_holder_item_pointer_holder = {'_present': _prik_holder_item_pointer_holder_require_present, 'code_get': _prik_pointer_holder_field_holder_item_code_get, 'code_set': _prik_pointer_holder_field_holder_item_code_set, 'weight_get': _prik_pointer_holder_field_holder_item_weight_get, 'weight_set': _prik_pointer_holder_field_holder_item_weight_set}\n\n_prik_ops_active_vector = {'_native_ops': _prik_origin_active_vector_26504a12_native_ops(), '_present': _prik_module_active_vector_require_present, 'x_get': _prik_module_field_active_vector_x_get, 'x_set': _prik_module_field_active_vector_x_set, 'y_get': _prik_module_field_active_vector_y_get, 'y_set': _prik_module_field_active_vector_y_set, 'samples_get': _prik_module_field_active_vector_samples_get}\n\n_prik_ops_selected_vector = {'_native_ops': _prik_origin_selected_vector_d2fd3c9d_native_ops(), '_present': _prik_module_selected_vector_require_present, 'x_get': _prik_module_field_selected_vector_x_get, 'x_set': _prik_module_field_selected_vector_x_set, 'y_get': _prik_module_field_selected_vector_y_get, 'y_set': _prik_module_field_selected_vector_y_set, 'samples_get': _prik_module_field_selected_vector_samples_get}", Py_file_input, root_python_dict, root_python_dict); + if (root_python_setup == NULL) { + return NULL; + } + Py_DECREF(root_python_setup); + Py_INCREF(mod); + prik_module_refactoring_goldens_workspace_owner = mod; + Py_INCREF(mod); + prik_module_refactoring_goldens_selected_owner = mod; + Py_INCREF(mod); + prik_module_refactoring_goldens_active_vector_derived_owner = mod; + Py_INCREF(mod); + prik_module_refactoring_goldens_selected_vector_derived_owner = mod; + int32_t constant_default_count_value_0 = 3; + PyObject * constant_default_count_object_0 = prik_int32_to_numpy(&constant_default_count_value_0); + if (constant_default_count_object_0 == NULL) { Py_DECREF(mod); return NULL; }; + if (PyModule_AddObject(mod, "default_count", constant_default_count_object_0) < 0) { Py_DECREF(constant_default_count_object_0); Py_DECREF(mod); return NULL; }; + return mod; +} \ No newline at end of file diff --git a/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/bridge.f90 b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/bridge.f90 new file mode 100644 index 000000000..40b800c78 --- /dev/null +++ b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/bridge.f90 @@ -0,0 +1,2322 @@ +module bind_c_refactoring_goldens_wrapper + use iso_c_binding, only: & + c_associated, & + c_bool, & + c_char, & + c_double, & + c_double_complex, & + c_f_pointer, & + c_float, & + c_float_complex, & + c_int8_t, & + c_int16_t, & + c_int, & + c_int32_t, & + c_int64_t, & + c_loc, & + c_null_char, & + c_ptr, & + c_null_ptr, & + c_size_t, & + c_sizeof, & + c_funptr, & + c_f_procpointer, & + c_funloc + use refactoring_goldens, only: & + prik_type_holder_item => holder_item, & + prik_type_vector => vector, & + native_summarize => summarize, & + native_make_values => make_values, & + native_apply_callback => apply_callback, & + native_split_value => split_value, & + native_reset_allocatable_item => reset_allocatable_item, & + native_shift_pointer_item => shift_pointer_item, & + operator(+), & + native__prik_overload_convert_0 => convert, & + native__prik_overload_convert_1 => convert, & + native_counter => counter, & + native_workspace => workspace, & + native_selected => selected, & + native_active_vector => active_vector, & + native_selected_vector => selected_vector + implicit none + type :: prik_holder_item_allocatable_holder + type(prik_type_holder_item), allocatable :: value + end type prik_holder_item_allocatable_holder + type :: prik_vector_allocatable_holder + type(prik_type_vector), allocatable :: value + end type prik_vector_allocatable_holder + type :: prik_holder_item_pointer_holder + type(prik_type_holder_item), pointer :: value + end type prik_holder_item_pointer_holder + type :: prik_vector_pointer_holder + type(prik_type_vector), pointer :: value + end type prik_vector_pointer_holder + abstract interface + function prik_derived_consumer(address, context) bind(c) result(status) + import :: c_ptr, c_int + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + end function prik_derived_consumer + function prik_derived_scoped(consumer, context) bind(c) result(status) + import :: c_ptr, c_funptr, c_int + type(c_funptr), value :: consumer + type(c_ptr), value :: context + integer(c_int) :: status + end function prik_derived_scoped + function prik_derived_checkout(holder) bind(c) result(status) + import :: c_ptr, c_int + type(c_ptr), intent(out) :: holder + integer(c_int) :: status + end function prik_derived_checkout + function prik_derived_restore(holder) bind(c) result(status) + import :: c_ptr, c_int + type(c_ptr), value :: holder + integer(c_int) :: status + end function prik_derived_restore + end interface + abstract interface + function prik_scalar_callback_299cdba6(value) result(prik_result) + import :: c_double + real(c_double), intent(in) :: value + real(c_double) :: prik_result + end function prik_scalar_callback_299cdba6 + end interface + interface + subroutine prik_workspace_descriptor_consumer(value, context) bind(c, name="prik_workspace_descriptor_consumer") + import :: c_double, c_ptr + real(c_double), allocatable, dimension(:), intent(in) :: value + type(c_ptr), value :: context + end subroutine prik_workspace_descriptor_consumer + end interface + interface + subroutine prik_field_handle_vector_samples_consumer(& + & value, & + & context) bind(c, name="prik_field_handle_vector_samples_consumer") + import :: c_double, c_ptr + real(c_double), allocatable, dimension(:), intent(in) :: value + type(c_ptr), value :: context + end subroutine prik_field_handle_vector_samples_consumer + subroutine prik_module_field_handle_active_vector_samples_consumer(& + & value, & + & context) bind(c, name="prik_module_field_handle_active_vector_samples_consumer") + import :: c_double, c_ptr + real(c_double), allocatable, dimension(:), intent(in) :: value + type(c_ptr), value :: context + end subroutine prik_module_field_handle_active_vector_samples_consumer + subroutine prik_module_field_handle_selected_vector_samples_consumer(& + & value, & + & context) bind(c, name="prik_module_field_handle_selected_vector_samples_consumer") + import :: c_double, c_ptr + real(c_double), allocatable, dimension(:), intent(in) :: value + type(c_ptr), value :: context + end subroutine prik_module_field_handle_selected_vector_samples_consumer + end interface + interface + function c_malloc(size) bind(c, name="prik_malloc") result(ptr) + import :: c_ptr, c_size_t + integer(c_size_t), value :: size + type(c_ptr) :: ptr + end function c_malloc + end interface +contains + function bind_c_summarize(& + & required, & + & bound_scale, & + & bound_values, & + & values_dense_actual, & + & values_extent_0, & + & values_upper_bound_0, & + & values_stride_0, & + & bound_label, & + & label_length, & + & bound_item, & + & bound_item_access, & + & bound_item_identity, & + & bound_item_scoped, & + & bound_item_checkout, & + & bound_item_restore, & + & bound_item_status) result(result) bind(c, name="bind_c_summarize") + integer(c_int32_t), value :: required + type(c_ptr), value :: bound_scale + type(c_ptr), value :: bound_values + integer(c_int), value :: values_dense_actual + integer(c_int64_t), value :: values_extent_0 + integer(c_int64_t), value :: values_upper_bound_0 + integer(c_int64_t), value :: values_stride_0 + type(c_ptr), value :: bound_label + integer(c_int64_t), value :: label_length + type(c_ptr), value :: bound_item + integer(c_int), value :: bound_item_access + type(c_ptr), value :: bound_item_identity + type(c_funptr), value :: bound_item_scoped + type(c_funptr), value :: bound_item_checkout + type(c_funptr), value :: bound_item_restore + integer(c_int), intent(out) :: bound_item_status + integer(c_int32_t) :: result + integer(c_int32_t), pointer :: scale + real(c_double), pointer, dimension(:) :: values_base + real(c_double), pointer, dimension(:) :: values + character(kind=c_char), pointer, dimension(:) :: label_bytes + character(kind=c_char, len=label_length) :: label + logical :: prik_derived_ready + type(prik_type_vector), pointer :: item + type(prik_vector_allocatable_holder), pointer :: item_allocatable_holder + type(prik_vector_pointer_holder), pointer :: item_pointer_holder + type(prik_type_vector), pointer :: item_call_pointer + type(c_ptr) :: item_transaction_address + integer(c_int) :: item_holder_status + integer(c_int) :: item_restore_status + logical :: item_created + logical :: item_acquired + procedure(prik_derived_scoped), pointer :: item_scoped_proc + procedure(prik_derived_checkout), pointer :: item_checkout_proc + procedure(prik_derived_restore), pointer :: item_restore_proc + bound_item_status = 0_c_int + item_created = .false. + item_acquired = .false. + item_transaction_address = c_null_ptr + nullify(item) + nullify(item_call_pointer) + prik_derived_ready = .true. + select case (bound_item_access) + case (0) + case (1) + if (c_associated(bound_item)) then + call c_f_pointer(bound_item, item) + else + bound_item_status = 1_c_int + end if + case (2) + if (c_associated(bound_item_scoped)) then + call c_f_procpointer(bound_item_scoped, item_scoped_proc) + else + bound_item_status = 6_c_int + end if + case (3) + item_holder_status = 0_c_int + if (c_associated(bound_item)) then + call c_f_pointer(bound_item, item_allocatable_holder) + else + allocate(item_allocatable_holder, stat=item_holder_status) + item_created = .true. + end if + if (item_holder_status /= 0_c_int) then + bound_item_status = 4_c_int + else + if (allocated(item_allocatable_holder%value)) then + item => item_allocatable_holder%value + else + bound_item_status = 1_c_int + end if + end if + case (4) + item_holder_status = 0_c_int + if (c_associated(bound_item)) then + call c_f_pointer(bound_item, item_pointer_holder) + else + allocate(item_pointer_holder, stat=item_holder_status) + nullify(item_pointer_holder%value) + item_created = .true. + end if + if (item_holder_status /= 0_c_int) then + bound_item_status = 4_c_int + else + if (associated(item_pointer_holder%value)) then + item => item_pointer_holder%value + else + bound_item_status = 1_c_int + end if + end if + case default + bound_item_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_item_status == 0_c_int) then + select case (bound_item_access) + case (5) + bound_item_status = item_checkout_proc(item_transaction_address) + if (bound_item_status == 0_c_int) then + call c_f_pointer(item_transaction_address, item_allocatable_holder) + item_acquired = .true. + end if + case (6) + bound_item_status = item_checkout_proc(item_transaction_address) + if (bound_item_status == 0_c_int) then + call c_f_pointer(item_transaction_address, item_pointer_holder) + item_acquired = .true. + end if + case default + end select + if (bound_item_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + call prik_derived_step_0() + end if + if (item_acquired) then + item_restore_status = item_restore_proc(item_transaction_address) + if (item_restore_status /= 0_c_int) then + bound_item_status = item_restore_status + end if + item_acquired = .false. + end if + if (item_created .and. bound_item_access == 3_c_int) then + deallocate(item_allocatable_holder) + end if + if (item_created .and. bound_item_access == 4_c_int) then + deallocate(item_pointer_holder) + end if + contains + subroutine prik_derived_optional_step_0() + if (bound_item_access /= 0_c_int) then + call prik_derived_optional_step_1(item) + else + call prik_derived_optional_step_1() + end if + end subroutine prik_derived_optional_step_0 + subroutine prik_derived_optional_step_1(prik_optional_item) + type(prik_type_vector), optional :: prik_optional_item + if (c_associated(bound_scale)) then + call c_f_pointer(bound_scale, scale) + if (c_associated(bound_values)) then + call c_f_pointer(bound_values, values_base, [values_extent_0]) + if (values_dense_actual /= 0_c_int) then + values => values_base + else + values => values_base(1:values_upper_bound_0 + 1:values_stride_0) + end if + if (c_associated(bound_label)) then + call c_f_pointer(bound_label, label_bytes, [label_length]) + label = transfer(label_bytes, label) + result = native_summarize(required=required, scale=scale, values=values, label=label, item=prik_optional_item) + else + result = native_summarize(required=required, scale=scale, values=values, item=prik_optional_item) + end if + else + if (c_associated(bound_label)) then + call c_f_pointer(bound_label, label_bytes, [label_length]) + label = transfer(label_bytes, label) + result = native_summarize(required=required, scale=scale, label=label, item=prik_optional_item) + else + result = native_summarize(required=required, scale=scale, item=prik_optional_item) + end if + end if + else + if (c_associated(bound_values)) then + call c_f_pointer(bound_values, values_base, [values_extent_0]) + if (values_dense_actual /= 0_c_int) then + values => values_base + else + values => values_base(1:values_upper_bound_0 + 1:values_stride_0) + end if + if (c_associated(bound_label)) then + call c_f_pointer(bound_label, label_bytes, [label_length]) + label = transfer(label_bytes, label) + result = native_summarize(required=required, values=values, label=label, item=prik_optional_item) + else + result = native_summarize(required=required, values=values, item=prik_optional_item) + end if + else + if (c_associated(bound_label)) then + call c_f_pointer(bound_label, label_bytes, [label_length]) + label = transfer(label_bytes, label) + result = native_summarize(required=required, label=label, item=prik_optional_item) + else + result = native_summarize(required=required, item=prik_optional_item) + end if + end if + end if + end subroutine prik_derived_optional_step_1 + subroutine prik_derived_step_0() + if (bound_item_access == 2_c_int) then + bound_item_status = item_scoped_proc(c_funloc(prik_derived_consumer_0), c_null_ptr) + else + call prik_derived_step_1() + end if + end subroutine prik_derived_step_0 + function prik_derived_consumer_0(address, context) result(status) bind(c) + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + if (c_associated(address)) then + call c_f_pointer(address, item) + call prik_derived_step_1() + status = 0_c_int + else + status = 1_c_int + end if + end function prik_derived_consumer_0 + subroutine prik_derived_step_1() + call prik_derived_optional_step_0() + end subroutine prik_derived_step_1 + end function bind_c_summarize + subroutine bind_c_make_values(count, fill_value, result) bind(c, name="bind_c_make_values") + integer(c_int32_t), value :: count + real(c_double), value :: fill_value + real(c_double), allocatable, dimension(:), intent(out) :: result + real(c_double), allocatable, dimension(:) :: result_value + result_value = native_make_values(count, fill_value) + if (allocated(result_value)) then + call move_alloc(result_value, result) + else + if (allocated(result)) then + deallocate(result) + end if + end if + end subroutine bind_c_make_values + function bind_c_owned_result_5531b6b6_allocated(& + & result) result(state) bind(c, name="bind_c_owned_result_5531b6b6_allocated") + real(c_double), allocatable, dimension(:), intent(in) :: result + logical(c_bool) :: state + state = allocated(result) + end function bind_c_owned_result_5531b6b6_allocated + subroutine bind_c_owned_result_5531b6b6_deallocate(& + & result) bind(c, name="bind_c_owned_result_5531b6b6_deallocate") + real(c_double), allocatable, dimension(:), intent(inout) :: result + if (allocated(result)) then + deallocate(result) + end if + end subroutine bind_c_owned_result_5531b6b6_deallocate + subroutine bind_c_owned_result_5531b6b6_destroy(result) bind(c, name="bind_c_owned_result_5531b6b6_destroy") + real(c_double), allocatable, dimension(:), intent(inout) :: result + if (allocated(result)) then + deallocate(result) + end if + end subroutine bind_c_owned_result_5531b6b6_destroy + subroutine bind_c_owned_result_5531b6b6_shape(& + & result, & + & extent_0) bind(c, name="bind_c_owned_result_5531b6b6_shape") + real(c_double), allocatable, dimension(:), intent(in) :: result + integer(c_int64_t) :: extent_0 + if (allocated(result)) then + extent_0 = size(result, 1, kind=c_int64_t) + else + extent_0 = 0_c_int64_t + end if + end subroutine bind_c_owned_result_5531b6b6_shape + function bind_c_apply_callback(value) result(result) bind(c, name="bind_c_apply_callback") + real(c_double), value :: value + real(c_double) :: result + procedure(prik_scalar_callback_299cdba6) :: prik_callback_adapter_callback_83b3d1d9 + result = native_apply_callback(prik_callback_adapter_callback_83b3d1d9, value) + end function bind_c_apply_callback + subroutine bind_c_split_value(value, doubled, status) bind(c, name="bind_c_split_value") + real(c_double), value :: value + real(c_double) :: doubled + integer(c_int32_t) :: status + call native_split_value(value, doubled, status) + end subroutine bind_c_split_value + subroutine bind_c_reset_allocatable_item(& + & bound_value, & + & bound_value_access, & + & bound_value_identity, & + & bound_value_scoped, & + & bound_value_checkout, & + & bound_value_restore, & + & bound_value_status, & + & bound_value_output, & + & bound_value_output_present) bind(c, name="bind_c_reset_allocatable_item") + type(c_ptr), value :: bound_value + integer(c_int), value :: bound_value_access + type(c_ptr), value :: bound_value_identity + type(c_funptr), value :: bound_value_scoped + type(c_funptr), value :: bound_value_checkout + type(c_funptr), value :: bound_value_restore + integer(c_int), intent(out) :: bound_value_status + type(c_ptr), intent(out) :: bound_value_output + integer(c_int), intent(out) :: bound_value_output_present + logical :: prik_derived_ready + type(prik_type_holder_item), pointer :: value + type(prik_holder_item_allocatable_holder), pointer :: value_allocatable_holder + type(prik_holder_item_pointer_holder), pointer :: value_pointer_holder + type(prik_type_holder_item), pointer :: value_call_pointer + type(c_ptr) :: value_transaction_address + integer(c_int) :: value_holder_status + integer(c_int) :: value_restore_status + logical :: value_created + logical :: value_acquired + procedure(prik_derived_scoped), pointer :: value_scoped_proc + procedure(prik_derived_checkout), pointer :: value_checkout_proc + procedure(prik_derived_restore), pointer :: value_restore_proc + bound_value_status = 0_c_int + value_created = .false. + value_acquired = .false. + value_transaction_address = c_null_ptr + nullify(value) + nullify(value_call_pointer) + bound_value_output = c_null_ptr + bound_value_output_present = 0_c_int + prik_derived_ready = .true. + select case (bound_value_access) + case (0) + case (3) + value_holder_status = 0_c_int + if (c_associated(bound_value)) then + call c_f_pointer(bound_value, value_allocatable_holder) + else + allocate(value_allocatable_holder, stat=value_holder_status) + value_created = .true. + end if + if (value_holder_status /= 0_c_int) then + bound_value_status = 4_c_int + end if + case (5) + if (c_associated(bound_value_checkout) .and. c_associated(bound_value_restore)) then + call c_f_procpointer(bound_value_checkout, value_checkout_proc) + call c_f_procpointer(bound_value_restore, value_restore_proc) + else + bound_value_status = 6_c_int + end if + case default + bound_value_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_value_status == 0_c_int) then + select case (bound_value_access) + case (5) + bound_value_status = value_checkout_proc(value_transaction_address) + if (bound_value_status == 0_c_int) then + call c_f_pointer(value_transaction_address, value_allocatable_holder) + value_acquired = .true. + end if + case (6) + bound_value_status = value_checkout_proc(value_transaction_address) + if (bound_value_status == 0_c_int) then + call c_f_pointer(value_transaction_address, value_pointer_holder) + value_acquired = .true. + end if + case default + end select + if (bound_value_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + call native_reset_allocatable_item(value_allocatable_holder%value) + end if + if (value_acquired) then + value_restore_status = value_restore_proc(value_transaction_address) + if (value_restore_status /= 0_c_int) then + bound_value_status = value_restore_status + end if + value_acquired = .false. + end if + if (bound_value_access == 3_c_int) then + bound_value_output = c_loc(value_allocatable_holder) + if (allocated(value_allocatable_holder%value)) then + bound_value_output_present = 1_c_int + else + bound_value_output_present = 0_c_int + end if + else + if (bound_value_access == 4_c_int) then + bound_value_output = c_loc(value_pointer_holder) + if (associated(value_pointer_holder%value)) then + bound_value_output_present = 1_c_int + else + bound_value_output_present = 0_c_int + end if + else + if (bound_value_access == 5_c_int .or. bound_value_access == 6_c_int) then + bound_value_output_present = 1_c_int + end if + end if + end if + end subroutine bind_c_reset_allocatable_item + subroutine bind_c_shift_pointer_item(& + & bound_value, & + & bound_value_access, & + & bound_value_identity, & + & bound_value_scoped, & + & bound_value_checkout, & + & bound_value_restore, & + & bound_value_status, & + & bound_value_output, & + & bound_value_output_present, & + & amount) bind(c, name="bind_c_shift_pointer_item") + type(c_ptr), value :: bound_value + integer(c_int), value :: bound_value_access + type(c_ptr), value :: bound_value_identity + type(c_funptr), value :: bound_value_scoped + type(c_funptr), value :: bound_value_checkout + type(c_funptr), value :: bound_value_restore + integer(c_int), intent(out) :: bound_value_status + type(c_ptr), intent(out) :: bound_value_output + integer(c_int), intent(out) :: bound_value_output_present + real(c_double), value :: amount + logical :: prik_derived_ready + type(prik_type_holder_item), pointer :: value + type(prik_holder_item_allocatable_holder), pointer :: value_allocatable_holder + type(prik_holder_item_pointer_holder), pointer :: value_pointer_holder + type(prik_type_holder_item), pointer :: value_call_pointer + type(c_ptr) :: value_transaction_address + integer(c_int) :: value_holder_status + integer(c_int) :: value_restore_status + logical :: value_created + logical :: value_acquired + procedure(prik_derived_scoped), pointer :: value_scoped_proc + procedure(prik_derived_checkout), pointer :: value_checkout_proc + procedure(prik_derived_restore), pointer :: value_restore_proc + bound_value_status = 0_c_int + value_created = .false. + value_acquired = .false. + value_transaction_address = c_null_ptr + nullify(value) + nullify(value_call_pointer) + bound_value_output = c_null_ptr + bound_value_output_present = 0_c_int + prik_derived_ready = .true. + select case (bound_value_access) + case (0) + case (4) + value_holder_status = 0_c_int + if (c_associated(bound_value)) then + call c_f_pointer(bound_value, value_pointer_holder) + else + allocate(value_pointer_holder, stat=value_holder_status) + nullify(value_pointer_holder%value) + value_created = .true. + end if + if (value_holder_status /= 0_c_int) then + bound_value_status = 4_c_int + end if + case (6) + if (c_associated(bound_value_checkout) .and. c_associated(bound_value_restore)) then + call c_f_procpointer(bound_value_checkout, value_checkout_proc) + call c_f_procpointer(bound_value_restore, value_restore_proc) + else + bound_value_status = 6_c_int + end if + case default + bound_value_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_value_status == 0_c_int) then + select case (bound_value_access) + case (5) + bound_value_status = value_checkout_proc(value_transaction_address) + if (bound_value_status == 0_c_int) then + call c_f_pointer(value_transaction_address, value_allocatable_holder) + value_acquired = .true. + end if + case (6) + bound_value_status = value_checkout_proc(value_transaction_address) + if (bound_value_status == 0_c_int) then + call c_f_pointer(value_transaction_address, value_pointer_holder) + value_acquired = .true. + end if + case default + end select + if (bound_value_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + if (bound_value_access == 4_c_int .or. bound_value_access == 6_c_int) then + if (associated(value_pointer_holder%value)) then + value_call_pointer => value_pointer_holder%value + else + nullify(value_call_pointer) + end if + else + if (associated(value)) then + value_call_pointer => value + else + nullify(value_call_pointer) + end if + end if + call native_shift_pointer_item(value_call_pointer, amount) + if (bound_value_access == 4_c_int .or. bound_value_access == 6_c_int) then + if (associated(value_call_pointer)) then + value_pointer_holder%value => value_call_pointer + else + nullify(value_pointer_holder%value) + end if + end if + end if + if (value_acquired) then + value_restore_status = value_restore_proc(value_transaction_address) + if (value_restore_status /= 0_c_int) then + bound_value_status = value_restore_status + end if + value_acquired = .false. + end if + if (bound_value_access == 3_c_int) then + bound_value_output = c_loc(value_allocatable_holder) + if (allocated(value_allocatable_holder%value)) then + bound_value_output_present = 1_c_int + else + bound_value_output_present = 0_c_int + end if + else + if (bound_value_access == 4_c_int) then + bound_value_output = c_loc(value_pointer_holder) + if (associated(value_pointer_holder%value)) then + bound_value_output_present = 1_c_int + else + bound_value_output_present = 0_c_int + end if + else + if (bound_value_access == 5_c_int .or. bound_value_access == 6_c_int) then + bound_value_output_present = 1_c_int + end if + end if + end if + end subroutine bind_c_shift_pointer_item + subroutine bind_c__prik_class_vector_scale(& + & bound_self, & + & bound_self_access, & + & bound_self_identity, & + & bound_self_polymorphic, & + & bound_self_scoped, & + & bound_self_checkout, & + & bound_self_restore, & + & bound_self_status, & + & factor) bind(c, name="bind_c__prik_class_vector_scale") + type(c_ptr), value :: bound_self + integer(c_int), value :: bound_self_access + type(c_ptr), value :: bound_self_identity + integer(c_int), value :: bound_self_polymorphic + type(c_funptr), value :: bound_self_scoped + type(c_funptr), value :: bound_self_checkout + type(c_funptr), value :: bound_self_restore + integer(c_int), intent(out) :: bound_self_status + real(c_double), value :: factor + logical :: prik_derived_ready + type(prik_type_vector), pointer :: self + type(prik_vector_allocatable_holder), pointer :: self_allocatable_holder + type(prik_vector_pointer_holder), pointer :: self_pointer_holder + type(prik_type_vector), pointer :: self_call_pointer + type(c_ptr) :: self_transaction_address + integer(c_int) :: self_holder_status + integer(c_int) :: self_restore_status + logical :: self_created + logical :: self_acquired + procedure(prik_derived_scoped), pointer :: self_scoped_proc + procedure(prik_derived_checkout), pointer :: self_checkout_proc + procedure(prik_derived_restore), pointer :: self_restore_proc + type(prik_type_vector), pointer :: self_polymorphic_1 + bound_self_status = 0_c_int + self_created = .false. + self_acquired = .false. + self_transaction_address = c_null_ptr + nullify(self) + nullify(self_call_pointer) + prik_derived_ready = .true. + select case (bound_self_access) + case (0) + case (1) + select case (bound_self_polymorphic) + case (1) + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_polymorphic_1) + else + bound_self_status = 1_c_int + end if + case default + bound_self_status = 6_c_int + end select + case (2) + if (c_associated(bound_self_scoped)) then + call c_f_procpointer(bound_self_scoped, self_scoped_proc) + else + bound_self_status = 6_c_int + end if + case (3) + self_holder_status = 0_c_int + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_allocatable_holder) + else + allocate(self_allocatable_holder, stat=self_holder_status) + self_created = .true. + end if + if (self_holder_status /= 0_c_int) then + bound_self_status = 4_c_int + else + if (allocated(self_allocatable_holder%value)) then + self => self_allocatable_holder%value + else + bound_self_status = 1_c_int + end if + end if + case (4) + self_holder_status = 0_c_int + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_pointer_holder) + else + allocate(self_pointer_holder, stat=self_holder_status) + nullify(self_pointer_holder%value) + self_created = .true. + end if + if (self_holder_status /= 0_c_int) then + bound_self_status = 4_c_int + else + if (associated(self_pointer_holder%value)) then + self => self_pointer_holder%value + else + bound_self_status = 1_c_int + end if + end if + case default + bound_self_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_self_status == 0_c_int) then + select case (bound_self_access) + case (5) + bound_self_status = self_checkout_proc(self_transaction_address) + if (bound_self_status == 0_c_int) then + call c_f_pointer(self_transaction_address, self_allocatable_holder) + self_acquired = .true. + end if + case (6) + bound_self_status = self_checkout_proc(self_transaction_address) + if (bound_self_status == 0_c_int) then + call c_f_pointer(self_transaction_address, self_pointer_holder) + self_acquired = .true. + end if + case default + end select + if (bound_self_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + call prik_derived_step_0() + end if + if (self_acquired) then + self_restore_status = self_restore_proc(self_transaction_address) + if (self_restore_status /= 0_c_int) then + bound_self_status = self_restore_status + end if + self_acquired = .false. + end if + if (self_created .and. bound_self_access == 3_c_int) then + deallocate(self_allocatable_holder) + end if + if (self_created .and. bound_self_access == 4_c_int) then + deallocate(self_pointer_holder) + end if + contains + subroutine prik_derived_step_0() + if (bound_self_access == 2_c_int) then + bound_self_status = self_scoped_proc(c_funloc(prik_derived_consumer_0), c_null_ptr) + else + call prik_derived_step_1() + end if + end subroutine prik_derived_step_0 + function prik_derived_consumer_0(address, context) result(status) bind(c) + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + if (c_associated(address)) then + call c_f_pointer(address, self) + call prik_derived_step_1() + status = 0_c_int + else + status = 1_c_int + end if + end function prik_derived_consumer_0 + subroutine prik_derived_step_1() + select case (bound_self_polymorphic) + case (1) + call self_polymorphic_1%scale(factor) + case default + end select + end subroutine prik_derived_step_1 + end subroutine bind_c__prik_class_vector_scale + subroutine bind_c__prik_class_vector_shift(& + & dx, & + & bound_owner, & + & bound_owner_access, & + & bound_owner_identity, & + & bound_owner_polymorphic, & + & bound_owner_scoped, & + & bound_owner_checkout, & + & bound_owner_restore, & + & bound_owner_status, & + & dy) bind(c, name="bind_c__prik_class_vector_shift") + real(c_double), value :: dx + type(c_ptr), value :: bound_owner + integer(c_int), value :: bound_owner_access + type(c_ptr), value :: bound_owner_identity + integer(c_int), value :: bound_owner_polymorphic + type(c_funptr), value :: bound_owner_scoped + type(c_funptr), value :: bound_owner_checkout + type(c_funptr), value :: bound_owner_restore + integer(c_int), intent(out) :: bound_owner_status + real(c_double), value :: dy + logical :: prik_derived_ready + type(prik_type_vector), pointer :: owner + type(prik_vector_allocatable_holder), pointer :: owner_allocatable_holder + type(prik_vector_pointer_holder), pointer :: owner_pointer_holder + type(prik_type_vector), pointer :: owner_call_pointer + type(c_ptr) :: owner_transaction_address + integer(c_int) :: owner_holder_status + integer(c_int) :: owner_restore_status + logical :: owner_created + logical :: owner_acquired + procedure(prik_derived_scoped), pointer :: owner_scoped_proc + procedure(prik_derived_checkout), pointer :: owner_checkout_proc + procedure(prik_derived_restore), pointer :: owner_restore_proc + type(prik_type_vector), pointer :: owner_polymorphic_1 + bound_owner_status = 0_c_int + owner_created = .false. + owner_acquired = .false. + owner_transaction_address = c_null_ptr + nullify(owner) + nullify(owner_call_pointer) + prik_derived_ready = .true. + select case (bound_owner_access) + case (0) + case (1) + select case (bound_owner_polymorphic) + case (1) + if (c_associated(bound_owner)) then + call c_f_pointer(bound_owner, owner_polymorphic_1) + else + bound_owner_status = 1_c_int + end if + case default + bound_owner_status = 6_c_int + end select + case (2) + if (c_associated(bound_owner_scoped)) then + call c_f_procpointer(bound_owner_scoped, owner_scoped_proc) + else + bound_owner_status = 6_c_int + end if + case (3) + owner_holder_status = 0_c_int + if (c_associated(bound_owner)) then + call c_f_pointer(bound_owner, owner_allocatable_holder) + else + allocate(owner_allocatable_holder, stat=owner_holder_status) + owner_created = .true. + end if + if (owner_holder_status /= 0_c_int) then + bound_owner_status = 4_c_int + else + if (allocated(owner_allocatable_holder%value)) then + owner => owner_allocatable_holder%value + else + bound_owner_status = 1_c_int + end if + end if + case (4) + owner_holder_status = 0_c_int + if (c_associated(bound_owner)) then + call c_f_pointer(bound_owner, owner_pointer_holder) + else + allocate(owner_pointer_holder, stat=owner_holder_status) + nullify(owner_pointer_holder%value) + owner_created = .true. + end if + if (owner_holder_status /= 0_c_int) then + bound_owner_status = 4_c_int + else + if (associated(owner_pointer_holder%value)) then + owner => owner_pointer_holder%value + else + bound_owner_status = 1_c_int + end if + end if + case default + bound_owner_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_owner_status == 0_c_int) then + select case (bound_owner_access) + case (5) + bound_owner_status = owner_checkout_proc(owner_transaction_address) + if (bound_owner_status == 0_c_int) then + call c_f_pointer(owner_transaction_address, owner_allocatable_holder) + owner_acquired = .true. + end if + case (6) + bound_owner_status = owner_checkout_proc(owner_transaction_address) + if (bound_owner_status == 0_c_int) then + call c_f_pointer(owner_transaction_address, owner_pointer_holder) + owner_acquired = .true. + end if + case default + end select + if (bound_owner_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + call prik_derived_step_0() + end if + if (owner_acquired) then + owner_restore_status = owner_restore_proc(owner_transaction_address) + if (owner_restore_status /= 0_c_int) then + bound_owner_status = owner_restore_status + end if + owner_acquired = .false. + end if + if (owner_created .and. bound_owner_access == 3_c_int) then + deallocate(owner_allocatable_holder) + end if + if (owner_created .and. bound_owner_access == 4_c_int) then + deallocate(owner_pointer_holder) + end if + contains + subroutine prik_derived_step_0() + if (bound_owner_access == 2_c_int) then + bound_owner_status = owner_scoped_proc(c_funloc(prik_derived_consumer_0), c_null_ptr) + else + call prik_derived_step_1() + end if + end subroutine prik_derived_step_0 + function prik_derived_consumer_0(address, context) result(status) bind(c) + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + if (c_associated(address)) then + call c_f_pointer(address, owner) + call prik_derived_step_1() + status = 0_c_int + else + status = 1_c_int + end if + end function prik_derived_consumer_0 + subroutine prik_derived_step_1() + select case (bound_owner_polymorphic) + case (1) + call owner_polymorphic_1%shift(dx, dy) + case default + end select + end subroutine prik_derived_step_1 + end subroutine bind_c__prik_class_vector_shift + function bind_c__prik_class_vector_magnitude(& + & bound_self, & + & bound_self_access, & + & bound_self_identity, & + & bound_self_polymorphic, & + & bound_self_scoped, & + & bound_self_checkout, & + & bound_self_restore, & + & bound_self_status) result(result) bind(c, name="bind_c__prik_class_vector_magnitude") + type(c_ptr), value :: bound_self + integer(c_int), value :: bound_self_access + type(c_ptr), value :: bound_self_identity + integer(c_int), value :: bound_self_polymorphic + type(c_funptr), value :: bound_self_scoped + type(c_funptr), value :: bound_self_checkout + type(c_funptr), value :: bound_self_restore + integer(c_int), intent(out) :: bound_self_status + real(c_double) :: result + logical :: prik_derived_ready + type(prik_type_vector), pointer :: self + type(prik_vector_allocatable_holder), pointer :: self_allocatable_holder + type(prik_vector_pointer_holder), pointer :: self_pointer_holder + type(prik_type_vector), pointer :: self_call_pointer + type(c_ptr) :: self_transaction_address + integer(c_int) :: self_holder_status + integer(c_int) :: self_restore_status + logical :: self_created + logical :: self_acquired + procedure(prik_derived_scoped), pointer :: self_scoped_proc + procedure(prik_derived_checkout), pointer :: self_checkout_proc + procedure(prik_derived_restore), pointer :: self_restore_proc + type(prik_type_vector), pointer :: self_polymorphic_1 + bound_self_status = 0_c_int + self_created = .false. + self_acquired = .false. + self_transaction_address = c_null_ptr + nullify(self) + nullify(self_call_pointer) + prik_derived_ready = .true. + select case (bound_self_access) + case (0) + case (1) + select case (bound_self_polymorphic) + case (1) + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_polymorphic_1) + else + bound_self_status = 1_c_int + end if + case default + bound_self_status = 6_c_int + end select + case (2) + if (c_associated(bound_self_scoped)) then + call c_f_procpointer(bound_self_scoped, self_scoped_proc) + else + bound_self_status = 6_c_int + end if + case (3) + self_holder_status = 0_c_int + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_allocatable_holder) + else + allocate(self_allocatable_holder, stat=self_holder_status) + self_created = .true. + end if + if (self_holder_status /= 0_c_int) then + bound_self_status = 4_c_int + else + if (allocated(self_allocatable_holder%value)) then + self => self_allocatable_holder%value + else + bound_self_status = 1_c_int + end if + end if + case (4) + self_holder_status = 0_c_int + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_pointer_holder) + else + allocate(self_pointer_holder, stat=self_holder_status) + nullify(self_pointer_holder%value) + self_created = .true. + end if + if (self_holder_status /= 0_c_int) then + bound_self_status = 4_c_int + else + if (associated(self_pointer_holder%value)) then + self => self_pointer_holder%value + else + bound_self_status = 1_c_int + end if + end if + case default + bound_self_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_self_status == 0_c_int) then + select case (bound_self_access) + case (5) + bound_self_status = self_checkout_proc(self_transaction_address) + if (bound_self_status == 0_c_int) then + call c_f_pointer(self_transaction_address, self_allocatable_holder) + self_acquired = .true. + end if + case (6) + bound_self_status = self_checkout_proc(self_transaction_address) + if (bound_self_status == 0_c_int) then + call c_f_pointer(self_transaction_address, self_pointer_holder) + self_acquired = .true. + end if + case default + end select + if (bound_self_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + call prik_derived_step_0() + end if + if (self_acquired) then + self_restore_status = self_restore_proc(self_transaction_address) + if (self_restore_status /= 0_c_int) then + bound_self_status = self_restore_status + end if + self_acquired = .false. + end if + if (self_created .and. bound_self_access == 3_c_int) then + deallocate(self_allocatable_holder) + end if + if (self_created .and. bound_self_access == 4_c_int) then + deallocate(self_pointer_holder) + end if + contains + subroutine prik_derived_step_0() + if (bound_self_access == 2_c_int) then + bound_self_status = self_scoped_proc(c_funloc(prik_derived_consumer_0), c_null_ptr) + else + call prik_derived_step_1() + end if + end subroutine prik_derived_step_0 + function prik_derived_consumer_0(address, context) result(status) bind(c) + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + if (c_associated(address)) then + call c_f_pointer(address, self) + call prik_derived_step_1() + status = 0_c_int + else + status = 1_c_int + end if + end function prik_derived_consumer_0 + subroutine prik_derived_step_1() + select case (bound_self_polymorphic) + case (1) + result = self_polymorphic_1%magnitude() + case default + end select + end subroutine prik_derived_step_1 + end function bind_c__prik_class_vector_magnitude + subroutine bind_c__prik_class_vector_replace_samples(& + & bound_self, & + & bound_self_access, & + & bound_self_identity, & + & bound_self_polymorphic, & + & bound_self_scoped, & + & bound_self_checkout, & + & bound_self_restore, & + & bound_self_status, & + & bound_values, & + & values_dense_actual, & + & values_extent_0, & + & values_upper_bound_0, & + & values_stride_0) bind(c, name="bind_c__prik_class_vector_replace_samples") + type(c_ptr), value :: bound_self + integer(c_int), value :: bound_self_access + type(c_ptr), value :: bound_self_identity + integer(c_int), value :: bound_self_polymorphic + type(c_funptr), value :: bound_self_scoped + type(c_funptr), value :: bound_self_checkout + type(c_funptr), value :: bound_self_restore + integer(c_int), intent(out) :: bound_self_status + type(c_ptr), value :: bound_values + integer(c_int), value :: values_dense_actual + integer(c_int64_t), value :: values_extent_0 + integer(c_int64_t), value :: values_upper_bound_0 + integer(c_int64_t), value :: values_stride_0 + real(c_double), pointer, dimension(:) :: values_base + real(c_double), pointer, dimension(:) :: values + logical :: prik_derived_ready + type(prik_type_vector), pointer :: self + type(prik_vector_allocatable_holder), pointer :: self_allocatable_holder + type(prik_vector_pointer_holder), pointer :: self_pointer_holder + type(prik_type_vector), pointer :: self_call_pointer + type(c_ptr) :: self_transaction_address + integer(c_int) :: self_holder_status + integer(c_int) :: self_restore_status + logical :: self_created + logical :: self_acquired + procedure(prik_derived_scoped), pointer :: self_scoped_proc + procedure(prik_derived_checkout), pointer :: self_checkout_proc + procedure(prik_derived_restore), pointer :: self_restore_proc + type(prik_type_vector), pointer :: self_polymorphic_1 + call c_f_pointer(bound_values, values_base, [values_extent_0]) + if (values_dense_actual /= 0_c_int) then + values => values_base + else + values => values_base(1:values_upper_bound_0 + 1:values_stride_0) + end if + bound_self_status = 0_c_int + self_created = .false. + self_acquired = .false. + self_transaction_address = c_null_ptr + nullify(self) + nullify(self_call_pointer) + prik_derived_ready = .true. + select case (bound_self_access) + case (0) + case (1) + select case (bound_self_polymorphic) + case (1) + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_polymorphic_1) + else + bound_self_status = 1_c_int + end if + case default + bound_self_status = 6_c_int + end select + case (2) + if (c_associated(bound_self_scoped)) then + call c_f_procpointer(bound_self_scoped, self_scoped_proc) + else + bound_self_status = 6_c_int + end if + case (3) + self_holder_status = 0_c_int + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_allocatable_holder) + else + allocate(self_allocatable_holder, stat=self_holder_status) + self_created = .true. + end if + if (self_holder_status /= 0_c_int) then + bound_self_status = 4_c_int + else + if (allocated(self_allocatable_holder%value)) then + self => self_allocatable_holder%value + else + bound_self_status = 1_c_int + end if + end if + case (4) + self_holder_status = 0_c_int + if (c_associated(bound_self)) then + call c_f_pointer(bound_self, self_pointer_holder) + else + allocate(self_pointer_holder, stat=self_holder_status) + nullify(self_pointer_holder%value) + self_created = .true. + end if + if (self_holder_status /= 0_c_int) then + bound_self_status = 4_c_int + else + if (associated(self_pointer_holder%value)) then + self => self_pointer_holder%value + else + bound_self_status = 1_c_int + end if + end if + case default + bound_self_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_self_status == 0_c_int) then + select case (bound_self_access) + case (5) + bound_self_status = self_checkout_proc(self_transaction_address) + if (bound_self_status == 0_c_int) then + call c_f_pointer(self_transaction_address, self_allocatable_holder) + self_acquired = .true. + end if + case (6) + bound_self_status = self_checkout_proc(self_transaction_address) + if (bound_self_status == 0_c_int) then + call c_f_pointer(self_transaction_address, self_pointer_holder) + self_acquired = .true. + end if + case default + end select + if (bound_self_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + call prik_derived_step_0() + end if + if (self_acquired) then + self_restore_status = self_restore_proc(self_transaction_address) + if (self_restore_status /= 0_c_int) then + bound_self_status = self_restore_status + end if + self_acquired = .false. + end if + if (self_created .and. bound_self_access == 3_c_int) then + deallocate(self_allocatable_holder) + end if + if (self_created .and. bound_self_access == 4_c_int) then + deallocate(self_pointer_holder) + end if + contains + subroutine prik_derived_step_0() + if (bound_self_access == 2_c_int) then + bound_self_status = self_scoped_proc(c_funloc(prik_derived_consumer_0), c_null_ptr) + else + call prik_derived_step_1() + end if + end subroutine prik_derived_step_0 + function prik_derived_consumer_0(address, context) result(status) bind(c) + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + if (c_associated(address)) then + call c_f_pointer(address, self) + call prik_derived_step_1() + status = 0_c_int + else + status = 1_c_int + end if + end function prik_derived_consumer_0 + subroutine prik_derived_step_1() + select case (bound_self_polymorphic) + case (1) + call self_polymorphic_1%replace_samples(values) + case default + end select + end subroutine prik_derived_step_1 + end subroutine bind_c__prik_class_vector_replace_samples + function bind_c__prik_class_vector___add___0(& + & bound_left, & + & bound_left_access, & + & bound_left_identity, & + & bound_left_polymorphic, & + & bound_left_scoped, & + & bound_left_checkout, & + & bound_left_restore, & + & bound_left_status, & + & bound_right, & + & bound_right_access, & + & bound_right_identity, & + & bound_right_scoped, & + & bound_right_checkout, & + & bound_right_restore, & + & bound_right_status) result(result) bind(c, name="bind_c__prik_class_vector___add___0") + type(c_ptr), value :: bound_left + integer(c_int), value :: bound_left_access + type(c_ptr), value :: bound_left_identity + integer(c_int), value :: bound_left_polymorphic + type(c_funptr), value :: bound_left_scoped + type(c_funptr), value :: bound_left_checkout + type(c_funptr), value :: bound_left_restore + integer(c_int), intent(out) :: bound_left_status + type(c_ptr), value :: bound_right + integer(c_int), value :: bound_right_access + type(c_ptr), value :: bound_right_identity + type(c_funptr), value :: bound_right_scoped + type(c_funptr), value :: bound_right_checkout + type(c_funptr), value :: bound_right_restore + integer(c_int), intent(out) :: bound_right_status + type(c_ptr) :: result + logical :: prik_derived_ready + type(prik_type_vector), pointer :: left + type(prik_vector_allocatable_holder), pointer :: left_allocatable_holder + type(prik_vector_pointer_holder), pointer :: left_pointer_holder + type(prik_type_vector), pointer :: left_call_pointer + type(c_ptr) :: left_transaction_address + integer(c_int) :: left_holder_status + integer(c_int) :: left_restore_status + logical :: left_created + logical :: left_acquired + procedure(prik_derived_scoped), pointer :: left_scoped_proc + procedure(prik_derived_checkout), pointer :: left_checkout_proc + procedure(prik_derived_restore), pointer :: left_restore_proc + type(prik_type_vector), pointer :: left_polymorphic_1 + type(prik_type_vector), pointer :: right + type(prik_vector_allocatable_holder), pointer :: right_allocatable_holder + type(prik_vector_pointer_holder), pointer :: right_pointer_holder + type(prik_type_vector), pointer :: right_call_pointer + type(c_ptr) :: right_transaction_address + integer(c_int) :: right_holder_status + integer(c_int) :: right_restore_status + logical :: right_created + logical :: right_acquired + procedure(prik_derived_scoped), pointer :: right_scoped_proc + procedure(prik_derived_checkout), pointer :: right_checkout_proc + procedure(prik_derived_restore), pointer :: right_restore_proc + type(prik_type_vector), pointer :: result_value + integer(c_int) :: prik_allocation_status + bound_left_status = 0_c_int + left_created = .false. + left_acquired = .false. + left_transaction_address = c_null_ptr + nullify(left) + nullify(left_call_pointer) + bound_right_status = 0_c_int + right_created = .false. + right_acquired = .false. + right_transaction_address = c_null_ptr + nullify(right) + nullify(right_call_pointer) + prik_derived_ready = .true. + select case (bound_left_access) + case (0) + case (1) + select case (bound_left_polymorphic) + case (1) + if (c_associated(bound_left)) then + call c_f_pointer(bound_left, left_polymorphic_1) + else + bound_left_status = 1_c_int + end if + case default + bound_left_status = 6_c_int + end select + case (2) + if (c_associated(bound_left_scoped)) then + call c_f_procpointer(bound_left_scoped, left_scoped_proc) + else + bound_left_status = 6_c_int + end if + case (3) + left_holder_status = 0_c_int + if (c_associated(bound_left)) then + call c_f_pointer(bound_left, left_allocatable_holder) + else + allocate(left_allocatable_holder, stat=left_holder_status) + left_created = .true. + end if + if (left_holder_status /= 0_c_int) then + bound_left_status = 4_c_int + else + if (allocated(left_allocatable_holder%value)) then + left => left_allocatable_holder%value + else + bound_left_status = 1_c_int + end if + end if + case (4) + left_holder_status = 0_c_int + if (c_associated(bound_left)) then + call c_f_pointer(bound_left, left_pointer_holder) + else + allocate(left_pointer_holder, stat=left_holder_status) + nullify(left_pointer_holder%value) + left_created = .true. + end if + if (left_holder_status /= 0_c_int) then + bound_left_status = 4_c_int + else + if (associated(left_pointer_holder%value)) then + left => left_pointer_holder%value + else + bound_left_status = 1_c_int + end if + end if + case default + bound_left_status = 6_c_int + end select + select case (bound_right_access) + case (0) + case (1) + if (c_associated(bound_right)) then + call c_f_pointer(bound_right, right) + else + bound_right_status = 1_c_int + end if + case (2) + if (c_associated(bound_right_scoped)) then + call c_f_procpointer(bound_right_scoped, right_scoped_proc) + else + bound_right_status = 6_c_int + end if + case (3) + right_holder_status = 0_c_int + if (c_associated(bound_right)) then + call c_f_pointer(bound_right, right_allocatable_holder) + else + allocate(right_allocatable_holder, stat=right_holder_status) + right_created = .true. + end if + if (right_holder_status /= 0_c_int) then + bound_right_status = 4_c_int + else + if (allocated(right_allocatable_holder%value)) then + right => right_allocatable_holder%value + else + bound_right_status = 1_c_int + end if + end if + case (4) + right_holder_status = 0_c_int + if (c_associated(bound_right)) then + call c_f_pointer(bound_right, right_pointer_holder) + else + allocate(right_pointer_holder, stat=right_holder_status) + nullify(right_pointer_holder%value) + right_created = .true. + end if + if (right_holder_status /= 0_c_int) then + bound_right_status = 4_c_int + else + if (associated(right_pointer_holder%value)) then + right => right_pointer_holder%value + else + bound_right_status = 1_c_int + end if + end if + case default + bound_right_status = 6_c_int + end select + if (prik_derived_ready) then + if (bound_left_status == 0_c_int) then + select case (bound_left_access) + case (5) + bound_left_status = left_checkout_proc(left_transaction_address) + if (bound_left_status == 0_c_int) then + call c_f_pointer(left_transaction_address, left_allocatable_holder) + left_acquired = .true. + end if + case (6) + bound_left_status = left_checkout_proc(left_transaction_address) + if (bound_left_status == 0_c_int) then + call c_f_pointer(left_transaction_address, left_pointer_holder) + left_acquired = .true. + end if + case default + end select + if (bound_left_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + if (bound_right_status == 0_c_int) then + select case (bound_right_access) + case (5) + bound_right_status = right_checkout_proc(right_transaction_address) + if (bound_right_status == 0_c_int) then + call c_f_pointer(right_transaction_address, right_allocatable_holder) + right_acquired = .true. + end if + case (6) + bound_right_status = right_checkout_proc(right_transaction_address) + if (bound_right_status == 0_c_int) then + call c_f_pointer(right_transaction_address, right_pointer_holder) + right_acquired = .true. + end if + case default + end select + if (bound_right_status /= 0_c_int) then + prik_derived_ready = .false. + end if + else + prik_derived_ready = .false. + end if + end if + if (prik_derived_ready) then + call prik_derived_step_0() + end if + if (right_acquired) then + right_restore_status = right_restore_proc(right_transaction_address) + if (right_restore_status /= 0_c_int) then + bound_right_status = right_restore_status + end if + right_acquired = .false. + end if + if (left_acquired) then + left_restore_status = left_restore_proc(left_transaction_address) + if (left_restore_status /= 0_c_int) then + bound_left_status = left_restore_status + end if + left_acquired = .false. + end if + if (left_created .and. bound_left_access == 3_c_int) then + deallocate(left_allocatable_holder) + end if + if (left_created .and. bound_left_access == 4_c_int) then + deallocate(left_pointer_holder) + end if + if (right_created .and. bound_right_access == 3_c_int) then + deallocate(right_allocatable_holder) + end if + if (right_created .and. bound_right_access == 4_c_int) then + deallocate(right_pointer_holder) + end if + contains + subroutine prik_derived_step_0() + if (bound_left_access == 2_c_int) then + bound_left_status = left_scoped_proc(c_funloc(prik_derived_consumer_0), c_null_ptr) + else + call prik_derived_step_1() + end if + end subroutine prik_derived_step_0 + function prik_derived_consumer_0(address, context) result(status) bind(c) + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + if (c_associated(address)) then + call c_f_pointer(address, left) + call prik_derived_step_1() + status = 0_c_int + else + status = 1_c_int + end if + end function prik_derived_consumer_0 + subroutine prik_derived_step_1() + if (bound_right_access == 2_c_int) then + if (bound_left_access == 2_c_int .and. c_associated(bound_right_identity, bound_left_identity)) then + right => left + call prik_derived_step_2() + else + bound_right_status = right_scoped_proc(c_funloc(prik_derived_consumer_1), c_null_ptr) + end if + else + call prik_derived_step_2() + end if + end subroutine prik_derived_step_1 + function prik_derived_consumer_1(address, context) result(status) bind(c) + type(c_ptr), value :: address + type(c_ptr), value :: context + integer(c_int) :: status + if (c_associated(address)) then + call c_f_pointer(address, right) + call prik_derived_step_2() + status = 0_c_int + else + status = 1_c_int + end if + end function prik_derived_consumer_1 + subroutine prik_derived_step_2() + result = c_null_ptr + allocate(result_value, stat=prik_allocation_status) + if (prik_allocation_status == 0) then + select case (bound_left_polymorphic) + case (1) + result_value = left_polymorphic_1 + right + case default + end select + result = c_loc(result_value) + end if + end subroutine prik_derived_step_2 + end function bind_c__prik_class_vector___add___0 + function bind_c__prik_overload_convert_0(value) result(result) bind(c, name="bind_c__prik_overload_convert_0") + integer(c_int32_t), value :: value + real(c_double) :: result + result = native__prik_overload_convert_0(value) + end function bind_c__prik_overload_convert_0 + function bind_c__prik_overload_convert_1(value) result(result) bind(c, name="bind_c__prik_overload_convert_1") + real(c_double), value :: value + integer(c_int32_t) :: result + result = native__prik_overload_convert_1(value) + end function bind_c__prik_overload_convert_1 + function bind_c_get_counter() result(result) bind(c, name="bind_c_get_counter") + integer(c_int32_t) :: result + result = native_counter + end function bind_c_get_counter + subroutine bind_c_set_counter(value) bind(c, name="bind_c_set_counter") + integer(c_int32_t), value :: value + native_counter = value + end subroutine bind_c_set_counter + function bind_c_workspace_allocated() result(result) bind(c, name="bind_c_workspace_allocated") + logical(c_bool) :: result + result = allocated(native_workspace) + end function bind_c_workspace_allocated + subroutine bind_c_workspace_array_actual(& + & callback_address, & + & context) bind(c, name="bind_c_workspace_array_actual") + type(c_funptr), value :: callback_address + type(c_ptr), value :: context + procedure(prik_workspace_descriptor_consumer), pointer :: callback + call c_f_procpointer(callback_address, callback) + call callback(native_workspace, context) + end subroutine bind_c_workspace_array_actual + subroutine bind_c_workspace_deallocate() bind(c, name="bind_c_workspace_deallocate") + if (allocated(native_workspace)) then + deallocate(native_workspace) + end if + end subroutine bind_c_workspace_deallocate + subroutine bind_c_workspace_descriptor(callback_address, context) bind(c, name="bind_c_workspace_descriptor") + type(c_funptr), value :: callback_address + type(c_ptr), value :: context + procedure(prik_workspace_descriptor_consumer), pointer :: callback + call c_f_procpointer(callback_address, callback) + call callback(native_workspace, context) + end subroutine bind_c_workspace_descriptor + subroutine bind_c_workspace_resize(extent_0) bind(c, name="bind_c_workspace_resize") + integer(c_int64_t), value :: extent_0 + if (allocated(native_workspace)) then + deallocate(native_workspace) + end if + allocate(native_workspace(extent_0)) + end subroutine bind_c_workspace_resize + subroutine bind_c_workspace_shape(extent_0) bind(c, name="bind_c_workspace_shape") + integer(c_int64_t) :: extent_0 + if (allocated(native_workspace)) then + extent_0 = size(native_workspace, 1, kind=c_int64_t) + else + extent_0 = 0_c_int64_t + end if + end subroutine bind_c_workspace_shape + function bind_c_selected_array_actual() result(result) bind(c, name="bind_c_selected_array_actual") + type(c_ptr) :: result + if (associated(native_selected)) then + result = c_loc(native_selected) + else + result = c_null_ptr + end if + end function bind_c_selected_array_actual + subroutine bind_c_selected_associate(source) bind(c, name="bind_c_selected_associate") + real(c_double), pointer, dimension(:), intent(in) :: source + native_selected => source + end subroutine bind_c_selected_associate + function bind_c_selected_associated() result(result) bind(c, name="bind_c_selected_associated") + logical(c_bool) :: result + result = associated(native_selected) + end function bind_c_selected_associated + function bind_c_selected_contiguous() result(result) bind(c, name="bind_c_selected_contiguous") + logical(c_bool) :: result + result = .not. (associated(native_selected)) .or. is_contiguous(native_selected) + end function bind_c_selected_contiguous + subroutine bind_c_selected_descriptor(descriptor) bind(c, name="bind_c_selected_descriptor") + real(c_double), pointer, dimension(:), intent(out) :: descriptor + if (associated(native_selected)) then + descriptor => native_selected + else + descriptor => null() + end if + end subroutine bind_c_selected_descriptor + subroutine bind_c_selected_nullify() bind(c, name="bind_c_selected_nullify") + native_selected => null() + end subroutine bind_c_selected_nullify + subroutine bind_c_selected_shape(extent_0) bind(c, name="bind_c_selected_shape") + integer(c_int64_t) :: extent_0 + if (associated(native_selected)) then + extent_0 = size(native_selected, 1, kind=c_int64_t) + else + extent_0 = 0_c_int64_t + end if + end subroutine bind_c_selected_shape + function bind_c_prik_module_active_vector_present() & + & result(result) bind(c, name="bind_c_prik_module_active_vector_present") + logical(c_bool) :: result + result = allocated(native_active_vector) + end function bind_c_prik_module_active_vector_present + function bind_c_prik_module_selected_vector_present() & + & result(result) bind(c, name="bind_c_prik_module_selected_vector_present") + logical(c_bool) :: result + result = associated(native_selected_vector) + end function bind_c_prik_module_selected_vector_present + function bind_c_prik_field_holder_item_code_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_field_holder_item_code_get") + type(c_ptr), value :: owner_address + integer(c_int32_t) :: result + type(prik_type_holder_item), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%code + end function bind_c_prik_field_holder_item_code_get + subroutine bind_c_prik_field_holder_item_code_set(& + & owner_address, & + & value) bind(c, name="bind_c_prik_field_holder_item_code_set") + type(c_ptr), value :: owner_address + integer(c_int32_t), value :: value + type(prik_type_holder_item), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%code = value + end subroutine bind_c_prik_field_holder_item_code_set + function bind_c_prik_field_holder_item_weight_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_field_holder_item_weight_get") + type(c_ptr), value :: owner_address + real(c_double) :: result + type(prik_type_holder_item), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%weight + end function bind_c_prik_field_holder_item_weight_get + subroutine bind_c_prik_field_holder_item_weight_set(& + & owner_address, & + & value) bind(c, name="bind_c_prik_field_holder_item_weight_set") + type(c_ptr), value :: owner_address + real(c_double), value :: value + type(prik_type_holder_item), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%weight = value + end subroutine bind_c_prik_field_holder_item_weight_set + function bind_c_prik_field_vector_x_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_field_vector_x_get") + type(c_ptr), value :: owner_address + real(c_double) :: result + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%x + end function bind_c_prik_field_vector_x_get + subroutine bind_c_prik_field_vector_x_set(owner_address, value) bind(c, name="bind_c_prik_field_vector_x_set") + type(c_ptr), value :: owner_address + real(c_double), value :: value + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%x = value + end subroutine bind_c_prik_field_vector_x_set + function bind_c_prik_field_vector_y_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_field_vector_y_get") + type(c_ptr), value :: owner_address + real(c_double) :: result + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%y + end function bind_c_prik_field_vector_y_get + subroutine bind_c_prik_field_vector_y_set(owner_address, value) bind(c, name="bind_c_prik_field_vector_y_set") + type(c_ptr), value :: owner_address + real(c_double), value :: value + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%y = value + end subroutine bind_c_prik_field_vector_y_set + function bind_c_prik_field_handle_vector_samples_allocated(& + & owner_address) result(result) bind(c, name="bind_c_prik_field_handle_vector_samples_allocated") + type(c_ptr), value :: owner_address + logical(c_bool) :: result + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + result = allocated(owner%samples) + end function bind_c_prik_field_handle_vector_samples_allocated + subroutine bind_c_prik_field_handle_vector_samples_deallocate(& + & owner_address) bind(c, name="bind_c_prik_field_handle_vector_samples_deallocate") + type(c_ptr), value :: owner_address + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + if (allocated(owner%samples)) then + deallocate(owner%samples) + end if + end subroutine bind_c_prik_field_handle_vector_samples_deallocate + subroutine bind_c_prik_field_handle_vector_samples_descriptor(& + & owner_address, & + & callback_address, & + & context) bind(c, name="bind_c_prik_field_handle_vector_samples_descriptor") + type(c_ptr), value :: owner_address + type(c_funptr), value :: callback_address + type(c_ptr), value :: context + type(prik_type_vector), pointer :: owner + procedure(prik_field_handle_vector_samples_consumer), pointer :: callback + call c_f_pointer(owner_address, owner) + call c_f_procpointer(callback_address, callback) + call callback(owner%samples, context) + end subroutine bind_c_prik_field_handle_vector_samples_descriptor + subroutine bind_c_prik_field_handle_vector_samples_resize(& + & owner_address, & + & extent_0) bind(c, name="bind_c_prik_field_handle_vector_samples_resize") + type(c_ptr), value :: owner_address + integer(c_int64_t), value :: extent_0 + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + if (allocated(owner%samples)) then + deallocate(owner%samples) + end if + allocate(owner%samples(extent_0)) + end subroutine bind_c_prik_field_handle_vector_samples_resize + subroutine bind_c_prik_field_handle_vector_samples_shape(& + & owner_address, & + & extent_0) bind(c, name="bind_c_prik_field_handle_vector_samples_shape") + type(c_ptr), value :: owner_address + integer(c_int64_t) :: extent_0 + type(prik_type_vector), pointer :: owner + call c_f_pointer(owner_address, owner) + if (allocated(owner%samples)) then + extent_0 = size(owner%samples, 1, kind=c_int64_t) + else + extent_0 = 0_c_int64_t + end if + end subroutine bind_c_prik_field_handle_vector_samples_shape + function bind_c_prik_module_field_active_vector_x_get() & + & result(result) bind(c, name="bind_c_prik_module_field_active_vector_x_get") + real(c_double) :: result + result = native_active_vector%x + end function bind_c_prik_module_field_active_vector_x_get + subroutine bind_c_prik_module_field_active_vector_x_set(& + & value) bind(c, name="bind_c_prik_module_field_active_vector_x_set") + real(c_double), value :: value + native_active_vector%x = value + end subroutine bind_c_prik_module_field_active_vector_x_set + function bind_c_prik_module_field_active_vector_y_get() & + & result(result) bind(c, name="bind_c_prik_module_field_active_vector_y_get") + real(c_double) :: result + result = native_active_vector%y + end function bind_c_prik_module_field_active_vector_y_get + subroutine bind_c_prik_module_field_active_vector_y_set(& + & value) bind(c, name="bind_c_prik_module_field_active_vector_y_set") + real(c_double), value :: value + native_active_vector%y = value + end subroutine bind_c_prik_module_field_active_vector_y_set + function bind_c_prik_module_field_handle_active_vector_samples_allocated() & + & result(result) bind(c, name="bind_c_prik_module_field_handle_active_vector_samples_allocated") + logical(c_bool) :: result + result = allocated(native_active_vector%samples) + end function bind_c_prik_module_field_handle_active_vector_samples_allocated + subroutine bind_c_prik_module_field_handle_active_vector_samples_deallocate() & + & bind(c, name="bind_c_prik_module_field_handle_active_vector_samples_deallocate") + if (allocated(native_active_vector%samples)) then + deallocate(native_active_vector%samples) + end if + end subroutine bind_c_prik_module_field_handle_active_vector_samples_deallocate + subroutine bind_c_prik_module_field_handle_active_vector_samples_descriptor(& + & callback_address, & + & context) bind(c, name="bind_c_prik_module_field_handle_active_vector_samples_descriptor") + type(c_funptr), value :: callback_address + type(c_ptr), value :: context + procedure(prik_module_field_handle_active_vector_samples_consumer), pointer :: callback + call c_f_procpointer(callback_address, callback) + call callback(native_active_vector%samples, context) + end subroutine bind_c_prik_module_field_handle_active_vector_samples_descriptor + subroutine bind_c_prik_module_field_handle_active_vector_samples_resize(& + & extent_0) bind(c, name="bind_c_prik_module_field_handle_active_vector_samples_resize") + integer(c_int64_t), value :: extent_0 + if (allocated(native_active_vector%samples)) then + deallocate(native_active_vector%samples) + end if + allocate(native_active_vector%samples(extent_0)) + end subroutine bind_c_prik_module_field_handle_active_vector_samples_resize + subroutine bind_c_prik_module_field_handle_active_vector_samples_shape(& + & extent_0) bind(c, name="bind_c_prik_module_field_handle_active_vector_samples_shape") + integer(c_int64_t) :: extent_0 + if (allocated(native_active_vector%samples)) then + extent_0 = size(native_active_vector%samples, 1, kind=c_int64_t) + else + extent_0 = 0_c_int64_t + end if + end subroutine bind_c_prik_module_field_handle_active_vector_samples_shape + function bind_c_prik_module_field_selected_vector_x_get() & + & result(result) bind(c, name="bind_c_prik_module_field_selected_vector_x_get") + real(c_double) :: result + result = native_selected_vector%x + end function bind_c_prik_module_field_selected_vector_x_get + subroutine bind_c_prik_module_field_selected_vector_x_set(& + & value) bind(c, name="bind_c_prik_module_field_selected_vector_x_set") + real(c_double), value :: value + native_selected_vector%x = value + end subroutine bind_c_prik_module_field_selected_vector_x_set + function bind_c_prik_module_field_selected_vector_y_get() & + & result(result) bind(c, name="bind_c_prik_module_field_selected_vector_y_get") + real(c_double) :: result + result = native_selected_vector%y + end function bind_c_prik_module_field_selected_vector_y_get + subroutine bind_c_prik_module_field_selected_vector_y_set(& + & value) bind(c, name="bind_c_prik_module_field_selected_vector_y_set") + real(c_double), value :: value + native_selected_vector%y = value + end subroutine bind_c_prik_module_field_selected_vector_y_set + function bind_c_prik_module_field_handle_selected_vector_samples_allocated() & + & result(result) bind(c, name="bind_c_prik_module_field_handle_selected_vector_samples_allocated") + logical(c_bool) :: result + result = allocated(native_selected_vector%samples) + end function bind_c_prik_module_field_handle_selected_vector_samples_allocated + subroutine bind_c_prik_module_field_handle_selected_vector_samples_deallocate() & + & bind(c, name="bind_c_prik_module_field_handle_selected_vector_samples_deallocate") + if (allocated(native_selected_vector%samples)) then + deallocate(native_selected_vector%samples) + end if + end subroutine bind_c_prik_module_field_handle_selected_vector_samples_deallocate + subroutine bind_c_prik_module_field_handle_selected_vector_samples_descriptor(& + & callback_address, & + & context) bind(c, name="bind_c_prik_module_field_handle_selected_vector_samples_descriptor") + type(c_funptr), value :: callback_address + type(c_ptr), value :: context + procedure(prik_module_field_handle_selected_vector_samples_consumer), pointer :: callback + call c_f_procpointer(callback_address, callback) + call callback(native_selected_vector%samples, context) + end subroutine bind_c_prik_module_field_handle_selected_vector_samples_descriptor + subroutine bind_c_prik_module_field_handle_selected_vector_samples_resize(& + & extent_0) bind(c, name="bind_c_prik_module_field_handle_selected_vector_samples_resize") + integer(c_int64_t), value :: extent_0 + if (allocated(native_selected_vector%samples)) then + deallocate(native_selected_vector%samples) + end if + allocate(native_selected_vector%samples(extent_0)) + end subroutine bind_c_prik_module_field_handle_selected_vector_samples_resize + subroutine bind_c_prik_module_field_handle_selected_vector_samples_shape(& + & extent_0) bind(c, name="bind_c_prik_module_field_handle_selected_vector_samples_shape") + integer(c_int64_t) :: extent_0 + if (allocated(native_selected_vector%samples)) then + extent_0 = size(native_selected_vector%samples, 1, kind=c_int64_t) + else + extent_0 = 0_c_int64_t + end if + end subroutine bind_c_prik_module_field_handle_selected_vector_samples_shape + function bind_c_prik_allocatable_holder_field_holder_item_code_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_allocatable_holder_field_holder_item_code_get") + type(c_ptr), value :: owner_address + integer(c_int32_t) :: result + type(prik_holder_item_allocatable_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%value%code + end function bind_c_prik_allocatable_holder_field_holder_item_code_get + subroutine bind_c_prik_allocatable_holder_field_holder_item_code_set(& + & owner_address, & + & value) bind(c, name="bind_c_prik_allocatable_holder_field_holder_item_code_set") + type(c_ptr), value :: owner_address + integer(c_int32_t), value :: value + type(prik_holder_item_allocatable_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%value%code = value + end subroutine bind_c_prik_allocatable_holder_field_holder_item_code_set + function bind_c_prik_allocatable_holder_field_holder_item_weight_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_allocatable_holder_field_holder_item_weight_get") + type(c_ptr), value :: owner_address + real(c_double) :: result + type(prik_holder_item_allocatable_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%value%weight + end function bind_c_prik_allocatable_holder_field_holder_item_weight_get + subroutine bind_c_prik_allocatable_holder_field_holder_item_weight_set(& + & owner_address, & + & value) bind(c, name="bind_c_prik_allocatable_holder_field_holder_item_weight_set") + type(c_ptr), value :: owner_address + real(c_double), value :: value + type(prik_holder_item_allocatable_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%value%weight = value + end subroutine bind_c_prik_allocatable_holder_field_holder_item_weight_set + function bind_c_prik_pointer_holder_field_holder_item_code_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_pointer_holder_field_holder_item_code_get") + type(c_ptr), value :: owner_address + integer(c_int32_t) :: result + type(prik_holder_item_pointer_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%value%code + end function bind_c_prik_pointer_holder_field_holder_item_code_get + subroutine bind_c_prik_pointer_holder_field_holder_item_code_set(& + & owner_address, & + & value) bind(c, name="bind_c_prik_pointer_holder_field_holder_item_code_set") + type(c_ptr), value :: owner_address + integer(c_int32_t), value :: value + type(prik_holder_item_pointer_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%value%code = value + end subroutine bind_c_prik_pointer_holder_field_holder_item_code_set + function bind_c_prik_pointer_holder_field_holder_item_weight_get(& + & owner_address) result(result) bind(c, name="bind_c_prik_pointer_holder_field_holder_item_weight_get") + type(c_ptr), value :: owner_address + real(c_double) :: result + type(prik_holder_item_pointer_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + result = owner%value%weight + end function bind_c_prik_pointer_holder_field_holder_item_weight_get + subroutine bind_c_prik_pointer_holder_field_holder_item_weight_set(& + & owner_address, & + & value) bind(c, name="bind_c_prik_pointer_holder_field_holder_item_weight_set") + type(c_ptr), value :: owner_address + real(c_double), value :: value + type(prik_holder_item_pointer_holder), pointer :: owner + call c_f_pointer(owner_address, owner) + owner%value%weight = value + end subroutine bind_c_prik_pointer_holder_field_holder_item_weight_set + function bind_c_prik_create_holder_item() result(result) bind(c, name="bind_c_prik_create_holder_item") + type(c_ptr) :: result + type(prik_type_holder_item), pointer :: value + integer(c_int) :: allocation_status + result = c_null_ptr + allocate(value, stat=allocation_status) + if (allocation_status == 0_c_int) then + result = c_loc(value) + end if + end function bind_c_prik_create_holder_item + function bind_c_prik_create_vector() result(result) bind(c, name="bind_c_prik_create_vector") + type(c_ptr) :: result + type(prik_type_vector), pointer :: value + integer(c_int) :: allocation_status + result = c_null_ptr + allocate(value, stat=allocation_status) + if (allocation_status == 0_c_int) then + result = c_loc(value) + end if + end function bind_c_prik_create_vector + subroutine bind_c_prik_destroy_holder_item(address) bind(c, name="bind_c_prik_destroy_holder_item") + type(c_ptr), value :: address + type(prik_type_holder_item), pointer :: value + call c_f_pointer(address, value) + if (associated(value)) then + deallocate(value) + end if + end subroutine bind_c_prik_destroy_holder_item + subroutine bind_c_prik_destroy_vector(address) bind(c, name="bind_c_prik_destroy_vector") + type(c_ptr), value :: address + type(prik_type_vector), pointer :: value + call c_f_pointer(address, value) + if (associated(value)) then + deallocate(value) + end if + end subroutine bind_c_prik_destroy_vector + subroutine bind_c_prik_destroy_holder_item_allocatable_holder(& + & address) bind(c, name="bind_c_prik_destroy_holder_item_allocatable_holder") + type(c_ptr), value :: address + type(prik_holder_item_allocatable_holder), pointer :: holder + call c_f_pointer(address, holder) + if (associated(holder)) then + deallocate(holder) + end if + end subroutine bind_c_prik_destroy_holder_item_allocatable_holder + subroutine bind_c_prik_destroy_vector_allocatable_holder(& + & address) bind(c, name="bind_c_prik_destroy_vector_allocatable_holder") + type(c_ptr), value :: address + type(prik_vector_allocatable_holder), pointer :: holder + call c_f_pointer(address, holder) + if (associated(holder)) then + deallocate(holder) + end if + end subroutine bind_c_prik_destroy_vector_allocatable_holder + function bind_c_prik_holder_item_allocatable_holder_present(& + & address) result(result) bind(c, name="bind_c_prik_holder_item_allocatable_holder_present") + type(c_ptr), value :: address + logical(c_bool) :: result + type(prik_holder_item_allocatable_holder), pointer :: holder + call c_f_pointer(address, holder) + result = allocated(holder%value) + end function bind_c_prik_holder_item_allocatable_holder_present + function bind_c_prik_vector_allocatable_holder_present(& + & address) result(result) bind(c, name="bind_c_prik_vector_allocatable_holder_present") + type(c_ptr), value :: address + logical(c_bool) :: result + type(prik_vector_allocatable_holder), pointer :: holder + call c_f_pointer(address, holder) + result = allocated(holder%value) + end function bind_c_prik_vector_allocatable_holder_present + subroutine bind_c_prik_destroy_holder_item_pointer_holder(& + & address) bind(c, name="bind_c_prik_destroy_holder_item_pointer_holder") + type(c_ptr), value :: address + type(prik_holder_item_pointer_holder), pointer :: holder + call c_f_pointer(address, holder) + if (associated(holder)) then + nullify(holder%value) + deallocate(holder) + end if + end subroutine bind_c_prik_destroy_holder_item_pointer_holder + subroutine bind_c_prik_destroy_vector_pointer_holder(& + & address) bind(c, name="bind_c_prik_destroy_vector_pointer_holder") + type(c_ptr), value :: address + type(prik_vector_pointer_holder), pointer :: holder + call c_f_pointer(address, holder) + if (associated(holder)) then + nullify(holder%value) + deallocate(holder) + end if + end subroutine bind_c_prik_destroy_vector_pointer_holder + function bind_c_prik_holder_item_pointer_holder_present(& + & address) result(result) bind(c, name="bind_c_prik_holder_item_pointer_holder_present") + type(c_ptr), value :: address + logical(c_bool) :: result + type(prik_holder_item_pointer_holder), pointer :: holder + call c_f_pointer(address, holder) + result = associated(holder%value) + end function bind_c_prik_holder_item_pointer_holder_present + function bind_c_prik_vector_pointer_holder_present(& + & address) result(result) bind(c, name="bind_c_prik_vector_pointer_holder_present") + type(c_ptr), value :: address + logical(c_bool) :: result + type(prik_vector_pointer_holder), pointer :: holder + call c_f_pointer(address, holder) + result = associated(holder%value) + end function bind_c_prik_vector_pointer_holder_present + function bind_c_prik_origin_active_vector_26504a12_present() & + & result(result) bind(c, name="bind_c_prik_origin_active_vector_26504a12_present") + logical(c_bool) :: result + result = allocated(native_active_vector) + end function bind_c_prik_origin_active_vector_26504a12_present + function bind_c_prik_origin_active_vector_26504a12_scoped(& + & consumer, & + & context) result(status) bind(c, name="bind_c_prik_origin_active_vector_26504a12_scoped") + type(c_funptr), value :: consumer + type(c_ptr), value :: context + integer(c_int) :: status + procedure(prik_derived_consumer), pointer :: consume + call c_f_procpointer(consumer, consume) + status = 1_c_int + if (allocated(native_active_vector)) then + status = prik_invoke_origin(native_active_vector) + end if + contains + function prik_invoke_origin(value) result(inner_status) + type(prik_type_vector), target :: value + integer(c_int) :: inner_status + inner_status = consume(c_loc(value), context) + end function prik_invoke_origin + end function bind_c_prik_origin_active_vector_26504a12_scoped + function bind_c_prik_origin_active_vector_26504a12_checkout(& + & holder_address) result(status) bind(c, name="bind_c_prik_origin_active_vector_26504a12_checkout") + type(c_ptr), intent(out) :: holder_address + integer(c_int) :: status + type(prik_vector_allocatable_holder), pointer :: holder + integer(c_int) :: allocation_status + holder_address = c_null_ptr + allocate(holder, stat=allocation_status) + if (allocation_status == 0_c_int) then + call move_alloc(native_active_vector, holder%value) + holder_address = c_loc(holder) + status = 0_c_int + else + status = 4_c_int + end if + end function bind_c_prik_origin_active_vector_26504a12_checkout + function bind_c_prik_origin_active_vector_26504a12_restore(& + & holder_address) result(status) bind(c, name="bind_c_prik_origin_active_vector_26504a12_restore") + type(c_ptr), value :: holder_address + integer(c_int) :: status + type(prik_vector_allocatable_holder), pointer :: holder + call c_f_pointer(holder_address, holder) + if (associated(holder)) then + call move_alloc(holder%value, native_active_vector) + deallocate(holder) + status = 0_c_int + else + status = 5_c_int + end if + end function bind_c_prik_origin_active_vector_26504a12_restore + function bind_c_prik_origin_selected_vector_d2fd3c9d_present() & + & result(result) bind(c, name="bind_c_prik_origin_selected_vector_d2fd3c9d_present") + logical(c_bool) :: result + result = associated(native_selected_vector) + end function bind_c_prik_origin_selected_vector_d2fd3c9d_present + function bind_c_prik_origin_selected_vector_d2fd3c9d_scoped(& + & consumer, & + & context) result(status) bind(c, name="bind_c_prik_origin_selected_vector_d2fd3c9d_scoped") + type(c_funptr), value :: consumer + type(c_ptr), value :: context + integer(c_int) :: status + procedure(prik_derived_consumer), pointer :: consume + call c_f_procpointer(consumer, consume) + status = 1_c_int + if (associated(native_selected_vector)) then + status = prik_invoke_origin(native_selected_vector) + end if + contains + function prik_invoke_origin(value) result(inner_status) + type(prik_type_vector), target :: value + integer(c_int) :: inner_status + inner_status = consume(c_loc(value), context) + end function prik_invoke_origin + end function bind_c_prik_origin_selected_vector_d2fd3c9d_scoped + function bind_c_prik_origin_selected_vector_d2fd3c9d_checkout(& + & holder_address) result(status) bind(c, name="bind_c_prik_origin_selected_vector_d2fd3c9d_checkout") + type(c_ptr), intent(out) :: holder_address + integer(c_int) :: status + type(prik_vector_pointer_holder), pointer :: holder + integer(c_int) :: allocation_status + holder_address = c_null_ptr + allocate(holder, stat=allocation_status) + if (allocation_status == 0_c_int) then + if (associated(native_selected_vector)) then + holder%value => native_selected_vector + else + nullify(holder%value) + end if + nullify(native_selected_vector) + holder_address = c_loc(holder) + status = 0_c_int + else + status = 4_c_int + end if + end function bind_c_prik_origin_selected_vector_d2fd3c9d_checkout + function bind_c_prik_origin_selected_vector_d2fd3c9d_restore(& + & holder_address) result(status) bind(c, name="bind_c_prik_origin_selected_vector_d2fd3c9d_restore") + type(c_ptr), value :: holder_address + integer(c_int) :: status + type(prik_vector_pointer_holder), pointer :: holder + call c_f_pointer(holder_address, holder) + if (associated(holder)) then + if (associated(holder%value)) then + native_selected_vector => holder%value + else + nullify(native_selected_vector) + end if + nullify(holder%value) + deallocate(holder) + status = 0_c_int + else + status = 5_c_int + end if + end function bind_c_prik_origin_selected_vector_d2fd3c9d_restore +end module bind_c_refactoring_goldens_wrapper +function prik_callback_adapter_callback_83b3d1d9(value) result(callback_result) + use iso_c_binding, only: c_double, c_loc, c_ptr + implicit none + real(c_double), intent(in) :: value + real(c_double) :: callback_result + type(c_ptr) :: value_data + real(c_double), target :: value_callback_storage + interface + function prik_callback_trampoline_callback_83b3d1d9_call(& + & value_data) bind(c, name="prik_callback_trampoline_callback_83b3d1d9") result(callback_result) + import :: c_ptr, c_double + type(c_ptr), value :: value_data + real(c_double) :: callback_result + end function prik_callback_trampoline_callback_83b3d1d9_call + end interface + value_callback_storage = value + value_data = c_loc(value_callback_storage) + callback_result = prik_callback_trampoline_callback_83b3d1d9_call(value_data) +end function prik_callback_adapter_callback_83b3d1d9 \ No newline at end of file diff --git a/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/contract.pyi.golden b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/contract.pyi.golden new file mode 100644 index 000000000..b79859239 --- /dev/null +++ b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/contract.pyi.golden @@ -0,0 +1,136 @@ +from prik.contracts import Addr, Allocatable, Annotated, Arg, Final, Float64, In, Int32, Pass, Pointer, PointerAssociation, Return, Returns, String, bind, native_call, native_type, overload, private, prototype + +class holder_item: + def __init__( + self, + *, + code: Int32 = 0, + weight: Float64 = 0.08 + ) -> None: ... + + code: Int32 = 0 + weight: Float64 = 0.08 + +@native_type(finalizers=('finalize_vector',)) +class vector: + def __init__( + self, + *, + x: Float64 = 0.08, + y: Float64 = 0.08 + ) -> None: ... + + x: Float64 = 0.08 + y: Float64 = 0.08 + samples: Allocatable[Float64[:]] + + @native_call([Pass(), Addr(Arg(0))]) + def scale( + self, + factor: Float64 + ) -> None: ... + + @bind("shift_vector") + @native_call([Addr(Arg(0)), Pass(), Addr(Arg(1))]) + def shift( + self, + dx: Float64, + dy: Float64 + ) -> None: ... + + def magnitude(self) -> Float64: ... + + def replace_samples( + self, + values: Float64[::] + ) -> None: ... + + @overload("add_vectors") + def __add__( + self, + right: vector + ) -> vector: ... + +@prototype +def scalar_callback( + value: In(Addr(Float64)) +) -> Float64: ... + +default_count: Final[Int32] = 3 + +counter: Int32 + +workspace: Allocatable[Float64[:]] + +selected: Annotated[Pointer[Float64[:]], PointerAssociation("runtime")] + +active_vector: Allocatable[vector] + +selected_vector: Pointer[vector] + +@private +def add_vectors( + left: vector, + right: vector +) -> vector: ... + +@private +@native_call([Addr(Arg(0))]) +def integer_to_real( + value: Int32 +) -> Float64: ... + +@private +@native_call([Addr(Arg(0))]) +def real_to_integer( + value: Float64 +) -> Int32: ... + +@native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2), Arg(3), Arg(4)]) +def summarize( + required: Int32, + scale: Int32 = ..., + values: Float64[::] = ..., + label: String = ..., + item: vector = ... +) -> Int32: ... + +@native_call([Addr(Arg(0)), Addr(Arg(1))]) +def make_values( + count: Int32, + fill_value: Float64 +) -> Allocatable[Float64[:]]: ... + +@native_call([Arg(0), Addr(Arg(1))]) +def apply_callback( + callback: scalar_callback, + value: Float64 +) -> Float64: ... + +@native_call([Addr(Arg(0)), Return('doubled', 0), Return('status', 1)]) +def split_value( + value: Float64 +) -> tuple[Float64, Int32]: ... + +@native_call([Allocatable(Arg(0))]) +def reset_allocatable_item( + value: holder_item | None +) -> Returns["value", holder_item] | None: ... + +@native_call([Pointer(Arg(0)), Addr(Arg(1))]) +def shift_pointer_item( + value: holder_item | None, + amount: Float64 +) -> Returns["value", holder_item] | None: ... + +@bind("convert") +@overload("integer_to_real") +def convert( + value: Int32 +) -> Float64: ... + +@bind("convert") +@overload("real_to_integer") +def convert( + value: Float64 +) -> Int32: ... diff --git a/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/native/refactoring_goldens.f90 b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/native/refactoring_goldens.f90 new file mode 100644 index 000000000..2d3fa3083 --- /dev/null +++ b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/native/refactoring_goldens.f90 @@ -0,0 +1,174 @@ +module refactoring_goldens + implicit none + private + + public :: vector, holder_item, counter, default_count, workspace, selected + public :: active_vector, selected_vector + public :: summarize, make_values, apply_callback, convert + public :: reset_allocatable_item, shift_pointer_item + public :: split_value + public :: operator(+) + + integer(4), parameter :: default_count = 3 + integer(4) :: counter = 1 + real(8), allocatable :: workspace(:) + real(8), pointer :: selected(:) => null() + + type :: holder_item + integer(4) :: code = 0 + real(8) :: weight = 0.0_8 + end type holder_item + + type :: vector + real(8) :: x = 0.0_8 + real(8) :: y = 0.0_8 + real(8), allocatable :: samples(:) + contains + procedure, public :: scale + procedure, public, pass(owner) :: shift => shift_vector + procedure, public :: magnitude + procedure, public :: replace_samples + final :: finalize_vector + end type vector + + type(vector), allocatable :: active_vector + type(vector), pointer :: selected_vector => null() + + abstract interface + function scalar_callback(value) result(output) + real(8), intent(in) :: value + real(8) :: output + end function scalar_callback + end interface + + interface convert + module procedure integer_to_real + module procedure real_to_integer + end interface convert + + interface operator(+) + module procedure add_vectors + end interface operator(+) + +contains + + subroutine scale(self, factor) + class(vector), intent(inout) :: self + real(8), intent(in) :: factor + + self%x = self%x * factor + self%y = self%y * factor + end subroutine scale + + subroutine shift_vector(dx, owner, dy) + real(8), intent(in) :: dx + class(vector), intent(inout) :: owner + real(8), intent(in) :: dy + + owner%x = owner%x + dx + owner%y = owner%y + dy + end subroutine shift_vector + + function magnitude(self) result(value) + class(vector), intent(in) :: self + real(8) :: value + + value = sqrt(self%x * self%x + self%y * self%y) + end function magnitude + + subroutine replace_samples(self, values) + class(vector), intent(inout) :: self + real(8), intent(in) :: values(:) + + if (allocated(self%samples)) deallocate(self%samples) + allocate(self%samples(size(values))) + self%samples = values + end subroutine replace_samples + + subroutine finalize_vector(self) + type(vector), intent(inout) :: self + + if (allocated(self%samples)) deallocate(self%samples) + end subroutine finalize_vector + + function add_vectors(left, right) result(output) + type(vector), intent(in) :: left + type(vector), intent(in) :: right + type(vector) :: output + + output%x = left%x + right%x + output%y = left%y + right%y + end function add_vectors + + function integer_to_real(value) result(output) + integer(4), intent(in) :: value + real(8) :: output + + output = real(value, 8) + end function integer_to_real + + function real_to_integer(value) result(output) + real(8), intent(in) :: value + integer(4) :: output + + output = int(value, 4) + end function real_to_integer + + function summarize(required, scale, values, label, item) result(output) + integer(4), intent(in) :: required + integer(4), intent(in), optional :: scale + real(8), intent(in), optional :: values(:) + character(len=*), intent(in), optional :: label + type(vector), intent(in), optional :: item + integer(4) :: output + + output = required + if (present(scale)) output = output + scale + if (present(values)) output = output + int(sum(values), 4) + if (present(label)) output = output + len_trim(label) + if (present(item)) output = output + int(item%x + item%y, 4) + end function summarize + + function make_values(count, fill_value) result(values) + integer(4), intent(in) :: count + real(8), intent(in) :: fill_value + real(8), allocatable :: values(:) + + allocate(values(count)) + values = fill_value + end function make_values + + function apply_callback(callback, value) result(output) + procedure(scalar_callback) :: callback + real(8), intent(in) :: value + real(8) :: output + + output = callback(value) + end function apply_callback + + subroutine split_value(value, doubled, status) + real(8), intent(in) :: value + real(8), intent(out) :: doubled + integer(4), intent(out) :: status + + doubled = 2.0_8 * value + status = 0 + end subroutine split_value + + subroutine reset_allocatable_item(value) + type(holder_item), allocatable, intent(inout) :: value + + if (allocated(value)) deallocate(value) + allocate(value) + end subroutine reset_allocatable_item + + subroutine shift_pointer_item(value, amount) + type(holder_item), pointer, intent(inout) :: value + real(8), intent(in) :: amount + + if (associated(value)) then + value%weight = value%weight + amount + end if + end subroutine shift_pointer_item + +end module refactoring_goldens diff --git a/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/parser.json b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/parser.json new file mode 100644 index 000000000..ec3135612 --- /dev/null +++ b/tests/fortran/infrastructure/codegen/fixtures/refactoring_goldens/parser.json @@ -0,0 +1,6391 @@ +{ + "files": [ + { + "filename": "refactoring_goldens.f90", + "source": "module refactoring_goldens\n implicit none\n private\n\n public :: vector, holder_item, counter, default_count, workspace, selected\n public :: active_vector, selected_vector\n public :: summarize, make_values, apply_callback, convert\n public :: reset_allocatable_item, shift_pointer_item\n public :: split_value\n public :: operator(+)\n\n integer(4), parameter :: default_count = 3\n integer(4) :: counter = 1\n real(8), allocatable :: workspace(:)\n real(8), pointer :: selected(:) => null()\n\n type :: holder_item\n integer(4) :: code = 0\n real(8) :: weight = 0.0_8\n end type holder_item\n\n type :: vector\n real(8) :: x = 0.0_8\n real(8) :: y = 0.0_8\n real(8), allocatable :: samples(:)\n contains\n procedure, public :: scale\n procedure, public, pass(owner) :: shift => shift_vector\n procedure, public :: magnitude\n procedure, public :: replace_samples\n final :: finalize_vector\n end type vector\n\n type(vector), allocatable :: active_vector\n type(vector), pointer :: selected_vector => null()\n\n abstract interface\n function scalar_callback(value) result(output)\n real(8), intent(in) :: value\n real(8) :: output\n end function scalar_callback\n end interface\n\n interface convert\n module procedure integer_to_real\n module procedure real_to_integer\n end interface convert\n\n interface operator(+)\n module procedure add_vectors\n end interface operator(+)\n\ncontains\n\n subroutine scale(self, factor)\n class(vector), intent(inout) :: self\n real(8), intent(in) :: factor\n\n self%x = self%x * factor\n self%y = self%y * factor\n end subroutine scale\n\n subroutine shift_vector(dx, owner, dy)\n real(8), intent(in) :: dx\n class(vector), intent(inout) :: owner\n real(8), intent(in) :: dy\n\n owner%x = owner%x + dx\n owner%y = owner%y + dy\n end subroutine shift_vector\n\n function magnitude(self) result(value)\n class(vector), intent(in) :: self\n real(8) :: value\n\n value = sqrt(self%x * self%x + self%y * self%y)\n end function magnitude\n\n subroutine replace_samples(self, values)\n class(vector), intent(inout) :: self\n real(8), intent(in) :: values(:)\n\n if (allocated(self%samples)) deallocate(self%samples)\n allocate(self%samples(size(values)))\n self%samples = values\n end subroutine replace_samples\n\n subroutine finalize_vector(self)\n type(vector), intent(inout) :: self\n\n if (allocated(self%samples)) deallocate(self%samples)\n end subroutine finalize_vector\n\n function add_vectors(left, right) result(output)\n type(vector), intent(in) :: left\n type(vector), intent(in) :: right\n type(vector) :: output\n\n output%x = left%x + right%x\n output%y = left%y + right%y\n end function add_vectors\n\n function integer_to_real(value) result(output)\n integer(4), intent(in) :: value\n real(8) :: output\n\n output = real(value, 8)\n end function integer_to_real\n\n function real_to_integer(value) result(output)\n real(8), intent(in) :: value\n integer(4) :: output\n\n output = int(value, 4)\n end function real_to_integer\n\n function summarize(required, scale, values, label, item) result(output)\n integer(4), intent(in) :: required\n integer(4), intent(in), optional :: scale\n real(8), intent(in), optional :: values(:)\n character(len=*), intent(in), optional :: label\n type(vector), intent(in), optional :: item\n integer(4) :: output\n\n output = required\n if (present(scale)) output = output + scale\n if (present(values)) output = output + int(sum(values), 4)\n if (present(label)) output = output + len_trim(label)\n if (present(item)) output = output + int(item%x + item%y, 4)\n end function summarize\n\n function make_values(count, fill_value) result(values)\n integer(4), intent(in) :: count\n real(8), intent(in) :: fill_value\n real(8), allocatable :: values(:)\n\n allocate(values(count))\n values = fill_value\n end function make_values\n\n function apply_callback(callback, value) result(output)\n procedure(scalar_callback) :: callback\n real(8), intent(in) :: value\n real(8) :: output\n\n output = callback(value)\n end function apply_callback\n\n subroutine split_value(value, doubled, status)\n real(8), intent(in) :: value\n real(8), intent(out) :: doubled\n integer(4), intent(out) :: status\n\n doubled = 2.0_8 * value\n status = 0\n end subroutine split_value\n\n subroutine reset_allocatable_item(value)\n type(holder_item), allocatable, intent(inout) :: value\n\n if (allocated(value)) deallocate(value)\n allocate(value)\n end subroutine reset_allocatable_item\n\n subroutine shift_pointer_item(value, amount)\n type(holder_item), pointer, intent(inout) :: value\n real(8), intent(in) :: amount\n\n if (associated(value)) then\n value%weight = value%weight + amount\n end if\n end subroutine shift_pointer_item\n\nend module refactoring_goldens\n", + "encoding": "utf-8", + "format": "modern", + "modules": [ + { + "name": "refactoring_goldens", + "filename": "refactoring_goldens.f90", + "uses": {}, + "variables": [ + { + "name": "default_count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "3", + "symbolic_value": "3", + "value_type": "expression", + "is_parameter": true, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "counter", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "workspace", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + { + "name": "selected", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "active_vector", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + { + "name": "selected_vector", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + } + ], + "procedures": [ + { + "name": "scale", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "factor", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "shift_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "dx", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "owner", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "dy", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "magnitude", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "replace_samples", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "finalize_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "finalize_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "add_vectors", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "left", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "right", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "integer_to_real", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "real_to_integer", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "summarize", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "required", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "scale", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "label", + "base_type": "character", + "kind": "len=*", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "item", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "make_values", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "fill_value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "apply_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "callback", + "base_type": "procedure", + "kind": "scalar_callback", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "split_value", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "doubled", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "status", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "reset_allocatable_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "reset_allocatable_item", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "shift_pointer_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "amount", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + } + ], + "derived_types": [ + { + "name": "holder_item", + "module": "refactoring_goldens", + "fields": [ + { + "name": "code", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "weight", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "methods": [], + "final_procedures": [], + "extends": null, + "attributes": [], + "procedure_bindings": [], + "generic_bindings": [] + }, + { + "name": "vector", + "module": "refactoring_goldens", + "fields": [ + { + "name": "x", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "y", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "samples", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "methods": [ + "scale", + "shift => shift_vector", + "magnitude", + "replace_samples" + ], + "final_procedures": [ + "finalize_vector" + ], + "extends": null, + "attributes": [], + "procedure_bindings": [ + { + "name": "scale", + "attrs": [ + "public" + ] + }, + { + "name": "shift => shift_vector", + "attrs": [ + "public", + "pass(owner)" + ] + }, + { + "name": "magnitude", + "attrs": [ + "public" + ] + }, + { + "name": "replace_samples", + "attrs": [ + "public" + ] + } + ], + "generic_bindings": [] + } + ], + "interfaces": [ + { + "name": null, + "module": "refactoring_goldens", + "procedures": [ + { + "name": "scalar_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scalar_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scalar_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": true, + "variables": {}, + "common_variables": [] + } + ], + "specific_procedures": [], + "abstract": true + }, + { + "name": "convert", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "integer_to_real", + "real_to_integer" + ], + "abstract": false + }, + { + "name": "operator(+)", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "add_vectors" + ], + "abstract": false + } + ], + "enums": [], + "default_visibility": "private", + "public_symbols": [ + "vector", + "holder_item", + "counter", + "default_count", + "workspace", + "selected", + "active_vector", + "selected_vector", + "summarize", + "make_values", + "apply_callback", + "convert", + "reset_allocatable_item", + "shift_pointer_item", + "split_value", + "operator(+)" + ], + "private_symbols": [], + "common_variables": [] + } + ], + "submodules": [], + "programs": [], + "block_data_units": [], + "procedures": [], + "interfaces": [], + "derived_types": [], + "variables": [], + "includes": [], + "diagnostics": [], + "symbols": { + "refactoring_goldens": { + "name": "refactoring_goldens", + "filename": "refactoring_goldens.f90", + "uses": {}, + "variables": [ + { + "name": "default_count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "3", + "symbolic_value": "3", + "value_type": "expression", + "is_parameter": true, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "counter", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "workspace", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + { + "name": "selected", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "active_vector", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + { + "name": "selected_vector", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + } + ], + "procedures": [ + { + "name": "scale", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "factor", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "shift_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "dx", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "owner", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "dy", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "magnitude", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "replace_samples", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "finalize_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "finalize_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "add_vectors", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "left", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "right", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "integer_to_real", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "real_to_integer", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "summarize", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "required", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "scale", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "label", + "base_type": "character", + "kind": "len=*", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "item", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "make_values", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "fill_value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "apply_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "callback", + "base_type": "procedure", + "kind": "scalar_callback", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "split_value", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "doubled", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "status", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "reset_allocatable_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "reset_allocatable_item", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "shift_pointer_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "amount", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + } + ], + "derived_types": [ + { + "name": "holder_item", + "module": "refactoring_goldens", + "fields": [ + { + "name": "code", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "weight", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "methods": [], + "final_procedures": [], + "extends": null, + "attributes": [], + "procedure_bindings": [], + "generic_bindings": [] + }, + { + "name": "vector", + "module": "refactoring_goldens", + "fields": [ + { + "name": "x", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "y", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "samples", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "methods": [ + "scale", + "shift => shift_vector", + "magnitude", + "replace_samples" + ], + "final_procedures": [ + "finalize_vector" + ], + "extends": null, + "attributes": [], + "procedure_bindings": [ + { + "name": "scale", + "attrs": [ + "public" + ] + }, + { + "name": "shift => shift_vector", + "attrs": [ + "public", + "pass(owner)" + ] + }, + { + "name": "magnitude", + "attrs": [ + "public" + ] + }, + { + "name": "replace_samples", + "attrs": [ + "public" + ] + } + ], + "generic_bindings": [] + } + ], + "interfaces": [ + { + "name": null, + "module": "refactoring_goldens", + "procedures": [ + { + "name": "scalar_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scalar_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scalar_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": true, + "variables": {}, + "common_variables": [] + } + ], + "specific_procedures": [], + "abstract": true + }, + { + "name": "convert", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "integer_to_real", + "real_to_integer" + ], + "abstract": false + }, + { + "name": "operator(+)", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "add_vectors" + ], + "abstract": false + } + ], + "enums": [], + "default_visibility": "private", + "public_symbols": [ + "vector", + "holder_item", + "counter", + "default_count", + "workspace", + "selected", + "active_vector", + "selected_vector", + "summarize", + "make_values", + "apply_callback", + "convert", + "reset_allocatable_item", + "shift_pointer_item", + "split_value", + "operator(+)" + ], + "private_symbols": [], + "common_variables": [] + } + } + } + ], + "modules": { + "refactoring_goldens": { + "name": "refactoring_goldens", + "filename": "refactoring_goldens.f90", + "uses": {}, + "variables": [ + { + "name": "default_count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "3", + "symbolic_value": "3", + "value_type": "expression", + "is_parameter": true, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "counter", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "workspace", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + { + "name": "selected", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "active_vector", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + { + "name": "selected_vector", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + } + ], + "procedures": [ + { + "name": "scale", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "factor", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "shift_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "dx", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "owner", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "dy", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "magnitude", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "replace_samples", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "finalize_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "finalize_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "add_vectors", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "left", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "right", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "integer_to_real", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "real_to_integer", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "summarize", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "required", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "scale", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "label", + "base_type": "character", + "kind": "len=*", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "item", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "make_values", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "fill_value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "apply_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "callback", + "base_type": "procedure", + "kind": "scalar_callback", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "split_value", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "doubled", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "status", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "reset_allocatable_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "reset_allocatable_item", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + { + "name": "shift_pointer_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "amount", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + } + ], + "derived_types": [ + { + "name": "holder_item", + "module": "refactoring_goldens", + "fields": [ + { + "name": "code", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "weight", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "methods": [], + "final_procedures": [], + "extends": null, + "attributes": [], + "procedure_bindings": [], + "generic_bindings": [] + }, + { + "name": "vector", + "module": "refactoring_goldens", + "fields": [ + { + "name": "x", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "y", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "samples", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "methods": [ + "scale", + "shift => shift_vector", + "magnitude", + "replace_samples" + ], + "final_procedures": [ + "finalize_vector" + ], + "extends": null, + "attributes": [], + "procedure_bindings": [ + { + "name": "scale", + "attrs": [ + "public" + ] + }, + { + "name": "shift => shift_vector", + "attrs": [ + "public", + "pass(owner)" + ] + }, + { + "name": "magnitude", + "attrs": [ + "public" + ] + }, + { + "name": "replace_samples", + "attrs": [ + "public" + ] + } + ], + "generic_bindings": [] + } + ], + "interfaces": [ + { + "name": null, + "module": "refactoring_goldens", + "procedures": [ + { + "name": "scalar_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scalar_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scalar_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": true, + "variables": {}, + "common_variables": [] + } + ], + "specific_procedures": [], + "abstract": true + }, + { + "name": "convert", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "integer_to_real", + "real_to_integer" + ], + "abstract": false + }, + { + "name": "operator(+)", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "add_vectors" + ], + "abstract": false + } + ], + "enums": [], + "default_visibility": "private", + "public_symbols": [ + "vector", + "holder_item", + "counter", + "default_count", + "workspace", + "selected", + "active_vector", + "selected_vector", + "summarize", + "make_values", + "apply_callback", + "convert", + "reset_allocatable_item", + "shift_pointer_item", + "split_value", + "operator(+)" + ], + "private_symbols": [], + "common_variables": [] + } + }, + "submodules": {}, + "programs": {}, + "procedures": { + "refactoring_goldens.scale": { + "name": "scale", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "factor", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "scale": { + "name": "scale", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "factor", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "scale", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.shift_vector": { + "name": "shift_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "dx", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "owner", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "dy", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "shift_vector": { + "name": "shift_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "dx", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "owner", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "dy", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.magnitude": { + "name": "magnitude", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "magnitude": { + "name": "magnitude", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "magnitude", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.replace_samples": { + "name": "replace_samples", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "replace_samples": { + "name": "replace_samples", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "replace_samples", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.finalize_vector": { + "name": "finalize_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "finalize_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "finalize_vector": { + "name": "finalize_vector", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "self", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "finalize_vector", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.add_vectors": { + "name": "add_vectors", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "left", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "right", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "add_vectors": { + "name": "add_vectors", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "left", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "right", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "add_vectors", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.integer_to_real": { + "name": "integer_to_real", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "integer_to_real": { + "name": "integer_to_real", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "integer_to_real", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.real_to_integer": { + "name": "real_to_integer", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "real_to_integer": { + "name": "real_to_integer", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "real_to_integer", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.summarize": { + "name": "summarize", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "required", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "scale", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "label", + "base_type": "character", + "kind": "len=*", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "item", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "summarize": { + "name": "summarize", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "required", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "scale", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "label", + "base_type": "character", + "kind": "len=*", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "item", + "base_type": "derived", + "kind": "vector", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": true, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "summarize", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.make_values": { + "name": "make_values", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "fill_value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "make_values": { + "name": "make_values", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "count", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "fill_value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "values", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "make_values", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.apply_callback": { + "name": "apply_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "callback", + "base_type": "procedure", + "kind": "scalar_callback", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "apply_callback": { + "name": "apply_callback", + "kind": "function", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "callback", + "base_type": "procedure", + "kind": "scalar_callback", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": { + "name": "output", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "apply_callback", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.split_value": { + "name": "split_value", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "doubled", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "status", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "split_value": { + "name": "split_value", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "doubled", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "status", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "split_value", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.reset_allocatable_item": { + "name": "reset_allocatable_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "reset_allocatable_item", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "reset_allocatable_item": { + "name": "reset_allocatable_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "reset_allocatable_item", + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "refactoring_goldens.shift_pointer_item": { + "name": "shift_pointer_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "amount", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + }, + "shift_pointer_item": { + "name": "shift_pointer_item", + "kind": "subroutine", + "module": "refactoring_goldens", + "arguments": [ + { + "name": "value", + "base_type": "derived", + "kind": "holder_item", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": true + }, + { + "name": "amount", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": "shift_pointer_item", + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "result": null, + "attributes": [], + "bind_name": null, + "uses": {}, + "in_interface": false, + "variables": {}, + "common_variables": [] + } + }, + "derived_types": { + "refactoring_goldens.holder_item": { + "name": "holder_item", + "module": "refactoring_goldens", + "fields": [ + { + "name": "code", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "weight", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "methods": [], + "final_procedures": [], + "extends": null, + "attributes": [], + "procedure_bindings": [], + "generic_bindings": [] + }, + "holder_item": { + "name": "holder_item", + "module": "refactoring_goldens", + "fields": [ + { + "name": "code", + "base_type": "integer", + "kind": "4", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "weight", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + } + ], + "methods": [], + "final_procedures": [], + "extends": null, + "attributes": [], + "procedure_bindings": [], + "generic_bindings": [] + }, + "refactoring_goldens.vector": { + "name": "vector", + "module": "refactoring_goldens", + "fields": [ + { + "name": "x", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "y", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "samples", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "methods": [ + "scale", + "shift => shift_vector", + "magnitude", + "replace_samples" + ], + "final_procedures": [ + "finalize_vector" + ], + "extends": null, + "attributes": [], + "procedure_bindings": [ + { + "name": "scale", + "attrs": [ + "public" + ] + }, + { + "name": "shift => shift_vector", + "attrs": [ + "public", + "pass(owner)" + ] + }, + { + "name": "magnitude", + "attrs": [ + "public" + ] + }, + { + "name": "replace_samples", + "attrs": [ + "public" + ] + } + ], + "generic_bindings": [] + }, + "vector": { + "name": "vector", + "module": "refactoring_goldens", + "fields": [ + { + "name": "x", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "y", + "base_type": "real", + "kind": "8", + "rank": 0, + "shape": [], + "lbound": [], + "ubound": [], + "value": "0", + "symbolic_value": "0.0_8", + "value_type": "expression", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": false, + "pointer": false + }, + { + "name": "samples", + "base_type": "real", + "kind": "8", + "rank": 1, + "shape": [ + ":" + ], + "lbound": [ + null + ], + "ubound": [ + null + ], + "value": null, + "symbolic_value": null, + "value_type": "unknown", + "is_parameter": false, + "target": false, + "dimensions": [], + "visibility": "public", + "procedure": null, + "optional": false, + "pass_by_value": false, + "allocatable": true, + "pointer": false + } + ], + "methods": [ + "scale", + "shift => shift_vector", + "magnitude", + "replace_samples" + ], + "final_procedures": [ + "finalize_vector" + ], + "extends": null, + "attributes": [], + "procedure_bindings": [ + { + "name": "scale", + "attrs": [ + "public" + ] + }, + { + "name": "shift => shift_vector", + "attrs": [ + "public", + "pass(owner)" + ] + }, + { + "name": "magnitude", + "attrs": [ + "public" + ] + }, + { + "name": "replace_samples", + "attrs": [ + "public" + ] + } + ], + "generic_bindings": [] + } + }, + "interfaces": { + "refactoring_goldens.convert": { + "name": "convert", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "integer_to_real", + "real_to_integer" + ], + "abstract": false + }, + "convert": { + "name": "convert", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "integer_to_real", + "real_to_integer" + ], + "abstract": false + }, + "refactoring_goldens.operator(+)": { + "name": "operator(+)", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "add_vectors" + ], + "abstract": false + }, + "operator(+)": { + "name": "operator(+)", + "module": "refactoring_goldens", + "procedures": [], + "specific_procedures": [ + "add_vectors" + ], + "abstract": false + } + }, + "dependencies": { + "refactoring_goldens": [] + }, + "include_dirs": [], + "diagnostics": [] +} diff --git a/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py b/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py new file mode 100644 index 000000000..c64a6ca4e --- /dev/null +++ b/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py @@ -0,0 +1,120 @@ +"""Temporary cross-stage golden contracts for maintainability refactoring. + +Refresh the reviewed baselines with:: + + REFACTORING_UPDATE_GOLDENS=1 python3 -m pytest -q \ + tests/fortran/infrastructure/codegen/test_refactoring_goldens.py + +Remove this module and its fixture directory after the focused permanent suite +subsumes the refactoring evidence. +""" + +from __future__ import annotations + +import json +import os +from dataclasses import asdict, dataclass +from pathlib import Path + +import pytest + +from prik import parse_fortran_project +from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.codegen.printers import emit_module_stubs +from prik.semantics.fortran2ir import fortran_project_to_semantic_modules +from prik.semantics.policy_completion import complete_semantic_policies + + +_FIXTURE_DIR = Path(__file__).parent / "fixtures" / "refactoring_goldens" +_NATIVE_DIR = _FIXTURE_DIR / "native" +_UPDATE_GOLDENS = os.getenv("REFACTORING_UPDATE_GOLDENS", "0") == "1" + + +@dataclass(frozen=True) +class _RefactoringGoldenOutputs: + """Store the four stable stage outputs protected during refactoring.""" + + parser_json: str + semantic_pyi: str + fortran_bridge: str + c_binding: str + + +def _strip_parent_fields(value): + """Remove recursive parser parent links before deterministic JSON emission.""" + if isinstance(value, dict): + return {_stable_parser_path(key): _strip_parent_fields(item) for key, item in value.items() if key != "parent"} + if isinstance(value, list): + return [_strip_parent_fields(item) for item in value] + if isinstance(value, tuple): + return tuple(_strip_parent_fields(item) for item in value) + if isinstance(value, set): + return [_strip_parent_fields(item) for item in sorted(value, key=repr)] + return _stable_parser_path(value) + + +def _stable_parser_path(value): + """Replace checkout-specific fixture prefixes with stable relative names.""" + if not isinstance(value, str): + return value + prefix = str(_NATIVE_DIR.resolve()) + os.sep + return value.replace(prefix, "") + + +def _rendered_source(artifacts, suffix: str) -> str: + """Return the unique generated source carrying the requested suffix.""" + matches = [source.text for source in artifacts.sources if source.path.suffix == suffix] + assert len(matches) == 1, f"Expected one generated {suffix} source, found {len(matches)}" + return matches[0] + + +@pytest.fixture(scope="module") +def refactoring_golden_outputs() -> _RefactoringGoldenOutputs: + """Run the complete source-to-wrapper pipeline once for all golden checks.""" + parsed = parse_fortran_project(_NATIVE_DIR) + parser_json = json.dumps(_strip_parent_fields(asdict(parsed)), indent=2) + "\n" + + semantic_modules = fortran_project_to_semantic_modules(parsed) + assert [module.name for module in semantic_modules] == ["refactoring_goldens"] + semantic_pyi = ( + emit_module_stubs( + semantic_modules, + normalize_fortran_public_names=True, + )["refactoring_goldens"] + + "\n" + ) + + complete_semantic_policies(semantic_modules) + plan = WrapperPlanner().build(semantic_modules[0]) + artifacts = WrapperCodeGenerator().generate(plan) + + return _RefactoringGoldenOutputs( + parser_json=parser_json, + semantic_pyi=semantic_pyi, + fortran_bridge=_rendered_source(artifacts, ".f90"), + c_binding=_rendered_source(artifacts, ".c"), + ) + + +def _assert_matches_golden(filename: str, actual: str) -> None: + """Compare one output byte-for-byte, optionally refreshing its baseline.""" + expected_path = _FIXTURE_DIR / filename + if _UPDATE_GOLDENS: + expected_path.write_text(actual, encoding="utf-8") + assert actual == expected_path.read_text(encoding="utf-8") + + +def test_parser_json_matches_refactoring_golden(refactoring_golden_outputs): + _assert_matches_golden("parser.json", refactoring_golden_outputs.parser_json) + + +def test_semantic_pyi_matches_refactoring_golden(refactoring_golden_outputs): + _assert_matches_golden("contract.pyi.golden", refactoring_golden_outputs.semantic_pyi) + + +def test_fortran_bridge_matches_refactoring_golden(refactoring_golden_outputs): + _assert_matches_golden("bridge.f90", refactoring_golden_outputs.fortran_bridge) + + +def test_c_binding_matches_refactoring_golden(refactoring_golden_outputs): + _assert_matches_golden("binding.c", refactoring_golden_outputs.c_binding) From 5644c580ac6b540bc93c29a9fc67a235ce837ace Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 13:26:51 +0100 Subject: [PATCH 02/22] _visit_ModulePlan() now computes scoped_origin_type_identities as a local frozenset and passes it through namespace --- prik/codegen/fortran/bridge.py | 159 ++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 73 deletions(-) diff --git a/prik/codegen/fortran/bridge.py b/prik/codegen/fortran/bridge.py index 73f0027ac..7f6b7d9c7 100644 --- a/prik/codegen/fortran/bridge.py +++ b/prik/codegen/fortran/bridge.py @@ -112,16 +112,6 @@ class FortranBridgeGenerator(ClassVisitor): defensively instead of being reinterpreted here. """ - def __init__(self, *, method_prefix: str | None = None): - """Initialize the visitor and clear the per-module scoped-type cache. - - The optional prefix is forwarded unchanged to :class:`ClassVisitor`. - Scoped identities are temporary visitor state and are restored after - each module visit. - """ - super().__init__(method_prefix=method_prefix) - self._active_scoped_type_identities: frozenset[tuple[str, str]] = frozenset() - def require_supported(self, plan: ModulePlan) -> None: """Preflight primitive spellings required by an already-validated plan. @@ -182,59 +172,55 @@ def _require_backend_type_supported( PrimitiveScalarTypeRegistry.type_for(semantic_type_name) def _visit_ModulePlan(self, plan: ModulePlan) -> FortranModule: - """Build one complete bridge module from one validated module plan. - - The temporary scoped-origin identity cache is installed only while this - visit runs and is restored even when a lowering helper fails. - """ + """Build one complete bridge module from one validated module plan.""" # Scoped origins are module-wide facts needed by derived-call lowering. - previous_scoped = self._active_scoped_type_identities - self._active_scoped_type_identities = self._scoped_origin_type_identities(plan) - try: - # Assemble imports, declarations, and procedures from plan projections. - return FortranModule( - name=f"bind_c_{plan.bridge.owner_path}_wrapper", - uses=( - FortranUse("iso_c_binding", self._iso_c_symbols(plan)), - *self._native_module_uses(plan), + scoped_origin_type_identities = self._scoped_origin_type_identities(plan) + # Assemble imports, declarations, and procedures from plan projections. + return FortranModule( + name=f"bind_c_{plan.bridge.owner_path}_wrapper", + uses=( + FortranUse("iso_c_binding", self._iso_c_symbols(plan)), + *self._native_module_uses(plan), + ), + type_definitions=self._derived_holder_definitions(plan), + interfaces=( + *self._derived_call_interfaces(plan), + *self._prototype_interfaces(plan), + *self._external_interfaces(plan), + *self._module_descriptor_callback_interfaces(plan), + *self._derived_array_callback_interfaces(plan), + *self._allocator_interfaces(plan), + ), + declarations=self._prototype_entity_declarations(plan), + procedures=( + *( + procedure + for namespace in plan.namespaces + for procedure in self.visit(namespace, scoped_origin_type_identities) ), - type_definitions=self._derived_holder_definitions(plan), - interfaces=( - *self._derived_call_interfaces(plan), - *self._prototype_interfaces(plan), - *self._external_interfaces(plan), - *self._module_descriptor_callback_interfaces(plan), - *self._derived_array_callback_interfaces(plan), - *self._allocator_interfaces(plan), + # Typed derived-field access remains separate from class orchestration. + *self._derived_field_procedures(plan), + # Native-aware opaque-owner destruction is Phase 8 substrate, not class orchestration. + *self._class_constructor_procedures(plan), + *(self._derived_destroy_procedure(derived) for derived in self._owned_derived_types(plan)), + *( + self._allocatable_holder_destroy_procedure(derived) + for derived in self._allocatable_holder_types(plan) ), - declarations=self._prototype_entity_declarations(plan), - procedures=( - *(procedure for namespace in plan.namespaces for procedure in self.visit(namespace)), - # Typed derived-field access remains separate from class orchestration. - *self._derived_field_procedures(plan), - # Native-aware opaque-owner destruction is Phase 8 substrate, not class orchestration. - *self._class_constructor_procedures(plan), - *(self._derived_destroy_procedure(derived) for derived in self._owned_derived_types(plan)), - *( - self._allocatable_holder_destroy_procedure(derived) - for derived in self._allocatable_holder_types(plan) - ), - *( - self._allocatable_holder_presence_procedure(derived) - for derived in self._allocatable_holder_types(plan) - ), - *(self._pointer_holder_destroy_procedure(derived) for derived in self._pointer_holder_types(plan)), - *(self._pointer_holder_presence_procedure(derived) for derived in self._pointer_holder_types(plan)), - *( - procedure - for variable in self._derived_origin_variables(plan) - for procedure in self._derived_origin_procedures(variable) - ), + *( + self._allocatable_holder_presence_procedure(derived) + for derived in self._allocatable_holder_types(plan) ), - standalone_procedures=self._callback_standalone_adapter_procedures(plan), - ) - finally: - self._active_scoped_type_identities = previous_scoped + *(self._pointer_holder_destroy_procedure(derived) for derived in self._pointer_holder_types(plan)), + *(self._pointer_holder_presence_procedure(derived) for derived in self._pointer_holder_types(plan)), + *( + procedure + for variable in self._derived_origin_variables(plan) + for procedure in self._derived_origin_procedures(variable) + ), + ), + standalone_procedures=self._callback_standalone_adapter_procedures(plan), + ) def _callback_standalone_adapter_procedures(self, plan: ModulePlan) -> tuple[FortranFunction, ...]: """Return separately linked callback adapters in stable site order.""" @@ -272,14 +258,18 @@ def _derived_holder_definitions(self, plan: ModulePlan) -> tuple[FortranTypeDefi ) return (*allocatable, *pointers) - def _visit_NamespacePlan(self, plan: NamespacePlan) -> tuple[FortranFunction, ...]: + def _visit_NamespacePlan( + self, + plan: NamespacePlan, + scoped_origin_type_identities: frozenset[tuple[str, str]] = frozenset(), + ) -> tuple[FortranFunction, ...]: """Return bridge procedures directly owned by one Python namespace.""" return ( *( procedure for function in plan.functions for procedure in ( - self.visit(function), + self.visit(function, scoped_origin_type_identities), *self._owned_native_array_result_operations(function), *self._default_native_array_argument_operations(function), ) @@ -287,7 +277,11 @@ def _visit_NamespacePlan(self, plan: NamespacePlan) -> tuple[FortranFunction, .. *(procedure for variable in plan.variables for procedure in self.visit(variable)), ) - def _visit_FunctionPlan(self, plan: FunctionPlan) -> FortranFunction: + def _visit_FunctionPlan( + self, + plan: FunctionPlan, + scoped_origin_type_identities: frozenset[tuple[str, str]] = frozenset(), + ) -> FortranFunction: """Build one bridge procedure through ABI, call, and cleanup stages. All declarations and nodes come from completed function-plan actions; @@ -327,7 +321,11 @@ def _visit_FunctionPlan(self, plan: FunctionPlan) -> FortranFunction: ) # Stage 3: wrap native execution in derived-result and carrier lifecycles. call_body = self._derived_result_execution(plan, result_name, native_body) - derived_body, internal_procedures = self._derived_call_execution(plan, call_body) + derived_body, internal_procedures = self._derived_call_execution( + plan, + call_body, + scoped_origin_type_identities, + ) return FortranFunction( name=bridge_name, parameters=parameters, @@ -807,38 +805,44 @@ def _derived_call_execution( self, plan: FunctionPlan, call_body: tuple, + scoped_origin_type_identities: frozenset[tuple[str, str]], ) -> tuple[tuple, tuple[FortranFunction, ...]]: """Prepare all carriers, invoke once, then restore in reverse order.""" arguments = self._derived_arguments(plan) if not arguments: return call_body, () - body = list(self._derived_call_preparation_nodes(arguments)) - scoped = self._scoped_derived_arguments(arguments) + body = list(self._derived_call_preparation_nodes(arguments, scoped_origin_type_identities)) + scoped = self._scoped_derived_arguments(arguments, scoped_origin_type_identities) invocation, internal = self._derived_call_invocation(arguments, scoped, call_body) body.append(invocation) body.extend(self._derived_transaction_restoration(argument) for argument in reversed(arguments)) body.extend(node for argument in arguments for node in self._derived_argument_output_and_cleanup(argument)) return tuple(body), internal - def _derived_call_preparation_nodes(self, arguments: tuple[ArgumentTransferPlan, ...]) -> tuple: + def _derived_call_preparation_nodes( + self, + arguments: tuple[ArgumentTransferPlan, ...], + scoped_origin_type_identities: frozenset[tuple[str, str]], + ) -> tuple: """Build carrier initialization, preparation, and transaction-acquisition nodes for derived arguments. Acquisition remains in argument order.""" return ( *(node for argument in arguments for node in self._derived_argument_initializers(argument)), FortranAssignment("prik_derived_ready", CodeExpression(".true.")), - *(self._derived_argument_preparation(argument) for argument in arguments), + *(self._derived_argument_preparation(argument, scoped_origin_type_identities) for argument in arguments), *(self._derived_transaction_acquisition(arguments, index) for index in range(len(arguments))), ) def _scoped_derived_arguments( self, arguments: tuple[ArgumentTransferPlan, ...], + scoped_origin_type_identities: frozenset[tuple[str, str]], ) -> tuple[ArgumentTransferPlan, ...]: - """Select derived arguments that need scoped-origin invocation and whose producer exists in the active module.""" + """Select derived arguments whose scoped-origin producer exists in this module.""" return tuple( argument for argument in arguments if self._derived_argument_uses_access(argument, DerivedActualAccess.SCOPED_ADDRESS) - and self._has_scoped_origin_for_argument(argument) + and self._has_scoped_origin_for_argument(argument, scoped_origin_type_identities) ) @staticmethod @@ -888,7 +892,11 @@ def _derived_argument_initializers(self, argument: ArgumentTransferPlan) -> tupl ) return tuple(nodes) - def _derived_argument_preparation(self, argument: ArgumentTransferPlan) -> FortranSelectCase: + def _derived_argument_preparation( + self, + argument: ArgumentTransferPlan, + scoped_origin_type_identities: frozenset[tuple[str, str]], + ) -> FortranSelectCase: """Dispatch one carrier only by its completed ABI code.""" compatible = { case.abi_code for case in argument.derived_call.cases if case.action is not DerivedCallAction.INCOMPATIBLE @@ -905,7 +913,8 @@ def _derived_argument_preparation(self, argument: ArgumentTransferPlan) -> Fortr cases.extend( FortranCase(code, builders[code](argument)) for code in sorted(compatible) - if code in builders and (code != 2 or self._has_scoped_origin_for_argument(argument)) + if code in builders + and (code != 2 or self._has_scoped_origin_for_argument(argument, scoped_origin_type_identities)) ) cases.append( FortranCase( @@ -1819,9 +1828,13 @@ def _scoped_origin_type_identities(self, plan: ModulePlan) -> frozenset[tuple[st if self._derived_origin_supports(variable, "scoped") ) - def _has_scoped_origin_for_argument(self, argument: ArgumentTransferPlan) -> bool: + @staticmethod + def _has_scoped_origin_for_argument( + argument: ArgumentTransferPlan, + scoped_origin_type_identities: frozenset[tuple[str, str]], + ) -> bool: """Return whether this bridge module can produce a scoped origin for the argument type.""" - return argument.derived is not None and argument.derived.type_identity in self._active_scoped_type_identities + return argument.derived is not None and argument.derived.type_identity in scoped_origin_type_identities def _derived_origin_procedures(self, variable: ModuleVariablePlan) -> tuple[FortranFunction, ...]: """Emit only the typed leaves supported by one completed module storage.""" From 4a85663cb756a944f02ef335fa6a49f0ec9deb03 Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 14:04:47 +0100 Subject: [PATCH 03/22] add _PyiEmissionContext where we gather all the necessary informationa and pass them as an argument --- prik/codegen/printers/pyi_printer.py | 855 +++++++++++------- .../test_calls_and_policy_metadata.py | 13 +- .../test_pyi_printer_imports_and_packages.py | 19 +- 3 files changed, 567 insertions(+), 320 deletions(-) diff --git a/prik/codegen/printers/pyi_printer.py b/prik/codegen/printers/pyi_printer.py index 4352272ab..c3565bf7c 100644 --- a/prik/codegen/printers/pyi_printer.py +++ b/prik/codegen/printers/pyi_printer.py @@ -11,6 +11,7 @@ import ast from collections.abc import Iterable from copy import deepcopy +from dataclasses import dataclass, field, replace import json import keyword import re @@ -71,16 +72,78 @@ _FLAT_DIMENSION_PRINT_SENTINEL = "@prik.Flat" +@dataclass(frozen=True) +class _PyiEmissionContext: + """Own all state accumulated while rendering one semantic node tree.""" + + normalize_fortran_public_names: bool + default_array_order: str | None = None + semantic_class_names: frozenset[str] = frozenset() + contract_aliases: dict[str, str] = field(default_factory=dict) + contract_imports: set[str] = field(default_factory=set) + naming_policy: NamingPolicy = field(default_factory=NamingPolicy) + reserved_public_names: dict[tuple[tuple[str, ...], str, object], str] = field(default_factory=dict) + public_namespace: tuple[str, ...] = () + + def contract(self, name: str) -> str: + """Return one local contract spelling and record its required import.""" + if name not in CONTRACT_SYMBOLS: + return name + self.contract_imports.add(name) + return self.contract_aliases.get(name, name) + + def contract_type(self, name: str) -> str: + """Return the local spelling for one contract type name.""" + if name in CONTRACT_TYPE_NAMES: + return self.contract(name) + return name + + def inside_class(self, name: str) -> _PyiEmissionContext: + """Return a child namespace view sharing this emission's accumulators.""" + return replace(self, public_namespace=(*self.public_namespace, name)) + + def public_name(self, raw_name: str, *, category: str, owner: object) -> str: + """Reserve and return one normalized name inside the current namespace.""" + key = (self.public_namespace, category, self._public_owner_key(owner)) + reserved = self.reserved_public_names.get(key) + if reserved is not None: + return reserved + public_name = self.naming_policy.reserve_public_name( + self.public_namespace, + raw_name, + category=category, + owner=raw_name, + ) + self.reserved_public_names[key] = public_name + return public_name + + def contract_import(self) -> str: + """Return the direct import for contract symbols used by this emission.""" + if not self.contract_imports: + return "" + items = [] + for name in sorted(self.contract_imports): + alias = self.contract_aliases.get(name) + items.append(f"{name} as {alias}" if alias else name) + return f"from {_CONTRACT_MODULE} import {', '.join(items)}" + + @staticmethod + def _public_owner_key(owner: object) -> object: + """Return a stable cache key for one emitted public declaration.""" + if isinstance(owner, str | int | tuple): + return owner + return id(owner) + + class PyiPrinter(ClassVisitor): """Emit editable Python stub text from semantic IR models. The class follows the same reading order as ``FortranParser``: its public entrypoint comes first, semantic model visitors follow in model-flow order, and formatting helpers remain next to the visitor group that owns them. - Use emit for a module or individual semantic model. Module emission - temporarily tracks imports, aliases, and namespace names, then restores - previous state so one printer instance can safely emit more than one - independent module. + Use emit for a module or individual semantic model. Each call creates one + explicit emission context for imports, aliases, array defaults, and public + names, so reusable printer instances never carry active module state. """ # ------------------------------------------------------------------ @@ -88,54 +151,40 @@ class PyiPrinter(ClassVisitor): # ------------------------------------------------------------------ def __init__(self, *, normalize_fortran_public_names: bool = False): - """Configure a printer and initialize its per-emission state. + """Configure public-name normalization for independent emissions. Set normalize_fortran_public_names when emitting source-derived Fortran - contracts whose public names need Python normalization. Import, alias, - and namespace state is reset or restored around module emission; normal - individual-node emission does not mutate the input model. + contracts whose public names need Python normalization. """ self._normalize_fortran_public_names = normalize_fortran_public_names - self._naming_policy = NamingPolicy() - self._public_namespace: tuple[str, ...] = () - self._reserved_public_names: dict[tuple[tuple[str, ...], str, object], str] = {} - self._semantic_class_names: set[str] = set() - self._contract_imports: set[str] = set() - self._contract_aliases: dict[str, str] = {} - self._default_array_order: str | None = None def emit(self, node) -> str: """Render one supported semantic model to semantic .pyi text. Pass a SemanticModule for a complete contract or another supported semantic record for an isolated representation. Module emission - installs source-language array defaults and, when requested, isolated - public-name normalization state; both are restored before this method - returns or raises. + constructs a fresh context containing source-language array defaults, + contract aliases, import accumulation, and public-name reservations. """ + context = self._emission_context(node) + return self._visit(node, context) + + def _emission_context(self, node) -> _PyiEmissionContext: + """Build isolated state for one public emission call.""" if not isinstance(node, SemanticModule): - return self._visit(node) - # Stage 1: install source-specific defaults for this module emission. - previous_default_order = self._default_array_order - self._default_array_order = self._native_default_array_order(node.origin.source_language) - try: - if not self._normalize_fortran_public_names: - return self._visit(node) - # Stage 2: normalize public names in isolated naming state. - previous_policy = self._naming_policy - previous_namespace = self._public_namespace - previous_reserved = self._reserved_public_names - self._naming_policy = NamingPolicy() - self._public_namespace = () - self._reserved_public_names = {} - try: - return self._visit(node) - finally: - self._naming_policy = previous_policy - self._public_namespace = previous_namespace - self._reserved_public_names = previous_reserved - finally: - self._default_array_order = previous_default_order + return _PyiEmissionContext( + normalize_fortran_public_names=self._normalize_fortran_public_names, + ) + return _PyiEmissionContext( + normalize_fortran_public_names=self._normalize_fortran_public_names, + default_array_order=self._native_default_array_order(node.origin.source_language), + semantic_class_names=frozenset( + str(cls.name) + for cls in node.classes + if cls.origin.source_language == "fortran" and cls.origin.source_kind == "derived_type" + ), + contract_aliases=self._contract_aliases_for_module(node), + ) @staticmethod def _visit_not_supported(node): @@ -146,19 +195,27 @@ def _visit_not_supported(node): # Model visitors # ------------------------------------------------------------------ - def _visit_SemanticConstraint(self, constraint: SemanticConstraint) -> str: + def _visit_SemanticConstraint( + self, + constraint: SemanticConstraint, + context: _PyiEmissionContext, + ) -> str: """Emit constraint syntax.""" if constraint.name == "Constant": raise ValueError("Constant constraints are emitted through Final[...] data declarations") if constraint.name == "Shape": raise ValueError("Shape constraints are not canonical; put dimensions inside T[...]") - name = self._contract(constraint.name) + name = context.contract(constraint.name) if not constraint.arguments: return name args = ", ".join(map(repr, constraint.arguments)) return f"{name}({args})" - def _visit_SemanticType(self, semantic_type: SemanticType) -> str: + def _visit_SemanticType( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str: """Emit semantic type syntax.""" if semantic_type.name == "Unknown" or semantic_type.dtype == "Unknown": raise ValueError("Cannot emit .pyi with unresolved semantic type 'Unknown'") @@ -167,87 +224,120 @@ def _visit_SemanticType(self, semantic_type: SemanticType) -> str: text = semantic_type.name elif array_descriptor is not None: wrapper = "Allocatable" if array_descriptor == "allocatable" else "Pointer" - text = f"{self._contract(wrapper)}[{self._visit(native_array_data_type(semantic_type))}]" + text = f"{context.contract(wrapper)}[{self._visit(native_array_data_type(semantic_type), context)}]" elif self._is_scalar_allocatable_descriptor(semantic_type): - text = f"{self._contract('Allocatable')}[{self._scalar_descriptor_inner_text(semantic_type)}]" + text = f"{context.contract('Allocatable')}[{self._scalar_descriptor_inner_text(semantic_type, context)}]" elif self._is_scalar_pointer_descriptor(semantic_type): - text = f"{self._contract('Pointer')}[{self._scalar_descriptor_inner_text(semantic_type)}]" + text = f"{context.contract('Pointer')}[{self._scalar_descriptor_inner_text(semantic_type, context)}]" elif semantic_type.storage is not None: - text = self._emit_storage_type(semantic_type) + text = self._emit_storage_type(semantic_type, context) else: - text = self._semantic_base_type(semantic_type) + text = self._semantic_base_type(semantic_type, context) annotations = [ - *self._semantic_annotation_metadata(semantic_type), + *self._semantic_annotation_metadata(semantic_type, context), ] if array_descriptor is None: - annotations.extend(self._visit(constraint) for constraint in semantic_type.constraints) + annotations.extend(self._visit(constraint, context) for constraint in semantic_type.constraints) if annotations: - return self._annotated_type_text(text, annotations) + return self._annotated_type_text(text, annotations, context) return text - def _visit_SemanticArgument(self, arg: SemanticArgument) -> str: + def _visit_SemanticArgument( + self, + arg: SemanticArgument, + context: _PyiEmissionContext, + ) -> str: """Emit argument syntax.""" name = self._parameter_target(arg.name) return self._emit_typed_name( name, arg, + context, original_name=arg.name if name != arg.name else None, ) - def _visit_SemanticVariable(self, arg: SemanticVariable) -> str: + def _visit_SemanticVariable( + self, + arg: SemanticVariable, + context: _PyiEmissionContext, + ) -> str: """Emit data member syntax.""" - return self._emit_data_member(arg) + return self._emit_data_member(arg, context) - def _visit_SemanticFunction(self, func: SemanticFunction) -> str: + def _visit_SemanticFunction( + self, + func: SemanticFunction, + context: _PyiEmissionContext, + ) -> str: """Emit function syntax.""" - return self._emit_function(func) + return self._emit_function(func, context) - def _visit_SemanticPrototype(self, prototype: SemanticPrototype) -> str: + def _visit_SemanticPrototype( + self, + prototype: SemanticPrototype, + context: _PyiEmissionContext, + ) -> str: """Emit one reusable exact native procedure signature.""" return_type = prototype.return_type or SemanticType("None", dtype="None") arguments = [] for argument in prototype.arguments: - text = f"{self._parameter_target(argument.name)}: {self._emit_prototype_argument(argument)}" + text = f"{self._parameter_target(argument.name)}: {self._emit_prototype_argument(argument, context)}" if argument.optional: text += " = ..." arguments.append(text) decorators = [] if prototype.pure: - decorators.append(f"@{self._contract('pure')}") - decorators.append(f"@{self._contract('prototype')}") + decorators.append(f"@{context.contract('pure')}") + decorators.append(f"@{context.contract('prototype')}") return self._emit_callable( name=prototype.name, arguments=arguments, - return_type=self._visit(return_type), + return_type=self._visit(return_type, context), decorator="\n".join(decorators) + "\n", def_indent="", parameter_indent=" ", ) - def _emit_function(self, func: SemanticFunction, *, name_owner: object | None = None) -> str: + def _emit_function( + self, + func: SemanticFunction, + context: _PyiEmissionContext, + *, + name_owner: object | None = None, + ) -> str: """Emit function syntax with an optional shared overload-set public name.""" - return_type = self._projected_return_annotation(func) - name = self._callable_name(func, owner=name_owner) - decorator = self._decorators(func, emitted_name=name) + return_type = self._projected_return_annotation(func, context) + name = self._callable_name(func, context, owner=name_owner) + decorator = self._decorators(func, context, emitted_name=name) return self._emit_callable( name=name, - arguments=[self._emit_call_argument(func, arg) for arg in self._call_arguments(func)], + arguments=[self._emit_call_argument(func, arg, context) for arg in self._call_arguments(func)], return_type=return_type, decorator=decorator, def_indent="", parameter_indent=" ", ) - def _visit_SemanticMethod(self, method: SemanticMethod) -> str: + def _visit_SemanticMethod( + self, + method: SemanticMethod, + context: _PyiEmissionContext, + ) -> str: """Emit method syntax.""" - return self._emit_method(method) + return self._emit_method(method, context) - def _emit_method(self, method: SemanticMethod, *, name_owner: object | None = None) -> str: + def _emit_method( + self, + method: SemanticMethod, + context: _PyiEmissionContext, + *, + name_owner: object | None = None, + ) -> str: """Emit method syntax with an optional shared overload-set public name.""" - return_type = self._projected_return_annotation(method) - name = self._callable_name(method, owner=name_owner) - decorator = self._decorators(method, indent=" ", emitted_name=name) - arguments = [self._emit_call_argument(method, arg) for arg in self._method_call_arguments(method)] + return_type = self._projected_return_annotation(method, context) + name = self._callable_name(method, context, owner=name_owner) + decorator = self._decorators(method, context, indent=" ", emitted_name=name) + arguments = [self._emit_call_argument(method, arg, context) for arg in self._method_call_arguments(method)] if not method.is_static: arguments.insert(0, "self") return self._emit_callable( @@ -259,7 +349,13 @@ def _emit_method(self, method: SemanticMethod, *, name_owner: object | None = No parameter_indent=" ", ).rstrip() - def _visit_ProcedureOverloadSet(self, overload_set: ProcedureOverloadSet, *, in_class: bool = False) -> str: + def _visit_ProcedureOverloadSet( + self, + overload_set: ProcedureOverloadSet, + context: _PyiEmissionContext, + *, + in_class: bool = False, + ) -> str: """Emit overload set syntax.""" definitions = [] for procedure in overload_set.procedures: @@ -269,6 +365,7 @@ def _visit_ProcedureOverloadSet(self, overload_set: ProcedureOverloadSet, *, in_ candidate = self._overload_method(overload_set, candidate) definition = self._emit_method( candidate, + context, name_owner=("overload", overload_set.name, candidate.name), ) indent = " " @@ -276,28 +373,32 @@ def _visit_ProcedureOverloadSet(self, overload_set: ProcedureOverloadSet, *, in_ candidate.name = overload_set.name definition = self._emit_function( candidate, + context, name_owner=("overload", overload_set.name), ) indent = "" generic = self._overload_generic_argument(candidate, overload_set.name) if in_class else "" bind_target = candidate.metadata.get(BIND_TARGET_METADATA) - bind = f"{indent}@{self._contract('bind')}({json.dumps(str(bind_target))})\n" if bind_target else "" - definitions.append(f'{bind}{indent}@{self._contract("overload")}("{target}"{generic})\n{definition}') + bind = f"{indent}@{context.contract('bind')}({json.dumps(str(bind_target))})\n" if bind_target else "" + definitions.append(f'{bind}{indent}@{context.contract("overload")}("{target}"{generic})\n{definition}') return "\n\n".join(definitions) - def _visit_SemanticClass(self, cls: SemanticClass) -> str: + def _visit_SemanticClass( + self, + cls: SemanticClass, + context: _PyiEmissionContext, + ) -> str: """Emit class syntax.""" - bases = f"({', '.join(self._class_base_text(base) for base in cls.base_classes)})" if cls.base_classes else "" - previous_namespace = self._public_namespace - self._public_namespace = (*self._public_namespace, cls.name) - try: - body = self._class_body(cls) - finally: - self._public_namespace = previous_namespace + bases = ( + f"({', '.join(self._class_base_text(base, context) for base in cls.base_classes)})" + if cls.base_classes + else "" + ) + body = self._class_body(cls, context.inside_class(cls.name)) decorators = [] if self._is_private(cls): - decorators.append(f"@{self._contract('private')}") - native_type = self._native_type_decorator(cls) + decorators.append(f"@{context.contract('private')}") + native_type = self._native_type_decorator(cls, context) if native_type: decorators.append(native_type) decorator_text = "\n".join(decorators) @@ -308,11 +409,13 @@ def _visit_SemanticClass(self, cls: SemanticClass) -> str: {body} """.strip() - def _class_base_text(self, base: str) -> str: + @staticmethod + def _class_base_text(base: str, context: _PyiEmissionContext) -> str: """Return an imported contract base name or a user base name.""" - return self._contract_type(base) + return context.contract_type(base) - def _native_type_decorator(self, cls: SemanticClass) -> str: + @staticmethod + def _native_type_decorator(cls: SemanticClass, context: _PyiEmissionContext) -> str: """Emit native derived-type metadata when the class needs it.""" if cls.origin.source_language != "fortran" or cls.origin.source_kind != "derived_type": return "" @@ -323,85 +426,101 @@ def _native_type_decorator(self, cls: SemanticClass) -> str: parts.append(f"attributes={attributes!r}") if finalizers: parts.append(f"finalizers={finalizers!r}") - return f"@{self._contract('native_type')}({', '.join(parts)})" if parts else "" + return f"@{context.contract('native_type')}({', '.join(parts)})" if parts else "" - def _visit_SemanticModule(self, module: SemanticModule) -> str: + def _visit_SemanticModule( + self, + module: SemanticModule, + context: _PyiEmissionContext, + ) -> str: """Render one module through alias setup, ordered bodies, and imports. - Class names and contract imports are temporary state because nested - visitors resolve references through them. The previous state is restored - even when a body visitor rejects invalid semantic input. + The explicit context is shared by nested visitors so they contribute + import and name reservations to this module without mutating the + reusable printer. """ - # Stage 1: establish names and aliases visible to nested visitors. - previous_class_names = self._semantic_class_names - previous_contract_imports = self._contract_imports - previous_contract_aliases = self._contract_aliases - self._semantic_class_names = { - str(cls.name) - for cls in module.classes - if cls.origin.source_language == "fortran" and cls.origin.source_kind == "derived_type" - } - self._contract_imports = set() - self._contract_aliases = self._contract_aliases_for_module(module) body_sections: list[str] = [] - try: - # Stage 2: render public bodies in stable contract order. - self._append_items(body_sections, self._contract_items(module.classes), self.emit) - self._append_items(body_sections, module.prototypes, self._visit) - self._append_items(body_sections, self._contract_items(module.variables), self._emit_module_variable) - overload_targets = self._module_overload_target_names(module) - self._append_items( - body_sections, - self._contract_items(module.functions, keep_names=overload_targets), - self._visit, - ) - self._append_items(body_sections, module.overload_sets, self._visit) - # Stage 3: synthesize imports after visitors have recorded requirements. - sections: list[str] = [] - self._append_imports(sections, module) - sections.extend(body_sections) - return "\n".join(sections).rstrip() - finally: - self._semantic_class_names = previous_class_names - self._contract_imports = previous_contract_imports - self._contract_aliases = previous_contract_aliases + self._append_items( + body_sections, + self._contract_items(module.classes), + lambda item: self._visit(item, context), + ) + self._append_items( + body_sections, + module.prototypes, + lambda item: self._visit(item, context), + ) + self._append_items( + body_sections, + self._contract_items(module.variables), + lambda item: self._emit_module_variable(item, context), + ) + overload_targets = self._module_overload_target_names(module) + self._append_items( + body_sections, + self._contract_items(module.functions, keep_names=overload_targets), + lambda item: self._visit(item, context), + ) + self._append_items( + body_sections, + module.overload_sets, + lambda item: self._visit(item, context), + ) + sections: list[str] = [] + self._append_imports(sections, module, context) + sections.extend(body_sections) + return "\n".join(sections).rstrip() # ------------------------------------------------------------------ # Shared helpers # ------------------------------------------------------------------ - def _emit_storage_type(self, semantic_type: SemanticType) -> str: + def _emit_storage_type( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str: """Emit storage type syntax.""" storage = semantic_type.storage - base_type = self._semantic_base_type(semantic_type) + base_type = self._semantic_base_type(semantic_type, context) if storage is None: return base_type if storage.kind == "value": return base_type if storage.kind in {"reference", "pointer", "address"}: - if self._is_normal_storage_address(semantic_type): + if self._is_normal_storage_address(semantic_type, context): return base_type - target = self._address_target_type(semantic_type) + target = self._address_target_type(semantic_type, context) if storage.pointer_depth > 1: - return f"{self._contract('Addr')}[{storage.pointer_depth}]({target})" - return f"{self._contract('Addr')}({target})" + return f"{context.contract('Addr')}[{storage.pointer_depth}]({target})" + return f"{context.contract('Addr')}({target})" if storage.kind == "array": - return self._emit_array_type(semantic_type) + return self._emit_array_type(semantic_type, context) return base_type - def _semantic_base_type(self, semantic_type: SemanticType, *, include_deferred_length: bool = False) -> str: + @staticmethod + def _semantic_base_type( + semantic_type: SemanticType, + context: _PyiEmissionContext, + *, + include_deferred_length: bool = False, + ) -> str: """Return the semantic dtype including fixed character length.""" if semantic_type.name != "String": - return self._contract_type(semantic_type.name) + return context.contract_type(semantic_type.name) length = semantic_type.metadata.get("fortran_character_length") - string = self._contract("String") + string = context.contract("String") if length is None or str(length) in {"", "*"}: return string if str(length) == ":": return f"{string}[:]" if include_deferred_length else string return f"{string}[{length}]" - def _is_normal_storage_address(self, semantic_type: SemanticType) -> bool: + @staticmethod + def _is_normal_storage_address( + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> bool: """Return whether address storage is hidden behind the normal Python object.""" storage = semantic_type.storage return bool( @@ -412,14 +531,22 @@ def _is_normal_storage_address(self, semantic_type: SemanticType) -> bool: and storage.metadata.get(ADDRESS_ROLE_METADATA) != ADDRESS_ROLE_RAW and ( semantic_type.name == "String" - or str(semantic_type.name) in self._semantic_class_names + or str(semantic_type.name) in context.semantic_class_names or semantic_type.metadata.get(_WRAPPED_CALLABLE_TYPE_METADATA) ) ) - def _address_target_type(self, semantic_type: SemanticType) -> str: + def _address_target_type( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str: """Return the pointee type spelling for a raw address contract.""" - base_type = self._semantic_base_type(semantic_type, include_deferred_length=semantic_type.rank > 0) + base_type = self._semantic_base_type( + semantic_type, + context, + include_deferred_length=semantic_type.rank > 0, + ) if semantic_type.rank <= 0: return base_type dimensions = semantic_type.shape @@ -429,24 +556,31 @@ def _address_target_type(self, semantic_type: SemanticType) -> str: dimensions = tuple(array.source_shape or array.shape) if array is not None else () return f"{base_type}[{', '.join(str(dimension) for dimension in dimensions)}]" - def _emit_array_type(self, semantic_type: SemanticType) -> str: + def _emit_array_type( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str: """Emit array type syntax.""" storage = semantic_type.storage array = storage.array if storage is not None else None if array is not None and array.category == SCALAR_STORAGE_CATEGORY: - return f"{self._semantic_base_type(semantic_type, include_deferred_length=True)}[()]" - dimensions = self._array_dimensions(semantic_type, array) - base = f"{self._semantic_base_type(semantic_type, include_deferred_length=True)}[{', '.join(dimensions)}]" + return f"{self._semantic_base_type(semantic_type, context, include_deferred_length=True)}[()]" + dimensions = self._array_dimensions(semantic_type, array, context) + base = ( + f"{self._semantic_base_type(semantic_type, context, include_deferred_length=True)}[{', '.join(dimensions)}]" + ) - metadata = self._array_annotation_metadata(array) + metadata = self._array_annotation_metadata(array, context) if metadata: - return self._annotated_type_text(base, metadata) + return self._annotated_type_text(base, metadata, context) return base def _array_dimensions( self, semantic_type: SemanticType, array: SemanticArrayContract | None, + context: _PyiEmissionContext, ) -> list[str]: """Handle array dimensions for the current generation context.""" if array is not None and array.category == "assumed_size" and array.source_shape: @@ -461,7 +595,7 @@ def _array_dimensions( if not shape and semantic_type.rank > 0: shape = [":" for _ in range(semantic_type.rank)] dimensions = [PyiPrinter._printed_array_dimension(dim) for dim in shape] - return [self._contract("Flat") if dim == _FLAT_DIMENSION_PRINT_SENTINEL else dim for dim in dimensions] + return [context.contract("Flat") if dim == _FLAT_DIMENSION_PRINT_SENTINEL else dim for dim in dimensions] @staticmethod def _assumed_size_array_dimension(dimension: object) -> str: @@ -494,7 +628,11 @@ def _printed_array_dimension(dimension: object) -> str: return text[: -len("Strided")] return text - def _array_annotation_metadata(self, array: SemanticArrayContract | None) -> list[str]: + @staticmethod + def _array_annotation_metadata( + array: SemanticArrayContract | None, + context: _PyiEmissionContext, + ) -> list[str]: """Handle array annotation metadata for the current generation context.""" if array is None: return [] @@ -503,18 +641,18 @@ def _array_annotation_metadata(self, array: SemanticArrayContract | None) -> lis metadata: list[str] = [] if ( array.order in {"ORDER_F", "ORDER_ANY"} - and array.order != self._default_array_order + and array.order != context.default_array_order and not (array.category == "assumed_size" and array.order == "ORDER_F") ): - metadata.append(self._contract(array.order)) - if array.order == "ORDER_C" and array.order != self._default_array_order: - metadata.append(self._contract("ORDER_C")) + metadata.append(context.contract(array.order)) + if array.order == "ORDER_C" and array.order != context.default_array_order: + metadata.append(context.contract("ORDER_C")) if array.copy_order == "ORDER_F": - metadata.append(self._contract("COPY_F")) + metadata.append(context.contract("COPY_F")) if array.allocatable: - metadata.append(self._contract("Allocatable")) + metadata.append(context.contract("Allocatable")) if array.pointer: - metadata.append(self._contract("Pointer")) + metadata.append(context.contract("Pointer")) return metadata @staticmethod @@ -526,36 +664,44 @@ def _native_default_array_order(native_language: str | None) -> str | None: return "ORDER_C" return "ORDER_F" - def _semantic_annotation_metadata(self, semantic_type: SemanticType) -> list[str]: + def _semantic_annotation_metadata( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> list[str]: """Handle semantic annotation metadata for the current generation context.""" metadata: list[str] = [] source_type = (semantic_type.origin.source_type or "").casefold().replace(" ", "") if source_type in {"type(*)", "class(*)"} or semantic_type.metadata.get("fortran_assumed_type"): - metadata.append(self._contract("AssumedType")) + metadata.append(context.contract("AssumedType")) if semantic_type.metadata.get("fortran_polymorphic"): - metadata.append(self._contract("Polymorphic")) + metadata.append(context.contract("Polymorphic")) if ( semantic_type.metadata.get("fortran_allocatable") and not self._is_scalar_allocatable_descriptor(semantic_type) and native_array_descriptor_kind(semantic_type) is None ): - metadata.append(self._contract("FortranAllocatable")) + metadata.append(context.contract("FortranAllocatable")) if semantic_type.metadata.get("aliased"): - metadata.append(self._contract("Aliased")) + metadata.append(context.contract("Aliased")) if semantic_type.metadata.get(PYTHON_VALUE_MUTABILITY_METADATA) == PYTHON_VALUE_IMMUTABLE: - metadata.append(self._contract("Immutable")) + metadata.append(context.contract("Immutable")) if semantic_type.metadata.get(MAYBE_UNALLOCATED_METADATA): - metadata.append(self._contract("MaybeUnallocated")) + metadata.append(context.contract("MaybeUnallocated")) pointer_association = semantic_type.metadata.get("fortran_pointer_association") if pointer_association is not None and not self._is_scalar_pointer_descriptor(semantic_type): - metadata.append(f"{self._contract('PointerAssociation')}({json.dumps(str(pointer_association))})") - pointer_policy = self._pointer_policy_annotation(semantic_type) + metadata.append(f"{context.contract('PointerAssociation')}({json.dumps(str(pointer_association))})") + pointer_policy = self._pointer_policy_annotation(semantic_type, context) if pointer_policy is not None: metadata.append(pointer_policy) - metadata.extend(self._ownership_policy_annotations(semantic_type)) + metadata.extend(self._ownership_policy_annotations(semantic_type, context)) return metadata - def _pointer_policy_annotation(self, semantic_type: SemanticType) -> str | None: + @staticmethod + def _pointer_policy_annotation( + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str | None: """Render one structured pointer policy annotation when present.""" pointer_policy = semantic_type.metadata.get(POINTER_POLICY_METADATA) if not isinstance(pointer_policy, dict): @@ -566,9 +712,13 @@ def _pointer_policy_annotation(self, semantic_type: SemanticType) -> str | None: if value is not None: rendered = repr(value) if isinstance(value, bool) else json.dumps(str(value)) arguments.append(f"{name}={rendered}") - return f"{self._contract('PointerPolicy')}({', '.join(arguments)})" + return f"{context.contract('PointerPolicy')}({', '.join(arguments)})" - def _ownership_policy_annotations(self, semantic_type: SemanticType) -> tuple[str, ...]: + @staticmethod + def _ownership_policy_annotations( + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> tuple[str, ...]: """Render explicit owner, transfer, and destruction policy metadata.""" ownership_policy = semantic_type.metadata.get(OWNERSHIP_POLICY_METADATA) if not isinstance(ownership_policy, dict): @@ -579,7 +729,7 @@ def _ownership_policy_annotations(self, semantic_type: SemanticType) -> tuple[st ("destruction", "Destruction"), ) return tuple( - f"{self._contract(contract)}({json.dumps(str(ownership_policy[key]))})" + f"{context.contract(contract)}({json.dumps(str(ownership_policy[key]))})" for key, contract in fields if ownership_policy.get(key) is not None ) @@ -603,9 +753,13 @@ def _is_scalar_pointer_descriptor(semantic_type: SemanticType) -> bool: and not (storage is not None and storage.array is not None) ) - def _scalar_descriptor_inner_text(self, semantic_type: SemanticType) -> str: + def _scalar_descriptor_inner_text( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str: """Emit the scalar descriptor element type without descriptor metadata.""" - return self._visit(self._visible_scalar_descriptor_type(semantic_type)) + return self._visit(self._visible_scalar_descriptor_type(semantic_type), context) @staticmethod def _scalar_descriptor_kind(semantic_type: SemanticType | None) -> str | None: @@ -629,32 +783,44 @@ def _visible_scalar_descriptor_type(semantic_type: SemanticType) -> SemanticType visible.ownership.mutable = False return visible - def _emit_prototype_argument(self, argument: SemanticArgument) -> str: + def _emit_prototype_argument( + self, + argument: SemanticArgument, + context: _PyiEmissionContext, + ) -> str: """Emit one exact prototype dummy with direction around transport.""" if self._is_prototype_descriptor_type(argument.semantic_type): - transport = self._prototype_descriptor_type_text(argument.semantic_type) + transport = self._prototype_descriptor_type_text(argument.semantic_type, context) else: - transport = self._prototype_argument_transport(argument) + transport = self._prototype_argument_transport(argument, context) intent = getattr(argument.origin, "metadata", {}).get(PROTOTYPE_INTENT_METADATA) if intent is None: return transport wrapper = {"in": "In", "out": "Out", "inout": "InOut"}.get(str(intent).casefold()) if wrapper is None: raise ValueError(f"Unsupported prototype intent {intent!r} on {argument.name!r}") - return f"{self._contract(wrapper)}({transport})" + return f"{context.contract(wrapper)}({transport})" - def _prototype_argument_transport(self, argument: SemanticArgument) -> str: + def _prototype_argument_transport( + self, + argument: SemanticArgument, + context: _PyiEmissionContext, + ) -> str: """Emit one prototype dummy's exact value or reference transport.""" - inner = self._prototype_argument_inner_type(argument.semantic_type) + inner = self._prototype_argument_inner_type(argument.semantic_type, context) if bool(getattr(argument.origin, "metadata", {}).get("value")): if self._is_prototype_primitive_value(argument.semantic_type): return inner - return f"{self._contract('Value')}({inner})" + return f"{context.contract('Value')}({inner})" if self._is_prototype_primitive_reference(argument.semantic_type): - return f"{self._contract('Addr')}({inner})" + return f"{context.contract('Addr')}({inner})" return inner - def _prototype_argument_inner_type(self, semantic_type: SemanticType) -> str: + def _prototype_argument_inner_type( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str: """Return the native prototype dummy type without transport wrappers.""" storage = semantic_type.storage if ( @@ -663,19 +829,23 @@ def _prototype_argument_inner_type(self, semantic_type: SemanticType) -> str: and storage.array is not None and storage.array.category == SCALAR_STORAGE_CATEGORY ): - return self._semantic_base_type(semantic_type, include_deferred_length=True) + return self._semantic_base_type(semantic_type, context, include_deferred_length=True) if storage is not None and storage.kind in {"reference", "address", "pointer"}: - return self._address_target_type(semantic_type) - return self._visit(semantic_type) + return self._address_target_type(semantic_type, context) + return self._visit(semantic_type, context) - def _prototype_descriptor_type_text(self, semantic_type: SemanticType) -> str: + def _prototype_descriptor_type_text( + self, + semantic_type: SemanticType, + context: _PyiEmissionContext, + ) -> str: """Render descriptor metadata without a second reference wrapper.""" if semantic_type.metadata.get("fortran_allocatable") or semantic_type.metadata.get("fortran_pointer"): - return self._visit(semantic_type) + return self._visit(semantic_type, context) visible = deepcopy(semantic_type) if visible.storage is not None and visible.storage.kind in {"reference", "address", "pointer"}: visible.storage = None - return self._visit(visible) + return self._visit(visible, context) @staticmethod def _is_prototype_primitive_value(semantic_type: SemanticType) -> bool: @@ -724,21 +894,31 @@ def _is_prototype_descriptor_type(semantic_type: SemanticType) -> bool: ) ) - def _emit_data_member(self, variable: SemanticVariable) -> str: + def _emit_data_member( + self, + variable: SemanticVariable, + context: _PyiEmissionContext, + ) -> str: """Emit a variable in class-field context rather than argument context.""" - name = self._data_member_name(variable) + name = self._data_member_name(variable, context) return self._emit_typed_name( self._annotation_target(name), variable, + context, original_name=variable.name if name != variable.name else None, ) - def _emit_module_variable(self, arg: SemanticVariable) -> str: + def _emit_module_variable( + self, + arg: SemanticVariable, + context: _PyiEmissionContext, + ) -> str: """Emit module variable syntax.""" - name = self._module_variable_name(arg) + name = self._module_variable_name(arg, context) return self._emit_typed_name( self._annotation_target(name), arg, + context, original_name=arg.name if name != arg.name else None, ) @@ -752,6 +932,7 @@ def _emit_typed_name( self, name: str, arg: SemanticVariable, + context: _PyiEmissionContext, *, original_name: str | None = None, nullable: bool = False, @@ -759,16 +940,16 @@ def _emit_typed_name( ) -> str: """Emit typed name syntax.""" semantic_type = self._without_constant_constraint(arg.semantic_type) - type_text = self._visit(semantic_type) + type_text = self._visit(semantic_type, context) annotation_metadata = [] if original_name is not None: - annotation_metadata.append(f"{self._contract('SourceName')}({json.dumps(original_name)})") + annotation_metadata.append(f"{context.contract('SourceName')}({json.dumps(original_name)})") if annotation_metadata: - type_text = self._annotated_type_text(type_text, annotation_metadata) + type_text = self._annotated_type_text(type_text, annotation_metadata, context) if self._is_constant(arg.semantic_type): - type_text = f"{self._contract('Final')}[{type_text}]" + type_text = f"{context.contract('Final')}[{type_text}]" if getattr(arg, "visibility", "public") == "private": - type_text = f"{self._contract('private')}[{type_text}]" + type_text = f"{context.contract('private')}[{type_text}]" optional_absent_handle = arg.semantic_type.metadata.get(OPTIONAL_ABSENT_HANDLE_METADATA) or ( arg.optional and native_array_descriptor_kind(arg.semantic_type) is not None ) @@ -786,7 +967,12 @@ def _emit_typed_name( text += f" = {default_value}" return text - def _emit_call_argument(self, func: SemanticFunction, arg: SemanticArgument) -> str: + def _emit_call_argument( + self, + func: SemanticFunction, + arg: SemanticArgument, + context: _PyiEmissionContext, + ) -> str: """Emit a callable argument with compact output metadata when possible.""" name = self._parameter_target(arg.name) emitted_arg = self._projected_address_argument(func, arg) @@ -801,6 +987,7 @@ def _emit_call_argument(self, func: SemanticFunction, arg: SemanticArgument) -> return self._emit_typed_name( name, emitted_arg, + context, original_name=arg.name if name != arg.name else None, nullable=descriptor_kind is not None, allow_optional_absent_handle=True, @@ -859,10 +1046,15 @@ def _mapping_projects_argument(mapping: ProjectionMapping, arg: SemanticArgument and (mapping.python_name or mapping.native_name) == arg.name ) - def _annotated_type_text(self, type_text: str, metadata: list[str]) -> str: + @staticmethod + def _annotated_type_text( + type_text: str, + metadata: list[str], + context: _PyiEmissionContext, + ) -> str: """Handle annotated type text for the current generation context.""" suffix = ", ".join(metadata) - annotated = self._contract("Annotated") + annotated = context.contract("Annotated") if type_text.startswith(f"{annotated}[") and type_text.endswith("]"): return f"{type_text[:-1]}, {suffix}]" return f"{annotated}[{type_text}, {suffix}]" @@ -1005,32 +1197,40 @@ def _emit_callable( args = (",\n" + parameter_indent).join(arguments) return f"{decorator}{def_indent}def {name}(\n{parameter_indent}{args}\n{def_indent}) -> {return_type}: ..." - def _class_body(self, cls: SemanticClass) -> str: + def _class_body( + self, + cls: SemanticClass, + context: _PyiEmissionContext, + ) -> str: """Handle class body for the current generation context.""" body_parts = [] nested_classes = "\n\n".join( - self._indent_block(self._visit(nested), " ") for nested in self._contract_items(cls.classes) + self._indent_block(self._visit(nested, context), " ") for nested in self._contract_items(cls.classes) ) if nested_classes: body_parts.append(nested_classes) - constructor = self._class_constructor(cls) + constructor = self._class_constructor(cls, context) if constructor: body_parts.append(constructor) - fields = "\n".join(f" {self._emit_data_member(field)}" for field in self._contract_items(cls.fields)) + fields = "\n".join( + f" {self._emit_data_member(field, context)}" for field in self._contract_items(cls.fields) + ) if fields: body_parts.append(fields) overload_targets = self._overload_target_names(cls.overload_sets) methods = "\n\n".join( - self._visit(method) for method in self._contract_items(cls.methods, keep_names=overload_targets) + self._visit(method, context) for method in self._contract_items(cls.methods, keep_names=overload_targets) ) if methods: body_parts.append(methods) - overload_sets = "\n\n".join(self._visit(overload_set, in_class=True) for overload_set in cls.overload_sets) + overload_sets = "\n\n".join( + self._visit(overload_set, context, in_class=True) for overload_set in cls.overload_sets + ) if overload_sets: body_parts.append(overload_sets) @@ -1038,12 +1238,16 @@ def _class_body(self, cls: SemanticClass) -> str: return " pass" return "\n\n".join(body_parts) - def _class_constructor(self, cls: SemanticClass) -> str: + def _class_constructor( + self, + cls: SemanticClass, + context: _PyiEmissionContext, + ) -> str: """Handle class constructor for the current generation context.""" if cls.origin.source_language != "fortran": return "" arguments = [ - self._constructor_argument(field) for field in cls.fields if self._constructor_accepts_field(field) + self._constructor_argument(field, context) for field in cls.fields if self._constructor_accepts_field(field) ] if not arguments and cls.fields: return " def __init__(self) -> None: ..." @@ -1058,11 +1262,15 @@ def _class_constructor(self, cls: SemanticClass) -> str: parameter_indent=" ", ).rstrip() - def _constructor_argument(self, field: SemanticVariable) -> str: + def _constructor_argument( + self, + field: SemanticVariable, + context: _PyiEmissionContext, + ) -> str: """Handle constructor argument for the current generation context.""" - name = self._data_member_name(field) + name = self._data_member_name(field, context) semantic_type = self._without_constant_constraint(field.semantic_type) - type_text = self._visit(semantic_type) + type_text = self._visit(semantic_type, context) default_value = ( self._fortran_literal_text(field.metadata.get("fortran_initializer")) or self._python_literal_text(field.default_value) @@ -1071,7 +1279,8 @@ def _constructor_argument(self, field: SemanticVariable) -> str: if name != field.name: type_text = self._annotated_type_text( type_text, - [f"{self._contract('SourceName')}({json.dumps(field.name)})"], + [f"{context.contract('SourceName')}({json.dumps(field.name)})"], + context, ) return f"{name}: {type_text} = {default_value}" @@ -1091,19 +1300,6 @@ def _indent_block(text: str, indent: str) -> str: """Handle indent block for the current generation context.""" return "\n".join(f"{indent}{line}" if line else line for line in text.splitlines()) - def _contract(self, name: str) -> str: - """Return the local imported spelling for one prik contract symbol.""" - if name not in CONTRACT_SYMBOLS: - return name - self._contract_imports.add(name) - return self._contract_aliases.get(name, name) - - def _contract_type(self, name: str) -> str: - """Return the local spelling for a contract type name.""" - if name in CONTRACT_TYPE_NAMES: - return self._contract(name) - return name - @classmethod def _contract_aliases_for_module(cls, module: SemanticModule) -> dict[str, str]: """Choose collision-free local names for contract imports used by a module.""" @@ -1179,9 +1375,14 @@ def _import_local_names(imp: str | SemanticImport) -> set[str]: names.add(alias or module_name.split(".", 1)[0]) return names - def _append_imports(self, sections: list[str], module: SemanticModule) -> None: + def _append_imports( + self, + sections: list[str], + module: SemanticModule, + context: _PyiEmissionContext, + ) -> None: """Append imports.""" - contract_import = self._contract_import() + contract_import = context.contract_import() if contract_import: sections.append(contract_import) imports = self._effective_imports(module) @@ -1190,16 +1391,6 @@ def _append_imports(self, sections: list[str], module: SemanticModule) -> None: if contract_import or imports: sections.append("") - def _contract_import(self) -> str: - """Emit the direct import for contract symbols used by the generated stub.""" - if not self._contract_imports: - return "" - items = [] - for name in sorted(self._contract_imports): - alias = self._contract_aliases.get(name) - items.append(f"{name} as {alias}" if alias else name) - return f"from {_CONTRACT_MODULE} import {', '.join(items)}" - @classmethod def _effective_imports(cls, module: SemanticModule) -> list[str | SemanticImport]: """Handle effective imports for the current generation context.""" @@ -1536,17 +1727,21 @@ def _is_user_private(node) -> bool: metadata = getattr(origin, "metadata", {}) return isinstance(metadata, dict) and bool(metadata.get(USER_PRIVATE_METADATA)) - def _projected_return_annotation(self, func: SemanticFunction) -> str: + def _projected_return_annotation( + self, + func: SemanticFunction, + context: _PyiEmissionContext, + ) -> str: """Handle projected return annotation for the current generation context.""" parts = [] if func.return_type: if self._scalar_descriptor_kind(func.return_type) is not None: visible_result = self._visible_scalar_descriptor_type(func.return_type) - parts.append(f"{self._visit(visible_result)} | None") + parts.append(f"{self._visit(visible_result, context)} | None") else: - parts.append(self._visit(self._visible_wrapped_callable_type(func.return_type))) + parts.append(self._visit(self._visible_wrapped_callable_type(func.return_type), context)) parts.extend( - self._projected_argument_return(func, arg, visible=visible) + self._projected_argument_return(func, context, arg, visible=visible) for _, arg, visible in sorted( self._projected_return_arguments(func), key=lambda item: item[0], @@ -1592,6 +1787,7 @@ def _is_visible_projected_return(func: SemanticFunction, mapping: ProjectionMapp def _projected_argument_return( self, func_or_arg: SemanticFunction | SemanticArgument, + context: _PyiEmissionContext, arg: SemanticArgument | None = None, *, visible: bool, @@ -1606,10 +1802,16 @@ def _projected_argument_return( func = None projected_arg = func_or_arg if visible: - return self._named_return(projected_arg, func=func) - return self._plain_projected_return(projected_arg) + return self._named_return(projected_arg, context, func=func) + return self._plain_projected_return(projected_arg, context) - def _named_return(self, arg: SemanticArgument, *, func: SemanticFunction | None = None) -> str: + def _named_return( + self, + arg: SemanticArgument, + context: _PyiEmissionContext, + *, + func: SemanticFunction | None = None, + ) -> str: """Handle named return for the current generation context.""" semantic_type = self._visible_projected_type( arg.semantic_type, @@ -1618,7 +1820,7 @@ def _named_return(self, arg: SemanticArgument, *, func: SemanticFunction | None descriptor_kind = self._scalar_descriptor_kind(semantic_type) if descriptor_kind is not None: semantic_type = self._visible_scalar_descriptor_type(semantic_type) - return_text = f'{self._contract("Returns")}["{arg.name}", {self._visit(semantic_type)}]' + return_text = f'{context.contract("Returns")}["{arg.name}", {self._visit(semantic_type, context)}]' if descriptor_kind is not None or arg.optional: return f"{return_text} | None" return return_text @@ -1673,7 +1875,11 @@ def _visible_wrapped_callable_type(semantic_type: SemanticType) -> SemanticType: visible.metadata[_WRAPPED_CALLABLE_TYPE_METADATA] = True return visible - def _plain_projected_return(self, arg: SemanticArgument) -> str: + def _plain_projected_return( + self, + arg: SemanticArgument, + context: _PyiEmissionContext, + ) -> str: """Handle plain projected return for the current generation context.""" semantic_type = deepcopy(arg.semantic_type) descriptor_kind = self._scalar_descriptor_kind(semantic_type) @@ -1686,87 +1892,86 @@ def _plain_projected_return(self, arg: SemanticArgument) -> str: and (storage.kind == "reference" or storage.kind == "address") ): semantic_type.storage = None - type_text = self._visit(semantic_type) + type_text = self._visit(semantic_type, context) if descriptor_kind is not None or arg.optional: return f"{type_text} | None" return type_text - def _callable_name(self, func: SemanticFunction, *, owner: object | None = None) -> str: + @staticmethod + def _callable_name( + func: SemanticFunction, + context: _PyiEmissionContext, + *, + owner: object | None = None, + ) -> str: """Return the Python-visible callable name to write in the contract.""" if ( - not self._normalize_fortran_public_names + not context.normalize_fortran_public_names or func.name.startswith("__") or func.origin.source_language != "fortran" ): return func.name - return self._public_name( + return context.public_name( func.name, category="method" if isinstance(func, SemanticMethod) else "function", owner=owner if owner is not None else func, ) - def _data_member_name(self, variable: SemanticVariable) -> str: + @staticmethod + def _data_member_name( + variable: SemanticVariable, + context: _PyiEmissionContext, + ) -> str: """Return the Python-visible class data-member name.""" - if not self._normalize_fortran_public_names: + if not context.normalize_fortran_public_names: return variable.name - return self._public_name(variable.name, category="field", owner=variable) + return context.public_name(variable.name, category="field", owner=variable) - def _module_variable_name(self, variable: SemanticVariable) -> str: + @staticmethod + def _module_variable_name( + variable: SemanticVariable, + context: _PyiEmissionContext, + ) -> str: """Return the Python-visible module variable name.""" - if not self._normalize_fortran_public_names: + if not context.normalize_fortran_public_names: return variable.name - return self._public_name(variable.name, category="variable", owner=variable) - - def _public_name(self, raw_name: str, *, category: str, owner: object) -> str: - """Reserve and return a normalized Python public name.""" - key = (self._public_namespace, category, self._public_owner_key(owner)) - reserved = self._reserved_public_names.get(key) - if reserved is not None: - return reserved - public_name = self._naming_policy.reserve_public_name( - self._public_namespace, - raw_name, - category=category, - owner=raw_name, - ) - self._reserved_public_names[key] = public_name - return public_name + return context.public_name(variable.name, category="variable", owner=variable) - @staticmethod - def _public_owner_key(owner: object) -> object: - """Return a stable cache key for one emitted public declaration.""" - if isinstance(owner, str | int | tuple): - return owner - return id(owner) - - def _decorators(self, func: SemanticFunction, *, indent: str = "", emitted_name: str | None = None) -> str: + def _decorators( + self, + func: SemanticFunction, + context: _PyiEmissionContext, + *, + indent: str = "", + emitted_name: str | None = None, + ) -> str: """Handle decorators for the current generation context.""" decorators = [] emitted_name = emitted_name or func.name if self._is_private(func): - decorators.append(f"{indent}@{self._contract('private')}") + decorators.append(f"{indent}@{context.contract('private')}") if isinstance(func, SemanticMethod) and func.is_static: decorators.append(f"{indent}@staticmethod") bind_target = func.metadata.get(BIND_TARGET_METADATA) if bind_target is None and func.native_name and func.native_name != emitted_name: bind_target = func.native_name if bind_target and not func.metadata.get(OVERLOAD_TARGET_METADATA): - decorators.append(f"{indent}@{self._contract('bind')}({json.dumps(str(bind_target))})") + decorators.append(f"{indent}@{context.contract('bind')}({json.dumps(str(bind_target))})") if ( func.origin.source_language == "fortran" and func.origin.native_scope is None and not isinstance(func, SemanticMethod) and not func.metadata.get(OVERLOAD_TARGET_METADATA) ): - decorators.append(f"{indent}@{self._contract('standalone')}") + decorators.append(f"{indent}@{context.contract('standalone')}") if not func.metadata.get(OVERLOAD_TARGET_METADATA) and self._requires_native_call(func): decorators.append( - f"{indent}{self._native_call(self._pyi_projection(func), self._native_result_projection(func))}" + f"{indent}{self._native_call(self._pyi_projection(func), context, self._native_result_projection(func))}" ) if isinstance(policy := func.metadata.get(RUNTIME_STATUS_ERROR_METADATA), dict): - decorators.append(f"{indent}{self._raises(policy)}") + decorators.append(f"{indent}{self._raises(policy, context)}") if func.metadata.get(RUNTIME_RELEASE_GIL_METADATA): - decorators.append(f"{indent}@{self._contract('nogil')}") + decorators.append(f"{indent}@{context.contract('nogil')}") if not decorators: return "" return "\n".join(decorators) + "\n" @@ -1919,7 +2124,11 @@ def _with_address_projections( mapping.value = {"kind": "arg", "position": mapping.python_position} return projection - def _raises(self, policy: dict[str, object]) -> str: + @staticmethod + def _raises( + policy: dict[str, object], + context: _PyiEmissionContext, + ) -> str: """Handle raises for the current generation context.""" status = policy.get("status") if not isinstance(status, str) or not status: @@ -1934,87 +2143,105 @@ def _raises(self, policy: dict[str, object]) -> str: if not isinstance(success, int) or isinstance(success, bool): raise ValueError("raises metadata success must be an integer") parts.append(f"success={success}") - return f"@{self._contract('raises')}({', '.join(parts)})" + return f"@{context.contract('raises')}({', '.join(parts)})" def _native_call( self, projection: list[ProjectionMapping], + context: _PyiEmissionContext, native_result: ProjectionMapping | None = None, ) -> str: """Handle native call for the current generation context.""" entries = ", ".join( - self._native_projection_entry(mapping) + self._native_projection_entry(mapping, context) for mapping in sorted( projection, key=lambda item: item.native_position if item.native_position is not None else -1 ) ) suffix = "" if native_result is not None: - suffix = f", result={self._native_projection_value(native_result)}" - return f"@{self._contract('native_call')}([{entries}]{suffix})" + suffix = f", result={self._native_projection_value(native_result, context)}" + return f"@{context.contract('native_call')}([{entries}]{suffix})" - def _native_projection_entry(self, mapping: ProjectionMapping) -> str: + def _native_projection_entry( + self, + mapping: ProjectionMapping, + context: _PyiEmissionContext, + ) -> str: """Handle native projection entry for the current generation context.""" if mapping.value_kind: - return self._native_projection_value(mapping) + return self._native_projection_value(mapping, context) if mapping.python_position is not None: - return f"{self._contract('Arg')}({mapping.python_position})" + return f"{context.contract('Arg')}({mapping.python_position})" if mapping.result_position is not None: if mapping.native_name: - return f"{self._contract('Return')}({mapping.native_name!r}, {mapping.result_position})" - return f"{self._contract('Return')}({mapping.result_position})" + return f"{context.contract('Return')}({mapping.native_name!r}, {mapping.result_position})" + return f"{context.contract('Return')}({mapping.result_position})" raise ValueError("native_call cannot represent a native-only projection entry") - def _native_projection_value(self, mapping: ProjectionMapping) -> str: + def _native_projection_value( + self, + mapping: ProjectionMapping, + context: _PyiEmissionContext, + ) -> str: """Handle native projection value for the current generation context.""" if mapping.value_kind == "addr": - return f"{self._contract('Addr')}({self._native_value_ref(mapping.value)})" + return f"{context.contract('Addr')}({self._native_value_ref(mapping.value, context)})" if mapping.value_kind == "value": - return f"{self._contract('Value')}({self._native_value_ref(mapping.value)})" + return f"{context.contract('Value')}({self._native_value_ref(mapping.value, context)})" if mapping.value_kind in {"allocatable", "pointer"}: helper = "Allocatable" if mapping.value_kind == "allocatable" else "Pointer" - return f"{self._contract(helper)}({self._native_value_ref(mapping.value)})" + return f"{context.contract(helper)}({self._native_value_ref(mapping.value, context)})" if mapping.value_kind == "literal": - return self._native_literal_value(mapping.value) + return self._native_literal_value(mapping.value, context) if mapping.value_kind == "len": - return f"{self._contract('Len')}({self._native_value_ref(mapping.value)})" + return f"{context.contract('Len')}({self._native_value_ref(mapping.value, context)})" if mapping.value_kind == "shape": - return f"{self._native_value_ref(mapping.value['value'])}.shape[{mapping.value['dim']}]" + return f"{self._native_value_ref(mapping.value['value'], context)}.shape[{mapping.value['dim']}]" if mapping.value_kind == "is_present": - return f"{self._contract('IsPresent')}({self._native_value_ref(mapping.value)})" + return f"{context.contract('IsPresent')}({self._native_value_ref(mapping.value, context)})" if mapping.value_kind == "work": - return f"{self._contract('Work')}({mapping.value!r})" + return f"{context.contract('Work')}({mapping.value!r})" if mapping.value_kind == "pass": - return f"{self._contract('Pass')}()" + return f"{context.contract('Pass')}()" raise ValueError(f"Unsupported native_call projection entry: {mapping.value_kind!r}") - def _native_literal_value(self, value: object) -> str: + def _native_literal_value( + self, + value: object, + context: _PyiEmissionContext, + ) -> str: """Emit a hidden native literal with its ABI type.""" if not isinstance(value, dict) or "type" not in value or "value" not in value: raise ValueError("native_call literal entries require 'type' and 'value'") literal = value["value"] rendered = json.dumps(literal) if isinstance(literal, str) else repr(literal) - return f"{self._native_literal_type(str(value['type']))}({rendered})" + return f"{self._native_literal_type(str(value['type']), context)}({rendered})" - def _native_literal_type(self, type_text: str) -> str: + @staticmethod + def _native_literal_type(type_text: str, context: _PyiEmissionContext) -> str: """Return a typed-literal type with imported contract base name.""" match = re.fullmatch(r"([A-Za-z_]\w*)(.*)", type_text) if match is None: return type_text name, suffix = match.groups() - return f"{self._contract(name)}{suffix}" if name in CONTRACT_SYMBOLS else type_text + return f"{context.contract(name)}{suffix}" if name in CONTRACT_SYMBOLS else type_text - def _native_value_ref(self, value: dict[str, int | str]) -> str: + @staticmethod + def _native_value_ref( + value: dict[str, int | str], + context: _PyiEmissionContext, + ) -> str: """Handle native value ref for the current generation context.""" kind = value.get("kind") if kind == "arg": - return f"{self._contract('Arg')}({value['position']})" + return f"{context.contract('Arg')}({value['position']})" if kind == "return": if value.get("name"): - return f"{self._contract('Return')}({value['name']!r}, {value['position']})" - return f"{self._contract('Return')}({value['position']})" + return f"{context.contract('Return')}({value['name']!r}, {value['position']})" + return f"{context.contract('Return')}({value['position']})" if kind == "work": - return f"{self._contract('Work')}({value['name']!r})" + return f"{context.contract('Work')}({value['name']!r})" raise ValueError(f"Unsupported native_call value reference: {kind!r}") @staticmethod @@ -2147,8 +2374,8 @@ def emit_module(module: SemanticModule, *, normalize_fortran_public_names: bool """Render one semantic module through the shared default printer. Use this convenience entrypoint for ordinary one-module emission. Set - normalize_fortran_public_names to use an isolated normalized printer; - otherwise the reusable default printer restores its module-local state. + normalize_fortran_public_names to use a printer configured for normalized + public names. Both paths create a fresh module emission context. """ if normalize_fortran_public_names: return PyiPrinter(normalize_fortran_public_names=True).emit(module) diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py b/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py index 5ee1df7d6..7991bdea8 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py @@ -485,19 +485,22 @@ def test_printer_projection_return_helpers_and_keyword_data_members(): ) ], ) + context = printer._emission_context(module) - assert printer._projected_argument_return(argument, visible=True) == 'Returns["x", Addr(Float64)] | None' - assert printer._named_return(plain) == 'Returns["value", Int32]' - assert printer._projected_argument_return(argument, visible=False) == "Float64 | None" - assert printer._projected_argument_return(plain, visible=False) == "Int32" + assert printer._projected_argument_return(argument, context, visible=True) == 'Returns["x", Addr(Float64)] | None' + assert printer._named_return(plain, context) == 'Returns["value", Int32]' + assert printer._projected_argument_return(argument, context, visible=False) == "Float64 | None" + assert printer._projected_argument_return(plain, context, visible=False) == "Int32" assert "var['class']: Int32" in emit_module(module) assert "@native_call([Return(0)])" in emit_module(module) def test_native_call_sorts_synthetic_entries_before_native_positions(): + printer = PyiPrinter() projection = [ ProjectionMapping(native_position=0, value_kind="literal", value={"type": "Int32", "value": 1}), ProjectionMapping(result_position=0), ] - assert PyiPrinter()._native_call(projection) == "@native_call([Return(0), Int32(1)])" + context = printer._emission_context(SemanticModule(name="native_call")) + assert printer._native_call(projection, context) == "@native_call([Return(0), Int32(1)])" diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py index 5229534d8..fc16e0f53 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py @@ -64,6 +64,22 @@ def test_fortran_generated_contracts_reserve_colliding_public_names_by_namespace assert "def lambda__3" not in code +def test_pyi_emission_context_isolates_modules_and_shares_nested_imports(): + printer = PyiPrinter(normalize_fortran_public_names=True) + first = printer._emission_context(SemanticModule(name="first")) + second = printer._emission_context(SemanticModule(name="second")) + nested = first.inside_class("record_t") + + first.contract("Addr") + nested.contract("Pointer") + + assert first.contract_import() == "from prik.contracts import Addr, Pointer" + assert nested.contract_import() == first.contract_import() + assert nested.public_namespace == ("record_t",) + assert first.public_namespace == () + assert second.contract_import() == "" + + def test_printer_validation_and_opaque_dependency_edge_cases(): printer = PyiPrinter() @@ -71,7 +87,8 @@ def test_printer_validation_and_opaque_dependency_edge_cases(): printer.emit(SemanticConstraint("Shape")) plain_type = SemanticType("Float64", dtype="Float64") - assert printer._emit_storage_type(plain_type) == "Float64" + context = printer._emission_context(SemanticModule(name="edge_cases")) + assert printer._emit_storage_type(plain_type, context) == "Float64" malformed_import = SemanticType( "external_type", From f503fc8850f5937de0e316183d1f68549e95aef3 Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 14:26:12 +0100 Subject: [PATCH 04/22] Implement _ProcedureState --- prik/parsers/fortran/parser.py | 263 +++++++++++------- ...est_procedure_and_interface_regressions.py | 18 +- .../test_declaration_and_scope_regressions.py | 34 ++- ...test_real_world_interaction_regressions.py | 11 +- ...source_form_and_diagnostics_regressions.py | 21 +- 5 files changed, 207 insertions(+), 140 deletions(-) diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index d6a0c30a1..de1d527e8 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -10,7 +10,7 @@ import re from copy import deepcopy -from dataclasses import dataclass, replace +from dataclasses import dataclass, field as dataclass_field, replace from pathlib import Path from prik.utilities.declaration_expressions import ( @@ -237,14 +237,44 @@ class EnumUnit(SourceUnit): } +@dataclass +class _ProcedureState: + """Accumulate mutable facts while parsing and finalizing one procedure. + + The enclosing :class:`_ParserScope` owns lexical nesting and native-module + ownership. This record owns only the procedure-local signature, symbol + table, specification metadata, and source locations collected before the + signature is finalized. + """ + + signature: FortranProcedureSignature + symbols: dict[str, FortranArgument] + typed_symbols: set[str] = dataclass_field(default_factory=set) + uses: dict[str, list[FortranUseMapping]] = dataclass_field(default_factory=dict) + local_uses: dict[str, list[FortranUseMapping]] = dataclass_field(default_factory=dict) + local_params: dict[str, str] = dataclass_field(default_factory=dict) + legacy_local_params: set[str] = dataclass_field(default_factory=set) + implicit_typed_symbols: dict[str, str] = dataclass_field(default_factory=dict) + declared_local_types: dict[str, dict[str, object]] = dataclass_field(default_factory=dict) + implicit_none: bool = False + imports: set[str] = dataclass_field(default_factory=set) + external_symbols: set[str] = dataclass_field(default_factory=set) + includes: list[str] = dataclass_field(default_factory=list) + common_variables: list[str] = dataclass_field(default_factory=list) + filename: str | None = None + header_lineno: int | None = None + header_source_line: str | None = None + explicit_result: bool = False + + @dataclass class _ParserScope: """Carry explicit ownership and mutable state while visiting one unit. ``model`` receives parsed declarations, ``parent`` preserves lexical - ownership, and procedure visitors use ``state`` for their temporary symbol - table. Helpers receive this record explicitly rather than relying on - parser-global scope. + ownership, and procedure visitors attach their temporary + :class:`_ProcedureState`. Helpers receive this record explicitly rather + than relying on parser-global scope. """ kind: str @@ -252,7 +282,7 @@ class _ParserScope: model: object | None = None parent: _ParserScope | None = None module_owner: str | None = None - state: dict | None = None + state: _ProcedureState | None = None @dataclass(frozen=True) @@ -898,13 +928,11 @@ def _visit_ProcedureUnit( source_line=header[2], code="PARSE_EXPECTED_UNIT", ) - proc_state["filename"] = filename - proc_state["header_lineno"] = header[1] - proc_state["header_source_line"] = header[2] - proc_state["uses"].update(getattr(parent_scope.model, "uses", {})) - scope = self._helper_scope_for_model( - "procedure", proc_state["signature"], parent=parent_scope, state=proc_state - ) + proc_state.filename = filename + proc_state.header_lineno = header[1] + proc_state.header_source_line = header[2] + proc_state.uses.update(getattr(parent_scope.model, "uses", {})) + scope = self._helper_scope_for_model("procedure", proc_state.signature, parent=parent_scope, state=proc_state) parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("procedure"), filename=filename) self._parse_specification_part(scope, parts.specification, filename=filename) child_units = self._helper_nonexecution_child_units(unit, parent_scope=scope, filename=filename) @@ -2597,7 +2625,7 @@ def _parse_procedure_header( filename: str | None = None, lineno: int | None = None, source_line: str | None = None, - ): + ) -> _ProcedureState | None: """Build procedure scope state from a subroutine or function header.""" module_proc = _REGEX["module_procedure_impl"].match(line) if module_proc and not in_interface: @@ -2618,7 +2646,12 @@ def _parse_procedure_header( source_line=source_line, ) - def _module_procedure_scope(self, match, module: str | None, in_interface: bool): + def _module_procedure_scope( + self, + match, + module: str | None, + in_interface: bool, + ) -> _ProcedureState: """Create temporary procedure state for one ``module procedure`` header. Such implementation declarations have no explicit dummy list here. @@ -2634,7 +2667,12 @@ def _module_procedure_scope(self, match, module: str | None, in_interface: bool) ) return self._new_procedure_scope_state(sig, symbols={}) - def _subroutine_scope(self, match, module: str | None, in_interface: bool): + def _subroutine_scope( + self, + match, + module: str | None, + in_interface: bool, + ) -> _ProcedureState: """Create procedure state from one recognized subroutine header. The header match supplies attributes, dummy names, optional ``bind(c)`` @@ -2665,7 +2703,7 @@ def _function_scope( filename: str | None, lineno: int | None, source_line: str | None, - ): + ) -> _ProcedureState: """Create procedure state from one recognized function header. The helper initializes dummy arguments and the result symbol, parses an @@ -2780,7 +2818,7 @@ def _helper_scope_for_model( *, parent: _ParserScope | None = None, module_owner: str | None = None, - state: dict | None = None, + state: _ProcedureState | None = None, ) -> _ParserScope: """Build the scope object passed through shared helpers. @@ -2819,42 +2857,34 @@ def _new_procedure_scope_state( symbols: dict[str, FortranArgument], typed_symbols: set[str] | None = None, explicit_result: bool = False, - ) -> dict: + ) -> _ProcedureState: """Create mutable procedure parsing state shared by spec-line helpers.""" - state = { - "signature": signature, - "symbols": symbols, - "typed_symbols": typed_symbols or set(), - "uses": {}, - "local_uses": {}, - "in_contains": False, - "local_params": {}, - "legacy_local_params": set(), - "implicit_typed_symbols": {}, - "declared_local_types": {}, - "implicit_none": False, - "imports": set(), - "external_symbols": set(), - "includes": [], - "common_variables": [], - "filename": None, - "local_type_depth": 0, - } - if explicit_result: - state["explicit_result"] = True - return state + return _ProcedureState( + signature=signature, + symbols=symbols, + typed_symbols=typed_symbols or set(), + explicit_result=explicit_result, + ) - def _proc_scope_get_symbol(self, proc_state: dict, name: str) -> FortranArgument | None: + def _proc_scope_get_symbol( + self, + proc_state: _ProcedureState, + name: str, + ) -> FortranArgument | None: """Return one procedure symbol by case-insensitive name.""" - return proc_state["symbols"].get(self._scope_key(name)) + return proc_state.symbols.get(self._scope_key(name)) - def _proc_scope_symbol_is_declared(self, proc_state: dict, name: str) -> bool: + def _proc_scope_symbol_is_declared( + self, + proc_state: _ProcedureState, + name: str, + ) -> bool: """Return whether a procedure symbol already has an explicit type.""" - return self._scope_key(name) in proc_state["typed_symbols"] + return self._scope_key(name) in proc_state.typed_symbols def _proc_scope_mark_declared_symbol( self, - proc_state: dict, + proc_state: _ProcedureState, name: str, *, filename: str | None = None, @@ -2863,35 +2893,52 @@ def _proc_scope_mark_declared_symbol( ) -> str: """Record an explicitly typed procedure symbol and reject duplicates.""" key = self._scope_key(name) - if key in proc_state["typed_symbols"]: + if key in proc_state.typed_symbols: raise FortranParseError( - f"Duplicate declaration of symbol '{name}' in procedure '{proc_state['signature'].name}'.", + f"Duplicate declaration of symbol '{name}' in procedure '{proc_state.signature.name}'.", filename=filename, line_number=line_number, source_line=source_line, code="PARSE_DUPLICATE_DECLARATION", ) - proc_state["typed_symbols"].add(key) + proc_state.typed_symbols.add(key) return key - def _proc_scope_add_external_symbol(self, proc_state: dict, name: str) -> str: + def _proc_scope_add_external_symbol( + self, + proc_state: _ProcedureState, + name: str, + ) -> str: """Record an external procedure symbol and update a matching dummy.""" key = self._scope_key(name) - proc_state.setdefault("external_symbols", set()).add(key) + proc_state.external_symbols.add(key) arg = self._proc_scope_get_symbol(proc_state, key) if arg is not None and arg.base_type == "unknown": arg.base_type = "procedure" return key - def _proc_scope_add_include(self, proc_state: dict, include_path: str) -> None: + def _proc_scope_add_include( + self, + proc_state: _ProcedureState, + include_path: str, + ) -> None: """Record one procedure-local include path.""" - proc_state.setdefault("includes", []).append(include_path) + proc_state.includes.append(include_path) - def _proc_scope_add_imports(self, proc_state: dict, names: list[str]) -> None: + def _proc_scope_add_imports( + self, + proc_state: _ProcedureState, + names: list[str], + ) -> None: """Record interface imports visible inside a procedure declaration.""" - proc_state.setdefault("imports", set()).update(self._scope_key(n) for n in names if n.strip()) + proc_state.imports.update(self._scope_key(n) for n in names if n.strip()) - def _proc_scope_set_declared_local_type(self, proc_state: dict, name: str, meta: dict) -> None: + def _proc_scope_set_declared_local_type( + self, + proc_state: _ProcedureState, + name: str, + meta: dict, + ) -> None: """Store type metadata for a declared local symbol.""" key = self._scope_key(name) declared_type = { @@ -2906,11 +2953,11 @@ def _proc_scope_set_declared_local_type(self, proc_state: dict, name: str, meta: ): if metadata_key in meta and (metadata_key != "polymorphic" or meta[metadata_key]): declared_type[metadata_key] = meta[metadata_key] - proc_state["declared_local_types"][key] = declared_type + proc_state.declared_local_types[key] = declared_type def _proc_scope_add_local_parameter( self, - proc_state: dict, + proc_state: _ProcedureState, name: str, value: str, *, @@ -2925,25 +2972,25 @@ def _proc_scope_add_local_parameter( key = self._scope_key(name) if require_declared and not self._proc_scope_symbol_is_declared(proc_state, key): raise FortranParseError( - f"Unknown datatype for PARAMETER symbol '{name}' in procedure '{proc_state['signature'].name}'.", + f"Unknown datatype for PARAMETER symbol '{name}' in procedure '{proc_state.signature.name}'.", filename=filename, line_number=line_number, source_line=source_line, code="PARSE_UNKNOWN_PARAMETER_TYPE", ) - if key in proc_state["local_params"]: + if key in proc_state.local_params: raise FortranParseError( - f"Duplicate PARAMETER declaration of symbol '{name}' in procedure '{proc_state['signature'].name}'.", + f"Duplicate PARAMETER declaration of symbol '{name}' in procedure '{proc_state.signature.name}'.", filename=filename, line_number=line_number, source_line=source_line, code="PARSE_DUPLICATE_PARAMETER", ) - proc_state["local_params"][key] = value + proc_state.local_params[key] = value if register_implicit_if_missing and not self._proc_scope_symbol_is_declared(proc_state, key): - proc_state["implicit_typed_symbols"][key] = self._infer_implicit_base_type(name) + proc_state.implicit_typed_symbols[key] = self._infer_implicit_base_type(name) if legacy: - proc_state["legacy_local_params"].add(key) + proc_state.legacy_local_params.add(key) @staticmethod def _insert_unique_scope_symbol( @@ -3193,7 +3240,7 @@ def _raise_unsupported_module_like_declaration(self, target, line, filename, lin def _parse_procedure_spec_line( self, line: str, - proc_state: dict, + proc_state: _ProcedureState, filename: str | None = None, lineno: int | None = None, source_line: str | None = None, @@ -3214,11 +3261,11 @@ def _parse_procedure_spec_line( """ stripped = line.strip() if re.match(r"^common\b", stripped, flags=re.IGNORECASE): - self._record_common_variables(proc_state["common_variables"], stripped) + self._record_common_variables(proc_state.common_variables, stripped) return if self._is_openmp_declarative_directive(stripped): raise FortranParseError( - f"Unsupported OpenMP declarative directive in procedure '{proc_state['signature'].name}': {stripped}", + f"Unsupported OpenMP declarative directive in procedure '{proc_state.signature.name}': {stripped}", filename=filename, line_number=lineno, source_line=source_line, @@ -3231,8 +3278,8 @@ def _parse_procedure_spec_line( parsed_use = self._parse_use_statement(stripped) if parsed_use: module_name, mappings = parsed_use - proc_state["uses"][module_name] = mappings - proc_state["local_uses"][module_name] = mappings + proc_state.uses[module_name] = mappings + proc_state.local_uses[module_name] = mappings return # This parser is a subset parser focused on wrapper-relevant metadata. # These statements do not affect extracted signature typing/shapes. @@ -3255,13 +3302,13 @@ def _parse_procedure_spec_line( stripped, _ParserScope( kind="procedure", - name=proc_state["signature"].name, - model=proc_state["signature"], + name=proc_state.signature.name, + model=proc_state.signature, state=proc_state, - module_owner=proc_state["signature"].module, + module_owner=proc_state.signature.module, ), role="procedure_symbol", - filename=proc_state.get("filename") or filename, + filename=proc_state.filename or filename, lineno=lineno, source_line=source_line, include_argument_access=True, @@ -3396,7 +3443,7 @@ def _parse_derived_type_contains_line( def _helper_apply_local_interface_declarations( self, - proc_state: dict, + proc_state: _ProcedureState, unit: SourceUnit, parts: _UnitParts, scope: _ParserScope, @@ -3606,7 +3653,7 @@ def _helper_push_declaration_to_scope( filename=filename, code="PARSE_INTERNAL_STATE", ) - if meta["base_type"] == "procedure" and meta["kind"] in proc_state.get("imports", set()): + if meta["base_type"] == "procedure" and meta["kind"] in proc_state.imports: meta["kind"] = None for entity in split_csv(right): raw_name, shape = self._var(entity) @@ -3884,24 +3931,32 @@ def _extract_bounds(shape: list[str]) -> tuple[list[str | None], list[str | None # Procedure specification handlers # ------------------------------------------------------------------ - def _handle_proc_implicit_line(self, line: str, proc_state: dict) -> bool: + def _handle_proc_implicit_line( + self, + line: str, + proc_state: _ProcedureState, + ) -> bool: """Handle a procedure ``implicit`` statement. Procedure parsing keeps implicit typing in the procedure state because it affects later finalization of undeclared dummy arguments. Example: - ``implicit none`` sets ``proc_state["implicit_none"]`` so + ``implicit none`` sets ``proc_state.implicit_none`` so `_finalize_proc` can require every argument to have an explicit declaration. """ if not re.match(r"^implicit\b", line, flags=re.IGNORECASE): return False if re.match(r"^implicit\s+none\b", line, flags=re.IGNORECASE): - proc_state["implicit_none"] = True + proc_state.implicit_none = True return True - def _handle_proc_external_line(self, line: str, proc_state: dict) -> bool: + def _handle_proc_external_line( + self, + line: str, + proc_state: _ProcedureState, + ) -> bool: """Handle a procedure ``external`` statement. External symbols are stored in the procedure state before declaration @@ -3937,7 +3992,11 @@ def _is_ignored_proc_spec_line(line: str) -> bool: ) return any(re.match(pattern, line, flags=re.IGNORECASE) for pattern in ignored_patterns) - def _handle_proc_include_or_import_line(self, line: str, proc_state: dict) -> bool: + def _handle_proc_include_or_import_line( + self, + line: str, + proc_state: _ProcedureState, + ) -> bool: """Handle procedure-level ``include`` and ``import`` statements. These statements are procedure-specific specification-part metadata, so @@ -3962,7 +4021,7 @@ def _handle_proc_include_or_import_line(self, line: str, proc_state: dict) -> bo def _handle_proc_parameter_line( self, line: str, - proc_state: dict, + proc_state: _ProcedureState, *, filename: str | None, lineno: int | None, @@ -4009,7 +4068,7 @@ def _handle_proc_parameter_line( filename=filename, line_number=lineno, source_line=source_line, - require_declared=proc_state.get("implicit_none", False), + require_declared=proc_state.implicit_none, register_implicit_if_missing=not declared, legacy=declared, ) @@ -4062,7 +4121,7 @@ def _looks_like_unknown_proc_declaration(line: str) -> bool: def _handle_unknown_proc_declaration( self, line: str, - proc_state: dict, + proc_state: _ProcedureState, *, filename: str | None, lineno: int | None, @@ -4083,7 +4142,7 @@ def _handle_unknown_proc_declaration( if not self._looks_like_unknown_proc_declaration(line): self._raise_invalid_fortran_syntax_line( line, - context=f"procedure '{proc_state['signature'].name}' specification part", + context=f"procedure '{proc_state.signature.name}' specification part", filename=filename, lineno=lineno, source_line=source_line, @@ -4091,7 +4150,7 @@ def _handle_unknown_proc_declaration( if _REGEX["unsupported_class_star"].search(line): raise FortranParseError( f"Unsupported assumed-type CLASS(*) declaration for procedure " - f"'{proc_state['signature'].name}': {line.strip()}", + f"'{proc_state.signature.name}': {line.strip()}", filename=filename, line_number=lineno, source_line=source_line, @@ -4100,7 +4159,7 @@ def _handle_unknown_proc_declaration( if any(_REGEX[pattern_key].search(line) for pattern_key in _UNSUPPORTED_PATTERN_KEYS): return raise FortranParseError( - f"Unknown or unsupported datatype declaration for procedure '{proc_state['signature'].name}': {line.strip()}", + f"Unknown or unsupported datatype declaration for procedure '{proc_state.signature.name}': {line.strip()}", filename=filename, line_number=lineno, source_line=source_line, @@ -4111,15 +4170,15 @@ def _handle_unknown_proc_declaration( # Finalization and compile-time resolution # ------------------------------------------------------------------ - def _finalize_proc(self, state: dict) -> FortranProcedureSignature: + def _finalize_proc(self, state: _ProcedureState) -> FortranProcedureSignature: """Validate and freeze one procedure signature from mutable scope state.""" - sig = state["signature"] - symbols = state["symbols"] - local_params = state.get("local_params", {}) - legacy_local_params = state.get("legacy_local_params", set()) - implicit_typed_symbols = state.get("implicit_typed_symbols", {}) - filename = state.get("filename") - implicit_none = state.get("implicit_none", False) + sig = state.signature + symbols = state.symbols + local_params = state.local_params + legacy_local_params = state.legacy_local_params + implicit_typed_symbols = state.implicit_typed_symbols + filename = state.filename + implicit_none = state.implicit_none sig.variables = {} sig.arguments = [symbols.get(a.name.lower(), a) for a in sig.arguments] if sig.result: @@ -4129,8 +4188,8 @@ def _finalize_proc(self, state: dict) -> FortranProcedureSignature: sig.arguments, sig.name, filename, - state.get("header_lineno"), - state.get("header_source_line"), + state.header_lineno, + state.header_source_line, ) # Safety check: if an argument has been explicitly declared in this @@ -4138,7 +4197,7 @@ def _finalize_proc(self, state: dict) -> FortranProcedureSignature: # This catches declaration-application regressions (e.g. legacy # star-kind list handling) while still allowing truly undeclared # arguments to be handled by semantic conversion or wrapper planning. - declared_symbols = state.get("typed_symbols", set()) + declared_symbols = state.typed_symbols for arg in sig.arguments: if ( arg.name.lower() in declared_symbols and arg.base_type == "unknown" @@ -4159,7 +4218,7 @@ def _finalize_proc(self, state: dict) -> FortranProcedureSignature: if sig.result and sig.result.kind: sig.result.kind = self._resolve_kind_expression(sig.result.kind, local_params, resolver=local_resolver) relevant_params = self._collect_relevant_local_params(sig, local_params) - declared_local_types = state.get("declared_local_types", {}) + declared_local_types = state.declared_local_types # Defensive reconciliation: some legacy declaration forms can be parsed into # `declared_local_types` before being matched back to argument symbols. # If an argument is still unknown but we have an exact-name local type @@ -4175,7 +4234,7 @@ def _finalize_proc(self, state: dict) -> FortranProcedureSignature: self._apply_internal_type_metadata(arg, inferred) if implicit_none and not sig.in_interface: - self._validate_all_args_declared(sig, filename, explicit_result=bool(state.get("explicit_result", False))) + self._validate_all_args_declared(sig, filename, explicit_result=state.explicit_result) for name, value in relevant_params.items(): if name.lower() in legacy_local_params: @@ -4208,14 +4267,14 @@ def _finalize_proc(self, state: dict) -> FortranProcedureSignature: ) if sig.kind == "function": self._validate_function_result(sig, filename) - for symbol in sorted(state.get("imports", set())): + for symbol in sorted(state.imports): attr = f"import({symbol})" if attr not in sig.attributes: sig.attributes.append(attr) - sig.uses = dict(state["uses"]) - sig.common_variables = list(state.get("common_variables", ())) + sig.uses = dict(state.uses) + sig.common_variables = list(state.common_variables) finalized = replace(sig) - finalized._local_uses = dict(state.get("local_uses", {})) + finalized._local_uses = dict(state.local_uses) return finalized @staticmethod diff --git a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py index d0233186d..df0bd451e 100644 --- a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py +++ b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py @@ -91,16 +91,16 @@ def test_finalize_proc_resolves_signature_arguments_imports_and_uses_without_exp ], ) - finalized = parser._finalize_proc( - { - "signature": signature, - "symbols": {argument.name.lower(): argument for argument in signature.arguments}, - "uses": {"precision_mod": []}, - "local_params": {"rk": "8", "count": "4"}, - "imports": {"state_t", "callback"}, - "filename": "finalize_contract.f90", - } + state = parser._new_procedure_scope_state( + signature, + symbols={argument.name.lower(): argument for argument in signature.arguments}, ) + state.uses = {"precision_mod": []} + state.local_params = {"rk": "8", "count": "4"} + state.imports = {"state_t", "callback"} + state.filename = "finalize_contract.f90" + + finalized = parser._finalize_proc(state) assert finalized is not signature assert [(argument.name, argument.base_type, argument.kind, argument.shape) for argument in finalized.arguments] == [ diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py index 9cba6910b..c4e1e5f72 100644 --- a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py @@ -151,8 +151,8 @@ def test_procedure_declaration_push_updates_dummy_or_records_local_type_and_dupl assert signature.arguments[0].base_type == "procedure" assert signature.arguments[0].kind == "callback_iface" - assert state["external_symbols"] == {"callback"} - assert state["declared_local_types"] == {"scratch": {"base_type": "real", "kind": "rk"}} + assert state.external_symbols == {"callback"} + assert state.declared_local_types == {"scratch": {"base_type": "real", "kind": "rk"}} with pytest.raises(FortranParseError) as error: parser._helper_push_declaration_to_scope( @@ -187,9 +187,9 @@ def test_procedure_parameter_lines_preserve_local_parameter_state_and_duplicate_ lineno=5, source_line="integer, parameter :: n = 4, m = n + 2", ) - assert state["local_params"] == {"n": "4", "m": "n + 2"} - assert state["legacy_local_params"] == set() - assert state["implicit_typed_symbols"] == {} + assert state.local_params == {"n": "4", "m": "n + 2"} + assert state.legacy_local_params == set() + assert state.implicit_typed_symbols == {} with pytest.raises(FortranParseError) as error: parser._handle_proc_parameter_line( @@ -211,7 +211,7 @@ def test_legacy_parameter_lines_respect_implicit_none_and_implicit_typing_contra parser = FortranParser() strict_signature = FortranProcedureSignature("strict", "subroutine") strict_state = parser._new_procedure_scope_state(strict_signature, symbols={}) - strict_state["implicit_none"] = True + strict_state.implicit_none = True with pytest.raises(FortranParseError) as error: parser._handle_proc_parameter_line( @@ -237,9 +237,9 @@ def test_legacy_parameter_lines_respect_implicit_none_and_implicit_typing_contra lineno=9, source_line="parameter (ival = 2, alpha = 1.0)", ) - assert loose_state["local_params"] == {"ival": "2", "alpha": "1.0"} - assert loose_state["implicit_typed_symbols"] == {"ival": "integer", "alpha": "real"} - assert loose_state["legacy_local_params"] == set() + assert loose_state.local_params == {"ival": "2", "alpha": "1.0"} + assert loose_state.implicit_typed_symbols == {"ival": "integer", "alpha": "real"} + assert loose_state.legacy_local_params == set() def test_namespace_collection_preserves_case_insensitive_dependencies_and_exact_payload(tmp_path: Path, monkeypatch): @@ -547,14 +547,15 @@ def test_project_encoding_is_forwarded_to_directory_namespace_collection(tmp_pat def test_scope_include_import_and_derived_type_binding_contracts(): parser = FortranParser() - state = {} + state = parser._new_procedure_scope_state( + FortranProcedureSignature("scope_contract", "subroutine"), + symbols={}, + ) parser._proc_scope_add_include(state, "shared.inc") parser._proc_scope_add_imports(state, ["State_T", " ", "Callback"]) - assert state == { - "includes": ["shared.inc"], - "imports": {"state_t", "callback"}, - } + assert state.includes == ["shared.inc"] + assert state.imports == {"state_t", "callback"} dtype = parser._init_derived_type( "type, extends(parent(kind)), public :: child", @@ -593,7 +594,10 @@ def test_scope_include_import_and_derived_type_binding_contracts(): def test_unknown_procedure_declaration_kind_preserves_declaration_and_invalid_syntax_split(): parser = FortranParser() - state = {"signature": FortranProcedureSignature(name="work", kind="subroutine")} + state = parser._new_procedure_scope_state( + FortranProcedureSignature(name="work", kind="subroutine"), + symbols={}, + ) with pytest.raises(FortranParseError) as declaration_error: parser._handle_unknown_proc_declaration( diff --git a/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py b/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py index 46326d399..7d200f9e7 100644 --- a/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py @@ -2,6 +2,7 @@ from prik import parse_fortran_file from prik.parsers.fortran.lexer import preprocess_lines, strip_comment +from prik.parsers.fortran.models import FortranProcedureSignature from prik.parsers.fortran.parser import FortranParser from prik.parsers.fortran.utils import split_csv @@ -120,7 +121,11 @@ def test_legacy_procedure_specifications_preserve_wrapper_relevant_facts(): def test_procedure_include_is_recorded_before_signature_finalization(): - state: dict[str, list[str]] = {} + parser = FortranParser() + state = parser._new_procedure_scope_state( + FortranProcedureSignature("include_contract", "subroutine"), + symbols={}, + ) - assert FortranParser()._handle_proc_include_or_import_line("include 'constants.inc'", state) is True - assert state["includes"] == ["'constants.inc'"] + assert parser._handle_proc_include_or_import_line("include 'constants.inc'", state) is True + assert state.includes == ["'constants.inc'"] diff --git a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py index 14622b2cd..5b77bf5de 100644 --- a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py @@ -273,17 +273,13 @@ def test_finalize_proc_duplicate_argument_diagnostic_preserves_header_metadata() arguments=[FortranArgument("value"), FortranArgument("VALUE")], ) + state = parser._new_procedure_scope_state(signature, symbols={}) + state.filename = "finalize_contract.f90" + state.header_lineno = 12 + state.header_source_line = "subroutine step(value, VALUE)" + with pytest.raises(FortranParseError) as error: - parser._finalize_proc( - { - "signature": signature, - "symbols": {}, - "uses": {}, - "filename": "finalize_contract.f90", - "header_lineno": 12, - "header_source_line": "subroutine step(value, VALUE)", - } - ) + parser._finalize_proc(state) assert error.value.base_message == "Duplicate argument name 'VALUE' in procedure 'step'." assert error.value.filename == "finalize_contract.f90" @@ -323,7 +319,10 @@ def test_declaration_push_preserves_type_field_metadata_and_duplicate_field_diag def test_unknown_procedure_declaration_diagnostic_preserves_public_metadata(): parser = FortranParser() - state = {"signature": FortranProcedureSignature(name="work", kind="subroutine")} + state = parser._new_procedure_scope_state( + FortranProcedureSignature(name="work", kind="subroutine"), + symbols={}, + ) with pytest.raises(FortranParseError) as error: parser._handle_unknown_proc_declaration( From c23fe3e4d0019cadb2007013cceeab181db0bffd Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 14:41:16 +0100 Subject: [PATCH 05/22] turn _finalize_proc to a short coordinator of Nine focused helpers --- prik/parsers/fortran/parser.py | 211 ++++++++++++++++++++++++++++----- 1 file changed, 180 insertions(+), 31 deletions(-) diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index de1d527e8..718d13c7a 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -4171,14 +4171,54 @@ def _handle_unknown_proc_declaration( # ------------------------------------------------------------------ def _finalize_proc(self, state: _ProcedureState) -> FortranProcedureSignature: - """Validate and freeze one procedure signature from mutable scope state.""" + """Run the ordered finalization phases for one parsed procedure. + + ``state`` contains the mutable signature and specification facts built + while visiting the procedure. The helpers below intentionally mutate + that signature in the same order as declaration parsing requires, then + the final phase returns a distinct shallow dataclass copy. + """ + # Replace header placeholders with declared symbols and reject duplicate dummy names. + sig = self._reconcile_procedure_signature_symbols(state) + + # Catch declaration-application regressions before implicit typing can hide them. + self._validate_declared_procedure_arguments(sig, state) + + # Resolve kinds and shapes, infer permitted dummy types, and find parameters used by the signature. + relevant_params = self._resolve_procedure_signature_types(sig, state) + + # Recover legacy local declarations that were recorded before they could be matched to dummies. + self._reconcile_procedure_local_declarations(sig, state) + + # Enforce explicit dummy and result declarations for non-interface IMPLICIT NONE procedures. + self._validate_procedure_implicit_none(sig, state) + + # Expose only signature-relevant modern parameters as parsed procedure variables. + self._materialize_procedure_parameters(sig, state, relevant_params) + + # Infer a permitted implicit function result type, then validate the completed result contract. + self._finalize_procedure_result_type(sig, state) + + # Attach procedure-scope imports, USE associations, and common-block facts. + self._attach_procedure_scope_metadata(sig, state) + + # Preserve the established shallow-copy boundary and attach local-only USE associations to the copy. + return self._copy_finalized_procedure_signature(sig, state) + + def _reconcile_procedure_signature_symbols( + self, + state: _ProcedureState, + ) -> FortranProcedureSignature: + """Replace header placeholders with the symbols declared in ``state``. + + ``state.signature`` supplies the header-created argument and result + objects, while ``state.symbols`` contains their declaration-updated + replacements. The method clears previously materialized variables, + updates the signature in place, validates dummy-name uniqueness using + the stored header location, and returns that same signature. + """ sig = state.signature symbols = state.symbols - local_params = state.local_params - legacy_local_params = state.legacy_local_params - implicit_typed_symbols = state.implicit_typed_symbols - filename = state.filename - implicit_none = state.implicit_none sig.variables = {} sig.arguments = [symbols.get(a.name.lower(), a) for a in sig.arguments] if sig.result: @@ -4187,65 +4227,132 @@ def _finalize_proc(self, state: _ProcedureState) -> FortranProcedureSignature: FortranParser._validate_no_duplicate_arg_names( sig.arguments, sig.name, - filename, + state.filename, state.header_lineno, state.header_source_line, ) + return sig - # Safety check: if an argument has been explicitly declared in this - # procedure, it must not remain unknown after declaration parsing. - # This catches declaration-application regressions (e.g. legacy - # star-kind list handling) while still allowing truly undeclared - # arguments to be handled by semantic conversion or wrapper planning. - declared_symbols = state.typed_symbols + @staticmethod + def _validate_declared_procedure_arguments( + sig: FortranProcedureSignature, + state: _ProcedureState, + ) -> None: + """Require every explicitly typed dummy to carry its parsed datatype. + + The signature provides the reconciled dummy arguments and + ``state.typed_symbols`` identifies names that declaration parsing + claimed to type. The method does not mutate either object; it raises a + source-located internal parser diagnostic if a declared dummy is still + unknown, while leaving truly undeclared dummies for implicit handling. + """ for arg in sig.arguments: if ( - arg.name.lower() in declared_symbols and arg.base_type == "unknown" + arg.name.lower() in state.typed_symbols and arg.base_type == "unknown" ): # pragma: no cover - defensive parser invariant. raise FortranParseError( f"Failed to resolve declared argument '{arg.name}' in procedure '{sig.name}'.", - filename=filename, + filename=state.filename, code="PARSE_UNRESOLVED_ARGUMENT_TYPE", ) + + def _resolve_procedure_signature_types( + self, + sig: FortranProcedureSignature, + state: _ProcedureState, + ) -> dict[str, str]: + """Resolve signature kinds and shapes against local parameters. + + ``sig`` supplies the reconciled arguments and optional result; + ``state.local_params`` supplies procedure-local compile-time symbols, + and ``state.implicit_none`` controls dummy inference. The method mutates + argument kinds, shapes, permitted implicit base types, and the result + kind, then returns the parameters referenced by the resolved signature. + """ + local_params = state.local_params local_resolver = _CompileTimeResolver(local_params) for arg in sig.arguments: if arg.kind: arg.kind = self._resolve_kind_expression(arg.kind, local_params, resolver=local_resolver) if arg.shape: arg.shape = [local_resolver.resolve(dim) for dim in arg.shape] - if arg.base_type == "unknown" and not implicit_none: + if arg.base_type == "unknown" and not state.implicit_none: arg.base_type = self._infer_implicit_base_type(arg.name) if sig.result and sig.result.kind: sig.result.kind = self._resolve_kind_expression(sig.result.kind, local_params, resolver=local_resolver) - relevant_params = self._collect_relevant_local_params(sig, local_params) - declared_local_types = state.declared_local_types - # Defensive reconciliation: some legacy declaration forms can be parsed into - # `declared_local_types` before being matched back to argument symbols. - # If an argument is still unknown but we have an exact-name local type - # record, apply it before implicit-none validation to avoid false positives. + return self._collect_relevant_local_params(sig, local_params) + + def _reconcile_procedure_local_declarations( + self, + sig: FortranProcedureSignature, + state: _ProcedureState, + ) -> None: + """Apply unmatched local declaration metadata to unknown dummies. + + Some legacy declarations enter ``state.declared_local_types`` before + the parser can match them to the signature symbol table. This method + looks up only arguments that remain unknown, copies their base type and + kind, and applies preserved internal spelling/storage metadata before + the later ``implicit none`` validation runs. + """ for arg in sig.arguments: if arg.base_type != "unknown": continue - inferred = declared_local_types.get(arg.name.lower()) + inferred = state.declared_local_types.get(arg.name.lower()) if not inferred: continue arg.base_type = inferred.get("base_type", arg.base_type) arg.kind = inferred.get("kind", arg.kind) self._apply_internal_type_metadata(arg, inferred) - if implicit_none and not sig.in_interface: - self._validate_all_args_declared(sig, filename, explicit_result=state.explicit_result) + def _validate_procedure_implicit_none( + self, + sig: FortranProcedureSignature, + state: _ProcedureState, + ) -> None: + """Enforce explicit declarations when this procedure uses IMPLICIT NONE. + + ``sig`` is the reconciled and locally repaired signature; ``state`` + supplies the implicit-typing flag, filename, and whether a function used + an explicit ``result(...)`` clause. Interface signatures retain their + existing exemption. The method performs validation only and raises the + established public diagnostics without mutating the signature. + """ + if state.implicit_none and not sig.in_interface: + self._validate_all_args_declared( + sig, + state.filename, + explicit_result=state.explicit_result, + ) + def _materialize_procedure_parameters( + self, + sig: FortranProcedureSignature, + state: _ProcedureState, + relevant_params: dict[str, str], + ) -> None: + """Create parsed variables for modern parameters used by the signature. + + ``relevant_params`` maps referenced local parameter names to their + expressions. ``state`` provides declared or implicitly inferred types + and identifies legacy ``PARAMETER (...)`` artifacts. The method skips + those legacy artifacts, constructs typed :class:`FortranVariable` + records, preserves symbolic values and internal metadata, and stores + them in ``sig.variables``. + """ for name, value in relevant_params.items(): - if name.lower() in legacy_local_params: + if name.lower() in state.legacy_local_params: # Legacy PARAMETER (...) constants are declaration artifacts in # fixed-form sources; keep them available for compile-time # resolution but do not expose them as parsed procedure variables. continue - local_decl = declared_local_types.get(name.lower(), {}) + local_decl = state.declared_local_types.get(name.lower(), {}) var = FortranVariable( name=name.lower(), - base_type=local_decl.get("base_type", implicit_typed_symbols.get(name.lower(), "unknown")), + base_type=local_decl.get( + "base_type", + state.implicit_typed_symbols.get(name.lower(), "unknown"), + ), kind=local_decl.get("kind"), value=self._normalize_parameter_value(value), value_type="expression", @@ -4254,25 +4361,67 @@ def _finalize_proc(self, state: _ProcedureState) -> FortranProcedureSignature: self._apply_internal_type_metadata(var, local_decl) var.symbolic_value = value sig.variables[name.lower()] = var + + def _finalize_procedure_result_type( + self, + sig: FortranProcedureSignature, + state: _ProcedureState, + ) -> None: + """Complete and validate the datatype of a function result. + + Subroutines are left untouched. For functions, ``state.implicit_none`` + decides whether an unknown result may receive Fortran implicit typing; + unresolved results raise the existing filename-aware diagnostic. The + method then delegates structural checks such as result/argument name + separation to ``_validate_function_result``. + """ if sig.kind == "function" and sig.result and sig.result.base_type == "unknown": - if not implicit_none: + if not state.implicit_none: sig.result.base_type = self._infer_implicit_base_type(sig.result.name) if ( sig.result.base_type == "unknown" ): # pragma: no cover - implicit-none result validation handles public cases first. raise FortranParseError( f"Unknown datatype for function result '{sig.result.name}' in procedure '{sig.name}'.", - filename=filename, + filename=state.filename, code="PARSE_UNKNOWN_FUNCTION_RESULT_TYPE", ) if sig.kind == "function": - self._validate_function_result(sig, filename) + self._validate_function_result(sig, state.filename) + + @staticmethod + def _attach_procedure_scope_metadata( + sig: FortranProcedureSignature, + state: _ProcedureState, + ) -> None: + """Attach scope metadata collected outside the signature declarations. + + ``state.imports`` becomes stable ``import(name)`` attributes without + duplicates, while inherited/local USE associations and common-block + object names are copied onto ``sig``. The method mutates those metadata + fields only; procedure-local USE associations remain reserved for the + distinct finalized copy created by the next phase. + """ for symbol in sorted(state.imports): attr = f"import({symbol})" if attr not in sig.attributes: sig.attributes.append(attr) sig.uses = dict(state.uses) sig.common_variables = list(state.common_variables) + + @staticmethod + def _copy_finalized_procedure_signature( + sig: FortranProcedureSignature, + state: _ProcedureState, + ) -> FortranProcedureSignature: + """Return the established shallow final signature copy. + + ``sig`` is the fully mutated working signature and ``state.local_uses`` + contains USE associations declared directly inside the procedure. The + method preserves the existing shallow ``dataclasses.replace`` boundary, + attaches a copied private local-USE mapping to the new object, and does + not deep-copy arguments or other signature members. + """ finalized = replace(sig) finalized._local_uses = dict(state.local_uses) return finalized From a351871f9c4d42eb6ba79d50a4aab5c4e28cd5b2 Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 16:00:05 +0100 Subject: [PATCH 06/22] Implement _SourceUnitScanner which extract the unit code instead of relying on helpers --- docs/developer/fortran-parser-reference.md | 21 +- .../internal-architecture/pipeline-map.md | 2 +- prik/parsers/fortran/parser.py | 3849 ++++++++--------- ...est_procedure_and_interface_regressions.py | 7 +- .../parsing/test_developer_tutorial.py | 22 +- ...test_real_world_interaction_regressions.py | 4 +- ...source_form_and_diagnostics_regressions.py | 81 +- 7 files changed, 1960 insertions(+), 2026 deletions(-) diff --git a/docs/developer/fortran-parser-reference.md b/docs/developer/fortran-parser-reference.md index e7ec8d7f3..7f5fc7bbb 100644 --- a/docs/developer/fortran-parser-reference.md +++ b/docs/developer/fortran-parser-reference.md @@ -180,13 +180,14 @@ end module m The parser handles it in this order: -1. `parse_file` preprocesses the source and calls `_helper_slice_child_units` - at file scope. The result is one `ModuleUnit` carrying the module name, - lines, and source locations. +1. `parse_file` preprocesses the source and asks the stateless + `_SourceUnitScanner.slice_child_units` collaborator to scan at file scope. + The result is one `ModuleUnit` carrying the module name, exact lines, and + source locations. The scanner receives the parent kind, not `_ParserScope`. 2. the shared `ClassVisitor._visit` dispatcher selects `_visit_ModuleUnit`. -3. `_visit_ModuleUnit` creates a module `_ParserScope`, calls - `_helper_split_unit_parts`, and sends only the module specification lines to - `_parse_specification_part`. +3. `_visit_ModuleUnit` creates a module `_ParserScope`, asks + `_SourceUnitScanner.split_unit_parts` for the structural regions, and sends + only the module specification lines to `_parse_specification_part`. 4. `_parse_specification_part` uses the shared declaration backend: `_helper_parse_declaration_line` parses `integer, parameter :: n = 4`, then `_helper_push_declaration_to_scope` appends the resulting parameter variable @@ -205,6 +206,14 @@ two modules can each define `type :: state` without conflict, while two same-level `module m` declarations or two same-level contained procedures with the same name are rejected by `_helper_validate_sibling_units`. +The ownership boundary is deliberate: `_SourceUnitScanner` recognizes unit +openers and terminators, matches nested boundaries, and separates +specification, execution, and `contains` regions. `FortranParser` owns scopes, +model visitors, declaration parsing, sibling validation, and diagnostics that +depend on constructed parser models. Splitting the scanner into another file +would not strengthen that boundary; its private source tuples, grammar records, +and unit classes are all local to this parser module. + End-name validation is strict for structural units whose names define exported scope boundaries, such as modules, submodules, programs, interfaces, and derived types. Procedure end-name mismatches are still tolerated while slicing diff --git a/docs/maintainer/internal-architecture/pipeline-map.md b/docs/maintainer/internal-architecture/pipeline-map.md index 384e78c2d..37fbed643 100644 --- a/docs/maintainer/internal-architecture/pipeline-map.md +++ b/docs/maintainer/internal-architecture/pipeline-map.md @@ -40,7 +40,7 @@ PRIK_C_DOCS_END --> | CLI request | `prik/cli.py` | source paths and stage flags | selected stage or wrapper build options | `tests/fortran/command_line_interface/pipeline/` | | Build orchestration | `prik/pipeline/build.py` | ordered Fortran sources or `.pyi` contracts plus explicit native artifacts | `WrapperBuildResult`, `NativeBuildPlan`, and generated artifact plan | wrapper build-mode tests | | Preprocessing | `prik/pipeline/preprocessing.py` | source path, compiler config | preprocessed source and dependency facts | preprocessing tests | -| Parser project model | `prik/parsers/fortran/parser.py` | preprocessed Fortran source | parser project with modules, procedures, types, visibility | Fortran parser fixture tests | +| Parser project model | `prik/parsers/fortran/parser.py` (`_SourceUnitScanner` for structural boundaries/regions; `FortranParser` for scopes and model construction) | preprocessed Fortran source | parser project with modules, procedures, types, visibility | Fortran parser fixture tests | | Target probes | `prik/probes/fortran_types.py` | semantic type requirements and compiler flags | resolved kind/storage facts | Fortran type probe tests | | Semantic IR | `prik/semantics/fortran2ir.py` | parser project and target facts | `SemanticModule` objects | semantic Fortran tests | | Semantic policy completion | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py` | full semantic modules with signatures and `.pyi` overrides | semantic modules annotated with every ownership, transfer, destruction, mutability, storage, accessor, and projection decision needed by wrapper generation | ownership-policy tests | diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index 718d13c7a..d04c9b54f 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -1,17 +1,21 @@ """Wrapper-oriented Fortran parser with class-based grammar-unit visitors. Read the module-level wrappers at the bottom first, then `FortranParser` -public parse entrypoints, source-unit visitors, `_helper_*` parsing methods, and -finally low-level lexical/static utilities. The detailed maintainer guide -below documents the same file order with a control-flow example. +public parse entrypoints and source-unit visitors. `_SourceUnitScanner` owns +structural boundary and grammar-region recognition; the parser's `_helper_*` +methods own model construction and validation orchestration. The detailed +maintainer guide below documents the same control flow. """ from __future__ import annotations import re +from collections.abc import Mapping from copy import deepcopy from dataclasses import dataclass, field as dataclass_field, replace from pathlib import Path +from types import MappingProxyType +from typing import ClassVar from prik.utilities.declaration_expressions import ( evaluate_integer_expression, @@ -46,7 +50,8 @@ Parser architecture quick guide =============================== -This module is intentionally centered on two public surfaces: +This module is intentionally centered on two public surfaces and one private +structural collaborator: 1) `FortranParser` - Stateful orchestration layer that runs block parsers, owns parser helper @@ -56,18 +61,24 @@ - Small convenience entrypoints (`parse_fortran_file`, `parse_fortran_project`) backed by one default parser instance. +3) `_SourceUnitScanner` + - Stateless recognition of source-unit boundaries, direct child substrings, + and specification/execution/contains regions. It never receives a parser + scope or constructs parser models. + Recommended reading order for maintainers: - Start from the module-level public wrappers (`parse_fortran_file`, `parse_fortran_project`) - Then read `FortranParser.parse_file` / `parse_project` - Then read the high-level `_visit_` methods at the top of the class -- Then drill into `_helper_*` implementations and low-level helpers +- Then read `_SourceUnitScanner` for structural source slicing +- Then drill into parser `_helper_*` implementations for model construction `FortranParser` class layout (top -> bottom): - Public `parse_*` entrypoints. - One `_visit_` handler per grammar-level source-unit model. -- Source preparation, preprocessor handling, source-unit slicing, and unit - grammar helpers. +- Source preparation and preprocessor handling, delegating structural slicing + to `_SourceUnitScanner`. - Header parsers, scope/state helpers, specification visitors, declaration parsing, finalization, and kind/shape resolution. - Project diagnostics and general lexical/static utilities. @@ -83,10 +94,10 @@ end subroutine scale end module m -`parse_file` preprocesses lines, validates obvious malformed headers, and calls -`_helper_slice_child_units` to create one file-level `ModuleUnit`. The -`_visit_ModuleUnit` handler then receives only that substring, -splits it into header/specification/contains with `_helper_split_unit_parts`, +`parse_file` preprocesses lines, validates obvious malformed headers, and asks +`_SourceUnitScanner.slice_child_units` to create one file-level `ModuleUnit`. +The `_visit_ModuleUnit` handler then receives only that substring and asks +`_SourceUnitScanner.split_unit_parts` for header/specification/contains regions, visits the module specification part in a module scope, and recursively slices its direct children. The contained procedure is dispatched to `_visit_ProcedureUnit`, which creates a procedure scope and visits only its @@ -311,1730 +322,1999 @@ class _UnitParts: footer: tuple[str, int | None, str | None] | None -@dataclass -class _ParsedFileUnits: - """Accumulate visited file-level models before building ``FortranFile``. - - Interface procedures remain attached to their interface rather than the - standalone-procedure list; all other collections preserve source order. - """ - - modules: list[FortranModule] - submodules: list[FortranSubmodule] - programs: list[FortranProgram] - block_data_units: list[FortranBlockData] - procedures: list[FortranProcedureSignature] - interfaces: list[FortranInterface] - derived_types: list[FortranDerivedType] +def _raise_invalid_fortran_syntax_line( + line: str, + *, + context: str, + filename: str | None, + lineno: int | None, + source_line: str | None, +) -> None: + """Raise the shared invalid-syntax diagnostic for one source line.""" + raise FortranParseError( + f"Invalid Fortran syntax in {context}: {line.strip()}", + filename=filename, + line_number=lineno, + source_line=source_line, + code="PARSE_INVALID_SYNTAX", + ) -# ----------------------------------------------------------------------------- -# Compile-time expression and symbol resolution -# ----------------------------------------------------------------------------- +class _SourceUnitScanner: + """Identify source-unit boundaries and split units into grammar regions. + The scanner is deliberately stateless. It recognizes lexical structure and + returns exact :class:`SourceUnit` substrings or :class:`_UnitParts`; it does + not construct semantic models, mutate parser scopes, or decide which parsed + declarations belong in the wrapper-facing representation. + """ -class _CompileTimeResolver: - """Resolve compile-time expressions against one immutable symbol snapshot.""" + _GRAMMARS: ClassVar[Mapping[str, _UnitGrammar]] = MappingProxyType( + { + "module": _UnitGrammar( + kind="module", + has_contains_part=True, + declaration_role="module_variable", + ), + "submodule": _UnitGrammar( + kind="submodule", + has_contains_part=True, + declaration_role="module_variable", + ), + "program": _UnitGrammar( + kind="program", + has_execution_part=True, + has_contains_part=True, + ignores_contains_children=True, + declaration_role="module_variable", + ), + "procedure": _UnitGrammar( + kind="procedure", + has_execution_part=True, + has_contains_part=True, + ignores_contains_children=True, + declaration_role="procedure_symbol", + ), + "derived_type": _UnitGrammar( + kind="derived_type", + has_contains_part=True, + declaration_role="type_field", + ), + "interface": _UnitGrammar(kind="interface"), + "block_data": _UnitGrammar(kind="block_data", declaration_role="module_variable"), + "file": _UnitGrammar(kind="file", has_contains_part=True), + } + ) - def __init__(self, symbols: dict[str, str]): - """Normalize symbol names and initialize the expression cache.""" - self.symbols = {name.lower(): str(value) for name, value in symbols.items()} - self.cache: dict[tuple[str, bool], str] = {} + @classmethod + def grammar(cls, kind: str) -> _UnitGrammar: + """Return the immutable grammar profile for one source-unit kind.""" + return cls._GRAMMARS.get(kind, _UnitGrammar(kind=kind)) - def resolve(self, expr: str, prefer_symbolic: bool = True, resolving: frozenset[str] = frozenset()) -> str: - """Resolve symbols in one expression and fold integer-only results.""" - text = expr.strip() - if not text: - return expr - cache_key = (text, prefer_symbolic) - if cache_key in self.cache: - return self.cache[cache_key] + def slice_child_units( + self, + lines: _PreprocessedLines, + *, + parent_kind: str, + allowed_kinds: set[str] | None = None, + filename: str | None = None, + skip_execution_region: bool = False, + ) -> list[SourceUnit]: + """Return exact substrings for the direct children of one parent. - parts = split_top_level_expression(text, ":") - if len(parts) > 1: - resolved = ":".join(self.resolve(p, prefer_symbolic=prefer_symbolic) if p.strip() else p for p in parts) - self.cache[cache_key] = resolved - return resolved + ``parent_kind`` supplies only the structural grammar context needed to + interpret interface declarations. Parser scopes and semantic models do + not cross this boundary. When ``skip_execution_region`` is true, unit- + like text after execution begins is intentionally left opaque. + """ + units: list[SourceUnit] = [] + index = 0 + region = "specification" + while index < len(lines): + line, lineno, _ = lines[index] + stripped = line.strip() + if stripped.startswith("#"): + index += 1 + continue + if skip_execution_region: + if self.is_contains_transition(stripped): + region = "contains" + index += 1 + continue + if region == "specification" and self.is_executable_statement_start(stripped): + region = "execution" + if region == "execution": + index += 1 + continue + if parent_kind == "interface" and re.match(r"^module\s+procedure\b", stripped, re.IGNORECASE): + index += 1 + continue + start = self.classify_unit_start(line) + if start is None: + index += 1 + continue + kind, name = start + if allowed_kinds is not None and kind not in allowed_kinds: + index += 1 + continue - replaced = text - max_passes = max(8, len(self.symbols) * 2) - for _ in range(max_passes): - changed = False + end_index = self.find_unit_end(lines, index, kind, filename=filename) + if end_index is None: + if kind == "interface" and (lines[index][2] or "").strip().lower().startswith("end interface"): + index += 1 + continue + if parent_kind == "interface" and kind == "procedure": + # A procedure declaration in an interface may be closed by + # `end interface` instead of its own explicit terminator. + end_index = len(lines) - 1 + else: + label = self.unit_label(kind) + raise FortranParseError( + f"Missing end {label} for {label} '{name or ''}'.", + filename=filename, + line_number=lineno, + source_line=lines[index][2], + code="PARSE_MISSING_UNIT_END", + ) - def replace_symbol(match: re.Match[str]) -> str: - """Replace one resolvable symbol while detecting cycles.""" - nonlocal changed - token = match.group(0) - key = token.lower() - if key not in self.symbols or key in resolving: - return token - resolved_value = self.resolve( - self.symbols[key], - prefer_symbolic=False, - resolving=resolving | {key}, + units.append( + _SOURCE_UNIT_TYPES[kind]( + kind=kind, + name=name, + lines=lines[index : end_index + 1], + start_line=lineno, + end_line=lines[end_index][1], ) - if prefer_symbolic and FortranParser._safe_eval_int_expr(resolved_value) is None: - return token - changed = True - return f"({resolved_value})" - - updated = _REGEX["identifier"].sub(replace_symbol, replaced) - if not changed or updated == replaced: - break - replaced = updated + ) + index = end_index + 1 + return units - evaluated = FortranParser._safe_eval_int_expr(replaced) - resolved = str(evaluated) if evaluated is not None else (replaced if replaced != text else text) - self.cache[cache_key] = resolved - return resolved + def find_unit_end( + self, + lines: _PreprocessedLines, + start_index: int, + kind: str, + *, + filename: str | None = None, + ) -> int | None: + """Return the matching terminator index while respecting nested units.""" + start = self.classify_unit_start(lines[start_index][0]) + start_name = start[1] if start is not None else None + stack: list[tuple[str, str | None, int | None, str | None, str]] = [ + (kind, start_name, lines[start_index][1], lines[start_index][2], "specification") + ] + index = start_index + 1 + while index < len(lines): + line, lineno, source_line = lines[index] + line = line.strip() + if not line: + index += 1 + continue + current_kind, current_name, current_line, current_source, current_region = stack[-1] + if current_kind == "interface" and re.match(r"^module\s+procedure\b", line, re.IGNORECASE): + index += 1 + continue + closes_current, end_name = self.parse_unit_end(current_kind, line) + if closes_current: + if end_name and current_name and end_name.lower() != current_name.lower(): + if current_kind == "procedure" and self.has_preferred_unit_end_ahead( + lines, + index, + current_kind, + current_name, + ): + index += 1 + continue + label = self.unit_label(current_kind) + if current_kind != "procedure": + raise FortranParseError( + f"Mismatched end {label} name '{end_name}' for {label} '{current_name}'.", + filename=filename, + line_number=lineno, + source_line=source_line, + code="PARSE_MISMATCHED_UNIT_END", + ) + stack.pop() + if not stack: + return index + index += 1 + continue -class FortranParser(ClassVisitor): - """Stateful parser entrypoint and orchestration object. + grammar = self.grammar(current_kind) + if self.is_contains_transition(line) and grammar.has_contains_part: + stack[-1] = (current_kind, current_name, current_line, current_source, "contains") + index += 1 + continue + if ( + current_region == "specification" + and grammar.has_execution_part + and self.is_executable_statement_start(line) + ): + stack[-1] = (current_kind, current_name, current_line, current_source, "execution") + index += 1 + continue + if current_region == "execution": + index += 1 + continue - Raw parser entrypoints preserve all CPP branch alternatives. Branch - selection belongs to the compiler preprocessing layer. + start = self.classify_unit_start(line) + if start is not None and self.has_unit_end_ahead(lines, index, start[0]): + nested_kind, nested_name = start + stack.append((nested_kind, nested_name, lineno, source_line, "specification")) + index += 1 + continue - Parsing pipeline used by `parse_file`: - 1. Preprocess source into normalized lines (`_preprocessed_lines`). - 2. Slice direct file-level source units (`module`, `submodule`, - `program`, standalone `procedure`, `block data`, file-level - `interface`, and file-level derived type). - 3. Dispatch each `SourceUnit` through its `_visit_` handler. - 4. Each unit visitor parses only that unit's own substring, builds its own - `_ParserScope`, splits the unit into grammar regions, visits the - specification part, and recursively slices direct children where the - grammar allows them. - 5. Shared declaration helpers push variables, procedure symbols, and type - fields into the active scope model. - 6. Build `FortranFile` symbol table and standalone entity lists. + for open_kind, _open_name, _open_line, _open_source, _open_region in reversed(stack): + closes_open, _end_name = self.parse_unit_end(open_kind, line) + if not closes_open: + continue + label = self.unit_label(current_kind) + expected = self.unit_label(open_kind) + raise FortranParseError( + f"Unexpected end {expected} while parsing {label} '{current_name or ''}'.", + filename=filename, + line_number=lineno, + source_line=source_line, + code="PARSE_UNEXPECTED_UNIT_END", + ) + index += 1 + return None - Class section map: - - Public parse entrypoints first. - - Unit visitors next (one `_visit_` method per unit model). - - Internal `_helper_*` methods after that (reusable scoped parsing logic). - - Lower-level declaration/header helpers and assembly utilities last. + def has_unit_end_ahead(self, lines: _PreprocessedLines, start_index: int, kind: str) -> bool: + """Return whether a candidate opener has a usable later terminator.""" + start = self.classify_unit_start(lines[start_index][0]) + start_name = start[1] if start is not None else None + if self.has_preferred_unit_end_ahead(lines, start_index, kind, start_name): + return True + if kind != "procedure": + return False + return any(self.parse_unit_end(kind, lines[index][0])[0] for index in range(start_index + 1, len(lines))) - Scope behavior summary: - - `_ParserScope` is passed explicitly into shared helpers; there is no - ambient `current_module` or interface stack. - - Module/submodule scopes own contained procedures, interfaces, and derived - types; program and block-data scopes collect their specification - variables only. - - Procedure scopes parse only wrapper-relevant specification declarations; - execution statements and internal procedures after `contains` are - ignored, except procedure-local interfaces are revisited to type callback - dummy arguments. - - Derived-type scopes parse fields in the specification region and - type-bound procedure/generic bindings in the `contains` region. - - Same-level unit names are validated by the slicer, while identical names - in different scopes remain valid. - - `parse_project` composes multiple `FortranFile` objects into one - `FortranProject` registry and validates duplicate symbols by scope. - """ - - # ------------------------------------------------------------------ - # Public parse entrypoints - # ------------------------------------------------------------------ - - def parse_file( - self, - source_or_path: str | Path, - filename: str | None = None, - encoding: str = "utf-8", - ) -> FortranFile: - """Parse one source string or path into a ``FortranFile`` model. - - Use this primary entrypoint for one Fortran translation unit. A path - is read with ``encoding`` when ``filename`` is omitted; otherwise the - input is treated as source text and ``filename`` supplies diagnostic - provenance. The returned parse-only model feeds project parsing or - semantic conversion and raises :class:`FortranParseError` for malformed - or unsupported wrapper-relevant syntax. - """ - - # Stage 1: obtain normalized input and slice direct file-level units. - code, filename = self._helper_read_source(source_or_path, filename, encoding) - lines, root_scope, top_units = self._helper_prepare_source_units(code, filename) - - # Stage 2: visit each unit and attach cross-unit parser facts. - units = self._helper_parse_file_units(top_units, root_scope, filename) - self._helper_resolve_file_types(units) - interfaces = self._helper_attach_file_interfaces(lines, filename, units) - self._helper_resolve_file_kinds(lines, filename, units) - - # Stage 3: assemble the stable file model and its source metadata. - return self._helper_build_fortran_file(code, filename, encoding, units, interfaces) - - def parse_project( + def has_preferred_unit_end_ahead( self, - files: dict[str, str] | list[str | Path] | tuple[str | Path, ...] | str | Path, - *, - encoding: str = "utf-8", - ) -> FortranProject: - """Parse explicit sources or paths into one dependency-aware project. - - Use this after collecting a related set of files, or pass a directory - for the supported Fortran source forms. The parser preserves the file - models while resolving project-level kind references and indexing - modules, procedures, and types. Duplicate project symbols and source - failures raise :class:`FortranParseError`. - """ + lines: _PreprocessedLines, + start_index: int, + kind: str, + start_name: str | None, + ) -> bool: + """Return whether an exact or unnamed terminator exists later.""" + for index in range(start_index + 1, len(lines)): + matched, end_name = self.parse_unit_end(kind, lines[index][0]) + if matched and (not start_name or not end_name or end_name.lower() == start_name.lower()): + return True + return False - # Stage 1: parse each requested source in dependency-aware order. - parsed_files = self._helper_parse_project_files(files, encoding) + def split_unit_parts(self, unit: SourceUnit, *, filename: str | None = None) -> _UnitParts: + """Split one unit substring into specification, execution, and contains.""" + grammar = self.grammar(unit.kind) + header = unit.lines[0] if unit.lines else None + footer = unit.lines[-1] if unit.lines and self.unit_end_matches(unit.kind, unit.lines[-1][0]) else None + body = unit.lines[1:-1] if footer is not None else unit.lines[1:] + specification: _PreprocessedLines = [] + execution: _PreprocessedLines = [] + contains: _PreprocessedLines = [] + region = "specification" + index = 0 - # Stage 2: complete cross-file kinds and construct project indexes. - self._helper_resolve_project_kinds(parsed_files) - project = FortranProject(files=parsed_files) - for parsed_file in parsed_files: - self._helper_index_project_file(project, parsed_file) - return project + while index < len(body): + line, _, _ = body[index] + stripped = line.strip() + if not stripped: + index += 1 + continue + if self.is_contains_transition(stripped): + if not grammar.has_contains_part: + _raise_invalid_fortran_syntax_line( + stripped, + context=f"{self.unit_label(grammar.kind)} '{unit.name or ''}'", + filename=filename, + lineno=body[index][1], + source_line=body[index][2], + ) + region = "contains" + index += 1 + continue - def parse_module(self, code: _SourceOrLines, filename: str | None = None) -> FortranModule: - """Parse exactly one module unit from source text or normalized lines. + if grammar.kind == "interface" and re.match(r"^module\s+procedure\b", stripped, re.IGNORECASE): + specification.append(body[index]) + index += 1 + continue - Use this narrow entrypoint when the caller expects a single module - rather than a whole ``FortranFile``. Its result includes module - variables, imports, contained procedures, interfaces, and derived - types. Inputs with zero or multiple module units raise - :class:`FortranParseError`. + start = self.classify_unit_start(stripped) + if start is not None: + child_kind, _ = start + child_end = self.find_unit_end(body, index, child_kind, filename=filename) + if child_end is not None: + index = child_end + 1 + continue + if grammar.kind == "interface" and child_kind == "procedure": + break - Example: - >>> FortranParser().parse_module("module m\\nend module m\\n").name - 'm' - """ - _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) - module_units = [unit for unit in all_units if unit.kind == "module"] - if not module_units and any(unit.kind == "procedure" for unit in all_units): - raise FortranParseError( - "parse_module() expected a module program unit, but only standalone procedures were found", - filename=filename, - code="PARSE_WRONG_ENTRYPOINT", - ) - unit = self._expect_single_parse_result( - module_units, - parser_name="parse_module", - entity_name="module", - filename=filename, - ) - return self._visit(unit, parent_scope=root_scope, filename=filename) + if ( + region == "specification" + and grammar.has_execution_part + and self.is_executable_statement_start(stripped) + ): + region = "execution" - def parse_submodule(self, code: _SourceOrLines, filename: str | None = None) -> FortranSubmodule: - """Parse exactly one submodule from source text or normalized lines. + if region == "specification": + specification.append(body[index]) + elif region == "execution": + execution.append(body[index]) + else: + contains.append(body[index]) + index += 1 - Use this narrow entrypoint when a caller already knows the input is one - submodule. It returns the submodule's parent/ancestor metadata and - wrapper-relevant specification facts, rejecting zero or multiple - submodule units with :class:`FortranParseError`. - """ - _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) - unit = self._expect_single_parse_result( - [unit for unit in all_units if unit.kind == "submodule"], - parser_name="parse_submodule", - entity_name="submodule", - filename=filename, + return _UnitParts( + header=header, + specification=specification, + execution=execution, + contains=contains, + footer=footer, ) - return self._visit(unit, parent_scope=root_scope, filename=filename) - - def parse_interface(self, code: _SourceOrLines, filename: str | None = None) -> FortranInterface: - """Parse exactly one interface block and its procedure declarations. - Use this for an isolated interface source fragment. The returned - model preserves generic specifics and interface-only procedure facts; - source containing zero or multiple interface blocks raises - :class:`FortranParseError`. - """ - unit, scope = self._expect_single_parse_result( - self._collect_interface_source_units(code, filename), - parser_name="parse_interface", - entity_name="interface", - filename=filename, + def child_unit_region(self, unit: SourceUnit, parts: _UnitParts, child: SourceUnit) -> str: + """Return the parent grammar region containing one direct child.""" + child_line = child.start_line + if child_line is None: + return "specification" + contains_line = self.direct_contains_line(unit, filename=None) + if contains_line is not None and child_line > contains_line: + return "contains" + execution_line = next( + (lineno for _line, lineno, _source_line in parts.execution if lineno is not None), + None, ) - return self._visit(unit, parent_scope=scope, filename=filename) - - def parse_derived_type(self, code: _SourceOrLines, filename: str | None = None) -> FortranDerivedType: - """Parse exactly one derived type and its wrapper-relevant fields. + if execution_line is not None and child_line >= execution_line: + return "execution" + return "specification" - Use this for an isolated ``type`` definition or a containing source - with one discoverable derived type. The result includes inheritance, - fields, and type-bound declarations; ambiguous input raises - :class:`FortranParseError`. - """ - unit, scope = self._expect_single_parse_result( - self._collect_derived_type_source_units(code, filename), - parser_name="parse_derived_type", - entity_name="derived type", + def nonexecution_child_units(self, unit: SourceUnit, *, filename: str | None) -> list[SourceUnit]: + """Return direct nested units outside an intentionally opaque execution part.""" + grammar = self.grammar(unit.kind) + child_units = self.slice_child_units( + unit.lines[1:-1], + parent_kind=unit.kind, filename=filename, + skip_execution_region=grammar.has_execution_part, ) - return self._visit(unit, parent_scope=scope, filename=filename) + if not grammar.has_execution_part: + return child_units + parts = self.split_unit_parts(unit, filename=filename) + return [child for child in child_units if self.child_unit_region(unit, parts, child) != "execution"] - def parse_program(self, code: _SourceOrLines, filename: str | None = None) -> FortranProgram: - """Parse exactly one program unit and its specification declarations. + def direct_contains_line(self, unit: SourceUnit, *, filename: str | None) -> int | None: + """Return the direct ``contains`` line while skipping nested units.""" + body = unit.lines[1:-1] + index = 0 + while index < len(body): + line, lineno, _source_line = body[index] + stripped = line.strip() + if self.is_contains_transition(stripped): + return lineno + start = self.classify_unit_start(stripped) + if start is not None: + child_end = self.find_unit_end(body, index, start[0], filename=filename) + if child_end is not None: + index = child_end + 1 + continue + index += 1 + return None - Use this when inspecting a single main program. Executable statements - are intentionally not represented, while declarations, imports, and - supported enumerations become parser facts. Ambiguous input raises - :class:`FortranParseError`. - """ - _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) - unit = self._expect_single_parse_result( - [unit for unit in all_units if unit.kind == "program"], - parser_name="parse_program", - entity_name="program", - filename=filename, - ) - return self._visit(unit, parent_scope=root_scope, filename=filename) + @staticmethod + def parse_derived_type_start(line: str) -> tuple[str, list[str]] | None: + """Parse modern or legacy derived-type opening syntax.""" + stripped = line.strip() + match = _REGEX["derived_type"].match(stripped) + if match: + attr_text = (match.group("attrs") or "").strip().lstrip(",").strip() + attrs = [attr.strip() for attr in split_csv(attr_text)] if attr_text else [] + return match.group("name"), attrs + legacy = re.match(r"^type\s+(?P\w+)\s*$", stripped, re.IGNORECASE) + if legacy: + return legacy.group("name"), [] + return None - def parse_block_data(self, code: _SourceOrLines, filename: str | None = None) -> FortranBlockData: - """Parse exactly one block-data unit and its specification declarations. + @staticmethod + def parse_interface_header(line: str) -> tuple[bool, str | None]: + """Return whether ``line`` opens an interface and its optional name.""" + lower = line.lower() + if not (lower.startswith("interface") or lower.startswith("abstract interface")): + return False, None + parts = line.split(maxsplit=1) + name = parts[1].strip() if len(parts) > 1 and not lower.startswith("abstract interface") else None + return True, name - Use this narrow entrypoint for a single ``block data`` source unit. - It returns common-block and variable facts but no execution model, and - raises :class:`FortranParseError` when the input is not singular. - """ - _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) - unit = self._expect_single_parse_result( - [unit for unit in all_units if unit.kind == "block_data"], - parser_name="parse_block_data", - entity_name="block data unit", - filename=filename, - ) - return self._visit(unit, parent_scope=root_scope, filename=filename) - - # ------------------------------------------------------------------ - # Source unit visitors - # ------------------------------------------------------------------ + def classify_unit_start(self, line: str) -> tuple[str, str | None] | None: + """Recognize a source-unit opener without constructing its model.""" + stripped = line.strip() + if not stripped: + return None + lower = stripped.lower() + if lower.startswith("end "): + return None + for kind, pattern_key in ( + ("submodule", "submodule"), + ("module", "module"), + ("program", "program"), + ("block_data", "block_data"), + ): + match = _REGEX[pattern_key].match(stripped) + if match: + return kind, match.group("name") + if lower == "enum" or lower.startswith("enum,"): + return "enum", None + starts_interface, interface_name = self.parse_interface_header(stripped) + if starts_interface: + return "interface", interface_name + module_procedure = _REGEX["module_procedure_impl"].match(stripped) + if module_procedure: + return "procedure", module_procedure.group("name") + if self.looks_like_procedure_header(stripped): + procedure = _REGEX["procedure"].match(stripped) or _REGEX["function"].match(stripped) + if procedure: + return "procedure", procedure.group("name") + parsed_type = self.parse_derived_type_start(stripped) + if parsed_type: + return "derived_type", parsed_type[0] + return None - def _visit_ModuleUnit( - self, - unit: ModuleUnit, - *, - parent_scope: _ParserScope, - filename: str | None, - ) -> FortranModule: - """Visit a sliced `module ... end module` unit.""" - header = unit.lines[0] - module = self._parse_module_header(header[0].strip(), filename, lineno=header[1], source_line=header[2]) - if module is None: # pragma: no cover - slicer only dispatches module units with module headers. - raise FortranParseError( - "Expected module unit.", - filename=filename, - line_number=header[1], - source_line=header[2], - code="PARSE_EXPECTED_UNIT", - ) - scope = self._helper_scope_for_model("module", module, parent=parent_scope) - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("module"), filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) + @staticmethod + def parse_unit_end(kind: str, line: str) -> tuple[bool, str | None]: + """Return whether ``line`` closes ``kind`` and its optional end name.""" + stripped = line.strip() + lower = stripped.lower() + patterns = { + "module": r"^end\s+module(?:\s+(?P\w+))?\s*$", + "submodule": r"^end\s+submodule(?:\s+(?P\w+))?\s*$", + "program": r"^end\s+program(?:\s+(?P\w+))?\s*$", + "interface": r"^end\s+interface(?:\s+(?P.+?))?\s*$", + "derived_type": r"^end\s+type(?:\s+(?P\w+))?\s*$", + "procedure": r"^end\s+(?:subroutine|function|procedure)(?:\s+(?P\w+))?\s*$", + } + if kind in {"block_data", "procedure"} and lower == "end": + return True, None + if kind == "block_data": + pattern = r"^end\s+block\s+data(?:\s+(?P\w+))?\s*$" + elif kind == "enum": + return (bool(re.match(r"^end\s+enum\s*$", stripped, re.IGNORECASE)), None) + else: + pattern = patterns.get(kind) + if pattern is None: + return False, None + match = re.match(pattern, stripped, re.IGNORECASE) + return match is not None, match.group("name") if match else None - child_units = self._helper_slice_child_units(unit.lines[1:-1], parent_scope=scope, filename=filename) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._helper_validate_contains_lines(scope, parts.contains, filename=filename) - self._helper_validate_sibling_units(child_units, parent_scope=scope, filename=filename) - self._populate_module_like_children(module, child_units, scope=scope, filename=filename) - self._validate_module_variables(module, filename) - self._apply_module_visibility(module, filename) - return module + @staticmethod + def unit_label(kind: str) -> str: + """Return a human-readable source-unit kind for diagnostics.""" + return kind.replace("_", " ") - def _visit_SubmoduleUnit( - self, - unit: SubmoduleUnit, - *, - parent_scope: _ParserScope, - filename: str | None, - ) -> FortranSubmodule: - """Visit a sliced `submodule (...) name ... end submodule` unit.""" - header = unit.lines[0] - submodule = self._parse_submodule_header(header[0].strip(), filename) - if submodule is None: # pragma: no cover - slicer only dispatches submodule units with submodule headers. - raise FortranParseError( - "Expected submodule unit.", - filename=filename, - line_number=header[1], - source_line=header[2], - code="PARSE_EXPECTED_UNIT", - ) - scope = self._helper_scope_for_model("submodule", submodule, parent=parent_scope) - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("submodule"), filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) + @classmethod + def unit_end_matches(cls, kind: str, line: str) -> bool: + """Return whether ``line`` closes a unit of ``kind``.""" + matched, _ = cls.parse_unit_end(kind, line) + return matched - child_units = self._helper_slice_child_units(unit.lines[1:-1], parent_scope=scope, filename=filename) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._helper_validate_contains_lines(scope, parts.contains, filename=filename) - self._helper_validate_sibling_units(child_units, parent_scope=scope, filename=filename) - self._populate_module_like_children(submodule, child_units, scope=scope, filename=filename) - self._validate_module_variables(submodule, filename) - return submodule + @staticmethod + def is_contains_transition(line: str) -> bool: + """Return whether ``line`` starts a unit's ``contains`` region.""" + return line.lower() == "contains" - def _parse_children_of_type(self, child_units, unit_type, *, scope, filename): - """Visit direct children of one source-unit model class.""" - return [ - self._visit(child, parent_scope=scope, filename=filename) - for child in child_units - if isinstance(child, unit_type) - ] + @staticmethod + def looks_like_procedure_header(line: str) -> bool: + """Return whether a line resembles a subroutine or function header.""" + stripped = line.strip() + if not stripped: + return False + lowered = stripped.lower() + if lowered.startswith(("end ", "call ")): + return False + without_strings = re.sub(r"'[^']*'|\"[^\"]*\"", "", stripped) + return bool(re.search(r"(?:^|[\s,])(?:subroutine|function)\s+[A-Za-z_]\w*", without_strings, re.IGNORECASE)) @staticmethod - def _belongs_to_module_like(item, target, *, exclude_interface: bool = False) -> bool: - """Return whether one visited model belongs to a module-like owner. + def is_openmp_directive(line: str) -> bool: + """Return whether a line begins an OpenMP sentinel directive.""" + return line.lstrip().lower().startswith("!$omp") - Ownership comparison is case-insensitive, matching Fortran naming. - ``exclude_interface`` keeps interface procedure signatures attached to - their interface instead of duplicating them in ``target.procedures``. - """ - belongs = bool(item.module and item.module.lower() == target.name.lower()) - return belongs and not (exclude_interface and item.in_interface) + @classmethod + def is_openmp_declarative_directive(cls, line: str) -> bool: + """Return whether an OpenMP directive belongs in a specification part.""" + directive = line.lstrip()[5:].strip().lower() if cls.is_openmp_directive(line) else "" + return directive.startswith( + ( + "threadprivate", + "declare simd", + "declare target", + "declare reduction", + "requires", + "declare mapper", + ) + ) - def _populate_module_like_children(self, target, child_units, *, scope, filename) -> None: - """Visit direct children and append the ones owned by ``target``. + @classmethod + def looks_like_declaration_or_spec(cls, line: str) -> bool: + """Return whether a line resembles a specification-part statement.""" + stripped = line.strip() + if not stripped: + return False + lowered = stripped.lower() + if cls.is_openmp_directive(stripped): + return cls.is_openmp_declarative_directive(stripped) + first_match = re.match(r"([a-z_][a-z0-9_]*)", lowered) + first = first_match.group(1) if first_match else lowered.split(None, 1)[0].rstrip(",") + if first in { + "do", + "if", + "where", + "call", + "select", + "case", + "allocate", + "deallocate", + "print", + "write", + "read", + "return", + "stop", + "cycle", + "exit", + "continue", + "end", + "else", + "elseif", + "contains", + "goto", + "go", + "format", + }: + return False + if "::" in stripped or "," in stripped: + return True + return bool(re.match(r"^[A-Za-z_]\w+\s+[A-Za-z_]\w*", stripped)) - The method preserves source order within each child category and shares - the caller's scope/filename for diagnostics. Interface-contained - procedure declarations are deliberately excluded from a module's - standalone procedure collection. - """ - signatures = self._parse_children_of_type(child_units, ProcedureUnit, scope=scope, filename=filename) - types = self._parse_children_of_type(child_units, DerivedTypeUnit, scope=scope, filename=filename) - interfaces = self._parse_children_of_type(child_units, InterfaceUnit, scope=scope, filename=filename) - enums = self._parse_children_of_type(child_units, EnumUnit, scope=scope, filename=filename) - target.procedures.extend( - item for item in signatures if self._belongs_to_module_like(item, target, exclude_interface=True) - ) - target.derived_types.extend(item for item in types if self._belongs_to_module_like(item, target)) - target.interfaces.extend(item for item in interfaces if self._belongs_to_module_like(item, target)) - target.enums.extend(item for item in enums if self._belongs_to_module_like(item, target)) + @staticmethod + def is_statement_function_statement(line: str) -> bool: + """Return whether a line has legacy statement-function syntax.""" + return bool(re.match(r"^[A-Za-z_]\w*\s*\([^()]*\)\s*=", line.strip(), flags=re.IGNORECASE)) - def _visit_ProgramUnit( - self, - unit: ProgramUnit, - *, - parent_scope: _ParserScope, - filename: str | None, - ) -> FortranProgram: - """Visit a sliced `program ... end program` unit.""" - header = unit.lines[0] - program = self._parse_program_header(header[0].strip(), filename) - if program is None: # pragma: no cover - slicer only dispatches program units with program headers. - raise FortranParseError( - "Expected program unit.", - filename=filename, - line_number=header[1], - source_line=header[2], - code="PARSE_EXPECTED_UNIT", + @staticmethod + def is_ignored_spec_statement(line: str) -> bool: + """Return whether a recognized specification statement needs no model.""" + return bool( + _REGEX["include"].match(line) + or re.match( + r"^(implicit|save|common|data|equivalence|external|intrinsic|parameter|namelist|entry)\b", + line, + flags=re.IGNORECASE, ) - scope = self._helper_scope_for_model("program", program, parent=parent_scope) - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("program"), filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) - child_units = self._helper_nonexecution_child_units(unit, parent_scope=scope, filename=filename) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._helper_validate_contains_lines(scope, parts.contains, filename=filename) - self._helper_validate_ignored_child_units( - [child for child in child_units if child.kind != "enum"], - parent_scope=scope, - filename=filename, - unit=unit, - parts=parts, - ) - program.enums.extend( - self._visit(child, parent_scope=scope, filename=filename) for child in child_units if child.kind == "enum" ) - self._validate_variable_declarations( - program.variables, - owner_kind="program", - owner_name=program.name, - filename=filename, - ) - return program - def _visit_BlockDataUnit( - self, - unit: BlockDataUnit, - *, - parent_scope: _ParserScope, - filename: str | None, - ) -> FortranBlockData: - """Visit a sliced `block data ... end block data` unit.""" - header = unit.lines[0] - block_data = self._parse_block_data_header(header[0].strip(), filename) - if block_data is None: # pragma: no cover - slicer only dispatches block-data units with block-data headers. - raise FortranParseError( - "Expected block data unit.", - filename=filename, - line_number=header[1], - source_line=header[2], - code="PARSE_EXPECTED_UNIT", - ) - scope = self._helper_scope_for_model("block_data", block_data, parent=parent_scope) - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("block_data"), filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) - child_units = self._helper_slice_child_units(unit.lines[1:-1], parent_scope=scope, filename=filename) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._validate_variable_declarations( - block_data.variables, - owner_kind="block data", - owner_name=block_data.name, - filename=filename, - ) - return block_data - - def _visit_DerivedTypeUnit( - self, - unit: DerivedTypeUnit, - *, - parent_scope: _ParserScope, - filename: str | None, - ) -> FortranDerivedType: - """Visit a sliced derived-type definition.""" - header = unit.lines[0] - dtype = self._init_derived_type(header[0].strip(), current_module=parent_scope.module_owner) - if dtype is None: # pragma: no cover - slicer only dispatches derived-type units with type headers. - raise FortranParseError( - "Expected derived-type unit.", - filename=filename, - line_number=header[1], - source_line=header[2], - code="PARSE_EXPECTED_UNIT", - ) - scope = self._helper_scope_for_model("derived_type", dtype, parent=parent_scope) - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("derived_type"), filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) - for line, lineno, source_line in parts.contains: - stripped = line.strip() + @classmethod + def is_executable_statement_start(cls, line: str) -> bool: + """Return whether a line starts execution rather than specification.""" + stripped = line.strip() + if not stripped: # pragma: no cover - callers skip blanks before this check. + return False + labeled = re.match(r"^\d+\s+(?P.*)$", stripped) + if labeled: + stripped = labeled.group("body").strip() if not stripped: - continue - self._parse_derived_type_contains_line( - stripped, - dtype, - filename=filename, - lineno=lineno, - source_line=source_line, + return False + lowered = stripped.lower() + if cls.is_openmp_directive(stripped): + return not cls.is_openmp_declarative_directive(stripped) + if _REGEX["use"].match(stripped) is not None or cls.is_ignored_spec_statement(stripped): + return False + first_match = re.match(r"([a-z_][a-z0-9_]*)", lowered) + first = first_match.group(1) if first_match else lowered.split(None, 1)[0] + if first.isdigit(): + return False + if first in { + "do", + "if", + "where", + "call", + "select", + "case", + "allocate", + "deallocate", + "print", + "write", + "read", + "return", + "stop", + "cycle", + "exit", + "continue", + "goto", + "go", + "open", + "close", + "rewind", + "backspace", + "inquire", + "flush", + "wait", + "nullify", + "associate", + "block", + "forall", + "error", + "pause", + }: + return True + if _REGEX["legacy_parameter"].match(stripped) or cls.is_statement_function_statement(stripped): + return False + if "=" in stripped and "::" not in stripped: + return not ( + _REGEX["char_star"].match(stripped) + or _REGEX["type"].match(stripped) + or _REGEX["type_field"].match(stripped) + or _REGEX["class_field"].match(stripped) ) - child_units = self._helper_slice_child_units(unit.lines[1:-1], parent_scope=scope, filename=filename) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._validate_derived_type_fields(dtype, filename) - return dtype + return False - def _visit_EnumUnit( + +@dataclass +class _ParsedFileUnits: + """Accumulate visited file-level models before building ``FortranFile``. + + Interface procedures remain attached to their interface rather than the + standalone-procedure list; all other collections preserve source order. + """ + + modules: list[FortranModule] + submodules: list[FortranSubmodule] + programs: list[FortranProgram] + block_data_units: list[FortranBlockData] + procedures: list[FortranProcedureSignature] + interfaces: list[FortranInterface] + derived_types: list[FortranDerivedType] + + +# ----------------------------------------------------------------------------- +# Compile-time expression and symbol resolution +# ----------------------------------------------------------------------------- + + +class _CompileTimeResolver: + """Resolve compile-time expressions against one immutable symbol snapshot.""" + + def __init__(self, symbols: dict[str, str]): + """Normalize symbol names and initialize the expression cache.""" + self.symbols = {name.lower(): str(value) for name, value in symbols.items()} + self.cache: dict[tuple[str, bool], str] = {} + + def resolve(self, expr: str, prefer_symbolic: bool = True, resolving: frozenset[str] = frozenset()) -> str: + """Resolve symbols in one expression and fold integer-only results.""" + text = expr.strip() + if not text: + return expr + cache_key = (text, prefer_symbolic) + if cache_key in self.cache: + return self.cache[cache_key] + + parts = split_top_level_expression(text, ":") + if len(parts) > 1: + resolved = ":".join(self.resolve(p, prefer_symbolic=prefer_symbolic) if p.strip() else p for p in parts) + self.cache[cache_key] = resolved + return resolved + + replaced = text + max_passes = max(8, len(self.symbols) * 2) + for _ in range(max_passes): + changed = False + + def replace_symbol(match: re.Match[str]) -> str: + """Replace one resolvable symbol while detecting cycles.""" + nonlocal changed + token = match.group(0) + key = token.lower() + if key not in self.symbols or key in resolving: + return token + resolved_value = self.resolve( + self.symbols[key], + prefer_symbolic=False, + resolving=resolving | {key}, + ) + if prefer_symbolic and FortranParser._safe_eval_int_expr(resolved_value) is None: + return token + changed = True + return f"({resolved_value})" + + updated = _REGEX["identifier"].sub(replace_symbol, replaced) + if not changed or updated == replaced: + break + replaced = updated + + evaluated = FortranParser._safe_eval_int_expr(replaced) + resolved = str(evaluated) if evaluated is not None else (replaced if replaced != text else text) + self.cache[cache_key] = resolved + return resolved + + +class FortranParser(ClassVisitor): + """Stateful parser entrypoint and orchestration object. + + Raw parser entrypoints preserve all CPP branch alternatives. Branch + selection belongs to the compiler preprocessing layer. + + Parsing pipeline used by `parse_file`: + 1. Preprocess source into normalized lines (`_preprocessed_lines`). + 2. Slice direct file-level source units (`module`, `submodule`, + `program`, standalone `procedure`, `block data`, file-level + `interface`, and file-level derived type). + 3. Dispatch each `SourceUnit` through its `_visit_` handler. + 4. Each unit visitor parses only that unit's own substring, builds its own + `_ParserScope`, splits the unit into grammar regions, visits the + specification part, and recursively slices direct children where the + grammar allows them. + 5. Shared declaration helpers push variables, procedure symbols, and type + fields into the active scope model. + 6. Build `FortranFile` symbol table and standalone entity lists. + + Class section map: + - Public parse entrypoints first. + - Unit visitors next (one `_visit_` method per unit model). + - Internal `_helper_*` methods after that (reusable scoped parsing logic). + - Lower-level declaration/header helpers and assembly utilities last. + + Scope behavior summary: + - `_ParserScope` is passed explicitly into shared helpers; there is no + ambient `current_module` or interface stack. + - Module/submodule scopes own contained procedures, interfaces, and derived + types; program and block-data scopes collect their specification + variables only. + - Procedure scopes parse only wrapper-relevant specification declarations; + execution statements and internal procedures after `contains` are + ignored, except procedure-local interfaces are revisited to type callback + dummy arguments. + - Derived-type scopes parse fields in the specification region and + type-bound procedure/generic bindings in the `contains` region. + - Same-level unit names are validated by the slicer, while identical names + in different scopes remain valid. + + `parse_project` composes multiple `FortranFile` objects into one + `FortranProject` registry and validates duplicate symbols by scope. + """ + + # ------------------------------------------------------------------ + # Public parse entrypoints + # ------------------------------------------------------------------ + + def __init__(self) -> None: + """Create the stateless structural scanner used by parser entrypoints.""" + self._source_unit_scanner = _SourceUnitScanner() + + def parse_file( self, - unit: EnumUnit, - *, - parent_scope: _ParserScope, - filename: str | None, - ) -> FortranEnum: - """Visit an `enum, bind(C)` unit and preserve enumerator constants.""" - return self._helper_parse_enum_unit(unit, filename=filename, module_owner=parent_scope.module_owner) + source_or_path: str | Path, + filename: str | None = None, + encoding: str = "utf-8", + ) -> FortranFile: + """Parse one source string or path into a ``FortranFile`` model. - def _visit_InterfaceUnit( + Use this primary entrypoint for one Fortran translation unit. A path + is read with ``encoding`` when ``filename`` is omitted; otherwise the + input is treated as source text and ``filename`` supplies diagnostic + provenance. The returned parse-only model feeds project parsing or + semantic conversion and raises :class:`FortranParseError` for malformed + or unsupported wrapper-relevant syntax. + """ + + # Stage 1: obtain normalized input and slice direct file-level units. + code, filename = self._helper_read_source(source_or_path, filename, encoding) + lines, root_scope, top_units = self._helper_prepare_source_units(code, filename) + + # Stage 2: visit each unit and attach cross-unit parser facts. + units = self._helper_parse_file_units(top_units, root_scope, filename) + self._helper_resolve_file_types(units) + interfaces = self._helper_attach_file_interfaces(lines, filename, units) + self._helper_resolve_file_kinds(lines, filename, units) + + # Stage 3: assemble the stable file model and its source metadata. + return self._helper_build_fortran_file(code, filename, encoding, units, interfaces) + + def parse_project( self, - unit: InterfaceUnit, + files: dict[str, str] | list[str | Path] | tuple[str | Path, ...] | str | Path, *, - parent_scope: _ParserScope, - filename: str | None, - ) -> FortranInterface: - """Visit a sliced interface block.""" - header = unit.lines[0] - starts_interface, interface_name = self._parse_interface_header(header[0].strip()) - if not starts_interface: # pragma: no cover - slicer only dispatches interface units with interface headers. + encoding: str = "utf-8", + ) -> FortranProject: + """Parse explicit sources or paths into one dependency-aware project. + + Use this after collecting a related set of files, or pass a directory + for the supported Fortran source forms. The parser preserves the file + models while resolving project-level kind references and indexing + modules, procedures, and types. Duplicate project symbols and source + failures raise :class:`FortranParseError`. + """ + + # Stage 1: parse each requested source in dependency-aware order. + parsed_files = self._helper_parse_project_files(files, encoding) + + # Stage 2: complete cross-file kinds and construct project indexes. + self._helper_resolve_project_kinds(parsed_files) + project = FortranProject(files=parsed_files) + for parsed_file in parsed_files: + self._helper_index_project_file(project, parsed_file) + return project + + def parse_module(self, code: _SourceOrLines, filename: str | None = None) -> FortranModule: + """Parse exactly one module unit from source text or normalized lines. + + Use this narrow entrypoint when the caller expects a single module + rather than a whole ``FortranFile``. Its result includes module + variables, imports, contained procedures, interfaces, and derived + types. Inputs with zero or multiple module units raise + :class:`FortranParseError`. + + Example: + >>> FortranParser().parse_module("module m\\nend module m\\n").name + 'm' + """ + _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) + module_units = [unit for unit in all_units if unit.kind == "module"] + if not module_units and any(unit.kind == "procedure" for unit in all_units): raise FortranParseError( - "Expected interface unit.", + "parse_module() expected a module program unit, but only standalone procedures were found", filename=filename, - line_number=header[1], - source_line=header[2], - code="PARSE_EXPECTED_UNIT", + code="PARSE_WRONG_ENTRYPOINT", ) - interface = FortranInterface( - name=interface_name, - module=parent_scope.module_owner, - abstract=header[0].strip().lower().startswith("abstract interface"), + unit = self._expect_single_parse_result( + module_units, + parser_name="parse_module", + entity_name="module", + filename=filename, ) - scope = self._helper_scope_for_model("interface", interface, parent=parent_scope) - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("interface"), filename=filename) - self._helper_validate_interface_lines(scope, parts.specification, filename=filename) - interface.specific_procedures.extend(self._interface_specific_procedure_names(parts.specification)) - child_units = self._helper_slice_child_units( - unit.lines[1:-1], - parent_scope=scope, + return self._visit(unit, parent_scope=root_scope, filename=filename) + + def parse_submodule(self, code: _SourceOrLines, filename: str | None = None) -> FortranSubmodule: + """Parse exactly one submodule from source text or normalized lines. + + Use this narrow entrypoint when a caller already knows the input is one + submodule. It returns the submodule's parent/ancestor metadata and + wrapper-relevant specification facts, rejecting zero or multiple + submodule units with :class:`FortranParseError`. + """ + _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) + unit = self._expect_single_parse_result( + [unit for unit in all_units if unit.kind == "submodule"], + parser_name="parse_submodule", + entity_name="submodule", filename=filename, ) - for child in child_units: - if child.kind != "procedure": - self._raise_invalid_fortran_syntax_line( - child.lines[0][0] if child.lines else child.kind, - context=f"interface '{scope.name or ''}'", - filename=filename, - lineno=child.start_line, - source_line=child.lines[0][2] if child.lines else None, - ) - sig = self._visit(child, parent_scope=scope, filename=filename, in_interface=True) - self._add_interface_attribute(sig, interface.name) - interface.procedures.append(sig) - return interface + return self._visit(unit, parent_scope=root_scope, filename=filename) - @staticmethod - def _interface_specific_procedure_names(lines: _PreprocessedLines) -> list[str]: - """Collect specific procedure names declared by a generic interface.""" - names: list[str] = [] - for line, _, _ in lines: - stripped = line.strip() - module_procedure = re.match( - r"^module\s+procedure\s*(?:::)?\s*(?P.+)$", - stripped, - re.IGNORECASE, - ) - if module_procedure: - names.extend(name.strip() for name in split_csv(module_procedure.group("names"))) - continue - procedure = re.match( - r"^procedure(?:\s*\([^)]*\))?(?:\s*,\s*[^:]*)?\s*::\s*(?P.+)$", - stripped, - re.IGNORECASE, - ) - if procedure: - names.extend(name.strip() for name in split_csv(procedure.group("names"))) - return names + def parse_interface(self, code: _SourceOrLines, filename: str | None = None) -> FortranInterface: + """Parse exactly one interface block and its procedure declarations. + + Use this for an isolated interface source fragment. The returned + model preserves generic specifics and interface-only procedure facts; + source containing zero or multiple interface blocks raises + :class:`FortranParseError`. + """ + unit, scope = self._expect_single_parse_result( + self._collect_interface_source_units(code, filename), + parser_name="parse_interface", + entity_name="interface", + filename=filename, + ) + return self._visit(unit, parent_scope=scope, filename=filename) + + def parse_derived_type(self, code: _SourceOrLines, filename: str | None = None) -> FortranDerivedType: + """Parse exactly one derived type and its wrapper-relevant fields. + + Use this for an isolated ``type`` definition or a containing source + with one discoverable derived type. The result includes inheritance, + fields, and type-bound declarations; ambiguous input raises + :class:`FortranParseError`. + """ + unit, scope = self._expect_single_parse_result( + self._collect_derived_type_source_units(code, filename), + parser_name="parse_derived_type", + entity_name="derived type", + filename=filename, + ) + return self._visit(unit, parent_scope=scope, filename=filename) + + def parse_program(self, code: _SourceOrLines, filename: str | None = None) -> FortranProgram: + """Parse exactly one program unit and its specification declarations. + + Use this when inspecting a single main program. Executable statements + are intentionally not represented, while declarations, imports, and + supported enumerations become parser facts. Ambiguous input raises + :class:`FortranParseError`. + """ + _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) + unit = self._expect_single_parse_result( + [unit for unit in all_units if unit.kind == "program"], + parser_name="parse_program", + entity_name="program", + filename=filename, + ) + return self._visit(unit, parent_scope=root_scope, filename=filename) + + def parse_block_data(self, code: _SourceOrLines, filename: str | None = None) -> FortranBlockData: + """Parse exactly one block-data unit and its specification declarations. + + Use this narrow entrypoint for a single ``block data`` source unit. + It returns common-block and variable facts but no execution model, and + raises :class:`FortranParseError` when the input is not singular. + """ + _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) + unit = self._expect_single_parse_result( + [unit for unit in all_units if unit.kind == "block_data"], + parser_name="parse_block_data", + entity_name="block data unit", + filename=filename, + ) + return self._visit(unit, parent_scope=root_scope, filename=filename) - def _visit_ProcedureUnit( + # ------------------------------------------------------------------ + # Source unit visitors + # ------------------------------------------------------------------ + + def _visit_ModuleUnit( self, - unit: ProcedureUnit, + unit: ModuleUnit, *, parent_scope: _ParserScope, filename: str | None, - in_interface: bool = False, - ) -> FortranProcedureSignature: - """Visit a sliced procedure body or interface procedure declaration.""" + ) -> FortranModule: + """Visit a sliced `module ... end module` unit.""" header = unit.lines[0] - proc_state = self._parse_procedure_header( - header[0].strip(), - parent_scope.module_owner, - in_interface or parent_scope.kind == "interface", - filename=filename, - lineno=header[1], - source_line=header[2], - ) - if proc_state is None: - self._raise_if_unparsed_procedure_header( - header[0].strip(), - in_interface=in_interface or parent_scope.kind == "interface", - filename=filename, - lineno=header[1], - source_line=header[2], - ) + module = self._parse_module_header(header[0].strip(), filename, lineno=header[1], source_line=header[2]) + if module is None: # pragma: no cover - slicer only dispatches module units with module headers. raise FortranParseError( - "Expected procedure unit.", + "Expected module unit.", filename=filename, line_number=header[1], source_line=header[2], code="PARSE_EXPECTED_UNIT", ) - proc_state.filename = filename - proc_state.header_lineno = header[1] - proc_state.header_source_line = header[2] - proc_state.uses.update(getattr(parent_scope.model, "uses", {})) - scope = self._helper_scope_for_model("procedure", proc_state.signature, parent=parent_scope, state=proc_state) - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("procedure"), filename=filename) + scope = self._helper_scope_for_model("module", module, parent=parent_scope) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) self._parse_specification_part(scope, parts.specification, filename=filename) - child_units = self._helper_nonexecution_child_units(unit, parent_scope=scope, filename=filename) + + child_units = self._source_unit_scanner.slice_child_units( + unit.lines[1:-1], parent_kind=scope.kind, filename=filename + ) self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) self._helper_validate_contains_lines(scope, parts.contains, filename=filename) - self._helper_validate_ignored_child_units( - [child for child in child_units if child.kind != "interface"], - parent_scope=scope, - filename=filename, - unit=unit, - parts=parts, - ) - self._helper_apply_local_interface_declarations(proc_state, unit, parts, scope, filename=filename) - return self._finalize_proc(proc_state) - - # ------------------------------------------------------------------ - # File and project orchestration - # ------------------------------------------------------------------ - - def _helper_read_source( - self, - source_or_path: str | Path, - filename: str | None, - encoding: str, - ) -> tuple[str, str | None]: - """Read a path-like input or preserve an inline source string.""" - if filename is None and self._looks_like_existing_source_path(source_or_path): - path = Path(source_or_path) - return path.read_text(encoding=encoding), str(path) - return str(source_or_path), filename - - def _helper_parse_file_units( - self, - top_units: list[SourceUnit], - root_scope: _ParserScope, - filename: str | None, - ) -> _ParsedFileUnits: - """Visit and collect each direct file-level source unit.""" - parsed = _ParsedFileUnits([], [], [], [], [], [], []) - for unit in top_units: - model = self._visit(unit, parent_scope=root_scope, filename=filename) - self._helper_append_file_unit(parsed, model) - return parsed - - @staticmethod - def _helper_append_file_unit(parsed: _ParsedFileUnits, model: object) -> None: - """Append one visited model to its file-level collection.""" - if isinstance(model, FortranModule): - parsed.modules.append(model) - elif isinstance(model, FortranSubmodule): - parsed.submodules.append(model) - elif isinstance(model, FortranProgram): - parsed.programs.append(model) - elif isinstance(model, FortranBlockData): - parsed.block_data_units.append(model) - elif isinstance(model, FortranProcedureSignature): - if not model.in_interface: - parsed.procedures.append(model) - elif isinstance(model, FortranInterface): - parsed.interfaces.append(model) - elif isinstance(model, FortranDerivedType): - parsed.derived_types.append(model) - - def _helper_resolve_file_types(self, units: _ParsedFileUnits) -> None: - """Resolve derived-type extension links across file-level owners.""" - all_types = [ - *units.derived_types, - *(dtype for module in units.modules for dtype in module.derived_types), - *(dtype for submodule in units.submodules for dtype in submodule.derived_types), - ] - self._resolve_derived_type_extensions(all_types) - - def _helper_attach_file_interfaces( - self, - lines: _PreprocessedLines, - filename: str | None, - units: _ParsedFileUnits, - ) -> list[FortranInterface]: - """Collect interfaces and attach module-owned blocks to their owners.""" - interfaces = [ - self._visit(unit, parent_scope=scope, filename=filename) - for unit, scope in self._collect_interface_source_units(lines, filename) - ] - for module in units.modules: - module.interfaces = [ - iface for iface in interfaces if iface.module and iface.module.lower() == module.name.lower() - ] - for submodule in units.submodules: - submodule.interfaces = [ - iface for iface in interfaces if iface.module and iface.module.lower() == submodule.name.lower() - ] - return [iface for iface in interfaces if iface.module is None] - - def _helper_resolve_file_kinds( - self, - lines: _PreprocessedLines, - filename: str | None, - units: _ParsedFileUnits, - ) -> None: - """Resolve variable and procedure kind references within one file.""" - variable_units = [*units.modules, *units.submodules, *units.programs, *units.block_data_units] - module_params = self._collect_module_parameters(lines, filename) - if any( - var.kind or var.value is not None or var.symbolic_value is not None - for unit in variable_units - for var in getattr(unit, "variables", []) - ): - for unit in variable_units: - self._resolve_module_variable_kinds(unit, module_params) - for procedure in self._helper_file_procedures(units): - self._resolve_signature_kinds(procedure, module_params, resolve_shapes=False) - derived_types = [ - *units.derived_types, - *(derived_type for module in (*units.modules, *units.submodules) for derived_type in module.derived_types), - ] - for derived_type in derived_types: - self._resolve_derived_type_field_kinds(derived_type, module_params) - - @staticmethod - def _helper_file_procedures(units: _ParsedFileUnits): - """Yield file procedures in their established resolution order.""" - yield from units.procedures - for module in units.modules: - yield from module.procedures - for submodule in units.submodules: - yield from submodule.procedures + self._helper_validate_sibling_units(child_units, parent_scope=scope, filename=filename) + self._populate_module_like_children(module, child_units, scope=scope, filename=filename) + self._validate_module_variables(module, filename) + self._apply_module_visibility(module, filename) + return module - def _helper_build_fortran_file( + def _visit_SubmoduleUnit( self, - code: str, + unit: SubmoduleUnit, + *, + parent_scope: _ParserScope, filename: str | None, - encoding: str, - units: _ParsedFileUnits, - interfaces: list[FortranInterface], - ) -> FortranFile: - """Build the file aggregate and its direct symbol registry.""" - parsed_file = FortranFile( - filename=filename, - source=code, - encoding=encoding, - format=self._source_form(filename), - modules=units.modules, - submodules=units.submodules, - programs=units.programs, - block_data_units=units.block_data_units, - procedures=units.procedures, - interfaces=interfaces, - derived_types=[dtype for dtype in units.derived_types if dtype.module is None], - ) - for model in [*units.modules, *units.submodules, *units.procedures]: - self._insert_unique_scope_symbol( - parsed_file.symbols, - model.name.lower(), - model, - label="file scope", + ) -> FortranSubmodule: + """Visit a sliced `submodule (...) name ... end submodule` unit.""" + header = unit.lines[0] + submodule = self._parse_submodule_header(header[0].strip(), filename) + if submodule is None: # pragma: no cover - slicer only dispatches submodule units with submodule headers. + raise FortranParseError( + "Expected submodule unit.", filename=filename, + line_number=header[1], + source_line=header[2], + code="PARSE_EXPECTED_UNIT", ) - return parsed_file - - def _helper_parse_project_files( - self, - files: dict[str, str] | list[str | Path] | tuple[str | Path, ...] | str | Path, - encoding: str, - ) -> list[FortranFile]: - """Normalize project inputs and parse each source file.""" - if isinstance(files, dict): - return [self.parse_file(code, filename=fname, encoding=encoding) for fname, code in files.items()] - if isinstance(files, str | Path): - namespace = self._helper_collect_namespace(files, encoding=encoding) - return [self.parse_file(path, encoding=encoding) for path in namespace["files"]] - return [self.parse_file(path, encoding=encoding) for path in files] - - def _helper_resolve_project_kinds(self, parsed_files: list[FortranFile]) -> None: - """Resolve project procedure and module-variable kinds from shared symbols.""" - module_params = self._helper_project_module_symbols(parsed_files) - - seen_procedures: set[int] = set() - for parsed_file in parsed_files: - for procedure in self._helper_project_file_procedures(parsed_file): - if id(procedure) not in seen_procedures: - self._resolve_signature_kinds(procedure, module_params, resolve_shapes=False) - seen_procedures.add(id(procedure)) - for owner in ( - *parsed_file.modules, - *parsed_file.submodules, - *parsed_file.programs, - *parsed_file.block_data_units, - ): - self._resolve_module_variable_kinds(owner, module_params) - for derived_type in parsed_file.derived_types: - self._resolve_derived_type_field_kinds(derived_type, module_params) - for module in parsed_file.modules: - for derived_type in module.derived_types: - self._resolve_derived_type_field_kinds(derived_type, module_params) + scope = self._helper_scope_for_model("submodule", submodule, parent=parent_scope) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) + self._parse_specification_part(scope, parts.specification, filename=filename) - def _helper_project_module_symbols(self, parsed_files: list[FortranFile]) -> dict[str, dict[str, str]]: - """Resolve module symbols and submodule host associations.""" - module_params: dict[str, dict[str, str]] = {} - owners: dict[str, FortranModule | FortranSubmodule] = {} - for parsed_file in parsed_files: - if parsed_file.source is not None: - module_params.update(self._collect_module_parameters(parsed_file.source, parsed_file.filename)) - owners.update((module.name.lower(), module) for module in parsed_file.modules) - owners.update((submodule.name.lower(), submodule) for submodule in parsed_file.submodules) + child_units = self._source_unit_scanner.slice_child_units( + unit.lines[1:-1], parent_kind=scope.kind, filename=filename + ) + self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + self._helper_validate_contains_lines(scope, parts.contains, filename=filename) + self._helper_validate_sibling_units(child_units, parent_scope=scope, filename=filename) + self._populate_module_like_children(submodule, child_units, scope=scope, filename=filename) + self._validate_module_variables(submodule, filename) + return submodule - resolved = self._resolve_module_parameter_values(module_params) - for _ in range(len(owners) + 1): - changed = False - for owner_name, owner in owners.items(): - symbols = dict(resolved.get(owner_name, {})) - if isinstance(owner, FortranSubmodule): - if owner.ancestor: - symbols.update(resolved.get(owner.ancestor.lower(), {})) - symbols.update(resolved.get(owner.parent.lower(), {})) - symbols.update(self._helper_owner_imported_symbols(owner, resolved)) - updated = self._resolve_module_parameter_values({owner_name: symbols})[owner_name] - if updated != resolved.get(owner_name, {}): - resolved[owner_name] = updated - changed = True - if not changed: - break - return resolved + def _parse_children_of_type(self, child_units, unit_type, *, scope, filename): + """Visit direct children of one source-unit model class.""" + return [ + self._visit(child, parent_scope=scope, filename=filename) + for child in child_units + if isinstance(child, unit_type) + ] @staticmethod - def _helper_owner_imported_symbols( - owner: FortranModule | FortranSubmodule, - resolved_modules: dict[str, dict[str, str]], - ) -> dict[str, str]: - """Return explicit compile-time symbols imported into one owner.""" - imported: dict[str, str] = {} - for dependency, mappings in owner.uses.items(): - dependency_name = dependency.lower() - dependency_symbols = resolved_modules.get(dependency_name, {}) - if not mappings: - imported.update(dependency_symbols) - continue - for mapping in mappings: - source_name = mapping.source.lower() - expression = dependency_symbols.get(source_name) - if expression is None and dependency_name in _INTRINSIC_COMPILE_TIME_MODULES: - expression = mapping.source - if expression is not None: - imported[mapping.local_name.lower()] = expression - return imported + def _belongs_to_module_like(item, target, *, exclude_interface: bool = False) -> bool: + """Return whether one visited model belongs to a module-like owner. - @staticmethod - def _helper_project_file_procedures(parsed_file: FortranFile): - """Yield direct and interface procedures in project resolution order.""" - yield from parsed_file.procedures - for interface in parsed_file.interfaces: - yield from interface.procedures - for module in parsed_file.modules: - yield from module.procedures - for interface in module.interfaces: - yield from interface.procedures - for submodule in parsed_file.submodules: - yield from submodule.procedures - for interface in submodule.interfaces: - yield from interface.procedures + Ownership comparison is case-insensitive, matching Fortran naming. + ``exclude_interface`` keeps interface procedure signatures attached to + their interface instead of duplicating them in ``target.procedures``. + """ + belongs = bool(item.module and item.module.lower() == target.name.lower()) + return belongs and not (exclude_interface and item.in_interface) - def _helper_index_project_file(self, project: FortranProject, parsed_file: FortranFile) -> None: - """Add one parsed file's public models to project registries.""" - for module in parsed_file.modules: - self._helper_index_project_module(project, module) - for submodule in parsed_file.submodules: - self._helper_index_project_submodule(project, submodule) - for program in parsed_file.programs: - self._helper_index_project_program(project, program) - for procedure in parsed_file.procedures: - self._helper_index_project_standalone_procedure(project, procedure) - for dtype in parsed_file.derived_types: - self._insert_unique_scope_symbol( - project.derived_types, - dtype.name.lower(), - dtype, - label="project derived-type scope", - ) - for interface in parsed_file.interfaces: - self._helper_index_project_interface(project, interface) + def _populate_module_like_children(self, target, child_units, *, scope, filename) -> None: + """Visit direct children and append the ones owned by ``target``. - def _helper_index_project_module(self, project: FortranProject, module: FortranModule) -> None: - """Index one module and its owned public models.""" - module_key = module.name.lower() - self._insert_unique_scope_symbol(project.modules, module_key, module, label="project module scope") - project.dependencies[module_key] = {name.lower() for name in module.uses} - self._helper_index_project_owner_members(project, module, module_key) + The method preserves source order within each child category and shares + the caller's scope/filename for diagnostics. Interface-contained + procedure declarations are deliberately excluded from a module's + standalone procedure collection. + """ + signatures = self._parse_children_of_type(child_units, ProcedureUnit, scope=scope, filename=filename) + types = self._parse_children_of_type(child_units, DerivedTypeUnit, scope=scope, filename=filename) + interfaces = self._parse_children_of_type(child_units, InterfaceUnit, scope=scope, filename=filename) + enums = self._parse_children_of_type(child_units, EnumUnit, scope=scope, filename=filename) + target.procedures.extend( + item for item in signatures if self._belongs_to_module_like(item, target, exclude_interface=True) + ) + target.derived_types.extend(item for item in types if self._belongs_to_module_like(item, target)) + target.interfaces.extend(item for item in interfaces if self._belongs_to_module_like(item, target)) + target.enums.extend(item for item in enums if self._belongs_to_module_like(item, target)) - def _helper_index_project_submodule(self, project: FortranProject, submodule: FortranSubmodule) -> None: - """Index one submodule, its dependencies, and its public models.""" - submodule_key = submodule.name.lower() - self._insert_unique_scope_symbol( - project.submodules, - submodule_key, - submodule, - label="project submodule scope", + def _visit_ProgramUnit( + self, + unit: ProgramUnit, + *, + parent_scope: _ParserScope, + filename: str | None, + ) -> FortranProgram: + """Visit a sliced `program ... end program` unit.""" + header = unit.lines[0] + program = self._parse_program_header(header[0].strip(), filename) + if program is None: # pragma: no cover - slicer only dispatches program units with program headers. + raise FortranParseError( + "Expected program unit.", + filename=filename, + line_number=header[1], + source_line=header[2], + code="PARSE_EXPECTED_UNIT", + ) + scope = self._helper_scope_for_model("program", program, parent=parent_scope) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) + self._parse_specification_part(scope, parts.specification, filename=filename) + child_units = self._source_unit_scanner.nonexecution_child_units(unit, filename=filename) + self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + self._helper_validate_contains_lines(scope, parts.contains, filename=filename) + self._helper_validate_ignored_child_units( + [child for child in child_units if child.kind != "enum"], + parent_scope=scope, + filename=filename, + unit=unit, + parts=parts, ) - dependencies = {submodule.parent.lower(), *(name.lower() for name in submodule.uses)} - if submodule.ancestor: - dependencies.add(submodule.ancestor.lower()) - project.dependencies[submodule_key] = dependencies - self._helper_index_project_owner_members(project, submodule, submodule_key) + program.enums.extend( + self._visit(child, parent_scope=scope, filename=filename) for child in child_units if child.kind == "enum" + ) + self._validate_variable_declarations( + program.variables, + owner_kind="program", + owner_name=program.name, + filename=filename, + ) + return program - def _helper_index_project_owner_members( + def _visit_BlockDataUnit( self, - project: FortranProject, - owner: FortranModule | FortranSubmodule, - owner_key: str, - ) -> None: - """Index qualified and first-seen unqualified owner members.""" - for procedure in owner.procedures: - qualified_name = f"{owner_key}.{procedure.name.lower()}" - self._insert_unique_scope_symbol( - project.procedures, - qualified_name, - procedure, - label="project procedure scope", + unit: BlockDataUnit, + *, + parent_scope: _ParserScope, + filename: str | None, + ) -> FortranBlockData: + """Visit a sliced `block data ... end block data` unit.""" + header = unit.lines[0] + block_data = self._parse_block_data_header(header[0].strip(), filename) + if block_data is None: # pragma: no cover - slicer only dispatches block-data units with block-data headers. + raise FortranParseError( + "Expected block data unit.", + filename=filename, + line_number=header[1], + source_line=header[2], + code="PARSE_EXPECTED_UNIT", ) - project.procedures.setdefault(procedure.name.lower(), procedure) - for dtype in owner.derived_types: - qualified_name = f"{owner_key}.{dtype.name.lower()}" - self._insert_unique_scope_symbol( - project.derived_types, - qualified_name, + scope = self._helper_scope_for_model("block_data", block_data, parent=parent_scope) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) + self._parse_specification_part(scope, parts.specification, filename=filename) + child_units = self._source_unit_scanner.slice_child_units( + unit.lines[1:-1], parent_kind=scope.kind, filename=filename + ) + self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + self._validate_variable_declarations( + block_data.variables, + owner_kind="block data", + owner_name=block_data.name, + filename=filename, + ) + return block_data + + def _visit_DerivedTypeUnit( + self, + unit: DerivedTypeUnit, + *, + parent_scope: _ParserScope, + filename: str | None, + ) -> FortranDerivedType: + """Visit a sliced derived-type definition.""" + header = unit.lines[0] + dtype = self._init_derived_type(header[0].strip(), current_module=parent_scope.module_owner) + if dtype is None: # pragma: no cover - slicer only dispatches derived-type units with type headers. + raise FortranParseError( + "Expected derived-type unit.", + filename=filename, + line_number=header[1], + source_line=header[2], + code="PARSE_EXPECTED_UNIT", + ) + scope = self._helper_scope_for_model("derived_type", dtype, parent=parent_scope) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) + self._parse_specification_part(scope, parts.specification, filename=filename) + for line, lineno, source_line in parts.contains: + stripped = line.strip() + if not stripped: + continue + self._parse_derived_type_contains_line( + stripped, dtype, - label="project derived-type scope", + filename=filename, + lineno=lineno, + source_line=source_line, ) - project.derived_types.setdefault(dtype.name.lower(), dtype) - for interface in owner.interfaces: - self._helper_index_project_interface(project, interface, owner_key) + child_units = self._source_unit_scanner.slice_child_units( + unit.lines[1:-1], parent_kind=scope.kind, filename=filename + ) + self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + self._validate_derived_type_fields(dtype, filename) + return dtype + + def _visit_EnumUnit( + self, + unit: EnumUnit, + *, + parent_scope: _ParserScope, + filename: str | None, + ) -> FortranEnum: + """Visit an `enum, bind(C)` unit and preserve enumerator constants.""" + return self._helper_parse_enum_unit(unit, filename=filename, module_owner=parent_scope.module_owner) - def _helper_index_project_standalone_procedure( + def _visit_InterfaceUnit( self, - project: FortranProject, - procedure: FortranProcedureSignature, - ) -> None: - """Index a standalone procedure ahead of any unqualified owner alias.""" - procedure_key = procedure.name.lower() - existing = project.procedures.get(procedure_key) - if existing is not None and existing.module is not None: - project.procedures[procedure_key] = procedure - return - self._insert_unique_scope_symbol( - project.procedures, - procedure_key, - procedure, - label="project procedure scope", + unit: InterfaceUnit, + *, + parent_scope: _ParserScope, + filename: str | None, + ) -> FortranInterface: + """Visit a sliced interface block.""" + header = unit.lines[0] + starts_interface, interface_name = self._source_unit_scanner.parse_interface_header(header[0].strip()) + if not starts_interface: # pragma: no cover - slicer only dispatches interface units with interface headers. + raise FortranParseError( + "Expected interface unit.", + filename=filename, + line_number=header[1], + source_line=header[2], + code="PARSE_EXPECTED_UNIT", + ) + interface = FortranInterface( + name=interface_name, + module=parent_scope.module_owner, + abstract=header[0].strip().lower().startswith("abstract interface"), + ) + scope = self._helper_scope_for_model("interface", interface, parent=parent_scope) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) + self._helper_validate_interface_lines(scope, parts.specification, filename=filename) + interface.specific_procedures.extend(self._interface_specific_procedure_names(parts.specification)) + child_units = self._source_unit_scanner.slice_child_units( + unit.lines[1:-1], + parent_kind=scope.kind, + filename=filename, ) + for child in child_units: + if child.kind != "procedure": + _raise_invalid_fortran_syntax_line( + child.lines[0][0] if child.lines else child.kind, + context=f"interface '{scope.name or ''}'", + filename=filename, + lineno=child.start_line, + source_line=child.lines[0][2] if child.lines else None, + ) + sig = self._visit(child, parent_scope=scope, filename=filename, in_interface=True) + self._add_interface_attribute(sig, interface.name) + interface.procedures.append(sig) + return interface - def _helper_index_project_program(self, project: FortranProject, program: FortranProgram) -> None: - """Index one named program and its module dependencies.""" - if not program.name: - return - program_key = program.name.lower() - self._insert_unique_scope_symbol(project.programs, program_key, program, label="project program scope") - project.dependencies[program_key] = {name.lower() for name in program.uses} + @staticmethod + def _interface_specific_procedure_names(lines: _PreprocessedLines) -> list[str]: + """Collect specific procedure names declared by a generic interface.""" + names: list[str] = [] + for line, _, _ in lines: + stripped = line.strip() + module_procedure = re.match( + r"^module\s+procedure\s*(?:::)?\s*(?P.+)$", + stripped, + re.IGNORECASE, + ) + if module_procedure: + names.extend(name.strip() for name in split_csv(module_procedure.group("names"))) + continue + procedure = re.match( + r"^procedure(?:\s*\([^)]*\))?(?:\s*,\s*[^:]*)?\s*::\s*(?P.+)$", + stripped, + re.IGNORECASE, + ) + if procedure: + names.extend(name.strip() for name in split_csv(procedure.group("names"))) + return names - def _helper_index_project_interface( + def _visit_ProcedureUnit( self, - project: FortranProject, - interface: FortranInterface, - owner_key: str | None = None, - ) -> None: - """Index one named interface, qualified when it has an owner.""" - if not interface.name: - return - interface_name = interface.name.lower() - registry_key = f"{owner_key}.{interface_name}" if owner_key else interface_name - self._insert_unique_scope_symbol( - project.interfaces, - registry_key, - interface, - label="project interface scope", + unit: ProcedureUnit, + *, + parent_scope: _ParserScope, + filename: str | None, + in_interface: bool = False, + ) -> FortranProcedureSignature: + """Visit a sliced procedure body or interface procedure declaration.""" + header = unit.lines[0] + proc_state = self._parse_procedure_header( + header[0].strip(), + parent_scope.module_owner, + in_interface or parent_scope.kind == "interface", + filename=filename, + lineno=header[1], + source_line=header[2], ) - if owner_key: - project.interfaces.setdefault(interface_name, interface) + if proc_state is None: + self._raise_if_unparsed_procedure_header( + header[0].strip(), + in_interface=in_interface or parent_scope.kind == "interface", + filename=filename, + lineno=header[1], + source_line=header[2], + ) + raise FortranParseError( + "Expected procedure unit.", + filename=filename, + line_number=header[1], + source_line=header[2], + code="PARSE_EXPECTED_UNIT", + ) + proc_state.filename = filename + proc_state.header_lineno = header[1] + proc_state.header_source_line = header[2] + proc_state.uses.update(getattr(parent_scope.model, "uses", {})) + scope = self._helper_scope_for_model("procedure", proc_state.signature, parent=parent_scope, state=proc_state) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) + self._parse_specification_part(scope, parts.specification, filename=filename) + child_units = self._source_unit_scanner.nonexecution_child_units(unit, filename=filename) + self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + self._helper_validate_contains_lines(scope, parts.contains, filename=filename) + self._helper_validate_ignored_child_units( + [child for child in child_units if child.kind != "interface"], + parent_scope=scope, + filename=filename, + unit=unit, + parts=parts, + ) + self._helper_apply_local_interface_declarations(proc_state, unit, parts, scope, filename=filename) + return self._finalize_proc(proc_state) # ------------------------------------------------------------------ - # Source preparation and unit slicing + # File and project orchestration # ------------------------------------------------------------------ - def _preprocessed_lines(self, source: _SourceOrLines, filename: str | None) -> _PreprocessedLines: - """Return preprocessed source lines, reusing file-level preprocessing when supplied.""" - if isinstance(source, list): - lines = source - else: - for lineno, source_line in enumerate(source.splitlines(), start=1): - self._raise_for_raw_cpp_directive(source_line, filename, lineno, source_line) - source_without_linemarkers = "".join( - re.sub(r"[^\r\n]", "", line) if _FORTRAN_LINEMARKER_RE.match(line.strip()) else line - for line in source.splitlines(keepends=True) - ) - lines = preprocess_lines(source_without_linemarkers, filename) - for line, lineno, source_line in lines: - self._raise_for_raw_cpp_directive(line, filename, lineno, source_line) - return lines + def _helper_read_source( + self, + source_or_path: str | Path, + filename: str | None, + encoding: str, + ) -> tuple[str, str | None]: + """Read a path-like input or preserve an inline source string.""" + if filename is None and self._looks_like_existing_source_path(source_or_path): + path = Path(source_or_path) + return path.read_text(encoding=encoding), str(path) + return str(source_or_path), filename + + def _helper_parse_file_units( + self, + top_units: list[SourceUnit], + root_scope: _ParserScope, + filename: str | None, + ) -> _ParsedFileUnits: + """Visit and collect each direct file-level source unit.""" + parsed = _ParsedFileUnits([], [], [], [], [], [], []) + for unit in top_units: + model = self._visit(unit, parent_scope=root_scope, filename=filename) + self._helper_append_file_unit(parsed, model) + return parsed + + @staticmethod + def _helper_append_file_unit(parsed: _ParsedFileUnits, model: object) -> None: + """Append one visited model to its file-level collection.""" + if isinstance(model, FortranModule): + parsed.modules.append(model) + elif isinstance(model, FortranSubmodule): + parsed.submodules.append(model) + elif isinstance(model, FortranProgram): + parsed.programs.append(model) + elif isinstance(model, FortranBlockData): + parsed.block_data_units.append(model) + elif isinstance(model, FortranProcedureSignature): + if not model.in_interface: + parsed.procedures.append(model) + elif isinstance(model, FortranInterface): + parsed.interfaces.append(model) + elif isinstance(model, FortranDerivedType): + parsed.derived_types.append(model) + + def _helper_resolve_file_types(self, units: _ParsedFileUnits) -> None: + """Resolve derived-type extension links across file-level owners.""" + all_types = [ + *units.derived_types, + *(dtype for module in units.modules for dtype in module.derived_types), + *(dtype for submodule in units.submodules for dtype in submodule.derived_types), + ] + self._resolve_derived_type_extensions(all_types) + + def _helper_attach_file_interfaces( + self, + lines: _PreprocessedLines, + filename: str | None, + units: _ParsedFileUnits, + ) -> list[FortranInterface]: + """Collect interfaces and attach module-owned blocks to their owners.""" + interfaces = [ + self._visit(unit, parent_scope=scope, filename=filename) + for unit, scope in self._collect_interface_source_units(lines, filename) + ] + for module in units.modules: + module.interfaces = [ + iface for iface in interfaces if iface.module and iface.module.lower() == module.name.lower() + ] + for submodule in units.submodules: + submodule.interfaces = [ + iface for iface in interfaces if iface.module and iface.module.lower() == submodule.name.lower() + ] + return [iface for iface in interfaces if iface.module is None] + + def _helper_resolve_file_kinds( + self, + lines: _PreprocessedLines, + filename: str | None, + units: _ParsedFileUnits, + ) -> None: + """Resolve variable and procedure kind references within one file.""" + variable_units = [*units.modules, *units.submodules, *units.programs, *units.block_data_units] + module_params = self._collect_module_parameters(lines, filename) + if any( + var.kind or var.value is not None or var.symbolic_value is not None + for unit in variable_units + for var in getattr(unit, "variables", []) + ): + for unit in variable_units: + self._resolve_module_variable_kinds(unit, module_params) + for procedure in self._helper_file_procedures(units): + self._resolve_signature_kinds(procedure, module_params, resolve_shapes=False) + derived_types = [ + *units.derived_types, + *(derived_type for module in (*units.modules, *units.submodules) for derived_type in module.derived_types), + ] + for derived_type in derived_types: + self._resolve_derived_type_field_kinds(derived_type, module_params) @staticmethod - def _raise_for_raw_cpp_directive( - line: str, + def _helper_file_procedures(units: _ParsedFileUnits): + """Yield file procedures in their established resolution order.""" + yield from units.procedures + for module in units.modules: + yield from module.procedures + for submodule in units.submodules: + yield from submodule.procedures + + def _helper_build_fortran_file( + self, + code: str, filename: str | None, - lineno: int, - source_line: str, - ) -> None: - """Reject raw CPP directives while accepting compiler linemarkers.""" - stripped = line.strip() - if stripped.startswith("#") and not _FORTRAN_LINEMARKER_RE.match(stripped): - raise FortranParseError( - "Fortran CPP directives require compiler preprocessing before parsing.", + encoding: str, + units: _ParsedFileUnits, + interfaces: list[FortranInterface], + ) -> FortranFile: + """Build the file aggregate and its direct symbol registry.""" + parsed_file = FortranFile( + filename=filename, + source=code, + encoding=encoding, + format=self._source_form(filename), + modules=units.modules, + submodules=units.submodules, + programs=units.programs, + block_data_units=units.block_data_units, + procedures=units.procedures, + interfaces=interfaces, + derived_types=[dtype for dtype in units.derived_types if dtype.module is None], + ) + for model in [*units.modules, *units.submodules, *units.procedures]: + self._insert_unique_scope_symbol( + parsed_file.symbols, + model.name.lower(), + model, + label="file scope", filename=filename, - line_number=lineno, - source_line=source_line, - code="PARSE_PREPROCESSING_REQUIRED", ) + return parsed_file - def _helper_collect_namespace( + def _helper_parse_project_files( self, - root: str | Path, - extensions: tuple[str, ...] = (".f", ".for", ".ftn", ".f77", ".f90", ".f95", ".f03", ".f08"), - *, - encoding: str = "utf-8", - ) -> dict: - """Collect parseable source files and dependency-order them. - - Project parsing uses this helper when the caller passes a directory - instead of an explicit file list. It performs a light first pass to map - modules/submodules to files, topologically orders files by ``use`` - dependencies, then parses them in that order. - - Example: - ``parse_project("src")`` calls this helper, receives - ``{"files": ordered_files, "module_to_file": ...}``, and then - parses each ordered path through the normal file entrypoint. - """ - root_path = Path(root) - files = sorted([p for p in root_path.rglob("*") if p.suffix.lower() in extensions]) - sources = {str(p): p.read_text(encoding=encoding) for p in files} - file_lines = {fname: preprocess_lines(code, fname) for fname, code in sources.items()} - - module_to_file: dict[str, str] = {} - submodule_to_file: dict[str, str] = {} - file_to_uses: dict[str, set[str]] = {fname: set() for fname in sources} - for fname, _code in sources.items(): - lines = file_lines[fname] - _lines, root_scope, all_units = self._helper_prepare_source_units(lines, fname) - modules = [ - self._visit(unit, parent_scope=root_scope, filename=fname) - for unit in all_units - if unit.kind == "module" - ] - submodules = [ - self._visit(unit, parent_scope=root_scope, filename=fname) - for unit in all_units - if unit.kind == "submodule" - ] - for m in modules: - module_to_file[m.name.lower()] = fname - file_to_uses[fname].update(u.lower() for u in m.uses) - for sm in submodules: - submodule_to_file[sm.name.lower()] = fname - file_to_uses[fname].add(sm.parent.lower()) - if sm.ancestor: - file_to_uses[fname].add(sm.ancestor.lower()) - file_to_uses[fname].update(u.lower() for u in sm.uses) - - file_dependencies: dict[str, set[str]] = {} - for fname, used_modules in file_to_uses.items(): - deps = set() - for mod in used_modules: - dep_file = module_to_file.get(mod) or submodule_to_file.get(mod) - if dep_file and dep_file != fname: - deps.add(dep_file) - file_dependencies[fname] = deps + files: dict[str, str] | list[str | Path] | tuple[str | Path, ...] | str | Path, + encoding: str, + ) -> list[FortranFile]: + """Normalize project inputs and parse each source file.""" + if isinstance(files, dict): + return [self.parse_file(code, filename=fname, encoding=encoding) for fname, code in files.items()] + if isinstance(files, str | Path): + namespace = self._helper_collect_namespace(files, encoding=encoding) + return [self.parse_file(path, encoding=encoding) for path in namespace["files"]] + return [self.parse_file(path, encoding=encoding) for path in files] - ordered_files = self._topological_files(file_dependencies) - types = [] - modules = [] - submodules = [] - programs = [] - block_data = [] - for f in ordered_files: - parsed_file = self.parse_file(sources[f], filename=f, encoding=encoding) - types.extend(parsed_file.derived_types) - types.extend(dtype for module in parsed_file.modules for dtype in module.derived_types) - types.extend(dtype for submodule in parsed_file.submodules for dtype in submodule.derived_types) - modules.extend(parsed_file.modules) - submodules.extend(parsed_file.submodules) - programs.extend(parsed_file.programs) - block_data.extend(parsed_file.block_data_units) + def _helper_resolve_project_kinds(self, parsed_files: list[FortranFile]) -> None: + """Resolve project procedure and module-variable kinds from shared symbols.""" + module_params = self._helper_project_module_symbols(parsed_files) - return { - "files": ordered_files, - "file_dependencies": {k: sorted(v) for k, v in file_dependencies.items()}, - "module_to_file": module_to_file, - "submodule_to_file": submodule_to_file, - "modules": modules, - "submodules": submodules, - "programs": programs, - "block_data": block_data, - "types": types, - } + seen_procedures: set[int] = set() + for parsed_file in parsed_files: + for procedure in self._helper_project_file_procedures(parsed_file): + if id(procedure) not in seen_procedures: + self._resolve_signature_kinds(procedure, module_params, resolve_shapes=False) + seen_procedures.add(id(procedure)) + for owner in ( + *parsed_file.modules, + *parsed_file.submodules, + *parsed_file.programs, + *parsed_file.block_data_units, + ): + self._resolve_module_variable_kinds(owner, module_params) + for derived_type in parsed_file.derived_types: + self._resolve_derived_type_field_kinds(derived_type, module_params) + for module in parsed_file.modules: + for derived_type in module.derived_types: + self._resolve_derived_type_field_kinds(derived_type, module_params) - def _helper_prepare_source_units( - self, - code: _SourceOrLines, - filename: str | None, - ) -> tuple[_PreprocessedLines, _ParserScope, list[SourceUnit]]: - """Preprocess, validate, and slice file-level source units. + def _helper_project_module_symbols(self, parsed_files: list[FortranFile]) -> dict[str, dict[str, str]]: + """Resolve module symbols and submodule host associations.""" + module_params: dict[str, dict[str, str]] = {} + owners: dict[str, FortranModule | FortranSubmodule] = {} + for parsed_file in parsed_files: + if parsed_file.source is not None: + module_params.update(self._collect_module_parameters(parsed_file.source, parsed_file.filename)) + owners.update((module.name.lower(), module) for module in parsed_file.modules) + owners.update((submodule.name.lower(), submodule) for submodule in parsed_file.submodules) - This is the first grammar-shaped step in `parse_file`: raw source is - normalized into line tuples, obvious malformed headers are rejected, - and only direct file-scope units are returned. + resolved = self._resolve_module_parameter_values(module_params) + for _ in range(len(owners) + 1): + changed = False + for owner_name, owner in owners.items(): + symbols = dict(resolved.get(owner_name, {})) + if isinstance(owner, FortranSubmodule): + if owner.ancestor: + symbols.update(resolved.get(owner.ancestor.lower(), {})) + symbols.update(resolved.get(owner.parent.lower(), {})) + symbols.update(self._helper_owner_imported_symbols(owner, resolved)) + updated = self._resolve_module_parameter_values({owner_name: symbols})[owner_name] + if updated != resolved.get(owner_name, {}): + resolved[owner_name] = updated + changed = True + if not changed: + break + return resolved - Example: - A file containing ``module constants`` and standalone - ``subroutine solve`` returns a root file scope plus two - `SourceUnit` objects, one module unit and one procedure unit, both - carrying original source line numbers. - """ - lines = self._preprocessed_lines(code, filename) - root_scope = _ParserScope(kind="file", name=None) - units = self._helper_slice_child_units(lines, parent_scope=root_scope, filename=filename) - self._helper_validate_file_scope_unparsed_lines(lines, filename) - self._helper_validate_sibling_units(units, parent_scope=root_scope, filename=filename) - return lines, root_scope, units + @staticmethod + def _helper_owner_imported_symbols( + owner: FortranModule | FortranSubmodule, + resolved_modules: dict[str, dict[str, str]], + ) -> dict[str, str]: + """Return explicit compile-time symbols imported into one owner.""" + imported: dict[str, str] = {} + for dependency, mappings in owner.uses.items(): + dependency_name = dependency.lower() + dependency_symbols = resolved_modules.get(dependency_name, {}) + if not mappings: + imported.update(dependency_symbols) + continue + for mapping in mappings: + source_name = mapping.source.lower() + expression = dependency_symbols.get(source_name) + if expression is None and dependency_name in _INTRINSIC_COMPILE_TIME_MODULES: + expression = mapping.source + if expression is not None: + imported[mapping.local_name.lower()] = expression + return imported - def _collect_interface_source_units( - self, - code: _SourceOrLines, - filename: str | None, - ) -> list[tuple[SourceUnit, _ParserScope]]: - """Collect interface units with the parent scope needed to parse them. + @staticmethod + def _helper_project_file_procedures(parsed_file: FortranFile): + """Yield direct and interface procedures in project resolution order.""" + yield from parsed_file.procedures + for interface in parsed_file.interfaces: + yield from interface.procedures + for module in parsed_file.modules: + yield from module.procedures + for interface in module.interfaces: + yield from interface.procedures + for submodule in parsed_file.submodules: + yield from submodule.procedures + for interface in submodule.interfaces: + yield from interface.procedures - Public plural/singular interface visitors both use this collector so - singular parsing selects one source unit directly instead of parsing a - plural result list and checking its length afterward. - """ - lines, root_scope, _all_units = self._helper_prepare_source_units(code, filename) - interfaces: list[tuple[SourceUnit, _ParserScope]] = [] + def _helper_index_project_file(self, project: FortranProject, parsed_file: FortranFile) -> None: + """Add one parsed file's public models to project registries.""" + for module in parsed_file.modules: + self._helper_index_project_module(project, module) + for submodule in parsed_file.submodules: + self._helper_index_project_submodule(project, submodule) + for program in parsed_file.programs: + self._helper_index_project_program(project, program) + for procedure in parsed_file.procedures: + self._helper_index_project_standalone_procedure(project, procedure) + for dtype in parsed_file.derived_types: + self._insert_unique_scope_symbol( + project.derived_types, + dtype.name.lower(), + dtype, + label="project derived-type scope", + ) + for interface in parsed_file.interfaces: + self._helper_index_project_interface(project, interface) - def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: - """Walk non-execution children and retain interface units.""" - for child in child_units: - if child.kind == "interface": - interfaces.append((child, scope)) - continue - if child.kind in {"module", "submodule"}: - child_scope = _ParserScope( - kind=child.kind, - name=child.name, - parent=scope, - module_owner=child.name, - ) - collect( - child_scope, - self._helper_nonexecution_child_units(child, parent_scope=child_scope, filename=filename), - ) - continue - if child.kind in {"procedure", "program"}: - child_scope = _ParserScope( - kind=child.kind, - name=child.name, - parent=scope, - module_owner=scope.module_owner, - ) - collect( - child_scope, - self._helper_nonexecution_child_units(child, parent_scope=child_scope, filename=filename), - ) + def _helper_index_project_module(self, project: FortranProject, module: FortranModule) -> None: + """Index one module and its owned public models.""" + module_key = module.name.lower() + self._insert_unique_scope_symbol(project.modules, module_key, module, label="project module scope") + project.dependencies[module_key] = {name.lower() for name in module.uses} + self._helper_index_project_owner_members(project, module, module_key) - collect(root_scope, self._helper_slice_child_units(lines, parent_scope=root_scope, filename=filename)) - return interfaces + def _helper_index_project_submodule(self, project: FortranProject, submodule: FortranSubmodule) -> None: + """Index one submodule, its dependencies, and its public models.""" + submodule_key = submodule.name.lower() + self._insert_unique_scope_symbol( + project.submodules, + submodule_key, + submodule, + label="project submodule scope", + ) + dependencies = {submodule.parent.lower(), *(name.lower() for name in submodule.uses)} + if submodule.ancestor: + dependencies.add(submodule.ancestor.lower()) + project.dependencies[submodule_key] = dependencies + self._helper_index_project_owner_members(project, submodule, submodule_key) - def _collect_derived_type_source_units( + def _helper_index_project_owner_members( self, - code: _SourceOrLines, - filename: str | None, - ) -> list[tuple[SourceUnit, _ParserScope]]: - """Collect derived-type units with their module/program scope context.""" - lines, root_scope, _all_units = self._helper_prepare_source_units(code, filename) - types: list[tuple[SourceUnit, _ParserScope]] = [] - - def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: - """Walk nested grammar units and retain derived-type units.""" - for child in child_units: - if child.kind == "derived_type": - types.append((child, scope)) - continue - if child.kind in {"module", "submodule", "program"}: - child_scope = _ParserScope( - kind=child.kind, - name=child.name, - parent=scope, - module_owner=child.name if child.kind in {"module", "submodule"} else scope.module_owner, - ) - collect( - child_scope, - self._helper_nonexecution_child_units(child, parent_scope=child_scope, filename=filename), - ) - continue - if child.kind == "procedure": - child_scope = _ParserScope( - kind=child.kind, - name=child.name, - parent=scope, - module_owner=scope.module_owner, - ) - collect( - child_scope, - self._helper_nonexecution_child_units(child, parent_scope=child_scope, filename=filename), - ) - - collect(root_scope, self._helper_slice_child_units(lines, parent_scope=root_scope, filename=filename)) - return types + project: FortranProject, + owner: FortranModule | FortranSubmodule, + owner_key: str, + ) -> None: + """Index qualified and first-seen unqualified owner members.""" + for procedure in owner.procedures: + qualified_name = f"{owner_key}.{procedure.name.lower()}" + self._insert_unique_scope_symbol( + project.procedures, + qualified_name, + procedure, + label="project procedure scope", + ) + project.procedures.setdefault(procedure.name.lower(), procedure) + for dtype in owner.derived_types: + qualified_name = f"{owner_key}.{dtype.name.lower()}" + self._insert_unique_scope_symbol( + project.derived_types, + qualified_name, + dtype, + label="project derived-type scope", + ) + project.derived_types.setdefault(dtype.name.lower(), dtype) + for interface in owner.interfaces: + self._helper_index_project_interface(project, interface, owner_key) - def _helper_validate_possible_unit_header( + def _helper_index_project_standalone_procedure( self, - line: str, - *, - filename: str | None, - lineno: int | None, - source_line: str | None, + project: FortranProject, + procedure: FortranProcedureSignature, ) -> None: - """Validate a line that lexically resembles a source-unit header.""" - stripped = line.strip() - self._parse_module_header(stripped, filename, lineno=lineno, source_line=source_line) - if stripped.lower().startswith("end "): - return - if re.match(r"^module\s+procedure\s*::", stripped, flags=re.IGNORECASE): + """Index a standalone procedure ahead of any unqualified owner alias.""" + procedure_key = procedure.name.lower() + existing = project.procedures.get(procedure_key) + if existing is not None and existing.module is not None: + project.procedures[procedure_key] = procedure return - if not (stripped.lower().startswith("module procedure") or self._looks_like_procedure_header(stripped)): + self._insert_unique_scope_symbol( + project.procedures, + procedure_key, + procedure, + label="project procedure scope", + ) + + def _helper_index_project_program(self, project: FortranProject, program: FortranProgram) -> None: + """Index one named program and its module dependencies.""" + if not program.name: return - if ( - self._parse_procedure_header( - stripped, - None, - False, - filename=filename, - lineno=lineno, - source_line=source_line, - ) - is None - ): - self._raise_if_unparsed_procedure_header( - stripped, - in_interface=False, - filename=filename, - lineno=lineno, - source_line=source_line, - ) + program_key = program.name.lower() + self._insert_unique_scope_symbol(project.programs, program_key, program, label="project program scope") + project.dependencies[program_key] = {name.lower() for name in program.uses} - def _helper_validate_file_scope_unparsed_lines(self, lines: _PreprocessedLines, filename: str | None) -> None: - """Reject any non-Fortran syntax outside recognized unit bodies. + def _helper_index_project_interface( + self, + project: FortranProject, + interface: FortranInterface, + owner_key: str | None = None, + ) -> None: + """Index one named interface, qualified when it has an owner.""" + if not interface.name: + return + interface_name = interface.name.lower() + registry_key = f"{owner_key}.{interface_name}" if owner_key else interface_name + self._insert_unique_scope_symbol( + project.interfaces, + registry_key, + interface, + label="project interface scope", + ) + if owner_key: + project.interfaces.setdefault(interface_name, interface) - This guard is intentionally language-agnostic: lines that are neither - valid file-scope Fortran constructs nor part of a recognized unit are - rejected via the generic invalid-syntax diagnostic path. - """ - index = 0 - while index < len(lines): - line, lineno, source_line = lines[index] - stripped = line.strip() - if not stripped: - index += 1 - continue + # ------------------------------------------------------------------ + # Source preparation and unit slicing + # ------------------------------------------------------------------ - self._helper_validate_possible_unit_header( - stripped, - filename=filename, - lineno=lineno, - source_line=source_line, - ) - start = self._helper_classify_unit_start(stripped) - if start is not None: - end_index = self._helper_find_unit_end(lines, index, start[0], filename=filename) - if end_index is not None: - index = end_index + 1 - continue - if self._is_executable_statement_start(stripped): - # A standalone include fragment can contain executable lines - # without an enclosing procedure. Once execution starts, the - # remaining fragment is intentionally opaque to this parser. - return - if self._is_allowed_unparsed_file_scope_line(stripped): - index += 1 - continue - self._raise_invalid_fortran_syntax_line( - stripped, - context="file scope", - filename=filename, - lineno=lineno, - source_line=source_line, + def _preprocessed_lines(self, source: _SourceOrLines, filename: str | None) -> _PreprocessedLines: + """Return preprocessed source lines, reusing file-level preprocessing when supplied.""" + if isinstance(source, list): + lines = source + else: + for lineno, source_line in enumerate(source.splitlines(), start=1): + self._raise_for_raw_cpp_directive(source_line, filename, lineno, source_line) + source_without_linemarkers = "".join( + re.sub(r"[^\r\n]", "", line) if _FORTRAN_LINEMARKER_RE.match(line.strip()) else line + for line in source.splitlines(keepends=True) ) + lines = preprocess_lines(source_without_linemarkers, filename) + for line, lineno, source_line in lines: + self._raise_for_raw_cpp_directive(line, filename, lineno, source_line) + return lines @staticmethod - def _is_allowed_unparsed_file_scope_line(line: str) -> bool: - """Return whether a file-scope line is intentionally metadata-only.""" - stripped = line.strip() - return ( - stripped.startswith("#") - or FortranParser._is_openmp_directive(stripped) - or _REGEX["include"].match(stripped) - ) - - @staticmethod - def _raise_invalid_fortran_syntax_line( + def _raise_for_raw_cpp_directive( line: str, - *, - context: str, filename: str | None, - lineno: int | None, - source_line: str | None, + lineno: int, + source_line: str, ) -> None: - """Raise the shared invalid-syntax diagnostic for one source line.""" - raise FortranParseError( - f"Invalid Fortran syntax in {context}: {line.strip()}", - filename=filename, - line_number=lineno, - source_line=source_line, - code="PARSE_INVALID_SYNTAX", - ) - - def _helper_slice_child_units( - self, - lines: _PreprocessedLines, - *, - parent_scope: _ParserScope, - allowed_kinds: set[str] | None = None, - filename: str | None = None, - skip_execution_region: bool = False, - ) -> list[SourceUnit]: - """Slice direct child units from a parent source substring. - - The name says "child" because this helper is recursive by design: - `parse_file` calls it for file-level units, `_visit_ModuleUnit` calls it - for module children, and `_visit_InterfaceUnit` calls it for interface - procedure declarations. Each returned `SourceUnit.lines` contains only - that child unit's substring. - - Example: - In ``module m`` with an ``interface`` block and a contained - ``subroutine run``, this helper returns two direct children for the - module scope: one interface unit and one procedure unit. The - interface's subroutine is not returned at module level; it is - returned when the interface visitor asks for its own children. - """ - units: list[SourceUnit] = [] - index = 0 - region = "specification" - while index < len(lines): - line, lineno, _ = lines[index] - stripped = line.strip() - if stripped.startswith("#"): - index += 1 - continue - if skip_execution_region: - if self._is_contains_transition(stripped): - region = "contains" - index += 1 - continue - if region == "specification" and self._is_executable_statement_start(stripped): - region = "execution" - if region == "execution": - index += 1 - continue - if parent_scope.kind == "interface" and re.match(r"^module\s+procedure\b", line.strip(), re.IGNORECASE): - index += 1 - continue - start = self._helper_classify_unit_start(line) - if start is None: - index += 1 - continue - kind, name = start - if allowed_kinds is not None and kind not in allowed_kinds: - index += 1 - continue - - end_index = self._helper_find_unit_end(lines, index, kind, filename=filename) - if end_index is None: - if kind == "interface" and (lines[index][2] or "").strip().lower().startswith("end interface"): - index += 1 - continue - if parent_scope.kind == "interface" and kind == "procedure": - # Interface bodies often contain a procedure declaration - # that is closed by `end interface`, not by an explicit - # `end subroutine`/`end function`. In that grammar context - # the whole remaining interface substring belongs to the - # declaration. - end_index = len(lines) - 1 - else: - label = self._helper_unit_label(kind) - raise FortranParseError( - f"Missing end {label} for {label} '{name or ''}'.", - filename=filename, - line_number=lineno, - source_line=lines[index][2], - code="PARSE_MISSING_UNIT_END", - ) - - end_line = lines[end_index][1] - units.append( - _SOURCE_UNIT_TYPES[kind]( - kind=kind, - name=name, - lines=lines[index : end_index + 1], - start_line=lineno, - end_line=end_line, - ) + """Reject raw CPP directives while accepting compiler linemarkers.""" + stripped = line.strip() + if stripped.startswith("#") and not _FORTRAN_LINEMARKER_RE.match(stripped): + raise FortranParseError( + "Fortran CPP directives require compiler preprocessing before parsing.", + filename=filename, + line_number=lineno, + source_line=source_line, + code="PARSE_PREPROCESSING_REQUIRED", ) - index = end_index + 1 - return units - def _helper_find_unit_end( + def _helper_collect_namespace( self, - lines: _PreprocessedLines, - start_index: int, - kind: str, + root: str | Path, + extensions: tuple[str, ...] = (".f", ".for", ".ftn", ".f77", ".f90", ".f95", ".f03", ".f08"), *, - filename: str | None = None, - ) -> int | None: - """Find the matching end line for a source unit. + encoding: str = "utf-8", + ) -> dict: + """Collect parseable source files and dependency-order them. - The helper walks nested parseable units with a small stack. It is used - before building a `SourceUnit`, so every visitor receives only its own - substring and original line numbers remain attached to each tuple. + Project parsing uses this helper when the caller passes a directory + instead of an explicit file list. It performs a light first pass to map + modules/submodules to files, topologically orders files by ``use`` + dependencies, then parses them in that order. Example: - Given a module containing an interface containing a subroutine, the - module end is returned, not the subroutine end, because the nested - interface/procedure pair is pushed and popped before the module is - closed. + ``parse_project("src")`` calls this helper, receives + ``{"files": ordered_files, "module_to_file": ...}``, and then + parses each ordered path through the normal file entrypoint. """ - start = self._helper_classify_unit_start(lines[start_index][0]) - start_name = start[1] if start is not None else None - stack: list[tuple[str, str | None, int | None, str | None, str]] = [ - (kind, start_name, lines[start_index][1], lines[start_index][2], "specification") - ] - idx = start_index + 1 - while idx < len(lines): - line, lineno, source_line = lines[idx] - line = line.strip() - if not line: - idx += 1 - continue - current_kind, current_name, current_line, current_source, current_region = stack[-1] - if current_kind == "interface" and re.match(r"^module\s+procedure\b", line, re.IGNORECASE): - idx += 1 - continue - - closes_current, end_name = self._helper_parse_unit_end(current_kind, line) - if closes_current: - if end_name and current_name and end_name.lower() != current_name.lower(): - if current_kind == "procedure" and self._helper_has_preferred_unit_end_ahead( - lines, - idx, - current_kind, - current_name, - ): - idx += 1 - continue - label = self._helper_unit_label(current_kind) - if current_kind != "procedure": - raise FortranParseError( - f"Mismatched end {label} name '{end_name}' for {label} '{current_name}'.", - filename=filename, - line_number=lineno, - source_line=source_line, - code="PARSE_MISMATCHED_UNIT_END", - ) - stack.pop() - if not stack: - return idx - idx += 1 - continue - - grammar = self._helper_unit_grammar(current_kind) - if self._is_contains_transition(line) and grammar.has_contains_part: - stack[-1] = (current_kind, current_name, current_line, current_source, "contains") - idx += 1 - continue - if ( - current_region == "specification" - and grammar.has_execution_part - and self._is_executable_statement_start(line) - ): - stack[-1] = (current_kind, current_name, current_line, current_source, "execution") - idx += 1 - continue - if current_region == "execution": - idx += 1 - continue - - start = self._helper_classify_unit_start(line) - if start is not None and self._helper_has_unit_end_ahead(lines, idx, start[0]): - nested_kind, _ = start - stack.append((nested_kind, start[1], lineno, source_line, "specification")) - idx += 1 - continue - - for open_kind, _open_name, _open_line, _open_source, _open_region in reversed(stack): - closes_open, end_name = self._helper_parse_unit_end(open_kind, line) - if not closes_open: - continue - label = self._helper_unit_label(current_kind) - expected = self._helper_unit_label(open_kind) - raise FortranParseError( - f"Unexpected end {expected} while parsing {label} '{current_name or ''}'.", - filename=filename, - line_number=lineno, - source_line=source_line, - code="PARSE_UNEXPECTED_UNIT_END", - ) - idx += 1 - return None + root_path = Path(root) + files = sorted([p for p in root_path.rglob("*") if p.suffix.lower() in extensions]) + sources = {str(p): p.read_text(encoding=encoding) for p in files} + file_lines = {fname: preprocess_lines(code, fname) for fname, code in sources.items()} - def _helper_has_unit_end_ahead(self, lines: _PreprocessedLines, start_index: int, kind: str) -> bool: - """Check whether a candidate unit opener has a later matching end. + module_to_file: dict[str, str] = {} + submodule_to_file: dict[str, str] = {} + file_to_uses: dict[str, set[str]] = {fname: set() for fname in sources} + for fname, _code in sources.items(): + lines = file_lines[fname] + _lines, root_scope, all_units = self._helper_prepare_source_units(lines, fname) + modules = [ + self._visit(unit, parent_scope=root_scope, filename=fname) + for unit in all_units + if unit.kind == "module" + ] + submodules = [ + self._visit(unit, parent_scope=root_scope, filename=fname) + for unit in all_units + if unit.kind == "submodule" + ] + for m in modules: + module_to_file[m.name.lower()] = fname + file_to_uses[fname].update(u.lower() for u in m.uses) + for sm in submodules: + submodule_to_file[sm.name.lower()] = fname + file_to_uses[fname].add(sm.parent.lower()) + if sm.ancestor: + file_to_uses[fname].add(sm.ancestor.lower()) + file_to_uses[fname].update(u.lower() for u in sm.uses) - The slicer uses this conservative look-ahead for ambiguous lines such - as ``type :: state``. With a later ``end type`` it is a derived-type - unit; without one the existing parser treats it as a declaration-like - line and continues. + file_dependencies: dict[str, set[str]] = {} + for fname, used_modules in file_to_uses.items(): + deps = set() + for mod in used_modules: + dep_file = module_to_file.get(mod) or submodule_to_file.get(mod) + if dep_file and dep_file != fname: + deps.add(dep_file) + file_dependencies[fname] = deps - Example: - ``type :: particle`` followed by ``end type particle`` returns - `True`, but a lone ``type :: local_state`` in a program - specification part returns `False`. - """ - start = self._helper_classify_unit_start(lines[start_index][0]) - start_name = start[1] if start is not None else None - if self._helper_has_preferred_unit_end_ahead(lines, start_index, kind, start_name): - return True - if kind != "procedure": - return False - for idx in range(start_index + 1, len(lines)): - matched, _end_name = self._helper_parse_unit_end(kind, lines[idx][0]) - if matched: - return True - return False + ordered_files = self._topological_files(file_dependencies) + types = [] + modules = [] + submodules = [] + programs = [] + block_data = [] + for f in ordered_files: + parsed_file = self.parse_file(sources[f], filename=f, encoding=encoding) + types.extend(parsed_file.derived_types) + types.extend(dtype for module in parsed_file.modules for dtype in module.derived_types) + types.extend(dtype for submodule in parsed_file.submodules for dtype in submodule.derived_types) + modules.extend(parsed_file.modules) + submodules.extend(parsed_file.submodules) + programs.extend(parsed_file.programs) + block_data.extend(parsed_file.block_data_units) - def _helper_has_preferred_unit_end_ahead( - self, - lines: _PreprocessedLines, - start_index: int, - kind: str, - start_name: str | None, - ) -> bool: - """Return whether an exact or unnamed terminator exists later.""" - for idx in range(start_index + 1, len(lines)): - matched, end_name = self._helper_parse_unit_end(kind, lines[idx][0]) - if matched and (not start_name or not end_name or end_name.lower() == start_name.lower()): - return True - return False + return { + "files": ordered_files, + "file_dependencies": {k: sorted(v) for k, v in file_dependencies.items()}, + "module_to_file": module_to_file, + "submodule_to_file": submodule_to_file, + "modules": modules, + "submodules": submodules, + "programs": programs, + "block_data": block_data, + "types": types, + } - def _helper_split_unit_parts( + def _helper_prepare_source_units( self, - unit: SourceUnit, - grammar: _UnitGrammar, - *, - filename: str | None = None, - ) -> _UnitParts: - """Split one unit substring into grammar regions. + code: _SourceOrLines, + filename: str | None, + ) -> tuple[_PreprocessedLines, _ParserScope, list[SourceUnit]]: + """Preprocess, validate, and slice file-level source units. - The helper follows the shape you described: every parseable unit has a - header and a specification part, some have an execution part, and some - have a contains part. Visitors can then be small and choose which - region matters for wrapping metadata. + This is the first grammar-shaped step in `parse_file`: raw source is + normalized into line tuples, obvious malformed headers are rejected, + and only direct file-scope units are returned. Example: - For a procedure, declarations before the first executable - statement go into `specification`, assignments/calls go into - `execution`, and internal procedures after `contains` go into - `contains`. The procedure visitor parses only `specification`. + A file containing ``module constants`` and standalone + ``subroutine solve`` returns a root file scope plus two + `SourceUnit` objects, one module unit and one procedure unit, both + carrying original source line numbers. """ - header = unit.lines[0] if unit.lines else None - footer = unit.lines[-1] if unit.lines and self._helper_unit_end_matches(unit.kind, unit.lines[-1][0]) else None - body = unit.lines[1:-1] if footer is not None else unit.lines[1:] - specification: _PreprocessedLines = [] - execution: _PreprocessedLines = [] - contains: _PreprocessedLines = [] - region = "specification" - index = 0 + lines = self._preprocessed_lines(code, filename) + root_scope = _ParserScope(kind="file", name=None) + units = self._source_unit_scanner.slice_child_units( + lines, + parent_kind=root_scope.kind, + filename=filename, + ) + self._helper_validate_file_scope_unparsed_lines(lines, filename) + self._helper_validate_sibling_units(units, parent_scope=root_scope, filename=filename) + return lines, root_scope, units - while index < len(body): - line, _, _ = body[index] - stripped = line.strip() - if not stripped: - index += 1 - continue - if self._is_contains_transition(stripped): - if not grammar.has_contains_part: - self._raise_invalid_fortran_syntax_line( - stripped, - context=f"{self._helper_unit_label(grammar.kind)} '{unit.name or ''}'", - filename=filename, - lineno=body[index][1], - source_line=body[index][2], - ) - region = "contains" - index += 1 - continue + def _collect_interface_source_units( + self, + code: _SourceOrLines, + filename: str | None, + ) -> list[tuple[SourceUnit, _ParserScope]]: + """Collect interface units with the parent scope needed to parse them. - if grammar.kind == "interface" and re.match(r"^module\s+procedure\b", stripped, re.IGNORECASE): - specification.append(body[index]) - index += 1 - continue + Public plural/singular interface visitors both use this collector so + singular parsing selects one source unit directly instead of parsing a + plural result list and checking its length afterward. + """ + lines, root_scope, _all_units = self._helper_prepare_source_units(code, filename) + interfaces: list[tuple[SourceUnit, _ParserScope]] = [] - start = self._helper_classify_unit_start(stripped) - if start is not None: - child_kind, _ = start - child_end = self._helper_find_unit_end(body, index, child_kind, filename=filename) - if child_end is not None: - index = child_end + 1 + def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: + """Walk non-execution children and retain interface units.""" + for child in child_units: + if child.kind == "interface": + interfaces.append((child, scope)) continue - if grammar.kind == "interface" and child_kind == "procedure": - break - - if ( - region == "specification" - and grammar.has_execution_part - and self._is_executable_statement_start(stripped) - ): - region = "execution" - - if region == "specification": - specification.append(body[index]) - elif region == "execution": - execution.append(body[index]) - else: - contains.append(body[index]) - index += 1 - - return _UnitParts( - header=header, - specification=specification, - execution=execution, - contains=contains, - footer=footer, - ) + if child.kind in {"module", "submodule"}: + child_scope = _ParserScope( + kind=child.kind, + name=child.name, + parent=scope, + module_owner=child.name, + ) + collect( + child_scope, + self._source_unit_scanner.nonexecution_child_units(child, filename=filename), + ) + continue + if child.kind in {"procedure", "program"}: + child_scope = _ParserScope( + kind=child.kind, + name=child.name, + parent=scope, + module_owner=scope.module_owner, + ) + collect( + child_scope, + self._source_unit_scanner.nonexecution_child_units(child, filename=filename), + ) - def _helper_child_unit_region( - self, - unit: SourceUnit, - parts: _UnitParts, - child: SourceUnit, - ) -> str: - """Return the grammar region containing one direct child unit.""" - child_line = child.start_line - if child_line is None: - return "specification" - contains_line = self._helper_direct_contains_line(unit, filename=None) - if contains_line is not None and child_line > contains_line: - return "contains" - execution_line = next( - (lineno for _line, lineno, _source_line in parts.execution if lineno is not None), - None, + collect( + root_scope, + self._source_unit_scanner.slice_child_units( + lines, + parent_kind=root_scope.kind, + filename=filename, + ), ) - if execution_line is not None and child_line >= execution_line: - return "execution" - return "specification" + return interfaces - def _helper_nonexecution_child_units( + def _collect_derived_type_source_units( self, - unit: SourceUnit, - *, - parent_scope: _ParserScope, + code: _SourceOrLines, filename: str | None, - ) -> list[SourceUnit]: - """Return direct nested units outside an intentionally skipped execution part.""" - grammar = self._helper_unit_grammar(unit.kind) - child_units = self._helper_slice_child_units( - unit.lines[1:-1], - parent_scope=parent_scope, - filename=filename, - skip_execution_region=grammar.has_execution_part, + ) -> list[tuple[SourceUnit, _ParserScope]]: + """Collect derived-type units with their module/program scope context.""" + lines, root_scope, _all_units = self._helper_prepare_source_units(code, filename) + types: list[tuple[SourceUnit, _ParserScope]] = [] + + def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: + """Walk nested grammar units and retain derived-type units.""" + for child in child_units: + if child.kind == "derived_type": + types.append((child, scope)) + continue + if child.kind in {"module", "submodule", "program"}: + child_scope = _ParserScope( + kind=child.kind, + name=child.name, + parent=scope, + module_owner=child.name if child.kind in {"module", "submodule"} else scope.module_owner, + ) + collect( + child_scope, + self._source_unit_scanner.nonexecution_child_units(child, filename=filename), + ) + continue + if child.kind == "procedure": + child_scope = _ParserScope( + kind=child.kind, + name=child.name, + parent=scope, + module_owner=scope.module_owner, + ) + collect( + child_scope, + self._source_unit_scanner.nonexecution_child_units(child, filename=filename), + ) + + collect( + root_scope, + self._source_unit_scanner.slice_child_units( + lines, + parent_kind=root_scope.kind, + filename=filename, + ), ) - if not grammar.has_execution_part: - return child_units - parts = self._helper_split_unit_parts(unit, grammar, filename=filename) - return [child for child in child_units if self._helper_child_unit_region(unit, parts, child) != "execution"] + return types - def _helper_direct_contains_line( + def _helper_validate_possible_unit_header( self, - unit: SourceUnit, + line: str, *, filename: str | None, - ) -> int | None: - """Return the direct `contains` transition, skipping nested child units.""" - body = unit.lines[1:-1] + lineno: int | None, + source_line: str | None, + ) -> None: + """Validate a line that lexically resembles a source-unit header.""" + stripped = line.strip() + self._parse_module_header(stripped, filename, lineno=lineno, source_line=source_line) + if stripped.lower().startswith("end "): + return + if re.match(r"^module\s+procedure\s*::", stripped, flags=re.IGNORECASE): + return + if not ( + stripped.lower().startswith("module procedure") + or self._source_unit_scanner.looks_like_procedure_header(stripped) + ): + return + if ( + self._parse_procedure_header( + stripped, + None, + False, + filename=filename, + lineno=lineno, + source_line=source_line, + ) + is None + ): + self._raise_if_unparsed_procedure_header( + stripped, + in_interface=False, + filename=filename, + lineno=lineno, + source_line=source_line, + ) + + def _helper_validate_file_scope_unparsed_lines(self, lines: _PreprocessedLines, filename: str | None) -> None: + """Reject any non-Fortran syntax outside recognized unit bodies. + + This guard is intentionally language-agnostic: lines that are neither + valid file-scope Fortran constructs nor part of a recognized unit are + rejected via the generic invalid-syntax diagnostic path. + """ index = 0 - while index < len(body): - line, lineno, _source_line = body[index] + while index < len(lines): + line, lineno, source_line = lines[index] stripped = line.strip() - if self._is_contains_transition(stripped): - return lineno - start = self._helper_classify_unit_start(stripped) + if not stripped: + index += 1 + continue + + self._helper_validate_possible_unit_header( + stripped, + filename=filename, + lineno=lineno, + source_line=source_line, + ) + start = self._source_unit_scanner.classify_unit_start(stripped) if start is not None: - child_end = self._helper_find_unit_end(body, index, start[0], filename=filename) - if child_end is not None: - index = child_end + 1 + end_index = self._source_unit_scanner.find_unit_end(lines, index, start[0], filename=filename) + if end_index is not None: + index = end_index + 1 continue - index += 1 - return None + if self._source_unit_scanner.is_executable_statement_start(stripped): + # A standalone include fragment can contain executable lines + # without an enclosing procedure. Once execution starts, the + # remaining fragment is intentionally opaque to this parser. + return + if self._is_allowed_unparsed_file_scope_line(stripped): + index += 1 + continue + _raise_invalid_fortran_syntax_line( + stripped, + context="file scope", + filename=filename, + lineno=lineno, + source_line=source_line, + ) + + @staticmethod + def _is_allowed_unparsed_file_scope_line(line: str) -> bool: + """Return whether a file-scope line is intentionally metadata-only.""" + stripped = line.strip() + return ( + stripped.startswith("#") + or _SourceUnitScanner.is_openmp_directive(stripped) + or _REGEX["include"].match(stripped) + ) def _helper_validate_child_unit_regions( self, @@ -2077,14 +2357,16 @@ def _helper_validate_child_unit_regions( } grammar_regions = allowed.get(unit.kind, {}) for child in child_units: - region = self._helper_child_unit_region(unit, parts, child) + region = self._source_unit_scanner.child_unit_region(unit, parts, child) if region == "execution": continue if child.kind in grammar_regions.get(region, set()): continue - self._raise_invalid_fortran_syntax_line( + _raise_invalid_fortran_syntax_line( child.lines[0][0] if child.lines else child.kind, - context=(f"{self._helper_unit_label(unit.kind)} '{unit.name or ''}' {region} part"), + context=( + f"{self._source_unit_scanner.unit_label(unit.kind)} '{unit.name or ''}' {region} part" + ), filename=filename, lineno=child.start_line, source_line=child.lines[0][2] if child.lines else None, @@ -2110,9 +2392,11 @@ def _helper_validate_contains_lines( lineno=lineno, source_line=source_line, ) - self._raise_invalid_fortran_syntax_line( + _raise_invalid_fortran_syntax_line( stripped, - context=f"{self._helper_unit_label(scope.kind)} '{scope.name or ''}' contains part", + context=( + f"{self._source_unit_scanner.unit_label(scope.kind)} '{scope.name or ''}' contains part" + ), filename=filename, lineno=lineno, source_line=source_line, @@ -2155,7 +2439,7 @@ def _helper_validate_interface_lines( lineno=lineno, source_line=source_line, ) - self._raise_invalid_fortran_syntax_line( + _raise_invalid_fortran_syntax_line( stripped, context=f"interface '{scope.name or ''}'", filename=filename, @@ -2175,7 +2459,7 @@ def _helper_parse_enum_unit( module_owner: str | None, ) -> FortranEnum: """Parse an interoperability enum block into enumerator constants.""" - parts = self._helper_split_unit_parts(unit, self._helper_unit_grammar("enum"), filename=filename) + parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) bind_c = bool(unit.lines and _REGEX["bind_c"].search(unit.lines[0][0])) enum = FortranEnum(name=unit.name, module=module_owner, bind_c=bind_c) symbols: dict[str, str] = {} @@ -2191,7 +2475,7 @@ def _helper_parse_enum_unit( enumerator, next_value = self._parse_enum_item(item, symbols, next_value) enum.enumerators.append(enumerator) except FortranParseError: - self._raise_invalid_fortran_syntax_line( + _raise_invalid_fortran_syntax_line( stripped, context="enum specification part", filename=filename, @@ -2199,15 +2483,17 @@ def _helper_parse_enum_unit( source_line=source_line, ) continue - self._raise_invalid_fortran_syntax_line( + _raise_invalid_fortran_syntax_line( stripped, context="enum specification part", filename=filename, lineno=lineno, source_line=source_line, ) - child_units = self._helper_slice_child_units( - unit.lines[1:-1], parent_scope=_ParserScope(kind="enum", name=unit.name), filename=filename + child_units = self._source_unit_scanner.slice_child_units( + unit.lines[1:-1], + parent_kind="enum", + filename=filename, ) self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) return enum @@ -2260,7 +2546,7 @@ def _helper_validate_ignored_child_units( if ( unit is not None and parts is not None - and self._helper_child_unit_region(unit, parts, child) == "execution" + and self._source_unit_scanner.child_unit_region(unit, parts, child) == "execution" ): continue if child.kind == "procedure": @@ -2324,184 +2610,6 @@ def _helper_validate_sibling_units( ) seen.setdefault(key, []).append(unit) - def _helper_unit_grammar(self, kind: str) -> _UnitGrammar: - """Return the grammar profile used by the source-unit visitor methods. - - The name is intentionally explicit: callers ask for the grammar of a - source unit before splitting its body into the same high-level regions - that appear in the Fortran standard: header, specification part, - execution part, and contains part. - - Example: - A procedure has a specification part and an execution part, but - wrapper metadata only needs the specification part:: - - grammar = self._helper_unit_grammar("procedure") - assert grammar.has_execution_part is True - assert grammar.ignores_contains_children is True - """ - grammars = { - "module": _UnitGrammar( - kind="module", - has_contains_part=True, - declaration_role="module_variable", - ), - "submodule": _UnitGrammar( - kind="submodule", - has_contains_part=True, - declaration_role="module_variable", - ), - "program": _UnitGrammar( - kind="program", - has_execution_part=True, - has_contains_part=True, - ignores_contains_children=True, - declaration_role="module_variable", - ), - "procedure": _UnitGrammar( - kind="procedure", - has_execution_part=True, - has_contains_part=True, - ignores_contains_children=True, - declaration_role="procedure_symbol", - ), - "derived_type": _UnitGrammar( - kind="derived_type", - has_contains_part=True, - declaration_role="type_field", - ), - "interface": _UnitGrammar(kind="interface"), - "block_data": _UnitGrammar(kind="block_data", declaration_role="module_variable"), - "file": _UnitGrammar(kind="file", has_contains_part=True), - } - return grammars.get(kind, _UnitGrammar(kind=kind)) - - def _helper_classify_unit_start(self, line: str) -> tuple[str, str | None] | None: - """Classify a line that opens a parseable Fortran source unit. - - The helper name uses "classify" because it does not parse the unit; it - only recognizes the header enough for the slicer and visitors to agree - on what visitor should be called next. - - Example: - ``module mesh`` becomes ``("module", "mesh")`` while - ``module procedure reset`` becomes ``("procedure", "reset")``. - That lets a submodule `contains` region reuse the normal procedure - visitor instead of having a special submodule-only loop. - """ - stripped = line.strip() - if not stripped: - return None - lower = stripped.lower() - if lower.startswith("end "): - return None - submodule = _REGEX["submodule"].match(stripped) - if submodule: - return "submodule", submodule.group("name") - module = _REGEX["module"].match(stripped) - if module: - return "module", module.group("name") - program = _REGEX["program"].match(stripped) - if program: - return "program", program.group("name") - block_data = _REGEX["block_data"].match(stripped) - if block_data: - return "block_data", block_data.group("name") - if lower == "enum" or lower.startswith("enum,"): - return "enum", None - starts_interface, interface_name = self._parse_interface_header(stripped) - if starts_interface: - return "interface", interface_name - module_proc = _REGEX["module_procedure_impl"].match(stripped) - if module_proc: - return "procedure", module_proc.group("name") - if FortranParser._looks_like_procedure_header(stripped): - proc_match = _REGEX["procedure"].match(stripped) or _REGEX["function"].match(stripped) - if proc_match: - return "procedure", proc_match.group("name") - parsed_type = self._parse_derived_type_start(stripped) - if parsed_type: - return "derived_type", parsed_type[0] - return None - - @staticmethod - def _helper_parse_unit_end(kind: str, line: str) -> tuple[bool, str | None]: - """Return whether `line` closes `kind`, plus the optional end name. - - This is the shared closing-token table for the slicer and the unit - splitter. Keeping it centralized prevents one visitor from accepting a - different end spelling than another visitor. - - Example: - ``_helper_parse_unit_end("module", "end module mesh")`` returns - ``(True, "mesh")``. ``_helper_parse_unit_end("procedure", - "end function norm")`` returns ``(True, "norm")``. - """ - stripped = line.strip() - lower = stripped.lower() - if kind == "module": - m = re.match(r"^end\s+module(?:\s+(?P\w+))?\s*$", stripped, re.IGNORECASE) - return (m is not None, m.group("name") if m else None) - if kind == "submodule": - m = re.match(r"^end\s+submodule(?:\s+(?P\w+))?\s*$", stripped, re.IGNORECASE) - return (m is not None, m.group("name") if m else None) - if kind == "program": - m = re.match(r"^end\s+program(?:\s+(?P\w+))?\s*$", stripped, re.IGNORECASE) - return (m is not None, m.group("name") if m else None) - if kind == "block_data": - if lower == "end": - return True, None - m = re.match(r"^end\s+block\s+data(?:\s+(?P\w+))?\s*$", stripped, re.IGNORECASE) - return (m is not None, m.group("name") if m else None) - if kind == "interface": - m = re.match(r"^end\s+interface(?:\s+(?P.+?))?\s*$", stripped, re.IGNORECASE) - return (m is not None, m.group("name") if m else None) - if kind == "derived_type": - m = re.match(r"^end\s+type(?:\s+(?P\w+))?\s*$", stripped, re.IGNORECASE) - return (m is not None, m.group("name") if m else None) - if kind == "enum": - m = re.match(r"^end\s+enum\s*$", stripped, re.IGNORECASE) - return (m is not None, None) - if kind == "procedure": - if lower == "end": - return True, None - m = re.match(r"^end\s+(?:subroutine|function|procedure)(?:\s+(?P\w+))?\s*$", stripped, re.IGNORECASE) - return (m is not None, m.group("name") if m else None) - return False, None - - @staticmethod - def _helper_unit_label(kind: str) -> str: - """Return a human-readable unit kind for diagnostics. - - The parser stores normalized kind names such as ``"block_data"`` in - `SourceUnit`; diagnostics should use Fortran-facing text instead. - - Example: - ``_helper_unit_label("derived_type")`` returns - ``"derived type"`` for an error like "Missing end derived type". - """ - return kind.replace("_", " ") - - @staticmethod - def _helper_unit_end_matches(kind: str, line: str) -> bool: - """Return whether `line` closes a unit of `kind`. - - The method isolates all end-token spelling differences so the slicer, - the body splitter, and unit visitors share one closing-rule table. - - Example: - The splitter calls ``_helper_unit_end_matches("program", line)`` - on the last line of a sliced unit to decide whether that line is - the footer or still belongs to the unit body. - """ - matched, _ = FortranParser._helper_parse_unit_end(kind, line) - return matched - - @staticmethod - def _is_contains_transition(line: str) -> bool: - """Return whether `line` starts a unit's `contains` region.""" - return line.lower() == "contains" - # ------------------------------------------------------------------ # Header parsers and source-unit construction # ------------------------------------------------------------------ @@ -2556,19 +2664,6 @@ def _parse_block_data_header(self, line: str, filename: str | None) -> FortranBl return None return FortranBlockData(name=match.group("name"), filename=filename) - def _parse_derived_type_start(self, line: str) -> tuple[str, list[str]] | None: - """Parse modern or legacy derived-type opening syntax.""" - stripped = line.strip() - tm = _REGEX["derived_type"].match(stripped) - if tm: - attr_txt = (tm.group("attrs") or "").strip().lstrip(",").strip() - attrs = [a.strip() for a in split_csv(attr_txt)] if attr_txt else [] - return tm.group("name"), attrs - legacy = re.match(r"^type\s+(?P\w+)\s*$", stripped, re.IGNORECASE) - if legacy: - return legacy.group("name"), [] - return None - def _init_derived_type( self, line: str, @@ -2576,7 +2671,7 @@ def _init_derived_type( current_module: str | None, ) -> FortranDerivedType | None: """Build a derived-type model from one recognized opening line.""" - parsed_type = self._parse_derived_type_start(line) + parsed_type = self._source_unit_scanner.parse_derived_type_start(line) if not parsed_type: return None @@ -2607,16 +2702,6 @@ def _init_derived_type( ) return derived_type - @staticmethod - def _parse_interface_header(line: str) -> tuple[bool, str | None]: - """Return whether `line` opens an interface and its optional name.""" - lower = line.lower() - if not (lower.startswith("interface") or lower.startswith("abstract interface")): - return False, None - parts = line.split(maxsplit=1) - name = parts[1].strip() if len(parts) > 1 and not lower.startswith("abstract interface") else None - return True, name - def _parse_procedure_header( self, line: str, @@ -2779,7 +2864,7 @@ def _raise_if_unparsed_procedure_header( source_line=source_line, code="PARSE_MALFORMED_HEADER", ) - if FortranParser._looks_like_procedure_header(stripped): + if _SourceUnitScanner.looks_like_procedure_header(stripped): raise FortranParseError( f"Unsupported or malformed procedure header: {stripped}", filename=filename, @@ -3025,8 +3110,8 @@ def _parse_specification_part( The helper name mirrors the grammar term "specification part". It is called by module, submodule, program, procedure, derived-type, and - block-data visitors after `_helper_split_unit_parts` has isolated the - relevant region. + block-data visitors after `_SourceUnitScanner.split_unit_parts` has + isolated the relevant region. Example: A program and a procedure both have executable statements, but this @@ -3101,7 +3186,7 @@ def _parse_module_like_spec_line( self._record_common_variables(target.common_variables, stripped) return - if self._is_openmp_declarative_directive(stripped): + if self._source_unit_scanner.is_openmp_declarative_directive(stripped): self._raise_unsupported_openmp_declaration(target, stripped, filename, lineno, source_line) if self._apply_default_module_visibility(scope, target, lower): @@ -3114,7 +3199,7 @@ def _parse_module_like_spec_line( return if _REGEX["derived_type"].match(stripped): - parsed_type = self._parse_derived_type_start(stripped) + parsed_type = self._source_unit_scanner.parse_derived_type_start(stripped) raise FortranParseError( f"Missing end derived type for derived type '{parsed_type[0] if parsed_type else ''}'.", filename=filename, @@ -3133,7 +3218,7 @@ def _parse_module_like_spec_line( parsed = self._helper_parse_declaration_line( stripped, scope, - role=self._helper_unit_grammar(scope.kind).declaration_role or "module_variable", + role=self._source_unit_scanner.grammar(scope.kind).declaration_role or "module_variable", filename=filename, lineno=lineno, source_line=source_line, @@ -3198,8 +3283,8 @@ def _handle_non_declaration_spec_line(self, scope, target, line, filename, linen program may contain execution statements after its declaration region; other module-like scopes receive a source-located error instead. """ - executable = self._is_executable_statement_start(line) - if not executable and not self._is_ignored_spec_statement(line): + executable = self._source_unit_scanner.is_executable_statement_start(line) + if not executable and not self._source_unit_scanner.is_ignored_spec_statement(line): return False if executable and scope.kind != "program": owner_kind, owner_name = self._variable_scope_label(target) @@ -3221,8 +3306,8 @@ def _raise_unsupported_module_like_declaration(self, target, line, filename, lin diagnostic. Both errors preserve the owner label and source location. """ owner_kind, owner_name = self._variable_scope_label(target) - if "::" not in line and not self._looks_like_declaration_or_spec(line): - self._raise_invalid_fortran_syntax_line( + if "::" not in line and not self._source_unit_scanner.looks_like_declaration_or_spec(line): + _raise_invalid_fortran_syntax_line( line, context=f"{owner_kind} '{owner_name or ''}' specification part", filename=filename, @@ -3263,7 +3348,7 @@ def _parse_procedure_spec_line( if re.match(r"^common\b", stripped, flags=re.IGNORECASE): self._record_common_variables(proc_state.common_variables, stripped) return - if self._is_openmp_declarative_directive(stripped): + if self._source_unit_scanner.is_openmp_declarative_directive(stripped): raise FortranParseError( f"Unsupported OpenMP declarative directive in procedure '{proc_state.signature.name}': {stripped}", filename=filename, @@ -3295,7 +3380,7 @@ def _parse_procedure_spec_line( source_line=source_line, ): return - if self._is_statement_function_statement(stripped): + if self._source_unit_scanner.is_statement_function_statement(stripped): return parsed = self._helper_parse_declaration_line( @@ -3365,7 +3450,7 @@ def _parse_type_spec_line( return if stripped.lower() == "private": return - if self._is_openmp_declarative_directive(stripped): + if self._source_unit_scanner.is_openmp_declarative_directive(stripped): raise FortranParseError( f"Unsupported OpenMP declarative directive in type '{dtype.name}': {stripped}", filename=filename, @@ -3384,8 +3469,8 @@ def _parse_type_spec_line( ) if parsed: return - if "::" not in stripped and not self._looks_like_declaration_or_spec(stripped): - self._raise_invalid_fortran_syntax_line( + if "::" not in stripped and not self._source_unit_scanner.looks_like_declaration_or_spec(stripped): + _raise_invalid_fortran_syntax_line( stripped, context=f"type '{dtype.name}' specification part", filename=filename, @@ -3463,15 +3548,15 @@ def _helper_apply_local_interface_declarations( ``subroutine cb(x)``, this helper updates the already-known dummy argument ``cb`` so its base type becomes ``"procedure"``. """ - interface_units = self._helper_slice_child_units( + interface_units = self._source_unit_scanner.slice_child_units( unit.lines[1:-1], - parent_scope=scope, + parent_kind=scope.kind, allowed_kinds={"interface"}, filename=filename, skip_execution_region=True, ) for interface_unit in interface_units: - if self._helper_child_unit_region(unit, parts, interface_unit) == "execution": + if self._source_unit_scanner.child_unit_region(unit, parts, interface_unit) == "execution": continue interface = self._visit(interface_unit, parent_scope=scope, filename=filename) for signature in interface.procedures: @@ -4140,7 +4225,7 @@ def _handle_unknown_proc_declaration( as an unsupported datatype declaration for the active procedure. """ if not self._looks_like_unknown_proc_declaration(line): - self._raise_invalid_fortran_syntax_line( + _raise_invalid_fortran_syntax_line( line, context=f"procedure '{proc_state.signature.name}' specification part", filename=filename, @@ -5099,112 +5184,9 @@ def _bind_c_name(tail: str) -> str | None: name = match.groupdict().get("name") return name if name else None - @staticmethod - def _looks_like_procedure_header(line: str) -> bool: - """Return whether a line resembles a subroutine or function header.""" - stripped = line.strip() - if not stripped: - return False - lowered = stripped.lower() - if lowered.startswith(("end ", "call ")): - return False - without_strings = re.sub(r"'[^']*'|\"[^\"]*\"", "", stripped) - return bool(re.search(r"(?:^|[\s,])(?:subroutine|function)\s+[A-Za-z_]\w*", without_strings, re.IGNORECASE)) - - @staticmethod - def _is_openmp_directive(line: str) -> bool: - """Return whether a line begins an OpenMP sentinel directive.""" - return line.lstrip().lower().startswith("!$omp") - - @staticmethod - def _is_openmp_declarative_directive(line: str) -> bool: - """Return whether an OpenMP directive belongs in a specification part.""" - directive = line.lstrip()[5:].strip().lower() if FortranParser._is_openmp_directive(line) else "" - return directive.startswith( - ( - "threadprivate", - "declare simd", - "declare target", - "declare reduction", - "requires", - "declare mapper", - ) - ) - - @staticmethod - def _looks_like_declaration_or_spec(line: str) -> bool: - """Return whether a line resembles a specification-part statement.""" - stripped = line.strip() - if not stripped: - return False - lowered = stripped.lower() - if FortranParser._is_openmp_directive(stripped): - return FortranParser._is_openmp_declarative_directive(stripped) - first_match = re.match(r"([a-z_][a-z0-9_]*)", lowered) - first = first_match.group(1) if first_match else lowered.split(None, 1)[0].rstrip(",") - non_decl_starts = { - "do", - "if", - "where", - "call", - "select", - "case", - "allocate", - "deallocate", - "print", - "write", - "read", - "return", - "stop", - "cycle", - "exit", - "continue", - "end", - "else", - "elseif", - "contains", - "goto", - "go", - "format", - } - if first in non_decl_starts: - return False - if "::" in stripped or "," in stripped: - return True - return bool(re.match(r"^[A-Za-z_]\w+\s+[A-Za-z_]\w*", stripped)) - - @staticmethod - def _is_statement_function_statement(line: str) -> bool: - """Return whether a line has legacy statement-function syntax.""" - stripped = line.strip() - return bool( - re.match( - r"^[A-Za-z_]\w*\s*\([^()]*\)\s*=", - stripped, - flags=re.IGNORECASE, - ) - ) - - @staticmethod - def _is_ignored_spec_statement(line: str) -> bool: - """Return whether a recognized specification statement needs no model.""" - return bool( - _REGEX["include"].match(line) - or re.match( - r"^(implicit|save|common|data|equivalence|external|intrinsic|parameter|namelist|entry)\b", - line, - flags=re.IGNORECASE, - ) - ) - @staticmethod def _parse_use_statement(line: str) -> tuple[str, list[FortranUseMapping]] | None: - """Parse a `use` statement into its module and explicit symbol mappings. - - Example: - >>> FortranParser._parse_use_statement("use kinds, only: local => remote") - ('kinds', [FortranUseMapping(source='remote', target='local')]) - """ + """Parse a ``use`` statement into its module and explicit mappings.""" match = _REGEX["use"].match(line) if not match: return None @@ -5228,81 +5210,6 @@ def _parse_use_statement(line: str) -> tuple[str, list[FortranUseMapping]] | Non mappings.append(FortranUseMapping(source=source, target=target)) return match.group("module"), mappings - @staticmethod - def _is_executable_statement_start(line: str) -> bool: - """Return whether a line starts execution rather than specification.""" - stripped = line.strip() - if not stripped: # pragma: no cover - callers skip blank lines before executable checks. - return False - labeled = re.match(r"^\d+\s+(?P.*)$", stripped) - if labeled: - stripped = labeled.group("body").strip() - if not stripped: - return False - lowered = stripped.lower() - if FortranParser._is_openmp_directive(stripped): - return not FortranParser._is_openmp_declarative_directive(stripped) - if FortranParser._parse_use_statement(stripped) is not None or FortranParser._is_ignored_spec_statement( - stripped - ): - return False - first_match = re.match(r"([a-z_][a-z0-9_]*)", lowered) - first = first_match.group(1) if first_match else lowered.split(None, 1)[0] - if first.isdigit(): - return False - executable_starts = { - "do", - "if", - "where", - "call", - "select", - "case", - "allocate", - "deallocate", - "print", - "write", - "read", - "return", - "stop", - "cycle", - "exit", - "continue", - "goto", - "go", - "open", - "close", - "rewind", - "backspace", - "inquire", - "flush", - "wait", - "nullify", - "associate", - "block", - "forall", - "error", - "pause", - } - if first in executable_starts: - return True - if _REGEX["legacy_parameter"].match(stripped): - return False - if FortranParser._is_statement_function_statement(stripped): - return False - if "=" in stripped and "::" not in stripped: - # Distinguish assignment/statements from declaration lines carrying - # type specs with named arguments, e.g.: - # integer ( kind = 4 ) i - # character ( len = * ) s - # Covers assignment and statement functions in execution part. - return not ( - _REGEX["char_star"].match(stripped) - or _REGEX["type"].match(stripped) - or _REGEX["type_field"].match(stripped) - or _REGEX["class_field"].match(stripped) - ) - return False - # ----------------------------------------------------------------------------- # Module-level convenience wrappers diff --git a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py index df0bd451e..267f9429e 100644 --- a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py +++ b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py @@ -7,7 +7,7 @@ ) from prik.parsers.fortran.parser import ( FortranParser, - _ParserScope, + _SourceUnitScanner, ) from tests.fortran._support.parser_regressions import _unit @@ -50,7 +50,7 @@ def test_procedure_bind_c_name_and_value_argument_are_preserved(): def test_nonexecution_child_units_keep_specification_and_contains_children_only(): - parser = FortranParser() + scanner = _SourceUnitScanner() unit = _unit( "procedure", "work", @@ -68,9 +68,8 @@ def test_nonexecution_child_units_keep_specification_and_contains_children_only( "end subroutine work", ) - children = parser._helper_nonexecution_child_units( + children = scanner.nonexecution_child_units( unit, - parent_scope=_ParserScope(kind="procedure", name="work"), filename="children.f90", ) diff --git a/tests/fortran/source_parsing/parsing/test_developer_tutorial.py b/tests/fortran/source_parsing/parsing/test_developer_tutorial.py index c98d57d60..242500509 100644 --- a/tests/fortran/source_parsing/parsing/test_developer_tutorial.py +++ b/tests/fortran/source_parsing/parsing/test_developer_tutorial.py @@ -1,16 +1,16 @@ """Executable developer tutorial for the grammar-style parser internals. This test is intentionally written as a small walkthrough rather than as a -black-box public API test. It shows the private visitor/helper sequence that +black-box public API test. It shows the private visitor/scanner sequence that maintainers should follow when changing `prik/parsers/fortran/parser.py`: -1. preprocess and slice file-level source units, -2. split one unit into grammar parts, +1. preprocess, then scan file-level source units, +2. ask `_SourceUnitScanner` to split one unit into grammar parts, 3. visit the unit with a scope, 4. recursively slice and inspect its direct children. """ -from prik.parsers.fortran.parser import FortranParser +from prik.parsers.fortran.parser import FortranParser, _SourceUnitScanner def test_developer_tutorial_recursive_unit_visitors_and_helpers(): @@ -31,6 +31,7 @@ def test_developer_tutorial_recursive_unit_visitors_and_helpers(): ) parser = FortranParser() + scanner = _SourceUnitScanner() lines, root_scope, top_units = parser._helper_prepare_source_units( source, @@ -42,10 +43,10 @@ def test_developer_tutorial_recursive_unit_visitors_and_helpers(): ] module_unit = top_units[0] - module_grammar = parser._helper_unit_grammar("module") - module_parts = parser._helper_split_unit_parts( + module_grammar = scanner.grammar("module") + assert module_grammar.has_contains_part is True + module_parts = scanner.split_unit_parts( module_unit, - module_grammar, filename="developer_tutorial.f90", ) assert module_parts.header == module_unit.lines[0] @@ -66,9 +67,9 @@ def test_developer_tutorial_recursive_unit_visitors_and_helpers(): assert module.variables[0].symbolic_value == "8" module_scope = parser._helper_scope_for_model("module", module, parent=root_scope) - child_units = parser._helper_slice_child_units( + child_units = scanner.slice_child_units( module_unit.lines[1:-1], - parent_scope=module_scope, + parent_kind=module_scope.kind, filename="developer_tutorial.f90", ) assert [(unit.kind, unit.name, unit.start_line, unit.end_line) for unit in child_units] == [ @@ -76,9 +77,8 @@ def test_developer_tutorial_recursive_unit_visitors_and_helpers(): ] procedure_unit = child_units[0] - procedure_parts = parser._helper_split_unit_parts( + procedure_parts = scanner.split_unit_parts( procedure_unit, - parser._helper_unit_grammar("procedure"), filename="developer_tutorial.f90", ) assert [line[0].strip() for line in procedure_parts.specification] == [ diff --git a/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py b/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py index 7d200f9e7..545c46902 100644 --- a/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py @@ -3,7 +3,7 @@ from prik import parse_fortran_file from prik.parsers.fortran.lexer import preprocess_lines, strip_comment from prik.parsers.fortran.models import FortranProcedureSignature -from prik.parsers.fortran.parser import FortranParser +from prik.parsers.fortran.parser import FortranParser, _SourceUnitScanner from prik.parsers.fortran.utils import split_csv @@ -117,7 +117,7 @@ def test_legacy_procedure_specifications_preserve_wrapper_relevant_facts(): assert signature.result is not None assert signature.result.kind == "selected_real_kind(12)" assert signature.common_variables == ["cache"] - assert FortranParser._is_executable_statement_start("square(value) = value * value") is False + assert _SourceUnitScanner.is_executable_statement_start("square(value) = value * value") is False def test_procedure_include_is_recorded_before_signature_finalization(): diff --git a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py index 5b77bf5de..cb0dfd654 100644 --- a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py @@ -15,6 +15,7 @@ from prik.parsers.fortran.parser import ( FortranParser, _ParserScope, + _SourceUnitScanner, _UnitParts, ) from tests.fortran._support.parser_regressions import ( @@ -25,7 +26,7 @@ def test_unit_region_helpers_preserve_specification_execution_and_contains_boundaries(): - parser = FortranParser() + scanner = _SourceUnitScanner() unit = _unit( "procedure", "work", @@ -47,15 +48,15 @@ def test_unit_region_helpers_preserve_specification_execution_and_contains_bound footer=unit.lines[-1], ) - assert parser._helper_direct_contains_line(unit, filename="regions.f90") == 6 - assert parser._helper_child_unit_region(unit, parts, _empty_unit("derived_type", "unknown", None, None)) == ( + assert scanner.direct_contains_line(unit, filename="regions.f90") == 6 + assert scanner.child_unit_region(unit, parts, _empty_unit("derived_type", "unknown", None, None)) == ( + "specification" + ) + assert scanner.child_unit_region(unit, parts, _unit("derived_type", "local_state", "type :: local_state")) == ( "specification" ) - assert parser._helper_child_unit_region( - unit, parts, _unit("derived_type", "local_state", "type :: local_state") - ) == ("specification") assert ( - parser._helper_child_unit_region( + scanner.child_unit_region( unit, parts, _empty_unit("interface", None, 5, 5), @@ -63,7 +64,7 @@ def test_unit_region_helpers_preserve_specification_execution_and_contains_bound == "execution" ) assert ( - parser._helper_child_unit_region( + scanner.child_unit_region( unit, parts, _empty_unit("procedure", "inner", 7, 8), @@ -71,18 +72,18 @@ def test_unit_region_helpers_preserve_specification_execution_and_contains_bound == "contains" ) - assert parser._helper_has_preferred_unit_end_ahead(unit.lines, 0, "procedure", "work") is True - assert parser._helper_has_preferred_unit_end_ahead(unit.lines, 0, "procedure", "missing") is False - assert parser._helper_has_preferred_unit_end_ahead(unit.lines[:-1], 0, "procedure", "work") is False + assert scanner.has_preferred_unit_end_ahead(unit.lines, 0, "procedure", "work") is True + assert scanner.has_preferred_unit_end_ahead(unit.lines, 0, "procedure", "missing") is False + assert scanner.has_preferred_unit_end_ahead(unit.lines[:-1], 0, "procedure", "work") is False immediate_type = _lines("type :: immediate", "end type immediate") - assert parser._helper_has_preferred_unit_end_ahead(immediate_type, 0, "derived_type", "immediate") is True - assert parser._helper_has_unit_end_ahead(immediate_type, 0, "derived_type") is True - assert parser._helper_has_unit_end_ahead(unit.lines, 2, "derived_type") is True - assert parser._helper_has_unit_end_ahead(unit.lines, 6, "procedure") is True - assert parser._helper_has_unit_end_ahead(unit.lines[:-1], 0, "procedure") is True + assert scanner.has_preferred_unit_end_ahead(immediate_type, 0, "derived_type", "immediate") is True + assert scanner.has_unit_end_ahead(immediate_type, 0, "derived_type") is True + assert scanner.has_unit_end_ahead(unit.lines, 2, "derived_type") is True + assert scanner.has_unit_end_ahead(unit.lines, 6, "procedure") is True + assert scanner.has_unit_end_ahead(unit.lines[:-1], 0, "procedure") is True immediate_contains = _unit("module", "owner", "module owner", "contains", "end module owner") - assert parser._helper_direct_contains_line(immediate_contains, filename="regions.f90") == 2 + assert scanner.direct_contains_line(immediate_contains, filename="regions.f90") == 2 def test_source_preparation_rejects_raw_cpp_and_preserves_root_units_and_source_form(tmp_path: Path): @@ -129,7 +130,7 @@ def test_source_preparation_rejects_raw_cpp_and_preserves_root_units_and_source_ def test_child_unit_slicing_skips_preprocessed_linemarkers_and_blank_unit_starts(): - parser = FortranParser() + scanner = _SourceUnitScanner() lines = _lines( '# 4 "generated.f90"', "", @@ -137,31 +138,32 @@ def test_child_unit_slicing_skips_preprocessed_linemarkers_and_blank_unit_starts "end module owner", ) - units = parser._helper_slice_child_units( + units = scanner.slice_child_units( lines, - parent_scope=_ParserScope(kind="file", name=None), + parent_kind="file", filename="generated.f90", ) assert [(unit.kind, unit.name) for unit in units] == [("module", "owner")] - assert parser._helper_classify_unit_start(" ") is None + assert scanner.classify_unit_start(" ") is None def test_unit_end_and_header_validation_preserve_public_diagnostics(): parser = FortranParser() + scanner = _SourceUnitScanner() - assert parser._helper_parse_unit_end("module", "end module owner_mod") == (True, "owner_mod") - assert parser._helper_parse_unit_end("block_data", "end") == (True, None) - assert parser._helper_parse_unit_end("procedure", "end function value") == (True, "value") - assert parser._helper_unit_end_matches("enum", "end enum") is True - assert parser._helper_unit_label("block_data") == "block data" + assert scanner.parse_unit_end("module", "end module owner_mod") == (True, "owner_mod") + assert scanner.parse_unit_end("block_data", "end") == (True, None) + assert scanner.parse_unit_end("procedure", "end function value") == (True, "value") + assert scanner.unit_end_matches("enum", "end enum") is True + assert scanner.unit_label("block_data") == "block data" assert parser._parse_submodule_header("submodule (ancestor_mod:parent_mod) child_mod", "headers.f90").parent == ( "parent_mod" ) assert parser._split_submodule_parent("ancestor_mod:parent_mod") == ("parent_mod", "ancestor_mod") assert parser._split_submodule_parent("parent_mod") == ("parent_mod", None) - assert parser._parse_interface_header("abstract interface") == (True, None) - assert parser._parse_interface_header("interface callbacks") == (True, "callbacks") + assert scanner.parse_interface_header("abstract interface") == (True, None) + assert scanner.parse_interface_header("interface callbacks") == (True, "callbacks") with pytest.raises(FortranParseError) as module_error: parser._parse_module_header( @@ -194,7 +196,7 @@ def test_unit_end_and_header_validation_preserve_public_diagnostics(): def test_unit_part_splitting_skips_nested_units_and_preserves_executable_boundary(): - parser = FortranParser() + scanner = _SourceUnitScanner() unit = _unit( "procedure", "work", @@ -212,7 +214,7 @@ def test_unit_part_splitting_skips_nested_units_and_preserves_executable_boundar "end subroutine work", ) - parts = parser._helper_split_unit_parts(unit, parser._helper_unit_grammar("procedure"), filename="parts.f90") + parts = scanner.split_unit_parts(unit, filename="parts.f90") assert [line for line, _lineno, _source in parts.specification] == ["integer :: counter"] assert [line for line, _lineno, _source in parts.execution] == ["counter = counter + 1"] @@ -562,7 +564,6 @@ def test_singular_parser_entrypoint_diagnostics_preserve_names_entities_and_file ("_parse_program_header", None, "program", "program"), ("_parse_block_data_header", None, "block_data", "block data"), ("_init_derived_type", None, "derived_type", "derived-type"), - ("_parse_interface_header", (False, None), "interface", "interface"), ], ) def test_source_unit_visitor_defensive_diagnostics_preserve_public_metadata( @@ -589,6 +590,24 @@ def test_source_unit_visitor_defensive_diagnostics_preserve_public_metadata( assert error.value.code == "PARSE_EXPECTED_UNIT" +def test_interface_unit_defensive_diagnostic_uses_scanner_header_recognition(monkeypatch): + parser = FortranParser() + monkeypatch.setattr(parser._source_unit_scanner, "parse_interface_header", lambda _line: (False, None)) + + with pytest.raises(FortranParseError) as error: + parser._visit( + _unit("interface", "broken", "broken header", "broken footer"), + parent_scope=_ParserScope(kind="file", name=None), + filename="visitor_contract.f90", + ) + + assert error.value.base_message == "Expected interface unit." + assert error.value.filename == "visitor_contract.f90" + assert error.value.line_number == 1 + assert error.value.source_line == "broken header" + assert error.value.code == "PARSE_EXPECTED_UNIT" + + def test_unit_models_preserve_filename_propagation(): parsed = parse_fortran_file( """ From 98bce0507d6e919b863974fd43ea7ccb7e8e39a3 Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 17:24:09 +0100 Subject: [PATCH 07/22] Implement SourceUnit/scanner workstream --- CHANGELOG.md | 3 + docs/developer/fortran-parser-reference.md | 54 +- prik/parsers/fortran/parser.py | 891 ++++++++++-------- tests/fortran/_support/parser_regressions.py | 14 +- ...est_procedure_and_interface_regressions.py | 23 +- .../parsing/test_developer_tutorial.py | 34 +- ...source_form_and_diagnostics_regressions.py | 150 +-- 7 files changed, 653 insertions(+), 516 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbaeefce..eb8fe6cc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ release tags add a leading `v` to the package version. - Moved exact overload selection from generated Python predicate chains to generated C dispatchers with planned candidate IDs and direct switch-based calls to the selected existing wrapper. +- Stopped standalone Fortran parser discovery from descending into inaccessible + procedure-internal subprograms; procedure-local callback interfaces remain + classified and discoverable. ## 0.2.1 — 2026-08-11 diff --git a/docs/developer/fortran-parser-reference.md b/docs/developer/fortran-parser-reference.md index 7f5fc7bbb..73e23cd6e 100644 --- a/docs/developer/fortran-parser-reference.md +++ b/docs/developer/fortran-parser-reference.md @@ -147,14 +147,14 @@ feature inventory, public API, diagnostics, project behavior, semantic handoff, or maintenance workflow changes. PRIK_C_DOCS_END --> -`parse_file` is the central orchestration path. It first slices the source into -direct file-level units, then each class visitor parses only its own substring -and recursively slices direct children. This is the key parser design: each +`parse_file` is the central orchestration path. The scanner first constructs +fully classified direct file-level units, then each class visitor parses only +the stored regions and children it owns. This is the key parser design: each Fortran grammar unit has a header, a specification region, optional execution -region, and optional `contains` region. The differences between modules, -programs, procedures, derived types, interfaces, and block data are expressed -by small visitor decisions and grammar flags rather than separate whole-file -parsing loops. +region, optional `contains` region, and retained direct children. The +differences between modules, programs, procedures, derived types, interfaces, +and block data are expressed by small visitor decisions and grammar flags +rather than separate whole-file parsing loops. Nested unit boundaries and placement outside execution regions are checked even when they are not exported as wrapper metadata. Internal procedures inside a @@ -181,23 +181,22 @@ end module m The parser handles it in this order: 1. `parse_file` preprocesses the source and asks the stateless - `_SourceUnitScanner.slice_child_units` collaborator to scan at file scope. - The result is one `ModuleUnit` carrying the module name, exact lines, and - source locations. The scanner receives the parent kind, not `_ParserScope`. + `_SourceUnitScanner.scan_file_units` collaborator to scan at file scope. + The result is one `ModuleUnit` carrying the module name, exact lines, source + locations, classified grammar regions, and retained direct children. The + scanner remains independent of `_ParserScope` and constructed models. 2. the shared `ClassVisitor._visit` dispatcher selects `_visit_ModuleUnit`. -3. `_visit_ModuleUnit` creates a module `_ParserScope`, asks - `_SourceUnitScanner.split_unit_parts` for the structural regions, and sends - only the module specification lines to `_parse_specification_part`. +3. `_visit_ModuleUnit` creates a module `_ParserScope` and sends the unit's + already-classified specification lines to `_parse_specification_part`. 4. `_parse_specification_part` uses the shared declaration backend: `_helper_parse_declaration_line` parses `integer, parameter :: n = 4`, then `_helper_push_declaration_to_scope` appends the resulting parameter variable to `FortranModule.variables`. -5. The module visitor recursively slices direct children from its substring. - It finds one procedure unit, `scale`, and dispatches it to +5. The module visitor reads the scanner-owned direct children. It finds one + procedure unit, `scale`, and dispatches it to `_visit_ProcedureUnit`. -6. `_visit_ProcedureUnit` creates a procedure `_ParserScope`, splits the - procedure into header/specification/execution/contains, and visits only the - specification part. The same declaration backend parses +6. `_visit_ProcedureUnit` creates a procedure `_ParserScope` and visits only + the stored specification part. The same declaration backend parses `real, intent(inout) :: x(n)` and pushes the metadata into the procedure argument symbol table. @@ -214,6 +213,25 @@ depend on constructed parser models. Splitting the scanner into another file would not strengthen that boundary; its private source tuples, grammar records, and unit classes are all local to this parser module. +Each `SourceUnit` is fully classified by the scanner. In addition to its exact +source span, kind, and name, it owns its header, specification, execution, +`contains`, footer, and retained direct child units. A child records whether it +occurred in its parent's specification or `contains` region, so a module-level +interface is not confused with a contained procedure. Children are retained +only when later parser work needs them for model construction or validation. +That includes module-like unit children, interface procedure declarations, +procedure-local interfaces used to type callback dummy arguments, and local +declarative units whose syntax still needs validation. Internal procedures +below a procedure's `contains` statement are structurally scanned but are not +retained as wrapper targets. Execution regions remain opaque. + +While matching one unit's terminator, the scanner keeps a stack of `_OpenUnit` +records. Each record names one unit that has opened but not yet closed and +stores its structural region. The top record is the innermost unit; popping it +after its terminator exposes the containing module, interface, or procedure. +This stack is structural parser state only: it contains no `_ParserScope` and +does not own declarations or parser models. + End-name validation is strict for structural units whose names define exported scope boundaries, such as modules, submodules, programs, interfaces, and derived types. Procedure end-name mismatches are still tolerated while slicing diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index d04c9b54f..d0a092c65 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -10,12 +10,12 @@ from __future__ import annotations import re -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from copy import deepcopy from dataclasses import dataclass, field as dataclass_field, replace from pathlib import Path from types import MappingProxyType -from typing import ClassVar +from typing import ClassVar, Literal from prik.utilities.declaration_expressions import ( evaluate_integer_expression, @@ -95,14 +95,14 @@ end module m `parse_file` preprocesses lines, validates obvious malformed headers, and asks -`_SourceUnitScanner.slice_child_units` to create one file-level `ModuleUnit`. -The `_visit_ModuleUnit` handler then receives only that substring and asks -`_SourceUnitScanner.split_unit_parts` for header/specification/contains regions, -visits the module specification part in a module scope, and recursively slices -its direct children. The contained procedure is dispatched to +`_SourceUnitScanner.scan_file_units` to create one file-level `ModuleUnit`. +The scanner classifies that unit's header, grammar regions, and retained direct +children while constructing it. `_visit_ModuleUnit` reads that stored +classification, visits the module specification part in a module scope, and +dispatches the contained procedure to `_visit_ProcedureUnit`, which creates a procedure scope and visits only its specification part; the execution part and internal subprograms are ignored for -wrapper metadata. Internal subprogram boundaries are still sliced so malformed +wrapper metadata. Internal subprogram boundaries are still scanned so malformed unit structure is rejected before their contents are skipped. Scoping follows the same recursion. A helper that parses `integer :: n` or @@ -176,16 +176,27 @@ _INTRINSIC_COMPILE_TIME_MODULES = frozenset({"iso_c_binding", "iso_fortran_env"}) -_PreprocessedLines = list[tuple[str, int | None, str | None]] +_PreprocessedLine = tuple[str, int | None, str | None] +_PreprocessedLines = list[_PreprocessedLine] _SourceOrLines = str | _PreprocessedLines +_UnitRegion = Literal["specification", "execution", "contains"] +_UnitEndAction = Literal["not_end", "ignore", "close"] @dataclass(frozen=True) class SourceUnit: - """Represent one recursively sliced Fortran grammar unit. - - The slicer retains the unit's normalized lines and original source bounds - so visitors can parse a local region without losing diagnostic locations. + """Represent one fully classified Fortran grammar unit. + + The source scanner owns both the unit's exact normalized source span and + its structural classification. ``specification``, ``execution``, and + ``contains`` exclude nested unit bodies, while ``children`` contains only + direct units that later parser work must visit or validate. A retained + child's ``parent_region`` preserves whether it appeared before or after + its parent's direct ``contains`` statement. + + Procedure-contained internal procedures are structurally scanned but are + not retained because PRIK cannot wrap them. Procedure-local interfaces are + retained because their declarations type callback dummy arguments. Concrete subclasses select the matching ``_visit_*`` handler. """ @@ -194,6 +205,13 @@ class SourceUnit: lines: _PreprocessedLines start_line: int | None end_line: int | None + parent_region: _UnitRegion | None + header: _PreprocessedLine + specification: _PreprocessedLines + execution: _PreprocessedLines + contains: _PreprocessedLines + footer: _PreprocessedLine | None + children: tuple[SourceUnit, ...] @dataclass(frozen=True) @@ -308,18 +326,42 @@ class _UnitGrammar: @dataclass(frozen=True) -class _UnitParts: - """Store a sliced unit's header, grammar regions, and optional footer. - - The four regions retain their original line mappings. Visitors parse only - the regions supported by their corresponding :class:`_UnitGrammar`. +class _OpenUnit: + """Describe one source unit that opened but has not yet closed. + + :meth:`_SourceUnitScanner.find_unit_end` stores these records in a stack. + The bottom record is the requested outer unit and the top record is the + innermost unit whose terminator is currently expected. Closing the top unit + pops it and exposes its container; the record itself performs no parsing. + + ``kind`` is the normalized unit category used to select the matching + terminator grammar. ``name`` is the optional source name checked against a + named terminator. ``region`` is ``"specification"`` before execution or + ``contains`` begins, ``"execution"`` while unit-like text must be treated + as executable-body content, or ``"contains"`` after the unit's direct + ``contains`` transition. + + For example, the stack evolves as follows while scanning nested units:: + + Source line Open-unit stack (outer -> inner) + module owner module owner [specification] + interface module owner [specification] + -> interface [specification] + subroutine callback() module owner [specification] + -> interface [specification] + -> procedure callback [specification] + end subroutine callback module owner [specification] + -> interface [specification] + end interface module owner [specification] + contains module owner [contains] + + This is structural scan state only. It never contains a + :class:`_ParserScope`, declarations, or constructed parser models. """ - header: tuple[str, int | None, str | None] | None - specification: _PreprocessedLines - execution: _PreprocessedLines - contains: _PreprocessedLines - footer: tuple[str, int | None, str | None] | None + kind: str + name: str | None + region: _UnitRegion = "specification" def _raise_invalid_fortran_syntax_line( @@ -341,12 +383,12 @@ def _raise_invalid_fortran_syntax_line( class _SourceUnitScanner: - """Identify source-unit boundaries and split units into grammar regions. + """Identify boundaries and construct fully classified source units. The scanner is deliberately stateless. It recognizes lexical structure and - returns exact :class:`SourceUnit` substrings or :class:`_UnitParts`; it does - not construct semantic models, mutate parser scopes, or decide which parsed - declarations belong in the wrapper-facing representation. + returns :class:`SourceUnit` objects that already own their regions and + retained direct children. It does not construct semantic models, mutate + parser scopes, or decide which declarations enter wrapper-facing models. """ _GRAMMARS: ClassVar[Mapping[str, _UnitGrammar]] = MappingProxyType( @@ -391,84 +433,272 @@ def grammar(cls, kind: str) -> _UnitGrammar: """Return the immutable grammar profile for one source-unit kind.""" return cls._GRAMMARS.get(kind, _UnitGrammar(kind=kind)) - def slice_child_units( + def scan_file_units( self, lines: _PreprocessedLines, *, - parent_kind: str, - allowed_kinds: set[str] | None = None, filename: str | None = None, - skip_execution_region: bool = False, ) -> list[SourceUnit]: - """Return exact substrings for the direct children of one parent. + """Construct the direct source units found at file scope. - ``parent_kind`` supplies only the structural grammar context needed to - interpret interface declarations. Parser scopes and semantic models do - not cross this boundary. When ``skip_execution_region`` is true, unit- - like text after execution begins is intentionally left opaque. + Nested children and every unit region are classified while each + returned unit is constructed. Parser scopes and semantic models do not + cross this boundary. """ units: list[SourceUnit] = [] index = 0 - region = "specification" while index < len(lines): - line, lineno, _ = lines[index] + line, _lineno, _source_line = lines[index] stripped = line.strip() if stripped.startswith("#"): index += 1 continue - if skip_execution_region: - if self.is_contains_transition(stripped): - region = "contains" - index += 1 - continue - if region == "specification" and self.is_executable_statement_start(stripped): - region = "execution" - if region == "execution": - index += 1 - continue - if parent_kind == "interface" and re.match(r"^module\s+procedure\b", stripped, re.IGNORECASE): - index += 1 - continue start = self.classify_unit_start(line) if start is None: index += 1 continue kind, name = start - if allowed_kinds is not None and kind not in allowed_kinds: - index += 1 - continue - - end_index = self.find_unit_end(lines, index, kind, filename=filename) - if end_index is None: - if kind == "interface" and (lines[index][2] or "").strip().lower().startswith("end interface"): - index += 1 - continue - if parent_kind == "interface" and kind == "procedure": - # A procedure declaration in an interface may be closed by - # `end interface` instead of its own explicit terminator. - end_index = len(lines) - 1 - else: - label = self.unit_label(kind) - raise FortranParseError( - f"Missing end {label} for {label} '{name or ''}'.", - filename=filename, - line_number=lineno, - source_line=lines[index][2], - code="PARSE_MISSING_UNIT_END", - ) - + end_index = self._required_unit_end( + lines, + index, + kind, + name, + filename=filename, + ) units.append( - _SOURCE_UNIT_TYPES[kind]( - kind=kind, - name=name, - lines=lines[index : end_index + 1], - start_line=lineno, - end_line=lines[end_index][1], + self._build_source_unit( + kind, + name, + lines[index : end_index + 1], + parent_region=None, + filename=filename, ) ) index = end_index + 1 return units + def _required_unit_end( + self, + lines: _PreprocessedLines, + start_index: int, + kind: str, + name: str | None, + *, + filename: str | None, + ) -> int: + """Return a file-level end index or raise the missing-end diagnostic.""" + end_index = self.find_unit_end(lines, start_index, kind, filename=filename) + if end_index is not None: + return end_index + label = self.unit_label(kind) + raise FortranParseError( + f"Missing end {label} for {label} '{name or ''}'.", + filename=filename, + line_number=lines[start_index][1], + source_line=lines[start_index][2], + code="PARSE_MISSING_UNIT_END", + ) + + def _build_source_unit( + self, + kind: str, + name: str | None, + lines: _PreprocessedLines, + *, + parent_region: _UnitRegion | None, + filename: str | None, + ) -> SourceUnit: + """Classify one exact source span and recursively build needed children.""" + grammar = self.grammar(kind) + header = lines[0] + footer = lines[-1] if lines and self.unit_end_matches(kind, lines[-1][0]) else None + body = lines[1:-1] if footer is not None else lines[1:] + specification, execution, contains, children = self._classify_unit_body( + grammar, + name, + body, + filename=filename, + ) + return _SOURCE_UNIT_TYPES[kind]( + kind=kind, + name=name, + lines=lines, + start_line=header[1], + end_line=lines[-1][1], + parent_region=parent_region, + header=header, + specification=specification, + execution=execution, + contains=contains, + footer=footer, + children=children, + ) + + def _classify_unit_body( + self, + grammar: _UnitGrammar, + name: str | None, + body: _PreprocessedLines, + *, + filename: str | None, + ) -> tuple[_PreprocessedLines, _PreprocessedLines, _PreprocessedLines, tuple[SourceUnit, ...]]: + """Separate body lines and construct retained direct child units. + + The input excludes the owning unit's header and explicit footer. Each + ordinary line is appended to exactly one grammar region. A complete + child span is removed from those line regions and, when later parser + work needs it, returned as one fully classified direct child. + """ + specification: _PreprocessedLines = [] + execution: _PreprocessedLines = [] + contains: _PreprocessedLines = [] + children: list[SourceUnit] = [] + region: _UnitRegion = "specification" + index = 0 + + while index < len(body): + source_line = body[index] + stripped = source_line[0].strip() + if not stripped: + index += 1 + continue + if self._is_direct_contains_transition( + grammar, + name, + source_line, + filename=filename, + ): + region = "contains" + index += 1 + continue + if region == "execution": + execution.append(source_line) + index += 1 + continue + if grammar.kind == "interface" and re.match(r"^module\s+procedure\b", stripped, re.IGNORECASE): + specification.append(source_line) + index += 1 + continue + child = self._classified_child_at( + grammar, + region, + body, + index, + filename=filename, + ) + if child is not None: + retained_child, next_index = child + if retained_child is not None: + children.append(retained_child) + index = next_index + continue + region = self._region_for_line(grammar, region, stripped) + self._append_region_line( + region, + source_line, + specification=specification, + execution=execution, + contains=contains, + ) + index += 1 + return specification, execution, contains, tuple(children) + + def _is_direct_contains_transition( + self, + grammar: _UnitGrammar, + name: str | None, + source_line: _PreprocessedLine, + *, + filename: str | None, + ) -> bool: + """Validate and recognize the owning unit's direct ``contains`` line.""" + stripped = source_line[0].strip() + if not self.is_contains_transition(stripped): + return False + if not grammar.has_contains_part: + _raise_invalid_fortran_syntax_line( + stripped, + context=f"{self.unit_label(grammar.kind)} '{name or ''}'", + filename=filename, + lineno=source_line[1], + source_line=source_line[2], + ) + return True + + def _classified_child_at( + self, + grammar: _UnitGrammar, + region: _UnitRegion, + body: _PreprocessedLines, + index: int, + *, + filename: str | None, + ) -> tuple[SourceUnit | None, int] | None: + """Return a classified child and next index when a complete child starts. + + The child value is ``None`` for a valid internal procedure that PRIK + cannot wrap and therefore does not retain. The returned index always + skips the entire structurally validated child span. + """ + start = self.classify_unit_start(body[index][0].strip()) + if start is None: + return None + child_kind, child_name = start + child_end = self.find_unit_end(body, index, child_kind, filename=filename) + if child_end is None and grammar.kind == "interface" and child_kind == "procedure": + child_end = len(body) - 1 + if child_end is None: + return None + if not self._retain_child(grammar, region, child_kind): + return None, child_end + 1 + child = self._build_source_unit( + child_kind, + child_name, + body[index : child_end + 1], + parent_region=region, + filename=filename, + ) + return child, child_end + 1 + + def _region_for_line( + self, + grammar: _UnitGrammar, + region: _UnitRegion, + stripped: str, + ) -> _UnitRegion: + """Advance a specification region when its first executable line appears.""" + if region == "specification" and grammar.has_execution_part and self.is_executable_statement_start(stripped): + return "execution" + return region + + @staticmethod + def _append_region_line( + region: _UnitRegion, + source_line: _PreprocessedLine, + *, + specification: _PreprocessedLines, + execution: _PreprocessedLines, + contains: _PreprocessedLines, + ) -> None: + """Append one non-child line to its already-selected grammar region.""" + if region == "specification": + specification.append(source_line) + elif region == "execution": + execution.append(source_line) + else: + contains.append(source_line) + + @staticmethod + def _retain_child(grammar: _UnitGrammar, region: _UnitRegion, child_kind: str) -> bool: + """Return whether later parser work needs the classified direct child. + + Program and procedure internal procedures are valid but inaccessible + wrapper targets, so their boundaries are checked and their bodies are + skipped without retaining another tree level. Other non-execution + children remain available for model construction or syntax validation. + """ + return not (grammar.ignores_contains_children and region == "contains" and child_kind == "procedure") + def find_unit_end( self, lines: _PreprocessedLines, @@ -480,87 +710,154 @@ def find_unit_end( """Return the matching terminator index while respecting nested units.""" start = self.classify_unit_start(lines[start_index][0]) start_name = start[1] if start is not None else None - stack: list[tuple[str, str | None, int | None, str | None, str]] = [ - (kind, start_name, lines[start_index][1], lines[start_index][2], "specification") - ] + stack = [_OpenUnit(kind=kind, name=start_name)] index = start_index + 1 while index < len(lines): line, lineno, source_line = lines[index] line = line.strip() - if not line: + current = stack[-1] + if self._skip_unit_scan_line(current, line): index += 1 continue - current_kind, current_name, current_line, current_source, current_region = stack[-1] - if current_kind == "interface" and re.match(r"^module\s+procedure\b", line, re.IGNORECASE): + + end_action = self._unit_end_action( + current, + lines, + index, + line, + filename=filename, + lineno=lineno, + source_line=source_line, + ) + if end_action == "ignore": index += 1 continue - - closes_current, end_name = self.parse_unit_end(current_kind, line) - if closes_current: - if end_name and current_name and end_name.lower() != current_name.lower(): - if current_kind == "procedure" and self.has_preferred_unit_end_ahead( - lines, - index, - current_kind, - current_name, - ): - index += 1 - continue - label = self.unit_label(current_kind) - if current_kind != "procedure": - raise FortranParseError( - f"Mismatched end {label} name '{end_name}' for {label} '{current_name}'.", - filename=filename, - line_number=lineno, - source_line=source_line, - code="PARSE_MISMATCHED_UNIT_END", - ) + if end_action == "close": stack.pop() if not stack: return index index += 1 continue - grammar = self.grammar(current_kind) - if self.is_contains_transition(line) and grammar.has_contains_part: - stack[-1] = (current_kind, current_name, current_line, current_source, "contains") - index += 1 - continue - if ( - current_region == "specification" - and grammar.has_execution_part - and self.is_executable_statement_start(line) - ): - stack[-1] = (current_kind, current_name, current_line, current_source, "execution") + transitioned = self._unit_after_region_transition(current, line) + if transitioned is not None: + stack[-1] = transitioned index += 1 continue - if current_region == "execution": + if current.region == "execution": index += 1 continue - start = self.classify_unit_start(line) - if start is not None and self.has_unit_end_ahead(lines, index, start[0]): - nested_kind, nested_name = start - stack.append((nested_kind, nested_name, lineno, source_line, "specification")) + nested = self._nested_open_unit(lines, index, line) + if nested is not None: + stack.append(nested) index += 1 continue - for open_kind, _open_name, _open_line, _open_source, _open_region in reversed(stack): - closes_open, _end_name = self.parse_unit_end(open_kind, line) - if not closes_open: - continue - label = self.unit_label(current_kind) - expected = self.unit_label(open_kind) - raise FortranParseError( - f"Unexpected end {expected} while parsing {label} '{current_name or ''}'.", - filename=filename, - line_number=lineno, - source_line=source_line, - code="PARSE_UNEXPECTED_UNIT_END", - ) + self._validate_no_outer_unit_end( + stack, + current, + line, + filename=filename, + lineno=lineno, + source_line=source_line, + ) index += 1 return None + @staticmethod + def _skip_unit_scan_line(current: _OpenUnit, line: str) -> bool: + """Skip blank lines and interface ``module procedure`` references.""" + return not line or bool(current.kind == "interface" and re.match(r"^module\s+procedure\b", line, re.IGNORECASE)) + + def _unit_end_action( + self, + current: _OpenUnit, + lines: _PreprocessedLines, + index: int, + line: str, + *, + filename: str | None, + lineno: int | None, + source_line: str | None, + ) -> _UnitEndAction: + """Classify how one line affects the innermost open unit. + + A non-terminator returns ``"not_end"``. A matching terminator returns + ``"close"``. A mismatched procedure terminator returns ``"ignore"`` + only when a preferred exact or unnamed terminator still occurs later; + structural unit name mismatches retain their existing diagnostic. + """ + closes_current, end_name = self.parse_unit_end(current.kind, line) + if not closes_current: + return "not_end" + names_mismatch = bool(end_name and current.name and end_name.lower() != current.name.lower()) + if not names_mismatch: + return "close" + if current.kind == "procedure": + if self.has_preferred_unit_end_ahead(lines, index, current.kind, current.name): + return "ignore" + return "close" + label = self.unit_label(current.kind) + raise FortranParseError( + f"Mismatched end {label} name '{end_name}' for {label} '{current.name}'.", + filename=filename, + line_number=lineno, + source_line=source_line, + code="PARSE_MISMATCHED_UNIT_END", + ) + + def _unit_after_region_transition(self, current: _OpenUnit, line: str) -> _OpenUnit | None: + """Return the updated open unit when ``line`` changes its region.""" + grammar = self.grammar(current.kind) + if self.is_contains_transition(line) and grammar.has_contains_part: + return replace(current, region="contains") + if ( + current.region == "specification" + and grammar.has_execution_part + and self.is_executable_statement_start(line) + ): + return replace(current, region="execution") + return None + + def _nested_open_unit( + self, + lines: _PreprocessedLines, + index: int, + line: str, + ) -> _OpenUnit | None: + """Return a nested open-unit record when a complete child starts here.""" + start = self.classify_unit_start(line) + if start is None or not self.has_unit_end_ahead(lines, index, start[0]): + return None + nested_kind, nested_name = start + return _OpenUnit(kind=nested_kind, name=nested_name) + + def _validate_no_outer_unit_end( + self, + stack: list[_OpenUnit], + current: _OpenUnit, + line: str, + *, + filename: str | None, + lineno: int | None, + source_line: str | None, + ) -> None: + """Reject a line that closes an outer unit before the current unit.""" + for open_unit in reversed(stack): + closes_open, _end_name = self.parse_unit_end(open_unit.kind, line) + if not closes_open: + continue + label = self.unit_label(current.kind) + expected = self.unit_label(open_unit.kind) + raise FortranParseError( + f"Unexpected end {expected} while parsing {label} '{current.name or ''}'.", + filename=filename, + line_number=lineno, + source_line=source_line, + code="PARSE_UNEXPECTED_UNIT_END", + ) + def has_unit_end_ahead(self, lines: _PreprocessedLines, start_index: int, kind: str) -> bool: """Return whether a candidate opener has a usable later terminator.""" start = self.classify_unit_start(lines[start_index][0]) @@ -585,123 +882,6 @@ def has_preferred_unit_end_ahead( return True return False - def split_unit_parts(self, unit: SourceUnit, *, filename: str | None = None) -> _UnitParts: - """Split one unit substring into specification, execution, and contains.""" - grammar = self.grammar(unit.kind) - header = unit.lines[0] if unit.lines else None - footer = unit.lines[-1] if unit.lines and self.unit_end_matches(unit.kind, unit.lines[-1][0]) else None - body = unit.lines[1:-1] if footer is not None else unit.lines[1:] - specification: _PreprocessedLines = [] - execution: _PreprocessedLines = [] - contains: _PreprocessedLines = [] - region = "specification" - index = 0 - - while index < len(body): - line, _, _ = body[index] - stripped = line.strip() - if not stripped: - index += 1 - continue - if self.is_contains_transition(stripped): - if not grammar.has_contains_part: - _raise_invalid_fortran_syntax_line( - stripped, - context=f"{self.unit_label(grammar.kind)} '{unit.name or ''}'", - filename=filename, - lineno=body[index][1], - source_line=body[index][2], - ) - region = "contains" - index += 1 - continue - - if grammar.kind == "interface" and re.match(r"^module\s+procedure\b", stripped, re.IGNORECASE): - specification.append(body[index]) - index += 1 - continue - - start = self.classify_unit_start(stripped) - if start is not None: - child_kind, _ = start - child_end = self.find_unit_end(body, index, child_kind, filename=filename) - if child_end is not None: - index = child_end + 1 - continue - if grammar.kind == "interface" and child_kind == "procedure": - break - - if ( - region == "specification" - and grammar.has_execution_part - and self.is_executable_statement_start(stripped) - ): - region = "execution" - - if region == "specification": - specification.append(body[index]) - elif region == "execution": - execution.append(body[index]) - else: - contains.append(body[index]) - index += 1 - - return _UnitParts( - header=header, - specification=specification, - execution=execution, - contains=contains, - footer=footer, - ) - - def child_unit_region(self, unit: SourceUnit, parts: _UnitParts, child: SourceUnit) -> str: - """Return the parent grammar region containing one direct child.""" - child_line = child.start_line - if child_line is None: - return "specification" - contains_line = self.direct_contains_line(unit, filename=None) - if contains_line is not None and child_line > contains_line: - return "contains" - execution_line = next( - (lineno for _line, lineno, _source_line in parts.execution if lineno is not None), - None, - ) - if execution_line is not None and child_line >= execution_line: - return "execution" - return "specification" - - def nonexecution_child_units(self, unit: SourceUnit, *, filename: str | None) -> list[SourceUnit]: - """Return direct nested units outside an intentionally opaque execution part.""" - grammar = self.grammar(unit.kind) - child_units = self.slice_child_units( - unit.lines[1:-1], - parent_kind=unit.kind, - filename=filename, - skip_execution_region=grammar.has_execution_part, - ) - if not grammar.has_execution_part: - return child_units - parts = self.split_unit_parts(unit, filename=filename) - return [child for child in child_units if self.child_unit_region(unit, parts, child) != "execution"] - - def direct_contains_line(self, unit: SourceUnit, *, filename: str | None) -> int | None: - """Return the direct ``contains`` line while skipping nested units.""" - body = unit.lines[1:-1] - index = 0 - while index < len(body): - line, lineno, _source_line = body[index] - stripped = line.strip() - if self.is_contains_transition(stripped): - return lineno - start = self.classify_unit_start(stripped) - if start is not None: - child_end = self.find_unit_end(body, index, start[0], filename=filename) - if child_end is not None: - index = child_end + 1 - continue - index += 1 - return None - @staticmethod def parse_derived_type_start(line: str) -> tuple[str, list[str]] | None: """Parse modern or legacy derived-type opening syntax.""" @@ -1044,14 +1224,13 @@ class FortranParser(ClassVisitor): Parsing pipeline used by `parse_file`: 1. Preprocess source into normalized lines (`_preprocessed_lines`). - 2. Slice direct file-level source units (`module`, `submodule`, + 2. Construct fully classified direct file-level source units (`module`, `submodule`, `program`, standalone `procedure`, `block data`, file-level `interface`, and file-level derived type). 3. Dispatch each `SourceUnit` through its `_visit_` handler. - 4. Each unit visitor parses only that unit's own substring, builds its own - `_ParserScope`, splits the unit into grammar regions, visits the - specification part, and recursively slices direct children where the - grammar allows them. + 4. Each unit visitor builds its own `_ParserScope`, visits the unit's + scanner-owned specification region, and consumes its retained direct + children where the grammar allows them. 5. Shared declaration helpers push variables, procedure symbols, and type fields into the active scope model. 6. Build `FortranFile` symbol table and standalone entity lists. @@ -1266,7 +1445,7 @@ def _visit_ModuleUnit( filename: str | None, ) -> FortranModule: """Visit a sliced `module ... end module` unit.""" - header = unit.lines[0] + header = unit.header module = self._parse_module_header(header[0].strip(), filename, lineno=header[1], source_line=header[2]) if module is None: # pragma: no cover - slicer only dispatches module units with module headers. raise FortranParseError( @@ -1277,14 +1456,11 @@ def _visit_ModuleUnit( code="PARSE_EXPECTED_UNIT", ) scope = self._helper_scope_for_model("module", module, parent=parent_scope) - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) + self._parse_specification_part(scope, unit.specification, filename=filename) - child_units = self._source_unit_scanner.slice_child_units( - unit.lines[1:-1], parent_kind=scope.kind, filename=filename - ) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._helper_validate_contains_lines(scope, parts.contains, filename=filename) + child_units = unit.children + self._helper_validate_child_unit_regions(unit, child_units, filename=filename) + self._helper_validate_contains_lines(scope, unit.contains, filename=filename) self._helper_validate_sibling_units(child_units, parent_scope=scope, filename=filename) self._populate_module_like_children(module, child_units, scope=scope, filename=filename) self._validate_module_variables(module, filename) @@ -1299,7 +1475,7 @@ def _visit_SubmoduleUnit( filename: str | None, ) -> FortranSubmodule: """Visit a sliced `submodule (...) name ... end submodule` unit.""" - header = unit.lines[0] + header = unit.header submodule = self._parse_submodule_header(header[0].strip(), filename) if submodule is None: # pragma: no cover - slicer only dispatches submodule units with submodule headers. raise FortranParseError( @@ -1310,14 +1486,11 @@ def _visit_SubmoduleUnit( code="PARSE_EXPECTED_UNIT", ) scope = self._helper_scope_for_model("submodule", submodule, parent=parent_scope) - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) + self._parse_specification_part(scope, unit.specification, filename=filename) - child_units = self._source_unit_scanner.slice_child_units( - unit.lines[1:-1], parent_kind=scope.kind, filename=filename - ) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._helper_validate_contains_lines(scope, parts.contains, filename=filename) + child_units = unit.children + self._helper_validate_child_unit_regions(unit, child_units, filename=filename) + self._helper_validate_contains_lines(scope, unit.contains, filename=filename) self._helper_validate_sibling_units(child_units, parent_scope=scope, filename=filename) self._populate_module_like_children(submodule, child_units, scope=scope, filename=filename) self._validate_module_variables(submodule, filename) @@ -1369,7 +1542,7 @@ def _visit_ProgramUnit( filename: str | None, ) -> FortranProgram: """Visit a sliced `program ... end program` unit.""" - header = unit.lines[0] + header = unit.header program = self._parse_program_header(header[0].strip(), filename) if program is None: # pragma: no cover - slicer only dispatches program units with program headers. raise FortranParseError( @@ -1380,17 +1553,14 @@ def _visit_ProgramUnit( code="PARSE_EXPECTED_UNIT", ) scope = self._helper_scope_for_model("program", program, parent=parent_scope) - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) - child_units = self._source_unit_scanner.nonexecution_child_units(unit, filename=filename) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._helper_validate_contains_lines(scope, parts.contains, filename=filename) + self._parse_specification_part(scope, unit.specification, filename=filename) + child_units = unit.children + self._helper_validate_child_unit_regions(unit, child_units, filename=filename) + self._helper_validate_contains_lines(scope, unit.contains, filename=filename) self._helper_validate_ignored_child_units( [child for child in child_units if child.kind != "enum"], parent_scope=scope, filename=filename, - unit=unit, - parts=parts, ) program.enums.extend( self._visit(child, parent_scope=scope, filename=filename) for child in child_units if child.kind == "enum" @@ -1411,7 +1581,7 @@ def _visit_BlockDataUnit( filename: str | None, ) -> FortranBlockData: """Visit a sliced `block data ... end block data` unit.""" - header = unit.lines[0] + header = unit.header block_data = self._parse_block_data_header(header[0].strip(), filename) if block_data is None: # pragma: no cover - slicer only dispatches block-data units with block-data headers. raise FortranParseError( @@ -1422,12 +1592,9 @@ def _visit_BlockDataUnit( code="PARSE_EXPECTED_UNIT", ) scope = self._helper_scope_for_model("block_data", block_data, parent=parent_scope) - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) - child_units = self._source_unit_scanner.slice_child_units( - unit.lines[1:-1], parent_kind=scope.kind, filename=filename - ) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + self._parse_specification_part(scope, unit.specification, filename=filename) + child_units = unit.children + self._helper_validate_child_unit_regions(unit, child_units, filename=filename) self._validate_variable_declarations( block_data.variables, owner_kind="block data", @@ -1444,7 +1611,7 @@ def _visit_DerivedTypeUnit( filename: str | None, ) -> FortranDerivedType: """Visit a sliced derived-type definition.""" - header = unit.lines[0] + header = unit.header dtype = self._init_derived_type(header[0].strip(), current_module=parent_scope.module_owner) if dtype is None: # pragma: no cover - slicer only dispatches derived-type units with type headers. raise FortranParseError( @@ -1455,9 +1622,8 @@ def _visit_DerivedTypeUnit( code="PARSE_EXPECTED_UNIT", ) scope = self._helper_scope_for_model("derived_type", dtype, parent=parent_scope) - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) - for line, lineno, source_line in parts.contains: + self._parse_specification_part(scope, unit.specification, filename=filename) + for line, lineno, source_line in unit.contains: stripped = line.strip() if not stripped: continue @@ -1468,10 +1634,8 @@ def _visit_DerivedTypeUnit( lineno=lineno, source_line=source_line, ) - child_units = self._source_unit_scanner.slice_child_units( - unit.lines[1:-1], parent_kind=scope.kind, filename=filename - ) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + child_units = unit.children + self._helper_validate_child_unit_regions(unit, child_units, filename=filename) self._validate_derived_type_fields(dtype, filename) return dtype @@ -1493,7 +1657,7 @@ def _visit_InterfaceUnit( filename: str | None, ) -> FortranInterface: """Visit a sliced interface block.""" - header = unit.lines[0] + header = unit.header starts_interface, interface_name = self._source_unit_scanner.parse_interface_header(header[0].strip()) if not starts_interface: # pragma: no cover - slicer only dispatches interface units with interface headers. raise FortranParseError( @@ -1509,14 +1673,9 @@ def _visit_InterfaceUnit( abstract=header[0].strip().lower().startswith("abstract interface"), ) scope = self._helper_scope_for_model("interface", interface, parent=parent_scope) - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - self._helper_validate_interface_lines(scope, parts.specification, filename=filename) - interface.specific_procedures.extend(self._interface_specific_procedure_names(parts.specification)) - child_units = self._source_unit_scanner.slice_child_units( - unit.lines[1:-1], - parent_kind=scope.kind, - filename=filename, - ) + self._helper_validate_interface_lines(scope, unit.specification, filename=filename) + interface.specific_procedures.extend(self._interface_specific_procedure_names(unit.specification)) + child_units = unit.children for child in child_units: if child.kind != "procedure": _raise_invalid_fortran_syntax_line( @@ -1563,7 +1722,7 @@ def _visit_ProcedureUnit( in_interface: bool = False, ) -> FortranProcedureSignature: """Visit a sliced procedure body or interface procedure declaration.""" - header = unit.lines[0] + header = unit.header proc_state = self._parse_procedure_header( header[0].strip(), parent_scope.module_owner, @@ -1592,19 +1751,16 @@ def _visit_ProcedureUnit( proc_state.header_source_line = header[2] proc_state.uses.update(getattr(parent_scope.model, "uses", {})) scope = self._helper_scope_for_model("procedure", proc_state.signature, parent=parent_scope, state=proc_state) - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - self._parse_specification_part(scope, parts.specification, filename=filename) - child_units = self._source_unit_scanner.nonexecution_child_units(unit, filename=filename) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) - self._helper_validate_contains_lines(scope, parts.contains, filename=filename) + self._parse_specification_part(scope, unit.specification, filename=filename) + child_units = unit.children + self._helper_validate_child_unit_regions(unit, child_units, filename=filename) + self._helper_validate_contains_lines(scope, unit.contains, filename=filename) self._helper_validate_ignored_child_units( [child for child in child_units if child.kind != "interface"], parent_scope=scope, filename=filename, - unit=unit, - parts=parts, ) - self._helper_apply_local_interface_declarations(proc_state, unit, parts, scope, filename=filename) + self._helper_apply_local_interface_declarations(proc_state, unit, scope, filename=filename) return self._finalize_proc(proc_state) # ------------------------------------------------------------------ @@ -2112,9 +2268,8 @@ def _helper_prepare_source_units( """ lines = self._preprocessed_lines(code, filename) root_scope = _ParserScope(kind="file", name=None) - units = self._source_unit_scanner.slice_child_units( + units = self._source_unit_scanner.scan_file_units( lines, - parent_kind=root_scope.kind, filename=filename, ) self._helper_validate_file_scope_unparsed_lines(lines, filename) @@ -2132,10 +2287,10 @@ def _collect_interface_source_units( singular parsing selects one source unit directly instead of parsing a plural result list and checking its length afterward. """ - lines, root_scope, _all_units = self._helper_prepare_source_units(code, filename) + _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) interfaces: list[tuple[SourceUnit, _ParserScope]] = [] - def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: + def collect(scope: _ParserScope, child_units: Sequence[SourceUnit]) -> None: """Walk non-execution children and retain interface units.""" for child in child_units: if child.kind == "interface": @@ -2148,10 +2303,7 @@ def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: parent=scope, module_owner=child.name, ) - collect( - child_scope, - self._source_unit_scanner.nonexecution_child_units(child, filename=filename), - ) + collect(child_scope, child.children) continue if child.kind in {"procedure", "program"}: child_scope = _ParserScope( @@ -2160,19 +2312,9 @@ def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: parent=scope, module_owner=scope.module_owner, ) - collect( - child_scope, - self._source_unit_scanner.nonexecution_child_units(child, filename=filename), - ) + collect(child_scope, child.children) - collect( - root_scope, - self._source_unit_scanner.slice_child_units( - lines, - parent_kind=root_scope.kind, - filename=filename, - ), - ) + collect(root_scope, all_units) return interfaces def _collect_derived_type_source_units( @@ -2181,10 +2323,10 @@ def _collect_derived_type_source_units( filename: str | None, ) -> list[tuple[SourceUnit, _ParserScope]]: """Collect derived-type units with their module/program scope context.""" - lines, root_scope, _all_units = self._helper_prepare_source_units(code, filename) + _lines, root_scope, all_units = self._helper_prepare_source_units(code, filename) types: list[tuple[SourceUnit, _ParserScope]] = [] - def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: + def collect(scope: _ParserScope, child_units: Sequence[SourceUnit]) -> None: """Walk nested grammar units and retain derived-type units.""" for child in child_units: if child.kind == "derived_type": @@ -2197,10 +2339,7 @@ def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: parent=scope, module_owner=child.name if child.kind in {"module", "submodule"} else scope.module_owner, ) - collect( - child_scope, - self._source_unit_scanner.nonexecution_child_units(child, filename=filename), - ) + collect(child_scope, child.children) continue if child.kind == "procedure": child_scope = _ParserScope( @@ -2209,19 +2348,9 @@ def collect(scope: _ParserScope, child_units: list[SourceUnit]) -> None: parent=scope, module_owner=scope.module_owner, ) - collect( - child_scope, - self._source_unit_scanner.nonexecution_child_units(child, filename=filename), - ) + collect(child_scope, child.children) - collect( - root_scope, - self._source_unit_scanner.slice_child_units( - lines, - parent_kind=root_scope.kind, - filename=filename, - ), - ) + collect(root_scope, all_units) return types def _helper_validate_possible_unit_header( @@ -2319,8 +2448,7 @@ def _is_allowed_unparsed_file_scope_line(line: str) -> bool: def _helper_validate_child_unit_regions( self, unit: SourceUnit, - parts: _UnitParts, - child_units: list[SourceUnit], + child_units: Sequence[SourceUnit], *, filename: str | None, ) -> None: @@ -2357,9 +2485,9 @@ def _helper_validate_child_unit_regions( } grammar_regions = allowed.get(unit.kind, {}) for child in child_units: - region = self._source_unit_scanner.child_unit_region(unit, parts, child) - if region == "execution": - continue + region = child.parent_region + if region is None: # pragma: no cover - only file-level units have no parent region. + raise AssertionError("A nested SourceUnit must record its parent region.") if child.kind in grammar_regions.get(region, set()): continue _raise_invalid_fortran_syntax_line( @@ -2459,12 +2587,11 @@ def _helper_parse_enum_unit( module_owner: str | None, ) -> FortranEnum: """Parse an interoperability enum block into enumerator constants.""" - parts = self._source_unit_scanner.split_unit_parts(unit, filename=filename) - bind_c = bool(unit.lines and _REGEX["bind_c"].search(unit.lines[0][0])) + bind_c = bool(_REGEX["bind_c"].search(unit.header[0])) enum = FortranEnum(name=unit.name, module=module_owner, bind_c=bind_c) symbols: dict[str, str] = {} next_value: int | None = 0 - for line, lineno, source_line in parts.specification: + for line, lineno, source_line in unit.specification: stripped = line.strip() if not stripped or stripped.startswith("#"): continue @@ -2490,12 +2617,7 @@ def _helper_parse_enum_unit( lineno=lineno, source_line=source_line, ) - child_units = self._source_unit_scanner.slice_child_units( - unit.lines[1:-1], - parent_kind="enum", - filename=filename, - ) - self._helper_validate_child_unit_regions(unit, parts, child_units, filename=filename) + self._helper_validate_child_unit_regions(unit, unit.children, filename=filename) return enum @staticmethod @@ -2534,23 +2656,15 @@ def _parse_enum_item( def _helper_validate_ignored_child_units( self, - child_units: list[SourceUnit], + child_units: Sequence[SourceUnit], *, parent_scope: _ParserScope, filename: str | None, - unit: SourceUnit | None = None, - parts: _UnitParts | None = None, ) -> None: """Check or skip nested units that are intentionally omitted from metadata.""" for child in child_units: - if ( - unit is not None - and parts is not None - and self._source_unit_scanner.child_unit_region(unit, parts, child) == "execution" - ): - continue if child.kind == "procedure": - # The slicer has already checked the nested unit boundary and + # The scanner has already checked the nested unit boundary and # the caller has checked its grammar region. Internal procedure # declarations and bodies do not affect wrapper metadata. continue @@ -2561,7 +2675,7 @@ def _helper_validate_ignored_child_units( def _helper_validate_sibling_units( self, - units: list[SourceUnit], + units: Sequence[SourceUnit], *, parent_scope: _ParserScope, filename: str | None, @@ -3110,8 +3224,8 @@ def _parse_specification_part( The helper name mirrors the grammar term "specification part". It is called by module, submodule, program, procedure, derived-type, and - block-data visitors after `_SourceUnitScanner.split_unit_parts` has - isolated the relevant region. + block-data visitors after `_SourceUnitScanner` has stored the relevant + region directly on their `SourceUnit`. Example: A program and a procedure both have executable statements, but this @@ -3530,7 +3644,6 @@ def _helper_apply_local_interface_declarations( self, proc_state: _ProcedureState, unit: SourceUnit, - parts: _UnitParts, scope: _ParserScope, *, filename: str | None, @@ -3539,25 +3652,17 @@ def _helper_apply_local_interface_declarations( Interface blocks inside procedures are not wrapper targets themselves, but their procedure names can be dummy arguments of the enclosing - procedure. This helper reuses the source-unit slicer to find those - local interface declarations and annotate the matching argument as a - procedure callback. + procedure. This helper consumes the procedure's already-classified + direct children and annotates the matching argument as a procedure + callback. Example: In ``subroutine apply(cb)`` with a local ``interface`` containing ``subroutine cb(x)``, this helper updates the already-known dummy argument ``cb`` so its base type becomes ``"procedure"``. """ - interface_units = self._source_unit_scanner.slice_child_units( - unit.lines[1:-1], - parent_kind=scope.kind, - allowed_kinds={"interface"}, - filename=filename, - skip_execution_region=True, - ) + interface_units = [child for child in unit.children if child.kind == "interface"] for interface_unit in interface_units: - if self._source_unit_scanner.child_unit_region(unit, parts, interface_unit) == "execution": - continue interface = self._visit(interface_unit, parent_scope=scope, filename=filename) for signature in interface.procedures: name = signature.name diff --git a/tests/fortran/_support/parser_regressions.py b/tests/fortran/_support/parser_regressions.py index 11ef285bd..84d472edf 100644 --- a/tests/fortran/_support/parser_regressions.py +++ b/tests/fortran/_support/parser_regressions.py @@ -5,7 +5,7 @@ from prik.parsers.fortran.parser import ( SourceUnit, - _SOURCE_UNIT_TYPES, + _SourceUnitScanner, ) @@ -15,8 +15,10 @@ def _lines(*values: str) -> list[tuple[str, int, str]]: def _unit(kind: str, name: str | None, *values: str) -> SourceUnit: lines = _lines(*values) - return _SOURCE_UNIT_TYPES[kind](kind=kind, name=name, lines=lines, start_line=1, end_line=len(lines)) - - -def _empty_unit(kind: str, name: str | None, start_line: int | None, end_line: int | None) -> SourceUnit: - return _SOURCE_UNIT_TYPES[kind](kind, name, [], start_line, end_line) + return _SourceUnitScanner()._build_source_unit( + kind, + name, + lines, + parent_region=None, + filename=None, + ) diff --git a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py index 267f9429e..f61fb111f 100644 --- a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py +++ b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py @@ -5,10 +5,7 @@ FortranArgument, FortranProcedureSignature, ) -from prik.parsers.fortran.parser import ( - FortranParser, - _SourceUnitScanner, -) +from prik.parsers.fortran.parser import FortranParser from tests.fortran._support.parser_regressions import _unit @@ -49,8 +46,7 @@ def test_procedure_bind_c_name_and_value_argument_are_preserved(): assert proc.arguments[0].pass_by_value is True -def test_nonexecution_child_units_keep_specification_and_contains_children_only(): - scanner = _SourceUnitScanner() +def test_procedure_children_exclude_execution_text_and_internal_procedures(): unit = _unit( "procedure", "work", @@ -68,14 +64,15 @@ def test_nonexecution_child_units_keep_specification_and_contains_children_only( "end subroutine work", ) - children = scanner.nonexecution_child_units( - unit, - filename="children.f90", - ) - - assert [(child.kind, child.name, child.start_line, child.end_line) for child in children] == [ + assert [(child.kind, child.name, child.start_line, child.end_line) for child in unit.children] == [ ("derived_type", "local_state", 2, 3), - ("procedure", "inner", 10, 11), + ] + assert [line.strip() for line, _lineno, _source in unit.execution] == [ + "call setup()", + "interface", + "subroutine hidden()", + "end subroutine hidden", + "end interface", ] diff --git a/tests/fortran/source_parsing/parsing/test_developer_tutorial.py b/tests/fortran/source_parsing/parsing/test_developer_tutorial.py index 242500509..835f7bb4a 100644 --- a/tests/fortran/source_parsing/parsing/test_developer_tutorial.py +++ b/tests/fortran/source_parsing/parsing/test_developer_tutorial.py @@ -4,10 +4,10 @@ black-box public API test. It shows the private visitor/scanner sequence that maintainers should follow when changing `prik/parsers/fortran/parser.py`: -1. preprocess, then scan file-level source units, -2. ask `_SourceUnitScanner` to split one unit into grammar parts, +1. preprocess, then scan fully classified file-level source units, +2. inspect the scanner-owned grammar regions and direct children, 3. visit the unit with a scope, -4. recursively slice and inspect its direct children. +4. inspect a retained child without rescanning its parent's source. """ from prik.parsers.fortran.parser import FortranParser, _SourceUnitScanner @@ -45,16 +45,12 @@ def test_developer_tutorial_recursive_unit_visitors_and_helpers(): module_unit = top_units[0] module_grammar = scanner.grammar("module") assert module_grammar.has_contains_part is True - module_parts = scanner.split_unit_parts( - module_unit, - filename="developer_tutorial.f90", - ) - assert module_parts.header == module_unit.lines[0] - assert [line[0].strip() for line in module_parts.specification] == [ + assert module_unit.header == module_unit.lines[0] + assert [line[0].strip() for line in module_unit.specification] == [ "implicit none", "integer, parameter :: n = 8", ] - assert module_parts.contains == [] + assert module_unit.contains == [] module = parser._visit( module_unit, @@ -66,28 +62,20 @@ def test_developer_tutorial_recursive_unit_visitors_and_helpers(): assert module.variables[0].value == "8" assert module.variables[0].symbolic_value == "8" - module_scope = parser._helper_scope_for_model("module", module, parent=root_scope) - child_units = scanner.slice_child_units( - module_unit.lines[1:-1], - parent_kind=module_scope.kind, - filename="developer_tutorial.f90", - ) + child_units = module_unit.children assert [(unit.kind, unit.name, unit.start_line, unit.end_line) for unit in child_units] == [ ("procedure", "total", 5, 9) ] + assert child_units[0].parent_region == "contains" procedure_unit = child_units[0] - procedure_parts = scanner.split_unit_parts( - procedure_unit, - filename="developer_tutorial.f90", - ) - assert [line[0].strip() for line in procedure_parts.specification] == [ + assert [line[0].strip() for line in procedure_unit.specification] == [ "implicit none", "real, intent(in) :: values(n)", "real :: out", ] - assert procedure_parts.execution == [] - assert procedure_parts.contains == [] + assert procedure_unit.execution == [] + assert procedure_unit.contains == [] proc = module.procedures[0] assert proc.name == "total" diff --git a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py index cb0dfd654..36dd20169 100644 --- a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py @@ -16,61 +16,76 @@ FortranParser, _ParserScope, _SourceUnitScanner, - _UnitParts, ) from tests.fortran._support.parser_regressions import ( - _empty_unit, _lines, _unit, ) -def test_unit_region_helpers_preserve_specification_execution_and_contains_boundaries(): +def test_source_unit_classification_preserves_child_regions_and_direct_ownership(): scanner = _SourceUnitScanner() - unit = _unit( - "procedure", - "work", - "subroutine work()", - "integer :: value", - "type :: local_state", - "end type local_state", - "value = 1", - "contains", - "subroutine inner()", - "end subroutine inner", - "end subroutine work", - ) - parts = _UnitParts( - header=unit.lines[0], - specification=[unit.lines[1]], - execution=[unit.lines[4]], - contains=[unit.lines[6], unit.lines[7]], - footer=unit.lines[-1], - ) + unit = scanner.scan_file_units( + _lines( + "module owner", + "integer :: value", + "interface callbacks", + " subroutine callback()", + " end subroutine callback", + "end interface callbacks", + "interface generic_work", + " module procedure work", + "end interface generic_work", + "contains", + "# generated marker", + "subroutine work()", + "end subroutine work", + "end module owner", + ), + filename="regions.f90", + )[0] + + assert unit.header == unit.lines[0] + assert unit.footer == unit.lines[-1] + assert [line for line, _lineno, _source in unit.specification] == ["integer :: value"] + assert unit.execution == [] + assert [line.strip() for line, _lineno, _source in unit.contains] == ["# generated marker"] + assert [(child.kind, child.name, child.parent_region) for child in unit.children] == [ + ("interface", "callbacks", "specification"), + ("interface", "generic_work", "specification"), + ("procedure", "work", "contains"), + ] + assert [(child.kind, child.name, child.parent_region) for child in unit.children[0].children] == [ + ("procedure", "callback", "specification") + ] + assert [line.strip() for line, _lineno, _source in unit.children[1].specification] == ["module procedure work"] - assert scanner.direct_contains_line(unit, filename="regions.f90") == 6 - assert scanner.child_unit_region(unit, parts, _empty_unit("derived_type", "unknown", None, None)) == ( - "specification" - ) - assert scanner.child_unit_region(unit, parts, _unit("derived_type", "local_state", "type :: local_state")) == ( - "specification" - ) - assert ( - scanner.child_unit_region( - unit, - parts, - _empty_unit("interface", None, 5, 5), - ) - == "execution" - ) - assert ( - scanner.child_unit_region( - unit, - parts, - _empty_unit("procedure", "inner", 7, 8), - ) - == "contains" - ) + +def test_procedure_classification_keeps_local_interfaces_and_omits_internal_procedures(): + scanner = _SourceUnitScanner() + unit = scanner.scan_file_units( + _lines( + "subroutine work(callback)", + "integer :: value", + "interface", + " subroutine callback()", + " end subroutine callback", + "end interface", + "value = 1", + "contains", + "subroutine inner()", + "end subroutine inner", + "end subroutine work", + ), + filename="regions.f90", + )[0] + + assert [line for line, _lineno, _source in unit.specification] == ["integer :: value"] + assert [line for line, _lineno, _source in unit.execution] == ["value = 1"] + assert unit.contains == [] + assert [(child.kind, child.name, child.parent_region) for child in unit.children] == [ + ("interface", None, "specification") + ] assert scanner.has_preferred_unit_end_ahead(unit.lines, 0, "procedure", "work") is True assert scanner.has_preferred_unit_end_ahead(unit.lines, 0, "procedure", "missing") is False @@ -78,12 +93,25 @@ def test_unit_region_helpers_preserve_specification_execution_and_contains_bound immediate_type = _lines("type :: immediate", "end type immediate") assert scanner.has_preferred_unit_end_ahead(immediate_type, 0, "derived_type", "immediate") is True assert scanner.has_unit_end_ahead(immediate_type, 0, "derived_type") is True - assert scanner.has_unit_end_ahead(unit.lines, 2, "derived_type") is True - assert scanner.has_unit_end_ahead(unit.lines, 6, "procedure") is True - assert scanner.has_unit_end_ahead(unit.lines[:-1], 0, "procedure") is True + assert scanner.has_unit_end_ahead(unit.lines, 0, "procedure") is True + + +def test_unit_end_search_tracks_nested_specification_and_contains_units(): + scanner = _SourceUnitScanner() + lines = _lines( + "subroutine work()", + "type :: local_state", + "end type local_state", + "contains", + "subroutine inner()", + "end subroutine inner", + "end subroutine work", + ) - immediate_contains = _unit("module", "owner", "module owner", "contains", "end module owner") - assert scanner.direct_contains_line(immediate_contains, filename="regions.f90") == 2 + assert scanner.find_unit_end(lines, 0, "procedure", filename="regions.f90") == 6 + assert scanner.has_unit_end_ahead(lines, 1, "derived_type") is True + assert scanner.has_unit_end_ahead(lines, 4, "procedure") is True + assert scanner.has_unit_end_ahead(lines[:-1], 0, "procedure") is True def test_source_preparation_rejects_raw_cpp_and_preserves_root_units_and_source_form(tmp_path: Path): @@ -129,7 +157,7 @@ def test_source_preparation_rejects_raw_cpp_and_preserves_root_units_and_source_ assert error.value.code == "PARSE_PREPROCESSING_REQUIRED" -def test_child_unit_slicing_skips_preprocessed_linemarkers_and_blank_unit_starts(): +def test_file_unit_scanning_skips_preprocessed_linemarkers_and_blank_unit_starts(): scanner = _SourceUnitScanner() lines = _lines( '# 4 "generated.f90"', @@ -138,9 +166,8 @@ def test_child_unit_slicing_skips_preprocessed_linemarkers_and_blank_unit_starts "end module owner", ) - units = scanner.slice_child_units( + units = scanner.scan_file_units( lines, - parent_kind="file", filename="generated.f90", ) @@ -195,8 +222,7 @@ def test_unit_end_and_header_validation_preserve_public_diagnostics(): assert procedure_error.value.code == "PARSE_MALFORMED_HEADER" -def test_unit_part_splitting_skips_nested_units_and_preserves_executable_boundary(): - scanner = _SourceUnitScanner() +def test_classified_unit_regions_skip_nested_units_and_preserve_executable_boundary(): unit = _unit( "procedure", "work", @@ -214,13 +240,11 @@ def test_unit_part_splitting_skips_nested_units_and_preserves_executable_boundar "end subroutine work", ) - parts = scanner.split_unit_parts(unit, filename="parts.f90") - - assert [line for line, _lineno, _source in parts.specification] == ["integer :: counter"] - assert [line for line, _lineno, _source in parts.execution] == ["counter = counter + 1"] - assert parts.contains == [] - assert parts.header == unit.lines[0] - assert parts.footer == unit.lines[-1] + assert [line for line, _lineno, _source in unit.specification] == ["integer :: counter"] + assert [line for line, _lineno, _source in unit.execution] == ["counter = counter + 1"] + assert unit.contains == [] + assert unit.header == unit.lines[0] + assert unit.footer == unit.lines[-1] def test_sibling_unit_validation_ignores_unnamed_units_and_preserves_duplicate_diagnostics(): From 9eab5ea8af0aee72ef4f61c19ac108743c13375f Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 19:01:37 +0100 Subject: [PATCH 08/22] Implement the project parsing separation --- CHANGELOG.md | 2 + docs/developer/fortran-parser-reference.md | 44 ++-- docs/developer/quality-assurance.md | 2 +- prik/parsers/fortran/README.md | 2 +- prik/parsers/fortran/parser.py | 220 +++++++++--------- .../parsing/test_project_scope_models.py | 4 +- .../test_declaration_and_scope_regressions.py | 65 +++--- 7 files changed, 188 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb8fe6cc3..a17e03eaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ release tags add a leading `v` to the package version. - Stopped standalone Fortran parser discovery from descending into inaccessible procedure-internal subprograms; procedure-local callback interfaces remain classified and discoverable. +- Made directory project parsing read and parse each discovered Fortran file + once before dependency ordering and project assembly. ## 0.2.1 — 2026-08-11 diff --git a/docs/developer/fortran-parser-reference.md b/docs/developer/fortran-parser-reference.md index 73e23cd6e..060c654c8 100644 --- a/docs/developer/fortran-parser-reference.md +++ b/docs/developer/fortran-parser-reference.md @@ -290,21 +290,25 @@ The Fortran data flow is: source path or source text -> compiler/native include preprocessing -> FortranParser.parse_file(...) - -> source-unit slices with original line numbers + -> classified source units with original line numbers -> scoped specification parsing -> FortranFile parser facts - -> parse_fortran_project(...) dependency ordering and namespace resolution + -> directory source discovery when requested + -> each FortranFile parsed exactly once + -> dependency ordering of the existing FortranFile objects + -> FortranProject cross-file resolution and indexes -> semantics.fortran2ir conversion -> policy completion, `.pyi`, and the implemented Fortran wrapper stages ``` The recursive parsing pattern is: -1. Identify direct child units at the current grammar level. -2. Split each child into header, specification part, execution part, and - `contains` part where that language construct allows them. -3. Parse declarations only from the specification part. -4. Recurse only into direct children that are legal for the current unit kind. +1. Construct each unit with its header, grammar regions, and retained direct + children already classified. +2. Parse declarations only from the stored specification part. +3. Recurse only into retained direct children that later parser work needs. +4. Keep procedure execution regions opaque and omit inaccessible internal + procedures from the retained child tree. 5. Validate sibling names and scope-local duplicate declarations. 6. Finalize procedure arguments/results after local declarations and parameters are known. @@ -652,23 +656,37 @@ function that created the diagnostic. ## 4) Python usage and expected outputs -### 4.1 Parse folder namespace +### 4.1 Parse a project directory ```python from prik import parse_fortran_project from pathlib import Path -files = list(Path("tests/fortran/source_parsing/parsing/fixtures/general").glob("*.f90"))[:5] -project = parse_fortran_project(files) +project = parse_fortran_project(Path("src")) print(len(project.files)) print(len(project.modules)) ``` Expected behavior: -- Recursively scans Fortran files. -- Resolves dependencies and module imports across files. -- Returns aggregate namespace parse output. +- Recursively discovers supported Fortran source paths. +- Parses each discovered file exactly once into a `FortranFile`. +- Orders those existing file models from dependency providers to consumers. +- Resolves cross-file kinds/imports and returns an indexed `FortranProject`. + +The directory control flow is deliberately explicit: + +```text +parse_project + -> _discover_project_paths + -> _parse_project_files + -> _order_project_files + -> _assemble_project +``` + +In-memory `{filename: source}` input uses `_parse_named_project_sources` +instead of filesystem discovery. Explicit file lists use +`_parse_project_files` and preserve caller order. ### 4.2 Parse single file and convert it to semantic IR diff --git a/docs/developer/quality-assurance.md b/docs/developer/quality-assurance.md index 67f907243..b476fea75 100644 --- a/docs/developer/quality-assurance.md +++ b/docs/developer/quality-assurance.md @@ -313,7 +313,7 @@ needed. Keep the ordinary regression tests and fixes that came from it: -- Fortran project namespace collection respecting the requested encoding; +- Fortran directory-project parsing respecting the requested encoding; - direct Fortran parser contracts for diagnostics, forwarding, registries, ownership, provenance, source locations, boundaries, and loop progress. diff --git a/prik/parsers/fortran/README.md b/prik/parsers/fortran/README.md index fbacbdee6..41d4c1585 100644 --- a/prik/parsers/fortran/README.md +++ b/prik/parsers/fortran/README.md @@ -12,7 +12,7 @@ callers may also use the stable parser functions and models exported from the | File | Owns | | --- | --- | -| `parser.py` | Recursive Fortran parser, project assembly, namespace collection, kind resolution hooks. | +| `parser.py` | Recursive Fortran parser, project source discovery, dependency ordering, assembly, and kind resolution. | | `lexer.py` | Fortran line preprocessing, comment stripping, and token preparation. | | `models.py` | Parser model dataclasses and diagnostics. | | `type_resolver.py` | Parser-level type and kind helpers. | diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index d0a092c65..c9163b479 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -174,6 +174,7 @@ re.IGNORECASE, ) _INTRINSIC_COMPILE_TIME_MODULES = frozenset({"iso_c_binding", "iso_fortran_env"}) +_FORTRAN_SOURCE_SUFFIXES = (".f", ".for", ".ftn", ".f77", ".f90", ".f95", ".f03", ".f08") _PreprocessedLine = tuple[str, int | None, str | None] @@ -1312,15 +1313,18 @@ def parse_project( failures raise :class:`FortranParseError`. """ - # Stage 1: parse each requested source in dependency-aware order. - parsed_files = self._helper_parse_project_files(files, encoding) + # Stage 1: normalize the requested input form and parse every file once. + if isinstance(files, dict): + parsed_files = self._parse_named_project_sources(files, encoding=encoding) + elif isinstance(files, str | Path): + paths = self._discover_project_paths(Path(files)) + parsed_files = self._parse_project_files(paths, encoding=encoding) + parsed_files = self._order_project_files(parsed_files) + else: + parsed_files = self._parse_project_files(files, encoding=encoding) - # Stage 2: complete cross-file kinds and construct project indexes. - self._helper_resolve_project_kinds(parsed_files) - project = FortranProject(files=parsed_files) - for parsed_file in parsed_files: - self._helper_index_project_file(project, parsed_file) - return project + # Stage 2: complete cross-file facts and construct the public project. + return self._assemble_project(parsed_files) def parse_module(self, code: _SourceOrLines, filename: str | None = None) -> FortranModule: """Parse exactly one module unit from source text or normalized lines. @@ -1907,18 +1911,113 @@ def _helper_build_fortran_file( ) return parsed_file - def _helper_parse_project_files( + @staticmethod + def _discover_project_paths( + root: Path, + extensions: tuple[str, ...] = _FORTRAN_SOURCE_SUFFIXES, + ) -> list[Path]: + """Return supported Fortran paths below one project directory. + + Discovery only identifies files; it does not read or parse them. The + paths are sorted so unrelated files have deterministic order before + dependency analysis. For example, a directory containing ``b.f90``, + ``a.f90``, and ``notes.txt`` produces ``[a.f90, b.f90]``. + """ + return sorted(path for path in root.rglob("*") if path.suffix.lower() in extensions) + + def _parse_project_files( self, - files: dict[str, str] | list[str | Path] | tuple[str | Path, ...] | str | Path, + paths: Sequence[str | Path], + *, encoding: str, ) -> list[FortranFile]: - """Normalize project inputs and parse each source file.""" - if isinstance(files, dict): - return [self.parse_file(code, filename=fname, encoding=encoding) for fname, code in files.items()] - if isinstance(files, str | Path): - namespace = self._helper_collect_namespace(files, encoding=encoding) - return [self.parse_file(path, encoding=encoding) for path in namespace["files"]] - return [self.parse_file(path, encoding=encoding) for path in files] + """Read and fully parse each project file exactly once. + + Input order is preserved. Each returned :class:`FortranFile` owns the + file's source, filename, encoding, and parsed program units. For + example, ``[types.f90, solver.f90]`` produces two file models in that + same order; dependency ordering is a separate later operation. + """ + return [self.parse_file(path, encoding=encoding) for path in paths] + + def _parse_named_project_sources( + self, + sources: Mapping[str, str], + *, + encoding: str, + ) -> list[FortranFile]: + """Parse an in-memory ``filename -> source`` project mapping. + + Mapping insertion order is preserved and every key becomes diagnostic + filename provenance. For example, ``{"api.f90": "module api ..."}`` + produces one :class:`FortranFile` named ``api.f90`` without filesystem + discovery. + """ + return [self.parse_file(source, filename=filename, encoding=encoding) for filename, source in sources.items()] + + @staticmethod + def _project_file_requirements(parsed_file: FortranFile) -> set[str]: + """Return module or submodule names required by one parsed file. + + Requirements come from module and submodule ``use`` statements plus a + submodule's parent and optional ancestor. For example, a child + submodule with parent ``api`` and ``use kinds`` returns at least + ``{"api", "kinds"}``. + """ + requirements: set[str] = set() + for module in parsed_file.modules: + requirements.update(name.lower() for name in module.uses) + for submodule in parsed_file.submodules: + requirements.update(name.lower() for name in submodule.uses) + requirements.add(submodule.parent.lower()) + if submodule.ancestor: + requirements.add(submodule.ancestor.lower()) + return requirements + + def _order_project_files(self, parsed_files: list[FortranFile]) -> list[FortranFile]: + """Return existing file models in dependency-first order. + + Module and submodule definitions are mapped to their provider files, + requirements are converted into file edges, and + :meth:`_topological_files` supplies deterministic cycle-tolerant order. + For example, parsed ``solver.f90`` using module ``kinds`` is moved + after the existing ``kinds.f90`` model without reparsing either file. + """ + files_by_name: dict[str, FortranFile] = {} + unit_to_file: dict[str, str] = {} + for parsed_file in parsed_files: + filename = parsed_file.filename + if filename is None: + raise ValueError("Dependency ordering requires every parsed project file to have a filename.") + files_by_name[filename] = parsed_file + unit_to_file.update((module.name.lower(), filename) for module in parsed_file.modules) + unit_to_file.update((submodule.name.lower(), filename) for submodule in parsed_file.submodules) + + file_dependencies: dict[str, set[str]] = {} + for filename, parsed_file in files_by_name.items(): + dependencies = { + provider + for requirement in self._project_file_requirements(parsed_file) + if (provider := unit_to_file.get(requirement)) is not None and provider != filename + } + file_dependencies[filename] = dependencies + + ordered_names = self._topological_files(file_dependencies) + return [files_by_name[filename] for filename in ordered_names] + + def _assemble_project(self, parsed_files: list[FortranFile]) -> FortranProject: + """Resolve cross-file facts and index one completed project model. + + The input files are already parsed and in their caller-selected or + dependency-derived order. For example, ``[kinds_file, solver_file]`` + is preserved in ``project.files`` while their modules, procedures, + types, interfaces, and dependency facts enter project registries. + """ + self._helper_resolve_project_kinds(parsed_files) + project = FortranProject(files=parsed_files) + for parsed_file in parsed_files: + self._helper_index_project_file(project, parsed_file) + return project def _helper_resolve_project_kinds(self, parsed_files: list[FortranFile]) -> None: """Resolve project procedure and module-variable kinds from shared symbols.""" @@ -2162,93 +2261,6 @@ def _raise_for_raw_cpp_directive( code="PARSE_PREPROCESSING_REQUIRED", ) - def _helper_collect_namespace( - self, - root: str | Path, - extensions: tuple[str, ...] = (".f", ".for", ".ftn", ".f77", ".f90", ".f95", ".f03", ".f08"), - *, - encoding: str = "utf-8", - ) -> dict: - """Collect parseable source files and dependency-order them. - - Project parsing uses this helper when the caller passes a directory - instead of an explicit file list. It performs a light first pass to map - modules/submodules to files, topologically orders files by ``use`` - dependencies, then parses them in that order. - - Example: - ``parse_project("src")`` calls this helper, receives - ``{"files": ordered_files, "module_to_file": ...}``, and then - parses each ordered path through the normal file entrypoint. - """ - root_path = Path(root) - files = sorted([p for p in root_path.rglob("*") if p.suffix.lower() in extensions]) - sources = {str(p): p.read_text(encoding=encoding) for p in files} - file_lines = {fname: preprocess_lines(code, fname) for fname, code in sources.items()} - - module_to_file: dict[str, str] = {} - submodule_to_file: dict[str, str] = {} - file_to_uses: dict[str, set[str]] = {fname: set() for fname in sources} - for fname, _code in sources.items(): - lines = file_lines[fname] - _lines, root_scope, all_units = self._helper_prepare_source_units(lines, fname) - modules = [ - self._visit(unit, parent_scope=root_scope, filename=fname) - for unit in all_units - if unit.kind == "module" - ] - submodules = [ - self._visit(unit, parent_scope=root_scope, filename=fname) - for unit in all_units - if unit.kind == "submodule" - ] - for m in modules: - module_to_file[m.name.lower()] = fname - file_to_uses[fname].update(u.lower() for u in m.uses) - for sm in submodules: - submodule_to_file[sm.name.lower()] = fname - file_to_uses[fname].add(sm.parent.lower()) - if sm.ancestor: - file_to_uses[fname].add(sm.ancestor.lower()) - file_to_uses[fname].update(u.lower() for u in sm.uses) - - file_dependencies: dict[str, set[str]] = {} - for fname, used_modules in file_to_uses.items(): - deps = set() - for mod in used_modules: - dep_file = module_to_file.get(mod) or submodule_to_file.get(mod) - if dep_file and dep_file != fname: - deps.add(dep_file) - file_dependencies[fname] = deps - - ordered_files = self._topological_files(file_dependencies) - types = [] - modules = [] - submodules = [] - programs = [] - block_data = [] - for f in ordered_files: - parsed_file = self.parse_file(sources[f], filename=f, encoding=encoding) - types.extend(parsed_file.derived_types) - types.extend(dtype for module in parsed_file.modules for dtype in module.derived_types) - types.extend(dtype for submodule in parsed_file.submodules for dtype in submodule.derived_types) - modules.extend(parsed_file.modules) - submodules.extend(parsed_file.submodules) - programs.extend(parsed_file.programs) - block_data.extend(parsed_file.block_data_units) - - return { - "files": ordered_files, - "file_dependencies": {k: sorted(v) for k, v in file_dependencies.items()}, - "module_to_file": module_to_file, - "submodule_to_file": submodule_to_file, - "modules": modules, - "submodules": submodules, - "programs": programs, - "block_data": block_data, - "types": types, - } - def _helper_prepare_source_units( self, code: _SourceOrLines, diff --git a/tests/fortran/modules/parsing/test_project_scope_models.py b/tests/fortran/modules/parsing/test_project_scope_models.py index 73546b7bc..caa9d1c73 100644 --- a/tests/fortran/modules/parsing/test_project_scope_models.py +++ b/tests/fortran/modules/parsing/test_project_scope_models.py @@ -129,7 +129,7 @@ def test_duplicate_project_scope_names_raise_public_parse_errors(): ) -def test_project_directory_namespace_orders_ancestor_submodule_dependencies(tmp_path): +def test_project_directory_orders_ancestor_submodule_dependencies(tmp_path): (tmp_path / "ancestor.f90").write_text( """ module ancestor_mod @@ -409,7 +409,7 @@ def test_project_resolves_submodule_host_associated_kind(): assert prototype.result.kind == "real64" -def test_directory_namespace_records_missing_and_parent_only_submodule_dependencies(tmp_path): +def test_directory_project_records_missing_and_parent_only_submodule_dependencies(tmp_path): (tmp_path / "parent.f90").write_text( """ module parent_mod diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py index c4e1e5f72..c92d4d2c9 100644 --- a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py @@ -242,7 +242,7 @@ def test_legacy_parameter_lines_respect_implicit_none_and_implicit_typing_contra assert loose_state.legacy_local_params == set() -def test_namespace_collection_preserves_case_insensitive_dependencies_and_exact_payload(tmp_path: Path, monkeypatch): +def test_directory_project_parses_once_and_assembles_dependency_ordered_models(tmp_path: Path, monkeypatch): sources = { "ancestor.f90": "module Ancestor_Mod\nend module Ancestor_Mod\n", "parent.f90": "module Parent_Mod\n use Ancestor_Mod\n type :: Parent_State\n end type Parent_State\nend module Parent_Mod\n", @@ -269,16 +269,18 @@ def test_namespace_collection_preserves_case_insensitive_dependencies_and_exact_ for filename, source in sources.items(): (tmp_path / filename).write_text(source, encoding="utf-8") - encodings = [] + read_paths: list[Path] = [] + encodings: list[str | None] = [] original_read_text = Path.read_text def read_text(path, *args, **kwargs): + read_paths.append(path) encodings.append(kwargs.get("encoding")) return original_read_text(path, *args, **kwargs) monkeypatch.setattr(Path, "read_text", read_text) - namespace = FortranParser()._helper_collect_namespace(tmp_path) + project = parse_fortran_project(tmp_path) ancestor = str(tmp_path / "ancestor.f90") parent = str(tmp_path / "parent.f90") @@ -287,42 +289,45 @@ def read_text(path, *args, **kwargs): grandchild = str(tmp_path / "grandchild.f90") units = str(tmp_path / "units.f90") - assert encodings and set(encodings) == {"utf-8"} - assert namespace["module_to_file"] == { - "ancestor_mod": ancestor, - "helper_mod": helper, - "parent_mod": parent, + assert len(read_paths) == len(sources) + assert set(read_paths) == {tmp_path / filename for filename in sources} + assert set(encodings) == {"utf-8"} + + ordered_files = [parsed_file.filename for parsed_file in project.files] + assert ordered_files.index(ancestor) < ordered_files.index(parent) + assert ordered_files.index(parent) < ordered_files.index(child) + assert ordered_files.index(helper) < ordered_files.index(child) + assert ordered_files.index(child) < ordered_files.index(grandchild) + assert units in ordered_files + + assert {module.name for module in project.modules.values()} == { + "Ancestor_Mod", + "Helper_Mod", + "Parent_Mod", } - assert namespace["submodule_to_file"] == { - "child_mod": child, - "grandchild_mod": grandchild, + assert {submodule.name for submodule in project.submodules.values()} == { + "Child_Mod", + "Grandchild_Mod", } - assert namespace["file_dependencies"] == { - ancestor: [], - child: [ancestor, helper, parent], - grandchild: [child], - helper: [], - parent: [ancestor], - units: [], + assert [program.name for program in project.programs.values()] == ["Driver"] + assert [block.name for parsed_file in project.files for block in parsed_file.block_data_units] == ["Init_Data"] + assert {dtype.name for dtype in project.derived_types.values()} == { + "Parent_State", + "Child_State", + "File_State", } - assert namespace["files"].index(ancestor) < namespace["files"].index(parent) - assert namespace["files"].index(parent) < namespace["files"].index(child) - assert namespace["files"].index(helper) < namespace["files"].index(child) - assert namespace["files"].index(child) < namespace["files"].index(grandchild) - assert {module.name for module in namespace["modules"]} == {"Ancestor_Mod", "Helper_Mod", "Parent_Mod"} - assert {submodule.name for submodule in namespace["submodules"]} == {"Child_Mod", "Grandchild_Mod"} - assert [program.name for program in namespace["programs"]] == ["Driver"] - assert [block.name for block in namespace["block_data"]] == ["Init_Data"] - assert {dtype.name for dtype in namespace["types"]} == {"Parent_State", "Child_State", "File_State"} - assert {module.name: module.filename for module in namespace["modules"]} == { + assert {module.name: module.filename for module in project.modules.values()} == { "Ancestor_Mod": ancestor, "Helper_Mod": helper, "Parent_Mod": parent, } - assert {submodule.name: submodule.filename for submodule in namespace["submodules"]} == { + assert {submodule.name: submodule.filename for submodule in project.submodules.values()} == { "Child_Mod": child, "Grandchild_Mod": grandchild, } + assert project.dependencies["parent_mod"] == {"ancestor_mod"} + assert project.dependencies["child_mod"] == {"ancestor_mod", "parent_mod", "helper_mod"} + assert project.dependencies["grandchild_mod"] == {"child_mod"} def test_project_registries_preserve_qualified_aliases_values_and_dependencies(): @@ -534,7 +539,7 @@ def test_project_encoding_is_forwarded_to_explicit_path_inputs(tmp_path: Path): assert project.files[0].source.startswith("! caf\xe9") -def test_project_encoding_is_forwarded_to_directory_namespace_collection(tmp_path: Path): +def test_project_encoding_is_forwarded_to_directory_file_parsing(tmp_path: Path): source = tmp_path / "latin1.f90" source.write_bytes("! caf\xe9\nmodule encoded_mod\nend module encoded_mod\n".encode("latin-1")) From cee5d6f349c1bc52334534ddad53534343fd90fa Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 20:06:20 +0100 Subject: [PATCH 09/22] Add _Declaration --- docs/developer/fortran-parser-reference.md | 21 +- prik/parsers/fortran/parser.py | 577 +++++++++++------- .../parsing/test_declarations_and_shapes.py | 8 +- .../test_declaration_and_scope_regressions.py | 83 ++- ...source_form_and_diagnostics_regressions.py | 10 +- 5 files changed, 442 insertions(+), 257 deletions(-) diff --git a/docs/developer/fortran-parser-reference.md b/docs/developer/fortran-parser-reference.md index 060c654c8..a8b571a73 100644 --- a/docs/developer/fortran-parser-reference.md +++ b/docs/developer/fortran-parser-reference.md @@ -189,16 +189,18 @@ The parser handles it in this order: 3. `_visit_ModuleUnit` creates a module `_ParserScope` and sends the unit's already-classified specification lines to `_parse_specification_part`. 4. `_parse_specification_part` uses the shared declaration backend: - `_helper_parse_declaration_line` parses `integer, parameter :: n = 4`, then - `_helper_push_declaration_to_scope` appends the resulting parameter variable - to `FortranModule.variables`. + `_helper_parse_declaration_line` parses `integer, parameter :: n = 4` into a + typed `_Declaration`, then `_store_declaration` dispatches to the + module-like-variable storage helper, which appends the resulting parameter + variable to `FortranModule.variables`. 5. The module visitor reads the scanner-owned direct children. It finds one procedure unit, `scale`, and dispatches it to `_visit_ProcedureUnit`. 6. `_visit_ProcedureUnit` creates a procedure `_ParserScope` and visits only the stored specification part. The same declaration backend parses - `real, intent(inout) :: x(n)` and pushes the metadata into the procedure - argument symbol table. + `real, intent(inout) :: x(n)` and sends the typed declaration to the + procedure-symbol storage helper, which updates `x` in the procedure argument + symbol table. Scope is always an explicit argument to the shared helpers. That is the reason two modules can each define `type :: state` without conflict, while two @@ -213,6 +215,15 @@ depend on constructed parser models. Splitting the scanner into another file would not strengthen that boundary; its private source tuples, grammar records, and unit classes are all local to this parser module. +Declaration parsing has its own local ownership boundary. `_Declaration` +records the normalized type spelling and declaration attributes shared by an +entity list. For example, `real(kind=rk), pointer, dimension(:) :: values` +produces one declaration with base type `real`, kind `rk`, pointer enabled, and +shape `[:]`; `values` remains a separate entity name. Storage helpers then turn +that record into procedure symbols, derived-type fields, or module-like +variables. The record is parser-internal and never becomes a second public +parser model or semantic-policy object. + Each `SourceUnit` is fully classified by the scanner. In addition to its exact source span, kind, and name, it owns its header, specification, execution, `contains`, footer, and retained direct child units. A child records whether it diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index c9163b479..2f396c4c1 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -107,11 +107,11 @@ Scoping follows the same recursion. A helper that parses `integer :: n` or `real :: x(n)` receives a `_ParserScope` argument. The shared declaration parser -builds the same metadata for variables, procedure arguments/results, and -derived-type fields; `_helper_push_declaration_to_scope` stores the symbol in -the current scope model. This is why the same derived-type name can exist in -two modules, while duplicate names at one sibling level are rejected by the -slicer validation. +builds the same typed `_Declaration` for variables, procedure +arguments/results, and derived-type fields; `_store_declaration` selects the +storage helper for the current scope model. This is why the same derived-type +name can exist in two modules, while duplicate names at one sibling level are +rejected by the slicer validation. """ _REGEX: dict[str, re.Pattern[str]] = { @@ -267,6 +267,56 @@ class EnumUnit(SourceUnit): } +_DeclarationRole = Literal["procedure_symbol", "type_field", "module_variable"] +_DECLARATION_FLAG_FIELDS = MappingProxyType( + { + "optional": "optional", + "value": "value", + "allocatable": "allocatable", + "pointer": "pointer", + "target": "target", + "contiguous": "contiguous", + "external": "external", + "parameter": "parameter", + } +) + + +@dataclass +class _Declaration: + """Hold normalized type and attribute facts for one declaration statement. + + The record describes the facts shared by every entity after ``::``; entity + names, inline shapes, and initializers are parsed separately before model + storage. For example, ``real(kind=rk), pointer, dimension(:) :: values`` + produces ``base_type="real"``, ``kind="rk"``, ``pointer=True``, and + ``shape=[":"]``. This parser-local record is not part of the public parser + model or the later semantic policy. + """ + + base_type: str + kind: str = "" + rank: int = 0 + shape: list[str] = dataclass_field(default_factory=list) + intent: str | None = None + reads_argument: bool | None = None + writes_argument: bool | None = None + optional: bool = False + value: bool = False + allocatable: bool = False + pointer: bool = False + target: bool = False + contiguous: bool = False + external: bool = False + parameter: bool = False + polymorphic: bool = False + visibility: str = "public" + explicit_visibility: str | None = None + target_kind_expression: str | None = None + character_length_syntax: bool = False + declared_storage_bits: int | None = None + + @dataclass class _ProcedureState: """Accumulate mutable facts while parsing and finalizing one procedure. @@ -285,7 +335,7 @@ class _ProcedureState: local_params: dict[str, str] = dataclass_field(default_factory=dict) legacy_local_params: set[str] = dataclass_field(default_factory=set) implicit_typed_symbols: dict[str, str] = dataclass_field(default_factory=dict) - declared_local_types: dict[str, dict[str, object]] = dataclass_field(default_factory=dict) + declared_local_types: dict[str, _Declaration] = dataclass_field(default_factory=dict) implicit_none: bool = False imports: set[str] = dataclass_field(default_factory=set) external_symbols: set[str] = dataclass_field(default_factory=set) @@ -323,7 +373,7 @@ class _UnitGrammar: has_execution_part: bool = False has_contains_part: bool = False ignores_contains_children: bool = False - declaration_role: str | None = None + declaration_role: _DeclarationRole | None = None @dataclass(frozen=True) @@ -1232,8 +1282,8 @@ class FortranParser(ClassVisitor): 4. Each unit visitor builds its own `_ParserScope`, visits the unit's scanner-owned specification region, and consumes its retained direct children where the grammar allows them. - 5. Shared declaration helpers push variables, procedure symbols, and type - fields into the active scope model. + 5. Shared declaration helpers create a typed declaration and store its + variables, procedure symbols, or type fields in the active scope model. 6. Build `FortranFile` symbol table and standalone entity lists. Class section map: @@ -3148,23 +3198,19 @@ def _proc_scope_set_declared_local_type( self, proc_state: _ProcedureState, name: str, - meta: dict, + declaration: _Declaration, ) -> None: - """Store type metadata for a declared local symbol.""" + """Store an independent typed declaration for one local symbol. + + For example, a local ``real(kind=rk) :: scratch`` stores a declaration + under ``scratch`` so procedure finalization can later type a matching + parameter or unresolved dummy without relying on dictionary keys. + """ key = self._scope_key(name) - declared_type = { - "base_type": meta["base_type"], - "kind": meta["kind"], - } - for metadata_key in ( - "target_kind_expression", - "character_length_syntax", - "declared_storage_bits", - "polymorphic", - ): - if metadata_key in meta and (metadata_key != "polymorphic" or meta[metadata_key]): - declared_type[metadata_key] = meta[metadata_key] - proc_state.declared_local_types[key] = declared_type + proc_state.declared_local_types[key] = replace( + declaration, + shape=list(declaration.shape), + ) def _proc_scope_add_local_parameter( self, @@ -3294,7 +3340,7 @@ def _parse_module_like_spec_line( Example: In a program scope, ``integer, parameter :: n = 8`` is parsed by - `_helper_parse_declaration_line` and pushed to + `_helper_parse_declaration_line` and stored in `program.variables`. In a module scope, ``private :: work`` updates module visibility instead of creating a variable. """ @@ -3702,7 +3748,7 @@ def _helper_parse_declaration_line( line: str, scope: _ParserScope, *, - role: str, + role: _DeclarationRole, filename: str | None, lineno: int | None, source_line: str | None, @@ -3714,7 +3760,7 @@ def _helper_parse_declaration_line( This is the common declaration backend for module variables, program variables, block-data variables, derived-type fields, and procedure arguments/results. The `role` argument captures the small differences - in where the parsed symbol is pushed. + in where the parsed symbol is stored. Example: ``real :: x(:)`` with role ``procedure_symbol`` updates @@ -3732,16 +3778,20 @@ def _helper_parse_declaration_line( parsed_decl = self._parse_declaration_left(left, parse_character_star=parse_character_star) if parsed_decl is None: return False - meta, attrs = parsed_decl + declaration, attrs = parsed_decl if not has_separator: - legacy_right = self._legacy_declaration_entities(left, meta) + legacy_right = self._legacy_declaration_entities(left, declaration) if legacy_right is not None: right = legacy_right attrs = [] - self._apply_decl_attrs(meta, attrs, include_argument_access=include_argument_access) - self._helper_push_declaration_to_scope( + self._apply_declaration_attributes( + declaration, + attrs, + include_argument_access=include_argument_access, + ) + self._store_declaration( scope, - meta=meta, + declaration=declaration, right=right, role=role, filename=filename, @@ -3755,8 +3805,14 @@ def _parse_declaration_left( left: str, *, parse_character_star: bool = True, - ) -> tuple[dict, list[str]] | None: - """Parse a declaration prefix into normalized metadata and attributes.""" + ) -> tuple[_Declaration, list[str]] | None: + """Parse a declaration prefix into a typed record and raw attributes. + + For example, ``real(kind=rk), pointer`` returns a declaration with + base type ``real`` and kind ``rk``, plus ``["pointer"]`` for the + attribute-normalization step. Entity names after ``::`` are not part of + this prefix parser. + """ star_kind = self._find_legacy_star_kind(left) char_star = _REGEX["char_star"].match(left) if parse_character_star else None if char_star: @@ -3764,48 +3820,53 @@ def _parse_declaration_left( if kind.startswith("(") and kind.endswith(")"): kind = kind[1:-1].strip() trailing = (char_star.group("rest") or "").strip().lstrip(", ") - meta = self._new_decl_meta("character", kind) - meta["character_length_syntax"] = True - return meta, split_csv(trailing) + declaration = self._new_declaration("character", kind) + declaration.character_length_syntax = True + return declaration, split_csv(trailing) if star_kind: base, kind = star_kind tail = self._strip_legacy_star_kind_prefix(left) attrs = split_csv(tail.lstrip(", ")) if tail.startswith(",") else [] - meta = self._new_decl_meta(base.lower(), kind) + declaration = self._new_declaration(base.lower(), kind) if base.lower() == "character": - meta["character_length_syntax"] = True + declaration.character_length_syntax = True else: - meta["declared_storage_bits"] = int(kind) * 8 - return meta, attrs + declaration.declared_storage_bits = int(kind) * 8 + return declaration, attrs intrinsic = self._split_intrinsic_type_spec(left) derived = _REGEX["type_field"].match(left) class_derived = _REGEX["class_field"].match(left) if intrinsic: base, type_spec, tail = intrinsic - meta = self._intrinsic_decl_meta(base, type_spec) - return meta, split_csv(tail.strip().lstrip(", ")) + declaration = self._intrinsic_declaration(base, type_spec) + return declaration, split_csv(tail.strip().lstrip(", ")) if derived or class_derived: decl = derived or class_derived - meta = self._new_decl_meta("derived", decl.group("dtype")) - meta["polymorphic"] = class_derived is not None - return meta, split_csv((decl.group("attrs") or "").strip().lstrip(", ")) + declaration = self._new_declaration("derived", decl.group("dtype")) + declaration.polymorphic = class_derived is not None + return declaration, split_csv((decl.group("attrs") or "").strip().lstrip(", ")) if re.match(r"^procedure\s*\(", left, re.IGNORECASE): procm = _REGEX["procedure_dummy"].match(left) iface = procm.group("iface").lower() if procm else None - return self._new_decl_meta("procedure", iface), split_csv( + return self._new_declaration("procedure", iface), split_csv( (procm.group("attrs") if procm else "").strip().lstrip(", ") ) return None - def _legacy_declaration_entities(self, left: str, meta: dict) -> str | None: - """Return the entity-list tail from a declaration without `::`.""" + def _legacy_declaration_entities(self, left: str, declaration: _Declaration) -> str | None: + """Return the entity-list tail from a declaration without ``::``. + + ``integer*4 values(3)`` returns ``values(3)``. The typed declaration + tells character-length syntax apart from intrinsic storage-width + syntax while the original prefix remains available for slicing. + """ char_star = _REGEX["char_star"].match(left) if char_star: return (char_star.group("rest") or "").strip().lstrip(", ") star_kind = self._find_legacy_star_kind(left) - if star_kind and meta["base_type"] != "character": + if star_kind and declaration.base_type != "character": return self._strip_legacy_star_kind_prefix(left).lstrip(", ") intrinsic = self._split_intrinsic_type_spec(left) @@ -3823,62 +3884,34 @@ def _legacy_declaration_entities(self, left: str, meta: dict) -> str | None: return tail return None # pragma: no cover - invalid legacy declaration tails are ignored. - def _helper_push_declaration_to_scope( + def _store_declaration( self, scope: _ParserScope, *, - meta: dict, + declaration: _Declaration, right: str, - role: str, + role: _DeclarationRole, filename: str | None, lineno: int | None, source_line: str | None, ) -> None: - """Push parsed declaration entities into the correct scope model. - - The name says "push" because parsing a declaration is only half of the - job; the other half is storing the resulting symbol in the active unit - scope. This helper is the common storage point for variables, fields, - and procedure arguments/results. + """Dispatch parsed entities to the storage owner selected by ``role``. - Example: - ``integer :: n`` in a module appends `FortranVariable("n")` to - `module.variables`, while the same declaration inside - ``subroutine step(n)`` updates the existing `FortranArgument("n")` - in the procedure signature. + Parsing a declaration is separate from storing its entities. For + example, ``integer :: n`` in a module appends a variable through + :meth:`_store_scope_variable_declaration`, while the same statement in + ``subroutine step(n)`` updates the existing dummy through + :meth:`_store_procedure_declaration`. """ if role == "procedure_symbol": - proc_state = scope.state - if proc_state is None: # pragma: no cover - internal helper misuse. - raise FortranParseError( - "Procedure declaration scope is missing state.", - filename=filename, - code="PARSE_INTERNAL_STATE", - ) - if meta["base_type"] == "procedure" and meta["kind"] in proc_state.imports: - meta["kind"] = None - for entity in split_csv(right): - raw_name, shape = self._var(entity) - if not raw_name: - continue - entity_meta = self._entity_decl_meta(raw_name, meta) - normalized_name = self._normalize_declared_name(raw_name, entity_meta) - if not normalized_name: - continue - lowered_name = self._proc_scope_mark_declared_symbol( - proc_state, - normalized_name, - filename=filename, - line_number=lineno, - source_line=source_line, - ) - if meta.get("external"): - self._proc_scope_add_external_symbol(proc_state, lowered_name) - arg = self._proc_scope_get_symbol(proc_state, lowered_name) - if arg is None: - self._proc_scope_set_declared_local_type(proc_state, lowered_name, entity_meta) - continue - self._apply(arg, entity_meta, shape) + self._store_procedure_declaration( + scope, + declaration, + right, + filename=filename, + lineno=lineno, + source_line=source_line, + ) return target = scope.model @@ -3888,38 +3921,133 @@ def _helper_push_declaration_to_scope( filename=filename, code="PARSE_INTERNAL_STATE", ) + if role == "type_field": + self._store_type_field_declaration(target, declaration, right) + return + self._store_scope_variable_declaration(scope, target, declaration, right) - for entity in split_csv(right): - declared_entity, initializer = split_declaration_assignment(entity) - raw_name, shape = self._var(declared_entity) - if not raw_name: - continue - entity_meta = self._entity_decl_meta(raw_name, meta) - normalized_name = self._normalize_declared_name(raw_name, entity_meta) - if not normalized_name: - continue - if role == "type_field": - field = FortranArgument(name=normalized_name) - self._apply(field, entity_meta, shape) - if initializer is not None: - field.value = self._normalize_parameter_value(initializer) - field.symbolic_value = initializer - field.value_type = "expression" - target.fields.append(field) + def _store_procedure_declaration( + self, + scope: _ParserScope, + declaration: _Declaration, + right: str, + *, + filename: str | None, + lineno: int | None, + source_line: str | None, + ) -> None: + """Store declaration entities in one procedure symbol table. + + Example: + ``real(kind=rk) :: value, scratch`` updates a dummy named ``value`` + and retains the same typed declaration for the local ``scratch``. + Source metadata is forwarded to duplicate-declaration diagnostics. + """ + proc_state = scope.state + if proc_state is None: # pragma: no cover - internal helper misuse. + raise FortranParseError( + "Procedure declaration scope is missing state.", + filename=filename, + code="PARSE_INTERNAL_STATE", + ) + if declaration.base_type == "procedure" and declaration.kind in proc_state.imports: + declaration.kind = "" + for normalized_name, shape, _initializer, entity_declaration in self._declaration_entities( + right, + declaration, + ): + lowered_name = self._proc_scope_mark_declared_symbol( + proc_state, + normalized_name, + filename=filename, + line_number=lineno, + source_line=source_line, + ) + if declaration.external: + self._proc_scope_add_external_symbol(proc_state, lowered_name) + arg = self._proc_scope_get_symbol(proc_state, lowered_name) + if arg is None: + self._proc_scope_set_declared_local_type(proc_state, lowered_name, entity_declaration) continue + self._apply_declaration(arg, entity_declaration, shape) + + def _store_type_field_declaration(self, target, declaration: _Declaration, right: str) -> None: + """Append every entity in one declaration to a derived-type model. + + For example, ``integer, pointer :: ids(:) => null()`` creates one field + with its inline shape, pointer attribute, and symbolic initializer. + """ + for normalized_name, shape, initializer, entity_declaration in self._declaration_entities( + right, + declaration, + ): + field = FortranArgument(name=normalized_name) + self._apply_declaration(field, entity_declaration, shape) + if initializer is not None: + field.value = self._normalize_parameter_value(initializer) + field.symbolic_value = initializer + field.value_type = "expression" + target.fields.append(field) + + def _store_scope_variable_declaration( + self, + scope: _ParserScope, + target, + declaration: _Declaration, + right: str, + ) -> None: + """Append declaration entities to a module-like variable collection. + + For example, ``integer, parameter :: n = 4`` creates one parameter + variable, preserves ``4`` as its symbolic value, and records explicit + module visibility when the declaration supplies it. + """ + for normalized_name, shape, initializer, entity_declaration in self._declaration_entities( + right, + declaration, + ): var = FortranArgument(name=normalized_name) - self._apply(var, entity_meta, shape) - self._record_declaration_visibility(scope, target, var, entity_meta) - if initializer is not None and meta["parameter"]: + self._apply_declaration(var, entity_declaration, shape) + self._record_declaration_visibility(scope, target, var, entity_declaration) + if initializer is not None and declaration.parameter: var.value = self._normalize_parameter_value(initializer) var.symbolic_value = initializer var.value_type = "expression" target.variables.append(var) + def _declaration_entities( + self, + right: str, + declaration: _Declaration, + ) -> list[tuple[str, list[str], str | None, _Declaration]]: + """Return normalized entities paired with their effective declaration. + + ``names(3), label*8 = 'ready'`` produces two entries. Each entry owns + its name, inline shape, optional initializer, and a declaration copy + only when entity-local character length changes the shared statement + declaration. + """ + entities: list[tuple[str, list[str], str | None, _Declaration]] = [] + for entity in split_csv(right): + declared_entity, initializer = split_declaration_assignment(entity) + raw_name, shape = self._var(declared_entity) + if not raw_name: + continue + entity_declaration = self._entity_declaration(raw_name, declaration) + normalized_name = self._normalize_declared_name(raw_name, entity_declaration) + if normalized_name: + entities.append((normalized_name, shape, initializer, entity_declaration)) + return entities + @staticmethod - def _record_declaration_visibility(scope: _ParserScope, target, var: FortranArgument, meta: dict) -> None: + def _record_declaration_visibility( + scope: _ParserScope, + target, + var: FortranArgument, + declaration: _Declaration, + ) -> None: """Make declaration-level module visibility survive finalization.""" - visibility = meta.get("explicit_visibility") + visibility = declaration.explicit_visibility if scope.kind != "module" or visibility not in {"public", "private"}: return symbols = getattr(target, f"{visibility}_symbols") @@ -3927,64 +4055,60 @@ def _record_declaration_visibility(scope: _ParserScope, target, var: FortranArgu symbols.append(var.name) @staticmethod - def _entity_decl_meta(raw_name: str, meta: dict) -> dict: - """Copy character metadata when one entity supplies ``*length`` syntax. + def _entity_declaration(raw_name: str, declaration: _Declaration) -> _Declaration: + """Return the effective declaration for one entity spelling. Non-character entities and declarations without an entity-level star - return the original ``meta`` mapping unchanged. For ``character`` - entities, a copied mapping records the length as ``kind`` without - mutating sibling entities' declaration metadata. + return the shared record unchanged. For ``character`` entities such as + ``label*8``, a copied declaration records kind ``8`` without mutating + sibling entities from the same statement. """ - if meta["base_type"] != "character": - return meta + if declaration.base_type != "character": + return declaration match = re.search(r"\*\s*(\([^)]*\)|\*|[A-Za-z_]\w*|\d+)\s*$", raw_name) if match is None: - return meta + return declaration length = match.group(1).strip() if length.startswith("(") and length.endswith(")"): length = length[1:-1].strip() - entity_meta = dict(meta) - entity_meta["kind"] = length - entity_meta["character_length_syntax"] = True - return entity_meta + return replace( + declaration, + kind=length, + shape=list(declaration.shape), + character_length_syntax=True, + ) @staticmethod - def _new_decl_meta(base_type: str, kind: str | None) -> dict: - """Return default declaration metadata for one normalized base type.""" - return { - "base_type": base_type, - "kind": kind or "", - "rank": 0, - "shape": [], - "intent": None, - "reads_argument": None, - "writes_argument": None, - "optional": False, - "value": False, - "allocatable": False, - "pointer": False, - "target": False, - "contiguous": False, - "external": False, - "parameter": False, - "polymorphic": False, - "visibility": "public", - "explicit_visibility": None, - } + def _new_declaration(base_type: str, kind: str | None) -> _Declaration: + """Create the default typed record for one normalized type spelling. + + For example, ``_new_declaration("real", "rk")`` returns a scalar, + public, non-pointer declaration whose remaining attributes are false + or absent until the attribute list is applied. + """ + return _Declaration(base_type=base_type, kind=kind or "") @staticmethod - def _intrinsic_decl_meta(base_type: str, type_spec: str) -> dict: - """Normalize one intrinsic spelling while retaining target-only facts.""" + def _intrinsic_declaration(base_type: str, type_spec: str) -> _Declaration: + """Normalize an intrinsic type spelling into a typed declaration. + + ``double precision`` records its compiler kind expression, while + ``character(len=8)`` retains that the parsed kind text denotes a + character length rather than a storage kind. + """ if base_type in {"double precision", "double complex"}: normalized = "real" if base_type == "double precision" else "complex" - meta = FortranParser._new_decl_meta(normalized, None) - meta["target_kind_expression"] = "kind(1.0d0)" - return meta + declaration = FortranParser._new_declaration(normalized, None) + declaration.target_kind_expression = "kind(1.0d0)" + return declaration - meta = FortranParser._new_decl_meta(base_type, extract_kind_from_type_spec(base_type, type_spec)) + declaration = FortranParser._new_declaration( + base_type, + extract_kind_from_type_spec(base_type, type_spec), + ) if base_type == "character" and type_spec and re.search(r"\bkind\s*=", type_spec, re.IGNORECASE) is None: - meta["character_length_syntax"] = True - return meta + declaration.character_length_syntax = True + return declaration @staticmethod def _apply_type_spelling_metadata(var: FortranVariable, spelling: str) -> None: @@ -4011,44 +4135,41 @@ def _apply_type_spelling_metadata(var: FortranVariable, spelling: str) -> None: var._character_length_syntax = True @staticmethod - def _apply_decl_attrs(meta: dict, attrs: list[str], *, include_argument_access: bool = False) -> None: - """Merge declaration attributes into normalized metadata.""" - for a in attrs: - la = a.lower() - if include_argument_access and la.startswith("intent") and "(" in la and ")" in la: - content = re.sub(r"\s+", "", la.split("(", 1)[1].rsplit(")", 1)[0]) - meta["intent"] = content - meta["reads_argument"] = content.startswith("in") - meta["writes_argument"] = content.endswith("out") - elif la == "optional": - meta["optional"] = True - elif la == "value": - meta["value"] = True - elif la == "allocatable": - meta["allocatable"] = True - elif la == "pointer": - meta["pointer"] = True - elif la == "target": - meta["target"] = True - elif la == "contiguous": - meta["contiguous"] = True - elif la == "external": - meta["external"] = True - elif la == "parameter": - meta["parameter"] = True - elif la in {"public", "private"}: - meta["visibility"] = la - meta["explicit_visibility"] = la - elif la.startswith("dimension") and "(" in a and ")" in a: - shape = split_csv(a[a.find("(") + 1 : a.rfind(")")]) - meta["shape"] = shape - meta["rank"] = len(shape) + def _apply_declaration_attributes( + declaration: _Declaration, + attributes: list[str], + *, + include_argument_access: bool = False, + ) -> None: + """Normalize source attributes into one typed declaration record. + + Simple flags such as ``pointer`` and ``optional`` map directly to + Boolean fields. ``intent(inout)`` additionally records read/write + access, while ``dimension(0:n)`` records shape and rank. + """ + for attribute in attributes: + lowered = attribute.lower() + flag_field = _DECLARATION_FLAG_FIELDS.get(lowered) + if flag_field is not None: + setattr(declaration, flag_field, True) + elif include_argument_access and lowered.startswith("intent") and "(" in lowered and ")" in lowered: + content = re.sub(r"\s+", "", lowered.split("(", 1)[1].rsplit(")", 1)[0]) + declaration.intent = content + declaration.reads_argument = content.startswith("in") + declaration.writes_argument = content.endswith("out") + elif lowered in {"public", "private"}: + declaration.visibility = lowered + declaration.explicit_visibility = lowered + elif lowered.startswith("dimension") and "(" in attribute and ")" in attribute: + shape = split_csv(attribute[attribute.find("(") + 1 : attribute.rfind(")")]) + declaration.shape = shape + declaration.rank = len(shape) @staticmethod - def _normalize_declared_name(name: str, meta: dict) -> str: + def _normalize_declared_name(name: str, declaration: _Declaration) -> str: """Strip legacy entity-local spelling from a declared symbol name.""" normalized_name = re.sub(r"^\*\s*[0-9]+\s*", "", name).strip() - if meta["base_type"] == "character" and "*" in normalized_name: + if declaration.base_type == "character" and "*" in normalized_name: # Legacy CHARACTER declarations may carry entity-local length # specifiers (e.g. NAME*(*) or SUBNAM*6). Strip the `*len` # suffix so symbol lookup matches procedure arguments. @@ -4077,40 +4198,40 @@ def _var(entry: str): return e, [] @staticmethod - def _apply(arg: FortranArgument, meta: dict, shape: list[str]): - """Apply normalized declaration metadata to an argument-like model.""" - arg.base_type = meta["base_type"] - arg.kind = meta["kind"] or "" - arg.intent = meta["intent"] - arg.reads_argument = meta["reads_argument"] - arg.writes_argument = meta["writes_argument"] - arg.optional = meta["optional"] - arg.pass_by_value = meta["value"] - arg.allocatable = meta["allocatable"] - arg.pointer = meta["pointer"] - arg.target = meta["target"] - arg.contiguous = meta["contiguous"] - arg.is_parameter = meta["parameter"] - arg.visibility = meta["visibility"] - FortranParser._apply_internal_type_metadata(arg, meta) + def _apply_declaration(arg: FortranArgument, declaration: _Declaration, shape: list[str]) -> None: + """Copy one typed declaration onto an argument-like parser model.""" + arg.base_type = declaration.base_type + arg.kind = declaration.kind + arg.intent = declaration.intent + arg.reads_argument = declaration.reads_argument + arg.writes_argument = declaration.writes_argument + arg.optional = declaration.optional + arg.pass_by_value = declaration.value + arg.allocatable = declaration.allocatable + arg.pointer = declaration.pointer + arg.target = declaration.target + arg.contiguous = declaration.contiguous + arg.is_parameter = declaration.parameter + arg.visibility = declaration.visibility + FortranParser._apply_internal_type_metadata(arg, declaration) if shape: arg.shape = shape arg.rank = len(shape) else: - arg.shape = list(meta["shape"]) - arg.rank = meta["rank"] + arg.shape = list(declaration.shape) + arg.rank = declaration.rank arg.lbound, arg.ubound = FortranParser._extract_bounds(arg.shape) @staticmethod - def _apply_internal_type_metadata(arg: FortranVariable, meta: dict) -> None: - """Apply compiler-relevant facts that stay outside serialized models.""" - if meta.get("target_kind_expression"): - arg._target_kind_expression = meta["target_kind_expression"] - if meta.get("character_length_syntax"): + def _apply_internal_type_metadata(arg: FortranVariable, declaration: _Declaration) -> None: + """Copy compiler-relevant declaration facts outside serialized fields.""" + if declaration.target_kind_expression: + arg._target_kind_expression = declaration.target_kind_expression + if declaration.character_length_syntax: arg._character_length_syntax = True - if meta.get("declared_storage_bits") is not None: - arg._declared_storage_bits = int(meta["declared_storage_bits"]) - if meta.get("polymorphic"): + if declaration.declared_storage_bits is not None: + arg._declared_storage_bits = declaration.declared_storage_bits + if declaration.polymorphic: arg._fortran_polymorphic = True @staticmethod @@ -4503,8 +4624,8 @@ def _reconcile_procedure_local_declarations( inferred = state.declared_local_types.get(arg.name.lower()) if not inferred: continue - arg.base_type = inferred.get("base_type", arg.base_type) - arg.kind = inferred.get("kind", arg.kind) + arg.base_type = inferred.base_type + arg.kind = inferred.kind self._apply_internal_type_metadata(arg, inferred) def _validate_procedure_implicit_none( @@ -4548,19 +4669,21 @@ def _materialize_procedure_parameters( # fixed-form sources; keep them available for compile-time # resolution but do not expose them as parsed procedure variables. continue - local_decl = state.declared_local_types.get(name.lower(), {}) + local_declaration = state.declared_local_types.get(name.lower()) var = FortranVariable( name=name.lower(), - base_type=local_decl.get( - "base_type", - state.implicit_typed_symbols.get(name.lower(), "unknown"), + base_type=( + local_declaration.base_type + if local_declaration is not None + else state.implicit_typed_symbols.get(name.lower(), "unknown") ), - kind=local_decl.get("kind"), + kind=local_declaration.kind if local_declaration is not None else None, value=self._normalize_parameter_value(value), value_type="expression", is_parameter=True, ) - self._apply_internal_type_metadata(var, local_decl) + if local_declaration is not None: + self._apply_internal_type_metadata(var, local_declaration) var.symbolic_value = value sig.variables[name.lower()] = var diff --git a/tests/fortran/data_types/parsing/test_declarations_and_shapes.py b/tests/fortran/data_types/parsing/test_declarations_and_shapes.py index 73fafe3ef..6b42d8bfd 100644 --- a/tests/fortran/data_types/parsing/test_declarations_and_shapes.py +++ b/tests/fortran/data_types/parsing/test_declarations_and_shapes.py @@ -102,10 +102,10 @@ def test_legacy_character_star_kind_sets_length_metadata_when_character_prefix_i parsed = FortranParser()._parse_declaration_left("character*8", parse_character_star=False) assert parsed is not None - metadata, attributes = parsed - assert metadata["base_type"] == "character" - assert metadata["kind"] == "8" - assert metadata["character_length_syntax"] is True + declaration, attributes = parsed + assert declaration.base_type == "character" + assert declaration.kind == "8" + assert declaration.character_length_syntax is True assert attributes == [] diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py index c92d4d2c9..5ca31bbd0 100644 --- a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py @@ -10,6 +10,7 @@ FortranProcedureSignature, ) from prik.parsers.fortran.parser import ( + _Declaration, FortranParser, _ParserScope, parse_fortran_project, @@ -88,16 +89,19 @@ def test_compile_time_resolution_helpers_preserve_kind_shape_values_and_literal_ assert parser._infer_implicit_base_type("alpha") == "real" -def test_declaration_push_preserves_module_variables_parameters_and_bounds(): +def test_declaration_storage_preserves_module_variables_parameters_visibility_and_bounds(): parser = FortranParser() module = FortranModule("owner_mod") scope = _ParserScope(kind="module", name=module.name, model=module, module_owner=module.name) - meta = parser._new_decl_meta("real", "rk") - meta.update({"parameter": True, "shape": ["0:n"], "rank": 1}) + declaration = parser._new_declaration("real", "rk") + parser._apply_declaration_attributes( + declaration, + ["parameter", "private", "dimension(0:n)"], + ) - parser._helper_push_declaration_to_scope( + parser._store_declaration( scope, - meta=meta, + declaration=declaration, right="weights = 1.0_rk", role="module_variable", filename="declarations.f90", @@ -112,9 +116,10 @@ def test_declaration_push_preserves_module_variables_parameters_and_bounds(): assert module.variables[0].value == "1" assert module.variables[0].symbolic_value == "1.0_rk" assert module.variables[0].value_type == "expression" + assert module.private_symbols == ["weights"] -def test_procedure_declaration_push_updates_dummy_or_records_local_type_and_duplicate_metadata(): +def test_procedure_declaration_storage_updates_dummy_or_records_local_type_and_duplicate_metadata(): parser = FortranParser() signature = FortranProcedureSignature( "apply", @@ -126,22 +131,22 @@ def test_procedure_declaration_push_updates_dummy_or_records_local_type_and_dupl symbols={argument.name.lower(): argument for argument in signature.arguments}, ) scope = _ParserScope(kind="procedure", name=signature.name, model=signature, state=state) - proc_meta = parser._new_decl_meta("procedure", "callback_iface") - proc_meta["external"] = True + procedure_declaration = parser._new_declaration("procedure", "callback_iface") + procedure_declaration.external = True - parser._helper_push_declaration_to_scope( + parser._store_declaration( scope, - meta=proc_meta, + declaration=procedure_declaration, right="callback", role="procedure_symbol", filename="declarations.f90", lineno=9, source_line="procedure(callback_iface), external :: callback", ) - local_meta = parser._new_decl_meta("real", "rk") - parser._helper_push_declaration_to_scope( + local_declaration = parser._new_declaration("real", "rk") + parser._store_declaration( scope, - meta=local_meta, + declaration=local_declaration, right="scratch", role="procedure_symbol", filename="declarations.f90", @@ -152,12 +157,12 @@ def test_procedure_declaration_push_updates_dummy_or_records_local_type_and_dupl assert signature.arguments[0].base_type == "procedure" assert signature.arguments[0].kind == "callback_iface" assert state.external_symbols == {"callback"} - assert state.declared_local_types == {"scratch": {"base_type": "real", "kind": "rk"}} + assert state.declared_local_types == {"scratch": _Declaration(base_type="real", kind="rk")} with pytest.raises(FortranParseError) as error: - parser._helper_push_declaration_to_scope( + parser._store_declaration( scope, - meta=parser._new_decl_meta("integer", ""), + declaration=parser._new_declaration("integer", ""), right="callback", role="procedure_symbol", filename="declarations.f90", @@ -172,6 +177,52 @@ def test_procedure_declaration_push_updates_dummy_or_records_local_type_and_dupl assert error.value.code == "PARSE_DUPLICATE_DECLARATION" +def test_entity_character_length_uses_an_independent_typed_declaration(): + parser = FortranParser() + declaration = parser._new_declaration("character", "default_len") + + entity_declaration = parser._entity_declaration("label*(name_len)", declaration) + + assert entity_declaration is not declaration + assert entity_declaration.kind == "name_len" + assert entity_declaration.character_length_syntax is True + assert declaration.kind == "default_len" + assert declaration.character_length_syntax is False + + +def test_procedure_finalization_consumes_typed_local_declarations(): + parser = FortranParser() + signature = FortranProcedureSignature( + "consume", + "subroutine", + arguments=[FortranArgument("value")], + ) + state = parser._new_procedure_scope_state( + signature, + symbols={"value": signature.arguments[0]}, + ) + state.declared_local_types["value"] = _Declaration( + base_type="real", + kind="rk", + declared_storage_bits=64, + ) + state.declared_local_types["n"] = _Declaration( + base_type="integer", + kind="i4", + target_kind_expression="kind(1)", + ) + + parser._reconcile_procedure_local_declarations(signature, state) + parser._materialize_procedure_parameters(signature, state, {"n": "4"}) + + assert signature.arguments[0].base_type == "real" + assert signature.arguments[0].kind == "rk" + assert signature.arguments[0].declared_storage_bits == 64 + assert signature.variables["n"].base_type == "integer" + assert signature.variables["n"].kind == "i4" + assert signature.variables["n"].target_kind_expression == "kind(1)" + + def test_procedure_parameter_lines_preserve_local_parameter_state_and_duplicate_metadata(): parser = FortranParser() signature = FortranProcedureSignature("shape", "subroutine", arguments=[FortranArgument("values")]) diff --git a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py index 36dd20169..8642efd23 100644 --- a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py @@ -314,16 +314,16 @@ def test_finalize_proc_duplicate_argument_diagnostic_preserves_header_metadata() assert error.value.code == "PARSE_DUPLICATE_ARGUMENT" -def test_declaration_push_preserves_type_field_metadata_and_duplicate_field_diagnostic(): +def test_declaration_storage_preserves_type_field_metadata_and_duplicate_field_diagnostic(): parser = FortranParser() dtype = FortranDerivedType("state_t") scope = _ParserScope(kind="derived_type", name=dtype.name, model=dtype) - meta = parser._new_decl_meta("integer", "i4") - meta.update({"pointer": True, "shape": [":"], "rank": 1}) + declaration = parser._new_declaration("integer", "i4") + parser._apply_declaration_attributes(declaration, ["pointer", "dimension(:)"]) - parser._helper_push_declaration_to_scope( + parser._store_declaration( scope, - meta=meta, + declaration=declaration, right="ids, IDs", role="type_field", filename="declarations.f90", From 0447d29497118ac6e6484a356598a1e7d87f4c97 Mon Sep 17 00:00:00 2001 From: said Date: Tue, 11 Aug 2026 23:30:14 +0100 Subject: [PATCH 10/22] unify compile-time resolutio og symbols --- CHANGELOG.md | 5 + docs/developer/fortran-parser-reference.md | 20 +- prik/cli.py | 32 +- prik/parsers/fortran/parser.py | 620 ++++++++++++------ .../pipeline/test_stage_dispatch.py | 32 + .../parsing/test_project_scope_models.py | 42 ++ .../test_declaration_and_scope_regressions.py | 22 +- 7 files changed, 529 insertions(+), 244 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a17e03eaf..dca1d67e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,11 @@ release tags add a leading `v` to the package version. - Made directory project parsing read and parse each discovered Fortran file once before dependency ordering and project assembly. +### Fixed + +- Unified source-level compile-time resolution across project and CLI parsing + so imported and host-associated kind facts also reach derived-type fields. + ## 0.2.1 — 2026-08-11 ### Added diff --git a/docs/developer/fortran-parser-reference.md b/docs/developer/fortran-parser-reference.md index a8b571a73..434f931cc 100644 --- a/docs/developer/fortran-parser-reference.md +++ b/docs/developer/fortran-parser-reference.md @@ -268,6 +268,21 @@ compile-time folding. If an initializer cannot be evaluated safely, such as original initializer for validation, debugging, downstream diagnostics, and JSON consumers. +Source-level compile-time resolution consumes those parsed parameter models; +it does not rescan stored source text for a second parameter representation. +The parser first builds one `_CompileTimeSymbols` table whose module entries +have already resolved transitive aliases. For example, module parameters +`word = 4` and `rk = word * 2` produce `{"word": "4", "rk": "8"}`; +`use kinds, only: wp => rk` then exposes `{"wp": "8"}` to the consuming +scope. File parsing and project/CLI parsing use the same table construction and +the same procedure, module-like-variable, and derived-field consumers. + +This resolution is limited to facts visible from source. Compiler-dependent +expressions such as `selected_real_kind(12)` remain symbolic for the later +probe/semantic stages. Imported module expressions in procedure argument +shapes also remain symbolic at file and project boundaries so policy completion +can retain their native spelling and role dependencies. + Procedure-local parameters may be folded into argument shapes during procedure finalization. Module-level and `use`-associated parameters used in procedure argument shapes are kept symbolic in the signature (`x(n)` remains `["n"]`) @@ -323,8 +338,9 @@ The recursive parsing pattern is: 5. Validate sibling names and scope-local duplicate declarations. 6. Finalize procedure arguments/results after local declarations and parameters are known. -7. Resolve cross-file or imported compile-time facts only at project or - semantic-conversion boundaries. +7. Resolve source-visible cross-file or imported compile-time aliases through + one project symbol table, while leaving compiler-dependent facts for + semantic conversion and target probing. When adding another parser, keep these test layers separate: diff --git a/prik/cli.py b/prik/cli.py index 09d80d0c5..7aa99a06b 100644 --- a/prik/cli.py +++ b/prik/cli.py @@ -489,37 +489,25 @@ def _parse_fortran_source_files( paths: list[Path], preprocessing: PreprocessingConfig, ): - """Parse Fortran sources and resolve cross-file module parameters.""" + """Parse Fortran sources and apply the parser's shared project resolution. + + Each path is preprocessed and parsed once while retaining its path/model + pair. The completed models are then passed together to the parser's project + compile-time coordinator. For example, a kind parameter from the first file + can resolve a procedure or derived field in the second file without the CLI + owning a second resolution algorithm. The ordered ``(path, file)`` pairs + are returned for stage reporting. + """ parser = FortranParser() parsed_files = [] for path in paths: code, _preprocessing_recipe = _fortran_source_for_path(path, preprocessing) parsed_files.append((path, parser.parse_file(code, filename=str(path)))) - _resolve_fortran_project_parameters(parser, [parsed for _path, parsed in parsed_files]) + parser._resolve_project_compile_time_facts([parsed for _path, parsed in parsed_files]) return parsed_files -def _resolve_fortran_project_parameters(parser: FortranParser, parsed_files) -> None: - """Apply project-wide parameter facts without enforcing global symbols.""" - module_params = parser._helper_project_module_symbols(parsed_files) - - seen_procedures: set[int] = set() - for parsed_file in parsed_files: - for proc in parser._helper_project_file_procedures(parsed_file): - if id(proc) not in seen_procedures: - parser._resolve_signature_kinds(proc, module_params, resolve_shapes=False) - seen_procedures.add(id(proc)) - for module in parsed_file.modules: - parser._resolve_module_variable_kinds(module, module_params) - for submodule in parsed_file.submodules: - parser._resolve_module_variable_kinds(submodule, module_params) - for program in parsed_file.programs: - parser._resolve_module_variable_kinds(program, module_params) - for block_data in parsed_file.block_data_units: - parser._resolve_module_variable_kinds(block_data, module_params) - - def _parse_c_semantic_sources(context: _SemanticPipelineContext) -> _ParsedSemanticSources: if not context.source_paths: return _ParsedSemanticSources(context.source_paths, None) diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index 2f396c4c1..a2b18bbcf 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -280,6 +280,42 @@ class EnumUnit(SourceUnit): "parameter": "parameter", } ) +_EMPTY_COMPILE_TIME_SYMBOLS: Mapping[str, str] = MappingProxyType({}) + + +@dataclass(frozen=True) +class _CompileTimeSymbols: + """Store resolved source-level symbols grouped by native module. + + ``modules`` receives expressions whose aliases have already been folded. + For example, ``{"kinds": {"word": "4", "rk": "8"}}`` records the + resolved result of ``rk = word * 2``. :meth:`in_module` then returns the + read-only ``{"word": "4", "rk": "8"}`` mapping for a consumer in + ``kinds``. Unknown or absent module names produce an empty mapping. + """ + + modules: Mapping[str, Mapping[str, str]] + + def __post_init__(self) -> None: + """Normalize names and freeze nested mappings after construction.""" + normalized = { + module_name.casefold(): MappingProxyType( + {symbol.casefold(): str(value) for symbol, value in symbols.items()} + ) + for module_name, symbols in self.modules.items() + } + object.__setattr__(self, "modules", MappingProxyType(normalized)) + + def in_module(self, module_name: str | None) -> Mapping[str, str]: + """Return resolved symbols owned or reexported by ``module_name``. + + For example, ``in_module("KINDS")`` finds the normalized ``kinds`` + entry. ``in_module(None)`` and an unknown module return the shared empty + read-only mapping rather than creating mutable state. + """ + if module_name is None: + return _EMPTY_COMPILE_TIME_SYMBOLS + return self.modules.get(module_name.casefold(), _EMPTY_COMPILE_TIME_SYMBOLS) @dataclass @@ -1214,7 +1250,7 @@ class _ParsedFileUnits: class _CompileTimeResolver: """Resolve compile-time expressions against one immutable symbol snapshot.""" - def __init__(self, symbols: dict[str, str]): + def __init__(self, symbols: Mapping[str, str]): """Normalize symbol names and initialize the expression cache.""" self.symbols = {name.lower(): str(value) for name, value in symbols.items()} self.cache: dict[tuple[str, bool], str] = {} @@ -1343,7 +1379,7 @@ def parse_file( units = self._helper_parse_file_units(top_units, root_scope, filename) self._helper_resolve_file_types(units) interfaces = self._helper_attach_file_interfaces(lines, filename, units) - self._helper_resolve_file_kinds(lines, filename, units) + self._resolve_file_compile_time_facts(units) # Stage 3: assemble the stable file model and its source metadata. return self._helper_build_fortran_file(code, filename, encoding, units, interfaces) @@ -1895,30 +1931,38 @@ def _helper_attach_file_interfaces( ] return [iface for iface in interfaces if iface.module is None] - def _helper_resolve_file_kinds( - self, - lines: _PreprocessedLines, - filename: str | None, - units: _ParsedFileUnits, - ) -> None: - """Resolve variable and procedure kind references within one file.""" + def _resolve_file_compile_time_facts(self, units: _ParsedFileUnits) -> None: + """Apply source-visible compile-time symbols within one parsed file. + + ``units`` receives the models already constructed from one source file. + Their parameter variables build a resolved symbol table; that table is + then applied to procedure kinds, module-like values/shapes, and derived + fields. For example, module parameters ``word = 4`` and + ``rk = word * 2`` resolve ``real(rk)`` to kind ``8`` without rescanning + the source text. The method mutates the supplied parser models and + returns nothing. + """ variable_units = [*units.modules, *units.submodules, *units.programs, *units.block_data_units] - module_params = self._collect_module_parameters(lines, filename) + symbols = self._build_compile_time_symbols(units.modules, units.submodules) if any( var.kind or var.value is not None or var.symbolic_value is not None for unit in variable_units for var in getattr(unit, "variables", []) ): for unit in variable_units: - self._resolve_module_variable_kinds(unit, module_params) + self._resolve_module_like_compile_time_facts(unit, symbols) for procedure in self._helper_file_procedures(units): - self._resolve_signature_kinds(procedure, module_params, resolve_shapes=False) + self._resolve_procedure_compile_time_facts( + procedure, + symbols, + resolve_shapes=False, + ) derived_types = [ *units.derived_types, *(derived_type for module in (*units.modules, *units.submodules) for derived_type in module.derived_types), ] for derived_type in derived_types: - self._resolve_derived_type_field_kinds(derived_type, module_params) + self._resolve_derived_type_compile_time_facts(derived_type, symbols) @staticmethod def _helper_file_procedures(units: _ParsedFileUnits): @@ -2063,21 +2107,34 @@ def _assemble_project(self, parsed_files: list[FortranFile]) -> FortranProject: is preserved in ``project.files`` while their modules, procedures, types, interfaces, and dependency facts enter project registries. """ - self._helper_resolve_project_kinds(parsed_files) + self._resolve_project_compile_time_facts(parsed_files) project = FortranProject(files=parsed_files) for parsed_file in parsed_files: self._helper_index_project_file(project, parsed_file) return project - def _helper_resolve_project_kinds(self, parsed_files: list[FortranFile]) -> None: - """Resolve project procedure and module-variable kinds from shared symbols.""" - module_params = self._helper_project_module_symbols(parsed_files) + def _resolve_project_compile_time_facts(self, parsed_files: list[FortranFile]) -> None: + """Apply one resolved source-symbol table across parsed project files. + + ``parsed_files`` contains models that were already parsed separately. + The method combines their module and submodule parameters, imports, + reexports, and host association, then updates every procedure, + module-like variable, and derived field in place. For example, a field + declared as ``real(wp)`` in a module importing ``wp => rk`` from a + module where ``rk = 8`` becomes kind ``8``. No file is read or parsed + again, and the method returns nothing. + """ + symbols = self._build_project_compile_time_symbols(parsed_files) seen_procedures: set[int] = set() for parsed_file in parsed_files: for procedure in self._helper_project_file_procedures(parsed_file): if id(procedure) not in seen_procedures: - self._resolve_signature_kinds(procedure, module_params, resolve_shapes=False) + self._resolve_procedure_compile_time_facts( + procedure, + symbols, + resolve_shapes=False, + ) seen_procedures.add(id(procedure)) for owner in ( *parsed_file.modules, @@ -2085,62 +2142,35 @@ def _helper_resolve_project_kinds(self, parsed_files: list[FortranFile]) -> None *parsed_file.programs, *parsed_file.block_data_units, ): - self._resolve_module_variable_kinds(owner, module_params) - for derived_type in parsed_file.derived_types: - self._resolve_derived_type_field_kinds(derived_type, module_params) - for module in parsed_file.modules: - for derived_type in module.derived_types: - self._resolve_derived_type_field_kinds(derived_type, module_params) - - def _helper_project_module_symbols(self, parsed_files: list[FortranFile]) -> dict[str, dict[str, str]]: - """Resolve module symbols and submodule host associations.""" - module_params: dict[str, dict[str, str]] = {} - owners: dict[str, FortranModule | FortranSubmodule] = {} - for parsed_file in parsed_files: - if parsed_file.source is not None: - module_params.update(self._collect_module_parameters(parsed_file.source, parsed_file.filename)) - owners.update((module.name.lower(), module) for module in parsed_file.modules) - owners.update((submodule.name.lower(), submodule) for submodule in parsed_file.submodules) - - resolved = self._resolve_module_parameter_values(module_params) - for _ in range(len(owners) + 1): - changed = False - for owner_name, owner in owners.items(): - symbols = dict(resolved.get(owner_name, {})) - if isinstance(owner, FortranSubmodule): - if owner.ancestor: - symbols.update(resolved.get(owner.ancestor.lower(), {})) - symbols.update(resolved.get(owner.parent.lower(), {})) - symbols.update(self._helper_owner_imported_symbols(owner, resolved)) - updated = self._resolve_module_parameter_values({owner_name: symbols})[owner_name] - if updated != resolved.get(owner_name, {}): - resolved[owner_name] = updated - changed = True - if not changed: - break - return resolved + self._resolve_module_like_compile_time_facts(owner, symbols) + for derived_type in self._project_file_derived_types(parsed_file): + self._resolve_derived_type_compile_time_facts(derived_type, symbols) + + def _build_project_compile_time_symbols(self, parsed_files: list[FortranFile]) -> _CompileTimeSymbols: + """Build resolved module symbols from existing project models. + + Each ``FortranFile`` contributes its parsed modules and submodules; the + source strings are deliberately ignored. For example, separate files + defining module ``kinds`` and a submodule of ``api`` produce one table + containing the module parameters, imports, and inherited host symbols + visible to both owners. The returned `_CompileTimeSymbols` is read-only. + """ + modules = [module for parsed_file in parsed_files for module in parsed_file.modules] + submodules = [submodule for parsed_file in parsed_files for submodule in parsed_file.submodules] + return self._build_compile_time_symbols(modules, submodules) @staticmethod - def _helper_owner_imported_symbols( - owner: FortranModule | FortranSubmodule, - resolved_modules: dict[str, dict[str, str]], - ) -> dict[str, str]: - """Return explicit compile-time symbols imported into one owner.""" - imported: dict[str, str] = {} - for dependency, mappings in owner.uses.items(): - dependency_name = dependency.lower() - dependency_symbols = resolved_modules.get(dependency_name, {}) - if not mappings: - imported.update(dependency_symbols) - continue - for mapping in mappings: - source_name = mapping.source.lower() - expression = dependency_symbols.get(source_name) - if expression is None and dependency_name in _INTRINSIC_COMPILE_TIME_MODULES: - expression = mapping.source - if expression is not None: - imported[mapping.local_name.lower()] = expression - return imported + def _project_file_derived_types(parsed_file: FortranFile): + """Yield every derived type owned by one parsed project file. + + For example, a file-level type followed by types inside a module and a + submodule is yielded in that same ownership order. The iterator lets + project resolution cover all type owners without constructing another + registry or duplicating nested loops. + """ + yield from parsed_file.derived_types + for module in (*parsed_file.modules, *parsed_file.submodules): + yield from module.derived_types @staticmethod def _helper_project_file_procedures(parsed_file: FortranFile): @@ -4936,169 +4966,331 @@ def _validate_no_duplicate_arg_names( ) seen.add(key) - def _collect_module_parameters(self, code: _SourceOrLines, filename: str | None) -> dict[str, dict[str, str]]: - """Collect module specification-part parameter expressions by module.""" - lines = self._preprocessed_lines(code, filename) - current_module = None - in_module_spec_part = False - output: dict[str, dict[str, str]] = {} - for line, _lineno, _source_line in lines: - s = line.strip() - if not s: - continue - lowered = s.lower() - if lowered.startswith("module ") and not re.match(r"^module\s+(procedure|subroutine|function)\b", lowered): - current_module = s.split()[1].lower() - in_module_spec_part = True - output.setdefault(current_module, {}) - continue - if lowered.startswith("contains") and current_module is not None: - in_module_spec_part = False - continue - if lowered.startswith("end module"): - current_module = None - in_module_spec_part = False - continue - if current_module is None or not in_module_spec_part: - continue - if lowered == "contains": - in_module_spec_part = False - continue - if not in_module_spec_part: - continue - pm = _REGEX["typed_parameter"].match(s) - if not pm: - continue - for assign in split_csv(pm.group("body")): - if "=" not in assign: + @staticmethod + def _module_parameter_expressions( + owners: Sequence[FortranModule | FortranSubmodule], + ) -> dict[str, dict[str, str]]: + """Collect parameter expressions from parsed module-like models. + + ``owners`` receives modules or submodules whose variables have already + been parsed. For example, variables representing ``word = 4`` and + ``rk = word * 2`` produce + ``{"module_name": {"word": "4", "rk": "word * 2"}}``. Symbolic + initializers are preferred so later resolution retains the original + dependency expression. No source text is read, and a mutable raw table + is returned for the resolution builder. + """ + expressions: dict[str, dict[str, str]] = {} + for owner in owners: + owner_expressions: dict[str, str] = {} + for variable in owner.variables: + if not variable.is_parameter: continue - k, v = [x.strip() for x in assign.split("=", 1)] - output[current_module][k.lower()] = v - return output + expression = variable.symbolic_value if variable.symbolic_value is not None else variable.value + if expression is not None: + owner_expressions[variable.name.casefold()] = expression + expressions[owner.name.casefold()] = owner_expressions + return expressions @staticmethod - def _resolve_module_parameter_values(module_params: dict[str, dict[str, str]]) -> dict[str, dict[str, str]]: - """Resolve transitive parameter expressions inside each module.""" + def _resolve_compile_time_symbols( + module_expressions: Mapping[str, Mapping[str, str]], + ) -> _CompileTimeSymbols: + """Resolve aliases and integer expressions inside each module entry. + + The input is a raw table such as + ``{"kinds": {"word": "4", "rk": "word * 2"}}``. Each module gets + its own `_CompileTimeResolver`, producing a read-only + `_CompileTimeSymbols` where ``rk`` is ``"8"``. Unsupported + compiler-dependent calls remain symbolic rather than being guessed. + """ resolved: dict[str, dict[str, str]] = {} - for module_name, params in module_params.items(): - resolver = _CompileTimeResolver(params) - resolved[module_name.lower()] = { - name.lower(): resolver.resolve(FortranParser._resolve_symbol_reference(value, resolver.symbols)) - for name, value in params.items() + for module_name, expressions in module_expressions.items(): + normalized = {name.casefold(): str(value) for name, value in expressions.items()} + resolver = _CompileTimeResolver(normalized) + resolved[module_name.casefold()] = { + name: resolver.resolve(FortranParser._resolve_symbol_reference(value, resolver.symbols)) + for name, value in normalized.items() } - return resolved + return _CompileTimeSymbols(resolved) + + def _build_compile_time_symbols( + self, + modules: Sequence[FortranModule], + submodules: Sequence[FortranSubmodule], + ) -> _CompileTimeSymbols: + """Build the resolved symbol table shared by one file or project. + + ``modules`` and ``submodules`` are already-parsed owners. Their local + parameters form the initial table; repeated passes then add explicit + imports, intrinsic aliases, and submodule parent/ancestor symbols until + no entry changes. For example, ``wp => rk`` reexports the resolved + value of ``rk``, while a child submodule inherits its parent's symbols. + The returned table is immutable and already transitively resolved. + """ + owners: dict[str, FortranModule | FortranSubmodule] = { + owner.name.casefold(): owner for owner in (*modules, *submodules) + } + raw_expressions = self._module_parameter_expressions([*modules, *submodules]) + initial = self._resolve_compile_time_symbols(raw_expressions) + resolved = {module_name: dict(symbols) for module_name, symbols in initial.modules.items()} + + for _ in range(len(owners) + 1): + changed = False + for owner_name, owner in owners.items(): + active = _CompileTimeSymbols(resolved) + owner_symbols = dict(active.in_module(owner_name)) + if isinstance(owner, FortranSubmodule): + if owner.ancestor: + owner_symbols.update(active.in_module(owner.ancestor)) + owner_symbols.update(active.in_module(owner.parent)) + owner_symbols.update( + self._imported_compile_time_symbols( + owner.uses, + active, + include_intrinsic_aliases=True, + ) + ) + updated = dict(self._resolve_compile_time_symbols({owner_name: owner_symbols}).in_module(owner_name)) + if updated != resolved.get(owner_name, {}): + resolved[owner_name] = updated + changed = True + if not changed: + break + return _CompileTimeSymbols(resolved) @staticmethod - def _resolve_signature_kinds( - sig: FortranProcedureSignature, - module_params: dict[str, dict[str, str]], + def _imported_compile_time_symbols( + uses: Mapping[str, list[FortranUseMapping]], + symbols: _CompileTimeSymbols, *, - resolve_shapes: bool = True, - ) -> None: - """Resolve procedure kind and optional shape expressions from scope facts.""" - module_params = FortranParser._resolve_module_parameter_values(module_params) - symbol_to_value: dict[str, str] = {} - if sig.module: - symbol_to_value.update(module_params.get(sig.module.lower(), {})) - for mod, mappings in sig.uses.items(): - params = module_params.get(mod.lower(), {}) - if not params: - continue + include_intrinsic_aliases: bool, + ) -> dict[str, str]: + """Return symbols introduced by one scope's ``use`` statements. + + ``uses`` supplies wildcard or explicit import mappings and ``symbols`` + supplies already-resolved dependency modules. For example, + ``use kinds, only: wp => rk`` with ``kinds.rk == "8"`` returns + ``{"wp": "8"}``. Module-table construction enables + ``include_intrinsic_aliases`` so ``rk => real64`` can be reexported even + when the intrinsic module has no parsed model; ordinary procedure scope + lookup leaves that target-dependent spelling untouched. + """ + imported: dict[str, str] = {} + for dependency, mappings in uses.items(): + dependency_name = dependency.casefold() + dependency_symbols = symbols.in_module(dependency_name) if not mappings: - symbol_to_value.update(params) + imported.update(dependency_symbols) continue for mapping in mappings: - source = mapping.source.lower() - local = mapping.local_name.lower() - if source in params: - symbol_to_value[local] = params[source] - for name, var in sig.variables.items(): - if var.value is not None: - symbol_to_value.setdefault(name.lower(), var.value) - elif var.symbolic_value is not None: - symbol_to_value.setdefault(name.lower(), var.symbolic_value) - variable_base_types = {name.lower(): var.base_type for name, var in sig.variables.items()} - variable_symbolic_values = { - name.lower(): var.symbolic_value or var.value for name, var in sig.variables.items() - } - resolved_variables = FortranParser._resolve_variables( - symbol_to_value, variable_base_types, variable_symbolic_values + source_name = mapping.source.casefold() + expression = dependency_symbols.get(source_name) + if ( + expression is None + and include_intrinsic_aliases + and dependency_name in _INTRINSIC_COMPILE_TIME_MODULES + ): + expression = mapping.source + if expression is not None: + imported[mapping.local_name.casefold()] = expression + return imported + + @staticmethod + def _compile_time_symbols_for_scope( + owner_name: str | None, + uses: Mapping[str, list[FortranUseMapping]], + symbols: _CompileTimeSymbols, + ) -> dict[str, str]: + """Return a mutable flat symbol map visible to one parsed scope. + + The method starts with symbols owned or reexported by ``owner_name`` and + applies the scope's direct imports. For example, a free procedure with + ``use kinds, only: wp => rk`` receives ``{"wp": "8"}``; a procedure + owned by ``solver`` also receives the symbols already attached to the + ``solver`` module entry. The returned copy may safely add local values. + """ + visible = dict(symbols.in_module(owner_name)) + visible.update( + FortranParser._imported_compile_time_symbols( + uses, + symbols, + include_intrinsic_aliases=False, + ) ) - for name in list(sig.variables): - if name.lower() in resolved_variables: - sig.variables[name] = resolved_variables[name.lower()] - resolver = _CompileTimeResolver(symbol_to_value) - for arg in sig.arguments: - if arg.kind: - arg.kind = FortranParser._resolve_kind_expression(arg.kind, symbol_to_value, resolver=resolver) - if resolve_shapes and arg.shape: - arg.shape = [resolver.resolve(dim) for dim in arg.shape] - if sig.result and sig.result.kind: - sig.result.kind = FortranParser._resolve_kind_expression( - sig.result.kind, symbol_to_value, resolver=resolver + return visible + + @staticmethod + def _procedure_compile_time_symbols( + signature: FortranProcedureSignature, + symbols: _CompileTimeSymbols, + ) -> dict[str, str]: + """Return source-level symbols visible while resolving one procedure. + + ``signature`` contributes its owning module, direct ``use`` mappings, + and local parameter variables; ``symbols`` contributes resolved module + entries. For example, an imported ``wp = 8`` plus local ``n = 4`` + returns ``{"wp": "8", "n": "4"}``. Existing module/import values win + over duplicate local names through the established ``setdefault`` rule. + """ + visible = FortranParser._compile_time_symbols_for_scope( + signature.module, + signature.uses, + symbols, + ) + for name, variable in signature.variables.items(): + expression = variable.value if variable.value is not None else variable.symbolic_value + if expression is not None: + visible.setdefault(name.casefold(), expression) + return visible + + @staticmethod + def _resolve_procedure_variables( + signature: FortranProcedureSignature, + visible_symbols: dict[str, str], + ) -> None: + """Rebuild procedure parameter variables from resolved visible values. + + The method receives one signature and its flat symbol map. For example, + a stored local parameter ``n = m + 1`` with visible ``m = 3`` is + replaced by a parameter variable whose literal value is ``4`` while its + original symbolic expression remains ``m + 1``. Non-parameter entries + in the visible map are ignored unless the signature owns that name. + """ + base_types = {name.casefold(): variable.base_type for name, variable in signature.variables.items()} + symbolic_values = { + name.casefold(): variable.symbolic_value or variable.value for name, variable in signature.variables.items() + } + resolved = FortranParser._resolve_variables(visible_symbols, base_types, symbolic_values) + for name in list(signature.variables): + if name.casefold() in resolved: + signature.variables[name] = resolved[name.casefold()] + + @staticmethod + def _resolve_procedure_signature_facts( + signature: FortranProcedureSignature, + visible_symbols: dict[str, str], + *, + resolve_shapes: bool, + ) -> None: + """Resolve procedure argument/result facts from one flat symbol map. + + Kinds are always resolved. ``resolve_shapes=True`` additionally folds + argument dimensions, so ``x(n)`` with ``n = 4`` becomes ``x(4)``; + file/project coordinators pass ``False`` to preserve imported native + shape spelling. The supplied signature is mutated and no value is + returned. + """ + resolver = _CompileTimeResolver(visible_symbols) + for argument in signature.arguments: + if argument.kind: + argument.kind = FortranParser._resolve_kind_expression( + argument.kind, + visible_symbols, + resolver=resolver, + ) + if resolve_shapes and argument.shape: + argument.shape = [resolver.resolve(dimension) for dimension in argument.shape] + if signature.result and signature.result.kind: + signature.result.kind = FortranParser._resolve_kind_expression( + signature.result.kind, + visible_symbols, + resolver=resolver, ) @staticmethod - def _resolve_module_variable_kinds( - module: FortranModule | FortranSubmodule | FortranProgram | FortranBlockData, - module_params: dict[str, dict[str, str]], + def _resolve_procedure_compile_time_facts( + signature: FortranProcedureSignature, + symbols: _CompileTimeSymbols, + *, + resolve_shapes: bool, ) -> None: - """Resolve kind, value, and shape facts for module-like variables.""" - module_params = FortranParser._resolve_module_parameter_values(module_params) - symbol_to_value: dict[str, str] = {} - if getattr(module, "name", None): - symbol_to_value.update(module_params.get(module.name.lower(), {})) - for mod, mappings in getattr(module, "uses", {}).items(): - params = module_params.get(mod.lower(), {}) - if not params: - continue - if not mappings: - symbol_to_value.update(params) - continue - for mapping in mappings: - source = mapping.source.lower() - local = mapping.local_name.lower() - if source in params: - symbol_to_value[local] = params[source] - for var in getattr(module, "variables", []): - source_value = var.value if var.value is not None else var.symbolic_value - if source_value is not None: - symbol_to_value.setdefault(var.name.lower(), source_value) - resolver = _CompileTimeResolver(symbol_to_value) - for var in getattr(module, "variables", []): - source_value = var.value if var.value is not None else var.symbolic_value - if source_value is not None: - resolved_value = resolver.resolve(source_value, prefer_symbolic=False) - var.value = FortranParser._normalize_parameter_value(resolved_value) - symbol_to_value[var.name.lower()] = var.value if var.value is not None else source_value - if var.kind: - var.kind = FortranParser._resolve_kind_expression(var.kind, symbol_to_value, resolver=resolver) - if var.shape: - var.shape = [resolver.resolve(dim) for dim in var.shape] - var.lbound, var.ubound = FortranParser._extract_bounds(var.shape) + """Resolve one procedure using an already-completed module table. + + ``signature`` is the model to update and ``symbols`` is the immutable + source-level table shared by its file or project. The method builds the + procedure's visible symbols, refreshes its parameter variables, and + resolves argument/result kinds plus optional shapes. For example, + imported ``wp = 8`` changes ``real(wp)`` to kind ``8``. It returns + nothing and never recomputes the module table. + """ + visible = FortranParser._procedure_compile_time_symbols(signature, symbols) + FortranParser._resolve_procedure_variables(signature, visible) + FortranParser._resolve_procedure_signature_facts( + signature, + visible, + resolve_shapes=resolve_shapes, + ) @staticmethod - def _resolve_derived_type_field_kinds( - derived_type: FortranDerivedType, - module_params: dict[str, dict[str, str]], + def _resolve_module_like_compile_time_facts( + owner: FortranModule | FortranSubmodule | FortranProgram | FortranBlockData, + symbols: _CompileTimeSymbols, ) -> None: - """Resolve kind and shape parameters for fields in their module scope. + """Resolve values, kinds, shapes, and bounds for module-like variables. + + ``owner`` supplies its variables and ``use`` mappings; ``symbols`` is + the already-resolved file/project table. For example, visible ``n = 4`` + changes ``real(kind=n) :: values(0:n)`` to kind ``4`` and shape + ``0:4``, then refreshes its lower/upper bounds. The owner is mutated and + the method returns nothing. + """ + visible = FortranParser._compile_time_symbols_for_scope( + getattr(owner, "name", None), + getattr(owner, "uses", {}), + symbols, + ) + variables = getattr(owner, "variables", []) + for variable in variables: + expression = variable.value if variable.value is not None else variable.symbolic_value + if expression is not None: + visible.setdefault(variable.name.casefold(), expression) + + resolver = _CompileTimeResolver(visible) + for variable in variables: + expression = variable.value if variable.value is not None else variable.symbolic_value + if expression is not None: + resolved_value = resolver.resolve(expression, prefer_symbolic=False) + variable.value = FortranParser._normalize_parameter_value(resolved_value) + visible[variable.name.casefold()] = variable.value if variable.value is not None else expression + if variable.kind: + variable.kind = FortranParser._resolve_kind_expression( + variable.kind, + visible, + resolver=resolver, + ) + if variable.shape: + variable.shape = [resolver.resolve(dimension) for dimension in variable.shape] + variable.lbound, variable.ubound = FortranParser._extract_bounds(variable.shape) - The helper consumes the same module parameter table as module-variable - resolution and mutates only the parsed field facts. Field declaration - order is preserved, and unresolved native expressions remain symbolic. + @staticmethod + def _resolve_derived_type_compile_time_facts( + derived_type: FortranDerivedType, + symbols: _CompileTimeSymbols, + ) -> None: + """Resolve field kinds and shapes visible from a derived-type owner. + + ``derived_type.module`` selects the owning module entry from + ``symbols``. Local type parameters are removed so declarations such as + ``type(buffer(k))`` keep their instance-dependent ``k`` symbolic, while + an imported module alias like ``wp = 8`` resolves ``real(wp)`` fields to + kind ``8``. Field order is preserved, bounds are refreshed, and the + method returns nothing. """ - resolved_params = FortranParser._resolve_module_parameter_values(module_params) - local_parameters = set(getattr(derived_type, "_type_parameters", ())) - symbols = { + local_parameters = {name.casefold() for name in getattr(derived_type, "_type_parameters", ())} + visible = { name: value - for name, value in resolved_params.get(str(derived_type.module or "").casefold(), {}).items() + for name, value in symbols.in_module(derived_type.module).items() if name.casefold() not in local_parameters } - resolver = _CompileTimeResolver(symbols) + resolver = _CompileTimeResolver(visible) for field in derived_type.fields: if field.kind: - field.kind = FortranParser._resolve_kind_expression(field.kind, symbols, resolver=resolver) + field.kind = FortranParser._resolve_kind_expression( + field.kind, + visible, + resolver=resolver, + ) if field.shape: field.shape = [resolver.resolve(dimension) for dimension in field.shape] field.lbound, field.ubound = FortranParser._extract_bounds(field.shape) @@ -5106,7 +5298,7 @@ def _resolve_derived_type_field_kinds( @staticmethod def _resolve_kind_expression( expr: str, - symbols: dict[str, str], + symbols: Mapping[str, str], *, resolver: _CompileTimeResolver | None = None, ) -> str: @@ -5119,7 +5311,7 @@ def _resolve_kind_expression( return active_resolver.resolve(resolved) @staticmethod - def _resolve_symbol_reference(expr: str, symbols: dict[str, str]) -> str: + def _resolve_symbol_reference(expr: str, symbols: Mapping[str, str]) -> str: """Follow direct symbol aliases until a stable expression is reached.""" out = expr.strip() seen: set[str] = set() @@ -5205,9 +5397,9 @@ def _is_literal_parameter_value(value: str) -> bool: @staticmethod def _resolve_variables( - symbols: dict[str, str], - base_types: dict[str, str] | None = None, - symbolic_values: dict[str, str | None] | None = None, + symbols: Mapping[str, str], + base_types: Mapping[str, str] | None = None, + symbolic_values: Mapping[str, str | None] | None = None, ) -> dict[str, FortranVariable]: """Build resolved parameter variables from a symbol-expression map.""" base_types = base_types or {} diff --git a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py b/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py index 8d7d1e9d6..ddb859cbf 100644 --- a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py +++ b/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py @@ -268,6 +268,38 @@ def test_single_file_cli_resolves_direct_intrinsic_kind_rename_before_probing(tm assert collect_semantic_compile_time_requirements(parsed) == [] +def test_cli_cross_file_resolution_reaches_imported_derived_field_kinds(tmp_path: Path): + precision = tmp_path / "precision.f90" + records = tmp_path / "records.f90" + precision.write_text( + """ +module precision + integer, parameter :: rk = 8 +end module precision +""", + encoding="utf-8", + ) + records.write_text( + """ +module records + use precision, only: wp => rk + type :: sample + real(kind=wp) :: value + end type sample +end module records +""", + encoding="utf-8", + ) + + parsed_files = prik_cli._parse_fortran_source_files( + [precision, records], + PreprocessingConfig(), + ) + record_file = next(parsed for path, parsed in parsed_files if path == records) + + assert record_file.modules[0].derived_types[0].fields[0].kind == "8" + + def test_prik_pyi_report_writes_opaque_dependency_stub_for_external_type(tmp_path: Path, monkeypatch): physics = tmp_path / "physics.f90" physics.write_text( diff --git a/tests/fortran/modules/parsing/test_project_scope_models.py b/tests/fortran/modules/parsing/test_project_scope_models.py index caa9d1c73..4f1d3c78e 100644 --- a/tests/fortran/modules/parsing/test_project_scope_models.py +++ b/tests/fortran/modules/parsing/test_project_scope_models.py @@ -3,6 +3,7 @@ import pytest from prik import FortranParseError, parse_fortran_file, parse_fortran_project +from prik.parsers.fortran.parser import FortranParser def test_module_visibility_public_and_private_spec_lines_are_applied(): @@ -327,6 +328,47 @@ def test_directory_project_tracks_renamed_kind_imports_from_other_files(tmp_path assert project.dependencies["solver_mod"] == {"precision_mod"} +def test_project_compile_time_resolution_uses_models_is_idempotent_and_preserves_symbolic_shapes(): + parser = FortranParser() + kinds_file = parser.parse_file( + """ +module kinds + integer, parameter :: word = 4 + integer, parameter :: rk = word * 2 + integer, parameter :: n = 3 +end module kinds +""", + filename="kinds.f90", + ) + consumer_file = parser.parse_file( + """ +module records + use kinds, only: wp => rk, n + type :: sample + real(kind=wp) :: values(0:n) + end type sample +contains + subroutine consume(values) + real(kind=wp), intent(in) :: values(1:n) + end subroutine consume +end module records +""", + filename="records.f90", + ) + kinds_file.source = None + consumer_file.source = None + + parser._resolve_project_compile_time_facts([kinds_file, consumer_file]) + field = consumer_file.modules[0].derived_types[0].fields[0] + argument = consumer_file.modules[0].procedures[0].arguments[0] + first_result = (field.kind, list(field.shape), argument.kind, list(argument.shape)) + + parser._resolve_project_compile_time_facts([kinds_file, consumer_file]) + + assert first_result == ("8", ["0:3"], "8", ["1:n"]) + assert (field.kind, field.shape, argument.kind, argument.shape) == first_result + + def test_project_resolves_reexported_intrinsic_kind_renames(): project = parse_fortran_project( { diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py index 5ca31bbd0..3a149da7e 100644 --- a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py @@ -34,11 +34,15 @@ def test_compile_time_resolution_helpers_preserve_kind_shape_values_and_literal_ is_parameter=True, ) - parser._resolve_signature_kinds( - signature, + symbols = parser._resolve_compile_time_symbols( { "api_mod": {"rk": "4 + 4", "rk_alias": "rk", "n": "3"}, - }, + } + ) + parser._resolve_procedure_compile_time_facts( + signature, + symbols, + resolve_shapes=True, ) assert signature.arguments[0].kind == "8" @@ -59,7 +63,7 @@ def test_compile_time_resolution_helpers_preserve_kind_shape_values_and_literal_ is_parameter=True, ) ) - parser._resolve_module_variable_kinds(module, {"api_mod": {"rk": "8", "rk_alias": "rk", "n": "3"}}) + parser._resolve_module_like_compile_time_facts(module, symbols) assert module.variables[0].kind == "8" assert module.variables[0].shape == ["0:3"] @@ -69,9 +73,15 @@ def test_compile_time_resolution_helpers_preserve_kind_shape_values_and_literal_ assert parser._resolve_kind_expression("len=n + 1", {"n": "3"}) == "len=4" assert parser._resolve_symbol_reference("alias", {"alias": "target", "target": "8"}) == "8" - assert parser._resolve_module_parameter_values( + resolved = parser._resolve_compile_time_symbols( {"M": {"a": "4", "b": "a + 2", "rk": "selected_real_kind(12)", "dp": "rk"}} - ) == {"m": {"a": "4", "b": "6", "rk": "selected_real_kind(12)", "dp": "selected_real_kind(12)"}} + ) + assert dict(resolved.in_module("m")) == { + "a": "4", + "b": "6", + "rk": "selected_real_kind(12)", + "dp": "selected_real_kind(12)", + } assert parser._collect_relevant_local_params( FortranProcedureSignature( "shape", From f2eeca264afe088eef9cab9bcdfb8442b74c15c9 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 02:59:01 +0100 Subject: [PATCH 11/22] Add frozen _FunctionPolicyContext --- prik/semantics/wrapper_policy.py | 333 ++++++++++++++++++------------- 1 file changed, 196 insertions(+), 137 deletions(-) diff --git a/prik/semantics/wrapper_policy.py b/prik/semantics/wrapper_policy.py index 3392e47a8..badf8fc78 100644 --- a/prik/semantics/wrapper_policy.py +++ b/prik/semantics/wrapper_policy.py @@ -11,10 +11,13 @@ from __future__ import annotations import ast +from collections.abc import Mapping from dataclasses import dataclass, replace from enum import Enum from typing import Any +from immutabledict import immutabledict + from prik.naming import NamingPolicy from prik.semantics import models from prik.semantics.metadata import ( @@ -1301,6 +1304,42 @@ class FunctionWrapperPolicy: release_actions: tuple[LifecyclePolicy, ...] = () +@dataclass(frozen=True) +class _FunctionPolicyContext: + """Keep the facts shared by every policy step for one function. + + ``function`` and ``owner_path`` identify the callable being completed. + The two mappings contain already-completed derived-type and polymorphic + facts, while ``class_call`` identifies an optional class-surface call. + For example, all argument, result, and native-slot builders for + ``math.scale`` receive the same context instead of separately threading + those five values through each helper. + + Step-local products such as native positions and call slots deliberately + remain ordinary helper arguments; they are not fixed function context. + """ + + function: models.SemanticFunction + owner_path: str + derived_types: Mapping[tuple[str, str], DerivedTypePolicy] + polymorphic_variants: Mapping[tuple[str, str], tuple[tuple[str, str], ...]] + class_call: ClassMethodPolicy | None + + +@dataclass(frozen=True) +class _ResultPolicyCandidate: + """Store one result policy candidate together with its support blockers. + + ``policy`` is ``None`` when completion cannot construct a usable result, + such as a direct return without completed ownership. Keeping blockers on + the same record prevents callers from coordinating parallel policy and + diagnostic tuples by position. + """ + + policy: ResultPolicy | None + blockers: tuple[str, ...] + + def completed_derived_type_policy(semantic_class: models.SemanticClass) -> DerivedTypePolicy: """Return one fully completed derived-type policy or fail closed.""" policy = semantic_class.metadata.get(models.RESOLVED_DERIVED_TYPE_POLICY_METADATA) @@ -1908,7 +1947,7 @@ def build_module_variable_policy( variable: models.SemanticVariable, *, module_name: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy] | None = None, + derived_types: Mapping[tuple[str, str], DerivedTypePolicy] | None = None, ) -> ModuleVariablePolicy: """Build one module-variable access policy from completed semantic decisions. @@ -2067,7 +2106,7 @@ def _derived_module_variable_policy( getter: OwnershipDecision, setter: OwnershipDecision | None, constant: bool, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> ModuleVariablePolicy: """Build one constant-copy or live derived module-object policy.""" builder = _derived_module_constant_policy if constant else _derived_module_object_policy @@ -2594,10 +2633,10 @@ def build_function_wrapper_policy( function: models.SemanticFunction, *, owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy] | None = None, + derived_types: Mapping[tuple[str, str], DerivedTypePolicy] | None = None, class_call: ClassMethodPolicy | None = None, module_export: bool | None = None, - polymorphic_variants: dict[tuple[str, str], tuple[tuple[str, str], ...]] | None = None, + polymorphic_variants: Mapping[tuple[str, str], tuple[tuple[str, str], ...]] | None = None, native_dispatch_name: str | None = None, ) -> FunctionWrapperPolicy: """Build a complete wrapper-facing function policy from post-IR decisions. @@ -2609,25 +2648,25 @@ def build_function_wrapper_policy( store the returned record in its resolved-policy metadata. """ - completed_derived_types = derived_types or {} - # Establish native ABI order before projecting Python-visible arguments. - argument_native_positions, native_call_slots, slot_blockers = _native_call_slot_policies( - function, - owner_path, - completed_derived_types, + # Freeze the facts shared by every function-policy step so native-slot, + # argument, and result completion cannot receive different ambient inputs. + context = _FunctionPolicyContext( + function=function, + owner_path=owner_path, + derived_types=immutabledict(derived_types or {}), + polymorphic_variants=immutabledict(polymorphic_variants or {}), + class_call=class_call, ) + # Establish native ABI order before projecting Python-visible arguments. + argument_native_positions, native_call_slots, slot_blockers = _native_call_slot_policies(context) arguments, argument_blockers = _argument_policies( - function, - owner_path, + context, argument_native_positions, native_call_slots, - completed_derived_types, - polymorphic_variants or {}, - class_call, ) # Complete result representation and declaration call targets, then bind # every array-extent producer to its immutable role. - results, result_blockers = _result_policies(function, owner_path, completed_derived_types) + results, result_blockers = _result_policies(context) declaration_callables = _function_declaration_callable_policies(function, owner_path) arguments, results, native_call_slots = _complete_function_array_extent_policies( function, @@ -2762,19 +2801,21 @@ def _array_requires_explicit_interface(array: ArrayHandoffPolicy | None) -> bool def _argument_policies( - function: models.SemanticFunction, - owner_path: str, + context: _FunctionPolicyContext, argument_native_positions: dict[int, int], native_call_slots: tuple[NativeCallSlotPolicy, ...], - derived_types: dict[tuple[str, str], DerivedTypePolicy], - polymorphic_variants: dict[tuple[str, str], tuple[tuple[str, str], ...]], - class_call: ClassMethodPolicy | None, ) -> tuple[list[ArgumentPolicy], tuple[str, ...]]: - """Complete visible arguments through one uniform per-argument leaf.""" + """Complete visible arguments using fixed context and native-slot products. + + ``context`` supplies the function-wide owner, type indexes, and optional + class call. ``argument_native_positions`` and ``native_call_slots`` are + products of the preceding native-slot step and therefore remain explicit. + The result contains ordered argument policies plus every support blocker. + """ policies: list[ArgumentPolicy] = [] blockers: list[str] = [] python_position = 0 - for argument in function.arguments: + for argument in context.function.arguments: decision = _ownership_decision(argument, models.RESOLVED_OWNERSHIP_POLICY_METADATA) if decision is None: blockers.append(f"argument {argument.name!r} is missing completed ownership policy") @@ -2788,16 +2829,12 @@ def _argument_policies( blockers.append(f"argument {argument.name!r} has no completed native-call slot") native_position = -1 policy, argument_blockers = _argument_policy( - function, + context, argument, decision, - owner_path, current_python_position, native_position, _native_call_slot_for_python_position(native_call_slots, current_python_position), - derived_types, - polymorphic_variants, - class_call, ) policies.append(policy) blockers.extend(argument_blockers) @@ -2813,19 +2850,22 @@ def _native_call_slot_for_python_position( def _argument_policy( - function: models.SemanticFunction, + context: _FunctionPolicyContext, argument: models.SemanticArgument, decision: OwnershipDecision, - owner_path: str, python_position: int, native_position: int, native_slot: NativeCallSlotPolicy | None, - derived_types: dict[tuple[str, str], DerivedTypePolicy], - polymorphic_variants: dict[tuple[str, str], tuple[tuple[str, str], ...]], - class_call: ClassMethodPolicy | None, ) -> tuple[ArgumentPolicy, tuple[str, ...]]: - """Complete one visible argument without mixing it with list traversal.""" - argument_path = f"{owner_path}.{argument.name}" + """Complete one visible argument from fixed and position-specific facts. + + ``context`` provides the callable-wide policy environment. The remaining + inputs identify this argument's completed ownership and its Python/native + positions. The returned pair contains the immutable argument policy and + any blockers found while constructing it. + """ + function = context.function + argument_path = f"{context.owner_path}.{argument.name}" scalar_logical_abi, scalar_native_type = _scalar_logical_argument_abi(argument) array_logical_abi, array_native_type, array_copy_in, array_copy_out = _array_logical_argument_abi( argument, @@ -2839,15 +2879,21 @@ def _argument_policy( decision, array_policy, ) - derived = _argument_derived_handoff(argument, decision, callback, argument_path, derived_types) + derived = _argument_derived_handoff( + argument, + decision, + callback, + argument_path, + context.derived_types, + ) derived_call = _argument_derived_call(argument, decision, callback, native_position) polymorphic = _polymorphic_dispatch_policy( argument, decision, derived, - polymorphic_variants, + context.polymorphic_variants, owner_path=argument_path, - force=_is_passed_object_argument(class_call, native_position) + force=_is_passed_object_argument(context.class_call, native_position) or _is_exported_passed_object_argument(function, native_position), ) bridge_data_action, bridge_copy_reason = _completed_argument_bridge_action( @@ -2869,7 +2915,7 @@ def _argument_policy( bridge_data_action, bridge_copy_reason, transformation_blockers, - derived_types, + context.derived_types, ) return ( ArgumentPolicy( @@ -2941,7 +2987,7 @@ def _argument_derived_handoff( decision: OwnershipDecision, callback: CallbackHandoffPolicy | None, owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> DerivedHandoffPolicy | None: """Complete the ordinary derived handoff, or leave callback transfer opaque.""" if callback is not None: @@ -3068,7 +3114,7 @@ def _completed_argument_blockers( bridge_data_action: BridgeDataAction, bridge_copy_reason: str | None, transformation_blockers: tuple[str, ...], - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[str, ...]: """Collect blockers after all semantic selectors have been completed.""" blockers = [ @@ -3108,7 +3154,7 @@ def _completed_argument_blockers( def _callback_derived_type_blockers( callback: CallbackHandoffPolicy, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[str, ...]: """Require every callback-derived transfer to use a local exact wrapper type.""" transfers = ( @@ -3123,94 +3169,108 @@ def _callback_derived_type_blockers( ) -def _result_policies( - function: models.SemanticFunction, - owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], -) -> tuple[tuple[ResultPolicy, ...], tuple[str, ...]]: - """Return every ordered binding result consumer for one function.""" - hidden_candidates = _hidden_result_policies(function, owner_path, derived_types) - hidden_results = tuple(policy for policy, _blockers in hidden_candidates if policy is not None) - hidden_blockers = tuple(reason for _policy, blockers in hidden_candidates for reason in blockers) - if function.return_type is None: - projected_arguments = _visible_projected_arguments(function) - if hidden_results and not projected_arguments: - return hidden_results, hidden_blockers - if projected_arguments and not hidden_results: - return (), hidden_blockers - if not hidden_results and not projected_arguments: - return (), hidden_blockers +def _result_policies(context: _FunctionPolicyContext) -> tuple[tuple[ResultPolicy, ...], tuple[str, ...]]: + """Combine direct and hidden results in their established diagnostic order. + + Hidden-output candidates are collected for every callable. Subroutines + return those candidates directly. Functions prepend one direct-result + candidate; if that candidate cannot be built, the existing fail-closed + behavior discards all results while retaining hidden diagnostics first. + """ + hidden_candidates = _hidden_result_policies(context) + hidden_results = tuple(candidate.policy for candidate in hidden_candidates if candidate.policy is not None) + hidden_blockers = tuple(reason for candidate in hidden_candidates for reason in candidate.blockers) + if context.function.return_type is None: return hidden_results, hidden_blockers + direct = _direct_result_policy(context) + if direct.policy is None: + return (), (*hidden_blockers, *direct.blockers) + return (direct.policy, *hidden_results), (*direct.blockers, *hidden_blockers) + + +def _direct_result_policy(context: _FunctionPolicyContext) -> _ResultPolicyCandidate: + """Build one function's direct return from completed ownership facts. + + ``context.function`` must have a return type. The method validates its + descriptor, bridge action, and derived-type handoff, then returns either a + completed ``ResultPolicy`` with blockers or a blocked empty candidate when + ownership completion is missing. + """ + function = context.function + return_type = function.return_type + if return_type is None: + raise ValueError("Direct result policy requires a function return type") decision = function.metadata.get(models.RESOLVED_RETURN_OWNERSHIP_POLICY_METADATA) if not isinstance(decision, OwnershipDecision): - return (), (*hidden_blockers, "function result is missing completed ownership policy") + return _ResultPolicyCandidate(None, ("function result is missing completed ownership policy",)) + result_path = f"{context.owner_path}.return" direct_handle = _native_array_handle_wrapper_policy( - function.return_type, + return_type, function.metadata.get(models.RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA), - f"{owner_path}.return", + result_path, ) - scalar_descriptor = _scalar_descriptor_result_policy(function.return_type, decision) - blockers = list(_result_blockers(function.return_type, decision)) + scalar_descriptor = _scalar_descriptor_result_policy(return_type, decision) + blockers = list(_result_blockers(return_type, decision)) if scalar_descriptor is not None and scalar_descriptor.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE: blockers.append( "direct allocatable scalar function results cannot preserve unallocated state; " "use an allocatable hidden output projection" ) - bridge_data_action, bridge_copy_reason = _result_bridge_data_action(function.return_type) + bridge_data_action, bridge_copy_reason = _result_bridge_data_action(return_type) if bridge_data_action is BridgeDataAction.BLOCKED and decision.kind is not ObjectKind.SCALAR: blockers.append("result has no completed bridge data action") derived = _derived_handoff_policy( - function.return_type, + return_type, decision, - owner_path=f"{owner_path}.return", + owner_path=result_path, origin=DerivedObjectOrigin.WRAPPER_RESULT, - derived_types=derived_types, + derived_types=context.derived_types, ) - blockers.extend(_derived_type_definition_blockers("result", derived, derived_types)) + blockers.extend(_derived_type_definition_blockers("result", derived, context.derived_types)) blockers.extend( _allocatable_holder_field_blockers( "result", derived, - derived_types, + context.derived_types, required=bool(derived is not None and derived.storage is DerivedObjectStorage.ALLOCATABLE_HOLDER), ) ) - direct_result = ResultPolicy( - owner_path=f"{owner_path}.return", - semantic_type_name=function.return_type.name, - rank=int(function.return_type.rank or 0), - direct_result_abi=_direct_result_abi(function.return_type, decision, scalar_descriptor), - ownership=decision, - codegen_action=decision.codegen_action, - python_barrier_action=decision.python_barrier_action, - native_barrier_action=decision.native_barrier_action, - storage_mode=decision.storage_mode, - boundary_storage_mode=decision.boundary_storage_mode or decision.storage_mode, - bridge_data_action=bridge_data_action, - bridge_copy_reason=bridge_copy_reason, - character_length=_character_length(function.return_type), - array=_array_handoff_policy(function.return_type), - native_array_handle=direct_handle, - scalar_descriptor=scalar_descriptor, - derived=derived, - ) - results = (direct_result, *hidden_results) - return ( - results, - ( - *blockers, - *hidden_blockers, + return _ResultPolicyCandidate( + ResultPolicy( + owner_path=result_path, + semantic_type_name=return_type.name, + rank=int(return_type.rank or 0), + direct_result_abi=_direct_result_abi(return_type, decision, scalar_descriptor), + ownership=decision, + codegen_action=decision.codegen_action, + python_barrier_action=decision.python_barrier_action, + native_barrier_action=decision.native_barrier_action, + storage_mode=decision.storage_mode, + boundary_storage_mode=decision.boundary_storage_mode or decision.storage_mode, + bridge_data_action=bridge_data_action, + bridge_copy_reason=bridge_copy_reason, + character_length=_character_length(return_type), + array=_array_handoff_policy(return_type), + native_array_handle=direct_handle, + scalar_descriptor=scalar_descriptor, + derived=derived, ), + tuple(blockers), ) -def _hidden_result_policies( - function: models.SemanticFunction, - owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], -) -> tuple[tuple[ResultPolicy | None, tuple[str, ...]], ...]: - """Return completed policy candidates for hidden scalar output projections.""" +def _hidden_result_policies(context: _FunctionPolicyContext) -> tuple[_ResultPolicyCandidate, ...]: + """Return hidden-output candidates using one function-wide policy context. + + Each hidden projected argument produces a candidate containing either its + completed result policy or ``None`` plus the blockers that prevented it. + Runtime-status outputs are omitted because their completed status policy + consumes them instead of exposing them as ordinary Python results. + """ + function = context.function + owner_path = context.owner_path + derived_types = context.derived_types policies = [] suppressed_outputs = _runtime_status_output_owner_paths(function) for argument in function.arguments: @@ -3228,7 +3288,12 @@ def _hidden_result_policies( None, ) if mapping is None: - policies.append((None, (f"hidden result {argument.name!r} has no completed return projection",))) + policies.append( + _ResultPolicyCandidate( + None, + (f"hidden result {argument.name!r} has no completed return projection",), + ) + ) continue blockers = _hidden_result_blockers(argument, decision, mapping) native_array_handle = _native_array_handle_wrapper_policy( @@ -3275,7 +3340,7 @@ def _hidden_result_policies( ), ) policies.append( - ( + _ResultPolicyCandidate( ResultPolicy( owner_path=f"{owner_path}.{argument.name}", semantic_type_name=argument.semantic_type.name, @@ -3306,25 +3371,32 @@ def _hidden_result_policies( def _native_call_slot_policies( - function: models.SemanticFunction, - owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + context: _FunctionPolicyContext, ) -> tuple[dict[int, int], tuple[NativeCallSlotPolicy, ...], tuple[str, ...]]: - """Complete ordered native call slots from explicit projections or declaration order. + """Complete ordered native call slots from one fixed function context. The returned mapping connects visible Python argument positions to native positions; slot records preserve native ABI order and blockers diagnose - missing completed decisions without mutating ``function``. + missing completed decisions without mutating ``context.function``. The + projected and implicit leaves still receive their exact dependencies. """ - if function.projection: - return _projected_native_call_slot_policies(function, owner_path, derived_types) - return _implicit_native_call_slot_policies(function, owner_path, derived_types) + if context.function.projection: + return _projected_native_call_slot_policies( + context.function, + context.owner_path, + context.derived_types, + ) + return _implicit_native_call_slot_policies( + context.function, + context.owner_path, + context.derived_types, + ) def _projected_native_call_slot_policies( function: models.SemanticFunction, owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[dict[int, int], tuple[NativeCallSlotPolicy, ...], tuple[str, ...]]: """Complete projected slots through one small mapping-dispatch leaf.""" slots: list[NativeCallSlotPolicy] = [] @@ -3368,7 +3440,7 @@ def _projected_native_call_slot_policy( mapping: models.ProjectionMapping, owner_path: str, visible_arguments: tuple[models.SemanticArgument, ...], - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[NativeCallSlotPolicy | None, int | None, tuple[str, ...]]: """Dispatch one projection mapping to its literal, result, or argument leaf.""" native_position = mapping.native_position @@ -3403,7 +3475,7 @@ def _projected_argument_native_call_slot_policy( native_position: int, python_position: int | None, visible_arguments: tuple[models.SemanticArgument, ...], - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[NativeCallSlotPolicy | None, int | None, tuple[str, ...]]: """Complete one Python argument projection after checking its position.""" if python_position is None: @@ -3436,7 +3508,7 @@ def _projected_argument_slot( owner_path: str, native_position: int, python_position: int, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[NativeCallSlotPolicy, tuple[str, ...]]: """Construct one completed native slot for a visible projected argument.""" argument_path = f"{owner_path}.{argument.name}" @@ -3543,7 +3615,7 @@ def _hidden_result_native_call_slot_policy( mapping: models.ProjectionMapping, owner_path: str, native_position: int, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[NativeCallSlotPolicy, tuple[str, ...]]: """Return one native slot for a hidden scalar `Return(...)` projection.""" argument = next((item for item in function.arguments if item.name == mapping.python_name), None) @@ -3712,7 +3784,7 @@ def _literal_projection_value( def _implicit_native_call_slot_policies( function: models.SemanticFunction, owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[dict[int, int], tuple[NativeCallSlotPolicy, ...], tuple[str, ...]]: """Build declaration-ordered slots when no explicit native projection exists. @@ -3833,7 +3905,7 @@ def _derived_argument_bridge_data_action( def _derived_argument_handoff_blockers( argument: models.SemanticArgument, derived: DerivedHandoffPolicy | None, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[str, ...]: """Require the exact native type definition for a typed value call.""" if derived is None: @@ -3844,7 +3916,7 @@ def _derived_argument_handoff_blockers( def _derived_type_definition_blockers( label: str, derived: DerivedHandoffPolicy | None, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> tuple[str, ...]: """Require an exported local wrapper definition for every derived handoff.""" if derived is None or derived.type_identity in derived_types: @@ -3855,7 +3927,7 @@ def _derived_type_definition_blockers( def _allocatable_holder_field_blockers( label: str, derived: DerivedHandoffPolicy | None, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], *, required: bool, ) -> tuple[str, ...]: @@ -3879,7 +3951,7 @@ def _derived_handoff_policy( owner_path: str, origin: DerivedObjectOrigin, native_value: bool = False, - derived_types: dict[tuple[str, str], DerivedTypePolicy] | None = None, + derived_types: Mapping[tuple[str, str], DerivedTypePolicy] | None = None, ) -> DerivedHandoffPolicy | None: """Complete one scalar-derived origin and lifetime before planning.""" if decision.kind is not ObjectKind.DERIVED_TYPE: @@ -4203,7 +4275,7 @@ def _resolve_derived_type_policy( semantic_type: models.SemanticType, *, requested_identity: tuple[str, str], - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> DerivedTypePolicy | None: """Resolve one reference to a completed canonical native type identity.""" exact = derived_types.get(requested_identity) @@ -5920,7 +5992,7 @@ def _derived_module_object_policy( setter: OwnershipDecision | None, *, owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> DerivedModuleObjectPolicy: """Complete direct-address versus typed-member module access.""" handoff = _derived_handoff_policy( @@ -5960,7 +6032,7 @@ def _derived_module_constant_policy( setter: OwnershipDecision | None, *, owner_path: str, - derived_types: dict[tuple[str, str], DerivedTypePolicy], + derived_types: Mapping[tuple[str, str], DerivedTypePolicy], ) -> DerivedModuleObjectPolicy: """Complete an explicit native constant as a fresh wrapper-owned value copy.""" handoff = _derived_handoff_policy( @@ -7182,19 +7254,6 @@ def _argument_result_position(function: models.SemanticFunction, python_position return None -def _visible_projected_arguments(function: models.SemanticFunction) -> tuple[models.SemanticArgument, ...]: - """Return Python-visible arguments whose completed policy projects results.""" - return tuple( - argument - for argument in function.arguments - if ( - (decision := _ownership_decision(argument, models.RESOLVED_OWNERSHIP_POLICY_METADATA)) is not None - and decision.projects_result - and decision.python_visible - ) - ) - - def _native_name(function: models.SemanticFunction) -> str: """Return the callable's resolved native spelling, preferring explicit semantic identity.""" return str(function.native_name or function.origin.native_name or function.name) From 51dd0e396b25a31ab98e239053fbd7b6a51e037e Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 05:56:03 +0100 Subject: [PATCH 12/22] add _ClassPolicyCatalog and _ClassPolicyEntry as planner-local, read-only organization --- prik/codegen/planner.py | 255 +++++++++------ prik/semantics/policy_completion.py | 107 ++++--- prik/semantics/wrapper_policy.py | 296 ++++++++++++------ .../infrastructure/codegen/test_planner.py | 28 ++ .../semantics/test_wrapper_policy.py | 40 +++ 5 files changed, 488 insertions(+), 238 deletions(-) diff --git a/prik/codegen/planner.py b/prik/codegen/planner.py index d6fffff58..ffe66bacf 100644 --- a/prik/codegen/planner.py +++ b/prik/codegen/planner.py @@ -11,7 +11,9 @@ from __future__ import annotations from collections import Counter, defaultdict -from dataclasses import replace +from collections.abc import Mapping +from dataclasses import dataclass, replace +from types import MappingProxyType from prik.semantics import models from prik.semantics.native_array_handles import NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER @@ -22,6 +24,7 @@ CallbackHandoffPolicy, CallbackResultPolicy, CallbackTransferPolicy, + ClassMethodPolicy, DeclarationCallablePolicy, ClassSurfacePolicy, DerivedCallPolicy, @@ -135,6 +138,101 @@ } +@dataclass(frozen=True) +class _ClassPolicyEntry: + """Organize the completed policies and callable declarations for one class. + + ``semantic_class`` is the source-ordered semantic declaration. The two + policy fields are the final post-IR decisions consumed by planning. The + owner-path maps connect constructor, method, and overload policy references + back to their semantic callables without reconstructing those relationships + in each planning helper. + + For example, an entry for ``geometry.Point`` maps the constructor path + ``geometry.Point.__init__`` to its ``SemanticMethod`` and an overload path + such as ``geometry.Point.move.move_real`` to its concrete function. + """ + + semantic_class: models.SemanticClass + derived_policy: DerivedTypePolicy + surface_policy: ClassSurfacePolicy + methods_by_owner_path: Mapping[str, models.SemanticMethod] + method_policies_by_owner_path: Mapping[str, ClassMethodPolicy] + overload_functions_by_owner_path: Mapping[str, models.SemanticFunction] + + @classmethod + def from_semantic_class(cls, semantic_class: models.SemanticClass) -> _ClassPolicyEntry: + """Build one entry from a class whose post-IR policy is complete. + + The completed accessors fail closed when the class is unsupported or + incomplete. Otherwise this method associates the class's method and + overload declarations with the stable owner paths already recorded by + its surface policy. It organizes existing policy and does not infer or + modify any semantic decision. + """ + derived_policy = completed_derived_type_policy(semantic_class) + surface_policy = completed_class_surface_policy(semantic_class) + owner_path = surface_policy.owner_path + return cls( + semantic_class=semantic_class, + derived_policy=derived_policy, + surface_policy=surface_policy, + methods_by_owner_path=MappingProxyType( + {f"{owner_path}.{method.name}": method for method in semantic_class.methods} + ), + method_policies_by_owner_path=MappingProxyType( + {method.owner_path: method for method in surface_policy.methods} + ), + overload_functions_by_owner_path=MappingProxyType( + { + f"{owner_path}.{overload.name}.{procedure.name}": procedure + for overload in semantic_class.overload_sets + for procedure in overload.procedures + } + ), + ) + + +@dataclass(frozen=True) +class _ClassPolicyCatalog: + """Organize completed class policies for one wrapper-planning operation. + + ``entries`` contains every public semantic class in stable depth-first + source order. Each entry joins the semantic declaration to its completed + derived-type policy, Python class-surface policy, and callable owner-path + maps so later planning helpers share one organized view. + + For example, the ``point`` entry supplies both its completed class surface + and the constructor callable selected by that surface. The catalog is a + read-only planning view; it neither owns nor completes semantic policy. + """ + + entries: tuple[_ClassPolicyEntry, ...] + + @classmethod + def from_module(cls, module: models.SemanticModule) -> _ClassPolicyCatalog: + """Collect one module's completed class policies in source order. + + The method recursively visits nested classes, creates one catalog entry + per public declaration, and gives planning one shared collection. For a + module containing public ``point`` followed by ``circle``, the returned + ``entries`` tuple preserves exactly that order. + """ + entries = tuple( + _ClassPolicyEntry.from_semantic_class(semantic_class) + for semantic_class in cls._semantic_classes(module.classes) + if semantic_class.visibility == "public" + ) + return cls(entries=entries) + + @classmethod + def _semantic_classes(cls, classes: list[models.SemanticClass]): + """Yield top-level and nested semantic classes in depth-first source order.""" + for semantic_class in classes: + yield semantic_class + yield from cls._semantic_classes(semantic_class.classes) + + class WrapperPlanner(ClassVisitor): """Project a policy-completed semantic module into an editable ``ModulePlan``. @@ -204,13 +302,28 @@ def _visit_SemanticModule(self, module: models.SemanticModule) -> ModulePlan: required_headers=self._required_headers(namespaces), ) - def _namespace_member_plans(self, module: models.SemanticModule) -> tuple[dict, dict, dict, dict, dict]: - """Build namespace-owned plan maps before linking private callables.""" + def _namespace_member_plans( + self, + module: models.SemanticModule, + ) -> tuple[dict, dict, dict, dict, dict]: + """Build namespace-owned plan maps from one shared class-policy catalog. + + Direct functions and variables are projected first. The local catalog + then organizes each public class once so derived-type and Python-class + projections consume the same semantic declaration, completed policies, + and callable owner-path maps. + """ + # Project ordinary module members independently from class-owned surfaces. + functions = self._functions_by_namespace(module) + variables = self._variables_by_namespace(module) + + # Join each class to its completed policies and callables once for both projections. + class_policies = _ClassPolicyCatalog.from_module(module) return ( - self._functions_by_namespace(module), - self._variables_by_namespace(module), - self._derived_types_by_namespace(module), - self._classes_by_namespace(module), + functions, + variables, + self._derived_types_by_namespace(class_policies), + self._classes_by_namespace(module.name, class_policies), self._module_overloads_by_namespace(module), ) @@ -317,15 +430,13 @@ def _derived_backend_symbol(self, type_identity: tuple[str, str]) -> str: # Derived-type definitions, fields, and class surfaces. def _derived_types_by_namespace( self, - module: models.SemanticModule, + class_policies: _ClassPolicyCatalog, ) -> dict[tuple[str, ...], list[DerivedTypePlan]]: """Project opaque types from completed class and field policies.""" grouped = defaultdict(list) - for semantic_class in self._semantic_classes(module.classes): - if semantic_class.visibility != "public": - continue - policy = completed_derived_type_policy(semantic_class) - surface = completed_class_surface_policy(semantic_class) + for entry in class_policies.entries: + policy = entry.derived_policy + surface = entry.surface_policy exports_by_namespace = defaultdict(list) for export in policy.python_exports: exports_by_namespace[export.namespace].append(export.name) @@ -365,60 +476,49 @@ def _derived_type_plan( # Generated class surfaces compose Phase 8 types and ordinary function plans. def _classes_by_namespace( self, - module: models.SemanticModule, + module_name: str, + class_policies: _ClassPolicyCatalog, ) -> dict[tuple[str, ...], list[ClassSurfacePlan]]: """Project completed class surfaces into their public namespaces.""" grouped = defaultdict(list) - for semantic_class in self._semantic_classes(module.classes): - if semantic_class.visibility != "public": - continue - policy = completed_class_surface_policy(semantic_class) + for entry in class_policies.entries: + policy = entry.surface_policy exports_by_namespace = defaultdict(list) for export in policy.python_exports: exports_by_namespace[export.namespace].append(export.name) for namespace, python_names in exports_by_namespace.items(): grouped[namespace].append( self._class_surface_plan( - module.name, + module_name, namespace, - semantic_class, - policy, + entry, tuple(python_names), ) ) return grouped - @staticmethod - def _semantic_classes(classes: list[models.SemanticClass]): - """Yield classes in source order while retaining nested declarations.""" - for semantic_class in classes: - yield semantic_class - yield from WrapperPlanner._semantic_classes(semantic_class.classes) - def _class_surface_plan( self, module_name: str, namespace: tuple[str, ...], - semantic_class: models.SemanticClass, - policy: ClassSurfacePolicy, + entry: _ClassPolicyEntry, python_names: tuple[str, ...], ) -> ClassSurfacePlan: """Compose one class plan from completed method and constructor facts.""" - methods = self._class_method_plans(module_name, namespace, semantic_class, policy) + policy = entry.surface_policy + methods = self._class_method_plans(module_name, namespace, entry) overloads_by_name = {overload.python_name: overload for overload in policy.overloads} overloads = self._class_overload_plans( module_name, namespace, - semantic_class, - policy.type_identity, + entry, overloads_by_name, ) fields = tuple(self._derived_field_plan(field) for field in policy.effective_fields) constructor = self._constructor_plan( module_name, namespace, - semantic_class, - policy, + entry, overloads_by_name, python_name=python_names[0], fields=fields, @@ -446,17 +546,17 @@ def _class_method_plans( self, module_name: str, namespace: tuple[str, ...], - semantic_class: models.SemanticClass, - policy: ClassSurfacePolicy, + entry: _ClassPolicyEntry, ) -> tuple[ClassMethodPlan, ...]: """Link public methods in source order.""" - methods_by_owner = self._class_methods_by_owner(policy) + semantic_class = entry.semantic_class + policy = entry.surface_policy methods = [] for method in semantic_class.methods: if method.name == "__init__": continue owner_path = f"{policy.owner_path}.{method.name}" - method_policy = methods_by_owner[owner_path] + method_policy = entry.method_policies_by_owner_path[owner_path] if not method_policy.public: continue methods.append( @@ -470,28 +570,24 @@ def _class_method_plans( ) return tuple(methods) - @staticmethod - def _class_methods_by_owner(policy: ClassSurfacePolicy) -> dict[str, object]: - """Index completed method records by their stable semantic owner path.""" - return {method.owner_path: method for method in policy.methods} - def _class_overload_plans( self, module_name: str, namespace: tuple[str, ...], - semantic_class: models.SemanticClass, - type_identity: tuple[str, str], + entry: _ClassPolicyEntry, policies: dict[str, OverloadPolicy], ) -> tuple[OverloadPlan, ...]: """Link every non-constructor overload set to ordinary function plans.""" - functions = self._class_overload_functions(semantic_class, policies.values()) return tuple( self._overload_plan( module_name, namespace, policy, - functions, - private_name=lambda name, index: self._class_callable_name(type_identity, f"{name}_{index}"), + entry.overload_functions_by_owner_path, + private_name=lambda name, index: self._class_callable_name( + entry.surface_policy.type_identity, + f"{name}_{index}", + ), ) for policy in policies.values() if policy.python_name != "__init__" @@ -501,26 +597,24 @@ def _constructor_plan( self, module_name: str, namespace: tuple[str, ...], - semantic_class: models.SemanticClass, - policy: ClassSurfacePolicy, + entry: _ClassPolicyEntry, overloads_by_name: dict, *, python_name: str, fields: tuple[DerivedFieldPlan, ...], ) -> ConstructorPlan: """Link one completed constructor to its target and lifecycle records.""" + policy = entry.surface_policy constructor = policy.constructor target = self._bound_constructor_target_plan( module_name, namespace, - semantic_class, - policy, + entry, ) overload = self._constructor_overload_plan( module_name, namespace, - semantic_class, - policy.type_identity, + entry, overloads_by_name, ) plan = ConstructorPlan( @@ -540,17 +634,14 @@ def _bound_constructor_target_plan( self, module_name: str, namespace: tuple[str, ...], - semantic_class: models.SemanticClass, - policy: ClassSurfacePolicy, + entry: _ClassPolicyEntry, ) -> FunctionPlan | None: """Project the direct constructor call selected by completed policy.""" + policy = entry.surface_policy target_path = policy.constructor.target_owner_path if target_path is None: return None - method = next( - (item for item in semantic_class.methods if f"{policy.owner_path}.{item.name}" == target_path), - None, - ) + method = entry.methods_by_owner_path.get(target_path) if method is None: return None return self._function_plan( @@ -567,20 +658,21 @@ def _constructor_overload_plan( self, module_name: str, namespace: tuple[str, ...], - semantic_class: models.SemanticClass, - type_identity: tuple[str, str], + entry: _ClassPolicyEntry, policies: dict[str, OverloadPolicy], ) -> OverloadPlan | None: """Return the constructor-owned overload set, when one was completed.""" policy = policies.get("__init__") if policy is not None: - functions = self._class_overload_functions(semantic_class, (policy,)) return self._overload_plan( module_name, namespace, policy, - functions, - private_name=lambda name, index: self._class_callable_name(type_identity, f"{name}_{index}"), + entry.overload_functions_by_owner_path, + private_name=lambda name, index: self._class_callable_name( + entry.surface_policy.type_identity, + f"{name}_{index}", + ), ) return None @@ -672,37 +764,6 @@ def _overload_plan( plan.docstring = self.docstrings.overload(plan) return plan - @staticmethod - def _class_overload_functions( - semantic_class: models.SemanticClass, - policies, - ) -> dict[str, models.SemanticFunction]: - """Index only concrete class procedures selected by completed overload policy.""" - selected = WrapperPlanner._selected_overload_owner_paths(policies) - owner_path = completed_class_surface_policy(semantic_class).owner_path - return { - path: procedure - for path, procedure in WrapperPlanner._class_overload_entries(semantic_class, owner_path) - if path in selected - } - - @staticmethod - def _selected_overload_owner_paths(policies) -> set[str]: - """Return concrete owner paths referenced by completed overloads.""" - return {candidate.owner_path for policy in policies for candidate in policy.candidates} - - @staticmethod - def _class_overload_entries( - semantic_class: models.SemanticClass, - owner_path: str, - ) -> tuple[tuple[str, models.SemanticFunction], ...]: - """Pair every concrete class procedure with its completed owner path.""" - return tuple( - (f"{owner_path}.{overload.name}.{procedure.name}", procedure) - for overload in semantic_class.overload_sets - for procedure in overload.procedures - ) - def _class_callable_name(self, type_identity: tuple[str, str], name: str) -> str: """Return one private callable export fixed during plan construction.""" return f"_prik_class_{self._derived_backend_symbol(type_identity)}_{name.casefold()}" diff --git a/prik/semantics/policy_completion.py b/prik/semantics/policy_completion.py index 17e454922..cd7962b17 100644 --- a/prik/semantics/policy_completion.py +++ b/prik/semantics/policy_completion.py @@ -177,6 +177,9 @@ def _complete_ownership_policies( # Resolve identities before any class, field, or callable policy uses them. _complete_local_derived_type_identities(module) + # Reuse one source-ordered class population while later phases replace its policies. + class_nodes = tuple(_iter_semantic_classes(module.classes)) + # Complete persistent module state and its accessors first. for variable in module.variables: _complete_variable(variable, OwnershipContext.module_variable()) @@ -186,20 +189,20 @@ def _complete_ownership_policies( for semantic_class in module.classes: class_scope = str(semantic_class.origin.native_scope or module.name) _complete_class(semantic_class, f"{class_scope}.{semantic_class.name}") - derived_types = _complete_derived_type_graph_policies(module.classes) + derived_types = _complete_derived_type_graph_policies(class_nodes) _complete_class_surface_policies( - module.classes, + class_nodes, derived_types, strict_wrapper_names=strict_wrapper_names, ) - polymorphic_variants = _polymorphic_variant_map(module.classes) + polymorphic_variants = _polymorphic_variant_map(class_nodes) _complete_class_method_policies( - module.classes, + class_nodes, module.functions, derived_types, polymorphic_variants, ) - _complete_class_overload_policies(module.classes) + _complete_class_overload_policies(class_nodes) # Attach module-variable policies after their type and accessor facts exist. for variable in module.variables: variable_scope = str(variable.origin.native_scope or module.name) @@ -337,32 +340,34 @@ def _complete_class(semantic_class: models.SemanticClass, owner_path: str) -> No def _derived_type_policy_map( - classes: list[models.SemanticClass], + class_nodes: tuple[models.SemanticClass, ...], ) -> dict[tuple[str, str], DerivedTypePolicy]: - """Return completed type policies by canonical semantic identity.""" - policies: dict[tuple[str, str], DerivedTypePolicy] = {} + """Index one ordered class population by completed native type identity. - def collect(semantic_class: models.SemanticClass) -> None: - """Store an already-completed class policy under its canonical identity.""" - policy = semantic_class.metadata.get(models.RESOLVED_DERIVED_TYPE_POLICY_METADATA) - if isinstance(policy, DerivedTypePolicy): - policies[policy.type_identity] = policy - for nested in semantic_class.classes: - collect(nested) - - for semantic_class in classes: - collect(semantic_class) - return policies + ``class_nodes`` is the stable flattened sequence collected for this policy + run. For example, a ``point`` policy with identity ``("geometry", "point")`` + is returned under that tuple for derived-member graph lookup. + """ + return { + policy.type_identity: policy + for semantic_class in class_nodes + for policy in (semantic_class.metadata.get(models.RESOLVED_DERIVED_TYPE_POLICY_METADATA),) + if isinstance(policy, DerivedTypePolicy) + } def _complete_derived_type_graph_policies( - classes: list[models.SemanticClass], + class_nodes: tuple[models.SemanticClass, ...], ) -> dict[tuple[str, str], DerivedTypePolicy]: - """Validate finite derived member graphs after every local type is known.""" - policies = _derived_type_policy_map(classes) + """Validate each ordered class's member graph after all local types are known. - def complete(semantic_class: models.SemanticClass) -> None: - """Complete recursive member-path policies for one class before indexing it.""" + The input is the shared flattened class sequence. Each derived policy is + replaced with its completed graph blockers and the returned identity map is + updated so later classes see the completed version. + """ + policies = _derived_type_policy_map(class_nodes) + + for semantic_class in class_nodes: policy = semantic_class.metadata.get(models.RESOLVED_DERIVED_TYPE_POLICY_METADATA) if isinstance(policy, DerivedTypePolicy): _paths, graph_blockers = derived_member_path_policies(policy, policies) @@ -370,28 +375,29 @@ def complete(semantic_class: models.SemanticClass) -> None: completed = replace(policy, supported=not blockers, blockers=blockers) semantic_class.metadata[models.RESOLVED_DERIVED_TYPE_POLICY_METADATA] = completed policies[completed.type_identity] = completed - for nested in semantic_class.classes: - complete(nested) - - for semantic_class in classes: - complete(semantic_class) return policies def _complete_class_surface_policies( - classes: list[models.SemanticClass], + class_nodes: tuple[models.SemanticClass, ...], derived_types: dict[tuple[str, str], DerivedTypePolicy], *, strict_wrapper_names: bool, ) -> None: - """Complete class orchestration after every derived identity is known.""" + """Complete every ordered class surface after derived identities are known. + + ``class_nodes`` is the shared depth-first sequence for this module. The pass + attaches constructor, method, export, inheritance, and effective-field + policy to each class while ``derived_types`` provides identity-based graph + lookup. + """ identities = { semantic_class.name: policy.type_identity - for semantic_class in _iter_semantic_classes(classes) + for semantic_class in class_nodes for policy in (semantic_class.metadata.get(models.RESOLVED_DERIVED_TYPE_POLICY_METADATA),) if isinstance(policy, DerivedTypePolicy) } - for semantic_class in _iter_semantic_classes(classes): + for semantic_class in class_nodes: derived = semantic_class.metadata.get(models.RESOLVED_DERIVED_TYPE_POLICY_METADATA) if not isinstance(derived, DerivedTypePolicy): continue @@ -414,7 +420,7 @@ def _complete_class_surface_policies( surfaces = { surface.type_identity: (semantic_class, surface) - for semantic_class in _iter_semantic_classes(classes) + for semantic_class in class_nodes for surface in (semantic_class.metadata.get(models.RESOLVED_CLASS_SURFACE_POLICY_METADATA),) if isinstance(surface, ClassSurfacePolicy) } @@ -444,15 +450,20 @@ def effective_fields( def _complete_class_method_policies( - classes: list[models.SemanticClass], + class_nodes: tuple[models.SemanticClass, ...], module_functions: list[models.SemanticFunction], derived_types: dict[tuple[str, str], DerivedTypePolicy], polymorphic_variants: dict[tuple[str, str], tuple[tuple[str, str], ...]], ) -> None: - """Complete methods once from class policy, type graphs, and dispatch sets.""" + """Complete each ordered class's methods from policy and dispatch graphs. + + The shared ``class_nodes`` sequence fixes traversal order; module functions, + derived policies, and polymorphic variants supply the completed targets used + to classify each method invocation. + """ type_bound_targets = _type_bound_target_names(module_functions) module_targets = {str(function.native_name or function.name) for function in module_functions} - for semantic_class in _iter_semantic_classes(classes): + for semantic_class in class_nodes: _complete_one_class_method_policy( semantic_class, type_bound_targets, @@ -670,9 +681,14 @@ def _native_type_bound_binding_name(method: models.SemanticMethod) -> str: return name -def _complete_class_overload_policies(classes: list[models.SemanticClass]) -> None: - """Attach exact runtime predicates after concrete calls are completed.""" - for semantic_class in _iter_semantic_classes(classes): +def _complete_class_overload_policies(class_nodes: tuple[models.SemanticClass, ...]) -> None: + """Attach exact runtime predicates to every ordered class overload. + + This final class phase reads the already-completed concrete call policies + for ``class_nodes`` and replaces each surface with its completed overload + candidates and blockers. + """ + for semantic_class in class_nodes: surface = semantic_class.metadata.get(models.RESOLVED_CLASS_SURFACE_POLICY_METADATA) if not isinstance(surface, ClassSurfacePolicy): continue @@ -832,12 +848,17 @@ def _iter_semantic_classes(classes: list[models.SemanticClass]): def _polymorphic_variant_map( - classes: list[models.SemanticClass], + class_nodes: tuple[models.SemanticClass, ...], ) -> dict[tuple[str, str], tuple[tuple[str, str], ...]]: - """Enumerate known extensions from completed base identities, most-derived first.""" + """Enumerate known extensions from ordered class surfaces, most-derived first. + + For each identity in ``class_nodes``, the result contains that type and every + transitive extension, ordered from the last discovered derived class back to + its base for runtime dispatch. + """ surfaces = tuple( surface - for semantic_class in _iter_semantic_classes(classes) + for semantic_class in class_nodes for surface in (semantic_class.metadata.get(models.RESOLVED_CLASS_SURFACE_POLICY_METADATA),) if isinstance(surface, ClassSurfacePolicy) ) diff --git a/prik/semantics/wrapper_policy.py b/prik/semantics/wrapper_policy.py index badf8fc78..e2a133582 100644 --- a/prik/semantics/wrapper_policy.py +++ b/prik/semantics/wrapper_policy.py @@ -3261,115 +3261,215 @@ def _direct_result_policy(context: _FunctionPolicyContext) -> _ResultPolicyCandi def _hidden_result_policies(context: _FunctionPolicyContext) -> tuple[_ResultPolicyCandidate, ...]: - """Return hidden-output candidates using one function-wide policy context. - - Each hidden projected argument produces a candidate containing either its - completed result policy or ``None`` plus the blockers that prevented it. - Runtime-status outputs are omitted because their completed status policy - consumes them instead of exposing them as ordinary Python results. + """Coordinate hidden-output selection and policy construction. + + A source-generated hidden output is normally a nonoptional Fortran dummy + such as ``integer, intent(out) :: status``. Native code receives writable + storage for that dummy, but Python does not pass an argument; the wrapper + returns the written value instead. Not every ``intent(out)`` dummy is + hidden—for example, ordinary output arrays can remain visible—and edited + semantic contracts can express the same role directly with ``Return(...)``. + Therefore this stage recognizes the completed policy facts + ``projects_result=True`` and ``python_visible=False`` rather than reading + Fortran ``intent`` again. + + The context supplies one completed semantic function. This coordinator + indexes its result projections, omits arguments reserved for runtime-status + handling, and asks ``_hidden_result_candidate`` to complete each remaining + hidden output. For example, a subroutine with hidden ``value`` and + ``status`` outputs returns only the ``value`` candidate when ``status`` is + consumed by ``Raises(...)``. """ function = context.function - owner_path = context.owner_path - derived_types = context.derived_types - policies = [] + + # Index result mappings once so each hidden argument has a direct lookup; + # first-entry-wins preserves the previous ``next(...)`` behavior. + projections = _hidden_result_projection_index(function) + + # Resolve outputs owned by runtime error handling before ordinary result + # selection so status and message values are not exposed twice. suppressed_outputs = _runtime_status_output_owner_paths(function) + + policies = [] for argument in function.arguments: - decision = _ownership_decision(argument, models.RESOLVED_OWNERSHIP_POLICY_METADATA) - if decision is None or not (decision.projects_result and not decision.python_visible): - continue - if f"{owner_path}.{argument.name}" in suppressed_outputs: - continue - mapping = next( - ( - item - for item in function.projection - if item.result_position is not None and item.python_name == argument.name - ), - None, - ) - if mapping is None: - policies.append( - _ResultPolicyCandidate( - None, - (f"hidden result {argument.name!r} has no completed return projection",), - ) - ) + # Select only non-visible projected arguments that remain ordinary + # Python results after runtime-status outputs have been removed. + decision = _hidden_result_ownership(context, argument, suppressed_outputs) + if decision is None: continue - blockers = _hidden_result_blockers(argument, decision, mapping) - native_array_handle = _native_array_handle_wrapper_policy( - argument.semantic_type, - argument.metadata.get(models.RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA), - f"{owner_path}.{argument.name}", - ) - scalar_descriptor = _scalar_descriptor_result_policy( - argument.semantic_type, - decision, - descriptor_kind=mapping.value_kind, - ) - bridge_data_action, bridge_copy_reason = _native_result_bridge_data_action( - argument.semantic_type, - descriptor_kind=mapping.value_kind, - ) - bridge_data_action, bridge_copy_reason = _logical_argument_bridge_action( - argument, - decision, - bridge_data_action, - bridge_copy_reason, - ) - if bridge_data_action is BridgeDataAction.BLOCKED and decision.kind is not ObjectKind.SCALAR: - blockers = (*blockers, f"hidden result {argument.name!r} has no completed bridge data action") - derived = _derived_handoff_policy( - argument.semantic_type, - decision, - owner_path=f"{owner_path}.{argument.name}", - origin=DerivedObjectOrigin.WRAPPER_RESULT, - derived_types=derived_types, - ) - blockers = ( - *blockers, - *_derived_type_definition_blockers( - f"hidden result {argument.name!r}", - derived, - derived_types, - ), - *_allocatable_holder_field_blockers( - f"hidden result {argument.name!r}", - derived, - derived_types, - required=bool(derived is not None and derived.storage is DerivedObjectStorage.ALLOCATABLE_HOLDER), - ), - ) + + # Complete the selected argument from its indexed projection and keep + # any failure beside the candidate that produced it. policies.append( - _ResultPolicyCandidate( - ResultPolicy( - owner_path=f"{owner_path}.{argument.name}", - semantic_type_name=argument.semantic_type.name, - rank=int(argument.semantic_type.rank or 0), - direct_result_abi=DirectResultABI.NOT_APPLICABLE, - ownership=decision, - codegen_action=decision.codegen_action, - python_barrier_action=decision.python_barrier_action, - native_barrier_action=decision.native_barrier_action, - storage_mode=decision.storage_mode, - boundary_storage_mode=decision.boundary_storage_mode or decision.storage_mode, - bridge_data_action=bridge_data_action, - bridge_copy_reason=bridge_copy_reason, - character_length=_character_length(argument.semantic_type), - array=_array_handoff_policy(argument.semantic_type), - source_kind="hidden_output", - native_name=mapping.native_name or argument.name, - native_position=mapping.native_position, - result_position=int(mapping.result_position), - native_array_handle=native_array_handle, - scalar_descriptor=scalar_descriptor, - derived=derived, - ), - tuple(blockers), + _hidden_result_candidate( + context, + argument, + decision, + projections.get(argument.name), ) ) return tuple(policies) +def _hidden_result_projection_index( + function: models.SemanticFunction, +) -> dict[str, models.ProjectionMapping]: + """Index the first named result projection for each hidden argument. + + ``function.projection`` may mix arguments, literals, and results. A result + mapping represents hidden native output storage—commonly an ``intent(out)`` + dummy—through ``Return(...)``. This + helper keeps mappings with both a Python name and result position, keyed by + that name, and preserves the first match used by the former linear lookup. + For example, ``Return('value')`` becomes ``{'value': mapping}``, while an + ordinary ``Arg(0)`` mapping is omitted. + """ + projections: dict[str, models.ProjectionMapping] = {} + for mapping in function.projection: + if mapping.result_position is None or not isinstance(mapping.python_name, str): + continue + projections.setdefault(mapping.python_name, mapping) + return projections + + +def _hidden_result_ownership( + context: _FunctionPolicyContext, + argument: models.SemanticArgument, + suppressed_outputs: frozenset[str], +) -> OwnershipDecision | None: + """Return ownership only when an argument is an exposed hidden result. + + The helper receives one possible native output dummy and the owner paths + reserved by runtime status handling. Source parsing may originally have + classified that dummy from ``intent(out)``, but this policy stage requires + the completed, source-independent ownership facts + ``projects_result=True`` and ``python_visible=False``, then rejects a + reserved path. For example, hidden ``value`` returns its decision, while + hidden ``status`` returns ``None`` when ``module.proc.status`` appears in + ``suppressed_outputs``. + """ + decision = _ownership_decision(argument, models.RESOLVED_OWNERSHIP_POLICY_METADATA) + if decision is None or not (decision.projects_result and not decision.python_visible): + return None + if f"{context.owner_path}.{argument.name}" in suppressed_outputs: + return None + return decision + + +def _hidden_result_candidate( + context: _FunctionPolicyContext, + argument: models.SemanticArgument, + decision: OwnershipDecision, + mapping: models.ProjectionMapping | None, +) -> _ResultPolicyCandidate: + """Build one hidden result and retain every blocker found while doing so. + + ``argument`` and ``decision`` identify a selected hidden native output + dummy, commonly a scalar or allocatable ``intent(out)`` argument; + ``mapping`` + supplies its native and Python result positions. The helper completes + descriptor, bridge, logical, and derived handoffs before returning a + ``ResultPolicy``. For example, hidden ``doubled`` at native position 1 and + result position 0 becomes a ``source_kind='hidden_output'`` candidate; a + missing mapping instead returns ``policy=None`` with a projection blocker. + """ + if mapping is None: + return _ResultPolicyCandidate( + None, + (f"hidden result {argument.name!r} has no completed return projection",), + ) + + owner_path = f"{context.owner_path}.{argument.name}" + label = f"hidden result {argument.name!r}" + + # Validate the completed ownership family and result positions before + # constructing backend-neutral descriptor and bridge details. + blockers = _hidden_result_blockers(argument, decision, mapping) + + # Complete persistent descriptor behavior for allocatable or pointer + # arrays; ordinary scalar and array results receive ``None`` here. + native_array_handle = _native_array_handle_wrapper_policy( + argument.semantic_type, + argument.metadata.get(models.RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA), + owner_path, + ) + + # Preserve nullable rank-zero descriptor state separately from normal + # scalar results, using the mapping's descriptor kind as the ABI selector. + scalar_descriptor = _scalar_descriptor_result_policy( + argument.semantic_type, + decision, + descriptor_kind=mapping.value_kind, + ) + + # Select result data movement and then apply any native logical-kind + # adaptation required for the same hidden argument. + bridge_data_action, bridge_copy_reason = _native_result_bridge_data_action( + argument.semantic_type, + descriptor_kind=mapping.value_kind, + ) + bridge_data_action, bridge_copy_reason = _logical_argument_bridge_action( + argument, + decision, + bridge_data_action, + bridge_copy_reason, + ) + if bridge_data_action is BridgeDataAction.BLOCKED and decision.kind is not ObjectKind.SCALAR: + blockers = (*blockers, f"{label} has no completed bridge data action") + + # Complete an opaque derived-object handoff only when the semantic type is + # a wrapped derived type; primitive and ordinary array results use ``None``. + derived = _derived_handoff_policy( + argument.semantic_type, + decision, + owner_path=owner_path, + origin=DerivedObjectOrigin.WRAPPER_RESULT, + derived_types=context.derived_types, + ) + + # Require the referenced derived definition and any allocatable-holder + # member support before exposing this candidate to wrapper planning. + blockers = ( + *blockers, + *_derived_type_definition_blockers(label, derived, context.derived_types), + *_allocatable_holder_field_blockers( + label, + derived, + context.derived_types, + required=bool(derived is not None and derived.storage is DerivedObjectStorage.ALLOCATABLE_HOLDER), + ), + ) + + # Store the completed selections in the immutable result record consumed + # mechanically by wrapper planning and both generated backends. + return _ResultPolicyCandidate( + ResultPolicy( + owner_path=owner_path, + semantic_type_name=argument.semantic_type.name, + rank=int(argument.semantic_type.rank or 0), + direct_result_abi=DirectResultABI.NOT_APPLICABLE, + ownership=decision, + codegen_action=decision.codegen_action, + python_barrier_action=decision.python_barrier_action, + native_barrier_action=decision.native_barrier_action, + storage_mode=decision.storage_mode, + boundary_storage_mode=decision.boundary_storage_mode or decision.storage_mode, + bridge_data_action=bridge_data_action, + bridge_copy_reason=bridge_copy_reason, + character_length=_character_length(argument.semantic_type), + array=_array_handoff_policy(argument.semantic_type), + source_kind="hidden_output", + native_name=mapping.native_name or argument.name, + native_position=mapping.native_position, + result_position=int(mapping.result_position), + native_array_handle=native_array_handle, + scalar_descriptor=scalar_descriptor, + derived=derived, + ), + tuple(blockers), + ) + + def _native_call_slot_policies( context: _FunctionPolicyContext, ) -> tuple[dict[int, int], tuple[NativeCallSlotPolicy, ...], tuple[str, ...]]: diff --git a/tests/fortran/infrastructure/codegen/test_planner.py b/tests/fortran/infrastructure/codegen/test_planner.py index 03a40f8c1..aa37b185a 100644 --- a/tests/fortran/infrastructure/codegen/test_planner.py +++ b/tests/fortran/infrastructure/codegen/test_planner.py @@ -13,6 +13,7 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.semantics.models import PYTHON_EXPORTS_METADATA from prik.semantics.policy_completion import complete_semantic_policies +from prik.codegen.planner import _ClassPolicyCatalog from prik.codegen import ( WrapperCodeGenerator, WrapperPlanner, @@ -136,6 +137,33 @@ def hidden(x: Int32) -> Int32: ... assert [function.binding.python_name for function in plan.namespaces[0].functions] == ["visible"] +def test_class_policy_catalog_organizes_nested_classes_and_callable_owner_paths(): + module = parse_pyi_text( + """ +class outer: + class inner: + @native_call([Pass(), Addr(Arg(0))]) + def shift(self, dx: Float64) -> None: ... + + @overload("shift") + def move(self, dx: Float64) -> None: ... +""", + module_name="nested_catalog", + ) + complete_semantic_policies(module) + + catalog = _ClassPolicyCatalog.from_module(module) + outer, inner = catalog.entries + + assert tuple(entry.semantic_class.name for entry in catalog.entries) == ("outer", "inner") + assert inner.methods_by_owner_path["nested_catalog.outer.inner.shift"].name == "shift" + assert inner.method_policies_by_owner_path["nested_catalog.outer.inner.shift"].python_name == "shift" + assert inner.overload_functions_by_owner_path["nested_catalog.outer.inner.move.shift"].name == "shift" + + with pytest.raises(TypeError): + inner.methods_by_owner_path["nested_catalog.outer.inner.shift"] = outer.semantic_class + + def test_planner_projects_required_array_buffer_policy(): module = parse_pyi_text( """ diff --git a/tests/fortran/infrastructure/semantics/test_wrapper_policy.py b/tests/fortran/infrastructure/semantics/test_wrapper_policy.py index bf7d7ae83..45950e965 100644 --- a/tests/fortran/infrastructure/semantics/test_wrapper_policy.py +++ b/tests/fortran/infrastructure/semantics/test_wrapper_policy.py @@ -35,6 +35,7 @@ NativeStatusErrorPolicy, OptionalMode, PythonExceptionKind, + build_function_wrapper_policy, completed_function_wrapper_policy, ) @@ -107,6 +108,45 @@ def hidden_storage_result() -> Float64[()]: ... assert hidden_policy.native_call_slots[0].native_barrier_action is hidden.native_barrier_action +def test_hidden_result_policy_reports_a_missing_return_projection_after_selection(): + module = parse_pyi_text( + """ +@native_call([Return("status", 0)]) +def hidden_status() -> Int32: ... +""", + module_name="missing_hidden_projection", + ) + complete_semantic_policies(module) + function = module.functions[0] + + # Preserve the completed hidden-output ownership decision while removing + # its result mapping to characterize the candidate builder's fail-closed path. + function.projection = [] + policy = build_function_wrapper_policy( + function, + owner_path="missing_hidden_projection.hidden_status", + ) + + assert policy.results == () + assert "hidden result 'status' has no completed return projection" in policy.blockers + + +def test_hidden_result_policy_keeps_blocked_bridge_action_on_the_candidate(): + module = parse_pyi_text( + """ +@native_call([Return("message", 0)]) +def hidden_message() -> String: ... +""", + module_name="blocked_hidden_bridge", + ) + complete_semantic_policies(module) + + policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + + assert policy.results[0].bridge_data_action is BridgeDataAction.BLOCKED + assert "hidden result 'message' has no completed bridge data action" in policy.blockers + + def test_external_declaration_mode_is_completed_from_native_abi_requirements(): module = parse_pyi_text( """ From 8d8319c9d061f393d3f5a732674d7451ed91c555 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 06:48:59 +0100 Subject: [PATCH 13/22] Documented the completed ownership vocabulary, lifetime-policy philosophy, + pointer-policy boundary --- CHANGELOG.md | 3 + .../ownership-tracking.md | 289 +++++++++++- .../documentation-content-checklist.md | 6 +- prik/semantics/ownership.py | 421 ++++++++++++++++-- .../semantics/test_ownership.py | 52 +++ 5 files changed, 726 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dca1d67e8..cc4379909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ release tags add a leading `v` to the package version. ### Changed +- Documented the completed ownership vocabulary, lifetime-policy philosophy, + pointer-policy boundary, and maintainer change routes in one maintained + architecture reference. - Moved exact overload selection from generated Python predicate chains to generated C dispatchers with planned candidate IDs and direct switch-based calls to the selected existing wrapper. diff --git a/docs/maintainer/internal-architecture/ownership-tracking.md b/docs/maintainer/internal-architecture/ownership-tracking.md index b51523de5..dd6edeb7d 100644 --- a/docs/maintainer/internal-architecture/ownership-tracking.md +++ b/docs/maintainer/internal-architecture/ownership-tracking.md @@ -2,17 +2,292 @@ title: Ownership Tracking audience: maintainers prerequisites: runtime layer, memory ownership model -related: runtime-layer.md, error-handling-pipeline.md -status: planned-documentation +related: runtime-layer.md, wrapper-generation-pipeline.md, ../../user/guide/memory-management.md +status: maintained publication: draft --- # Ownership Tracking -Reserved maintainer page for ownership policy resolution, transfer actions, -destruction, borrowed views, and finalization. +PRIK represents ownership as a completed semantic contract, not as one label +such as "owned" or "borrowed." A value is lowering-ready only after policy +completion answers separate questions about its representation, storage owner, +boundary transfer, release responsibility, storage form, and generated actions. -## TODO +This separation is deliberate. A NumPy view may be a Python object while its +buffer remains native-owned; a generated Python object may control a +wrapper-owned native instance; and a caller-owned array may be mutated in +place without transferring ownership. Combining those cases under one boolean +would make cleanup and alias behavior ambiguous. -- TODO: Document ownership policy entrypoints and dispatch tables. -- TODO: Link each ownership action to generated code and runtime tests. +The canonical pipeline is: + +```text +semantic type + semantic use context + explicit metadata + -> OwnershipPolicyResolver + -> immutable OwnershipDecision + -> post-IR policy validation + -> wrapper plan + -> binding and bridge lowering +``` + +The binding and bridge generators consume completed actions. They must not +reconstruct ownership from datatype, source `intent`, rank, allocation flags, +or local memory checks. + +## The Three Lifetime Questions + +Read the central lifetime triple from left to right: + +1. `OwnershipOwner` says who owns the represented storage. +2. `TransferMode` says how the value or storage relationship crosses the + Python/native boundary. +3. `DestructionPolicy` says who releases any owned resource. + +For example: + +```text +PYTHON + COPY_RETURN + PYTHON_REFCOUNT +``` + +means that native output is copied into independent Python-owned storage and +that ordinary Python or NumPy lifetime releases that copy. In contrast: + +```text +NATIVE + BORROWED_VIEW + NATIVE_OWNER +``` + +means that Python observes live native storage without owning or releasing it. +The resolver accepts only implemented triples and converts contradictory or +unsupported combinations into an explicit blocked decision. + +## Completed Policy Vocabulary + +### Object kind + +`ObjectKind` selects the Python-facing representation family before lifetime +or lowering actions are chosen. + +| Value | Meaning | +| --- | --- | +| `SCALAR` | An ordinary scalar Python value or scalar storage cell. | +| `STRING` | A Python string value or mutable native character storage. | +| `NUMPY_ARRAY` | NumPy-compatible array storage, including descriptor-backed arrays. | +| `DERIVED_TYPE` | An opaque native object represented through a generated wrapper. | + +### Storage owner + +| Value | Meaning | +| --- | --- | +| `PYTHON` | Python, NumPy, or a Python-owned capsule owns the represented value or buffer. | +| `CALLER` | The caller supplied the object and retains ownership across the call. | +| `NATIVE` | A Fortran module or another native owner keeps the storage alive and releases it. | +| `WRAPPER` | A generated wrapper or handle owns or controls the native resource. | +| `TEMPORARY` | Generated call-local storage exists only for the current invocation. | +| `UNKNOWN` | No safe owner is known; this is used by fail-closed decisions. | + +### Boundary transfer + +| Value | Meaning | +| --- | --- | +| `BY_VALUE` | An independent scalar-like value crosses the boundary. | +| `IN_PLACE` | Native code reads or writes caller-visible storage without replacement. | +| `COPY_RETURN` | Native output is copied or converted into a fresh Python result. | +| `SNAPSHOT_COPY` | Python receives a detached copy of current native state. | +| `BORROWED_VIEW` | Python observes storage owned elsewhere without taking ownership. | +| `CALL_LOCAL` | Storage or an association exists only for one wrapped call. | +| `WRAPPER_INSTANCE` | Python receives an object that owns or controls a native instance. | +| `BLOCKED` | No supported safe transfer exists; generation must stop. | + +### Destruction responsibility + +| Value | Meaning | +| --- | --- | +| `PYTHON_REFCOUNT` | Python, NumPy, or a Python-owned capsule releases the resource. | +| `CALLER` | The caller retains release responsibility; PRIK must not destroy the object. | +| `WRAPPER_DEALLOC` | A generated wrapper or handle deallocator releases the native resource. | +| `NATIVE_OWNER` | The independent native owner releases the storage. | +| `CALL_LOCAL` | Generated cleanup releases a temporary before the wrapper call ends. | +| `NONE` | This boundary value creates no resource that PRIK must release. | +| `BLOCKED` | Release responsibility is unsafe, contradictory, or unimplemented. | + +`NONE` does not mean that the value has no storage. It means that this wrapper +boundary did not create an owned resource requiring a release action. For +example, a wrapper-owned derived input can use `CALL_LOCAL + NONE` because the +existing wrapper remains responsible for its instance. + +### Contract and boundary storage + +`StorageMode` describes where PRIK keeps the contract value and, separately, +its ABI boundary representation. + +| Value | Meaning | +| --- | --- | +| `STACK` | Direct or call-frame storage with no persistent heap allocation. | +| `HEAP` | Storage whose lifetime extends beyond a native stack value. | +| `ALIAS` | A reference to existing storage; no independent value is owned here. | + +Pointers always use alias storage, allocatables use heap storage, and borrowed +array views use alias storage. Those are storage invariants, not backend +guesses. + +### General lowering action + +| Value | Meaning | +| --- | --- | +| `DIRECT_VALUE` | Convert or return a direct independent value. | +| `CALL_LOCAL_INPUT` | Prepare input storage valid only during the call. | +| `IN_PLACE_ARGUMENT` | Pass mutable caller-visible storage through to native code. | +| `IDENTITY_OUTPUT` | Mutate and project the same supplied object rather than replacing it. | +| `COPY_IN_OUT` | Copy immutable Python input into mutable call storage and return its final value. | +| `COPY_OUT` | Materialize native output as a new Python result. | +| `SNAPSHOT_COPY` | Materialize a detached snapshot of persistent native state. | +| `BORROWED_VIEW` | Expose existing owner-controlled storage. | +| `WRAPPER_INSTANCE` | Construct or return a generated native-object wrapper. | +| `BLOCKED` | Reject lowering because the completed policy is unsupported. | + +### Python-to-wrapper barrier + +| Value | Meaning | +| --- | --- | +| `SCALAR_VALUE` | Read an ordinary Python scalar value. | +| `SCALAR_STORAGE` | Read or create addressable scalar storage. | +| `ARRAY_STORAGE` | Validate and use NumPy-compatible array storage. | +| `STRING_VALUE` | Read an immutable Python string value. | +| `STRING_STORAGE` | Use mutable addressable character storage. | +| `RAW_ADDRESS` | Accept an explicit raw-address contract. | +| `WRAPPER_INSTANCE` | Extract an opaque native instance or descriptor from a wrapper. | +| `NONE` | No Python argument crosses this boundary. | +| `BLOCKED` | Reject Python-boundary lowering. | + +### Wrapper-to-native barrier + +| Value | Meaning | +| --- | --- | +| `PASS_VALUE` | Pass the converted value directly. | +| `PASS_CALL_LOCAL_ADDRESS` | Pass the address of wrapper-created call-local storage. | +| `PASS_STORAGE_ADDRESS` | Pass the address of existing mutable storage. | +| `PASS_RAW_ADDRESS` | Forward the explicitly supplied raw address. | +| `PASS_ARRAY_BUFFER` | Pass a validated array data buffer. | +| `PASS_NATIVE_DESCRIPTOR` | Pass a native allocatable or pointer descriptor. | +| `PASS_WRAPPER_ADDRESS` | Pass the opaque address held by a generated object wrapper. | +| `NONE` | No native argument is required for this value. | +| `BLOCKED` | Reject native-boundary lowering. | + +### Assignment and setter actions + +| Axis | Value | Meaning | +| --- | --- | --- | +| `AssignmentMode` | `NONE` | No native assignment is generated. | +| `AssignmentMode` | `VALUE_COPY` | Copy the incoming value into existing native storage. | +| `AssignmentMode` | `ALIAS` | Associate the destination with existing storage. | +| `SetterAction` | `WRITE_THROUGH` | Expose a Python setter that updates native state. | +| `SetterAction` | `REJECT_REPLACEMENT` | Keep the property readable but reject replacing its storage. | +| `SetterAction` | `OMIT` | Do not expose a Python setter. | + +## Supported Triples + +The resolver's validated triples are the authoritative combinations: + +| Owner + transfer + destruction | Typical use | +| --- | --- | +| `PYTHON + BY_VALUE + PYTHON_REFCOUNT` | Scalar result. | +| `PYTHON + COPY_RETURN + PYTHON_REFCOUNT` | Array or string copied into a Python result. | +| `PYTHON + SNAPSHOT_COPY + PYTHON_REFCOUNT` | Detached view of current native state. | +| `CALLER + CALL_LOCAL + NONE` | Read-only caller value used only during the call. | +| `CALLER + CALL_LOCAL + CALL_LOCAL` | Caller-classified value with wrapper-created call storage that needs local cleanup. | +| `CALLER + IN_PLACE + CALLER` | Caller array mutated without ownership transfer. | +| `NATIVE + BORROWED_VIEW + NATIVE_OWNER` | Live module-state view. | +| `WRAPPER + CALL_LOCAL + NONE` | Existing wrapper instance used for one call without creating a resource. | +| `WRAPPER + IN_PLACE + WRAPPER_DEALLOC` | Existing wrapper-controlled storage mutated in place. | +| `WRAPPER + BORROWED_VIEW + WRAPPER_DEALLOC` | Field storage retained by its parent wrapper. | +| `WRAPPER + WRAPPER_INSTANCE + WRAPPER_DEALLOC` | Generated object or owned descriptor handle. | +| `TEMPORARY + CALL_LOCAL + CALL_LOCAL` | Generated bridge temporary. | + +Not every syntactically possible triple is meaningful. Adding a new triple is +a semantic feature: update the resolver validation, completed wrapper policy, +planner validation, backend lowering, documentation, and focused runtime tests +together. + +## Explicit Overrides and Pointer Policy + +`Ownership(...)`, `Transfer(...)`, and `Destruction(...)` override the general +lifetime triple. The resolver normalizes the metadata, applies storage +invariants, and then validates the resulting combination. An override never +bypasses the normal safety gates. + +`PointerPolicy(...)` is a separate descriptor/target contract with ten fields: + +| Field | Question answered | +| --- | --- | +| `nullable` | May the descriptor be unassociated? | +| `transfer` | How is the pointer or target relationship used at the boundary? | +| `target_owner` | Who owns the target allocation? | +| `lifetime` | What proves the target outlives the Python use? | +| `deallocation` | Which target-release operations are permitted? | +| `shape_source` | Where are rank and extents obtained? | +| `contiguity` | What storage-layout guarantee is available? | +| `reassociation` | Which association-changing operations are permitted? | +| `aliasing` | Is the result a live alias, descriptor, or independent copy? | +| `mutability` | May Python or native code modify the target through this path? | + +The strings are retained so contracts can describe project-specific facts; +policy completion still accepts only mechanisms the current wrapper runtime +can implement. Pointer-array module variables, fields, arguments, and results +are descriptor containers. Their container ownership is fixed by their native +location, so `PointerPolicy` governs extraction and descriptor operations +rather than silently replacing that ownership with the general override. + +## Resolution and Validation Order + +`OwnershipPolicyResolver.decide_semantic_type()` performs these stages in +order: + +1. Normalize the semantic type into immutable storage facts. +2. Select a default decision for the object kind and semantic use context. +3. Apply explicit ownership or pointer metadata. +4. Reject unsupported pointer lifetimes and reassociation. +5. Complete immutable-value policy and validate result projection. +6. Validate the owner/transfer/destruction triple. +7. Derive general, Python-barrier, and native-barrier lowering actions. + +This order prevents an explicit annotation from bypassing a later safety +check, and prevents a backend from selecting an easier but semantically +different implementation. + +## Change and Test Routes + +The main source owners are: + +- `prik/semantics/ownership.py`: vocabulary, defaults, overrides, validation, + and completed actions; +- `prik/semantics/policy_completion.py`: attachment of decisions to semantic + variables, functions, fields, classes, and module state; +- `prik/semantics/wrapper_policy.py`: completed wrapper-policy records and + cross-feature validation; +- `prik/codegen/planner.py`: projection into the immutable wrapper plan; +- `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py`: strict + dispatch from planned actions into emitted mechanisms. + +The enums documented on this page are the shared ownership vocabulary defined +in `ownership.py`. Feature-specific completed policies also define narrower +mechanical enums in `wrapper_policy.py`, such as derived-object owner +retention/release and native-array descriptor ownership/release. Those values +refine an already completed ownership decision for one implementation family; +they do not form another competing ownership system. + +Start focused verification in +`tests/fortran/infrastructure/semantics/test_ownership.py`. Add feature-specific +policy and runtime evidence under the owning `tests/fortran//` +directory whenever a decision gains a new observable mechanism. The user-facing +lifetime and stale-view rules remain in +[Memory Management](../../user/guide/memory-management.md). + +## Safety Boundary + +Completed ownership policy makes release responsibility explicit; it does not +make every live view memory-safe. Native deallocation, allocatable +reallocation, pointer reassociation, or owner destruction can invalidate an +existing NumPy view. The generated runtime cannot revoke every previously +exported view, so users must copy data that needs to outlive such a native +change and request a fresh view afterward. diff --git a/docs/maintainer/roadmap/documentation-content-checklist.md b/docs/maintainer/roadmap/documentation-content-checklist.md index 4824e037d..3e95592a0 100644 --- a/docs/maintainer/roadmap/documentation-content-checklist.md +++ b/docs/maintainer/roadmap/documentation-content-checklist.md @@ -151,9 +151,6 @@ PRIK_C_DOCS_END --> - [ ] `docs/maintainer/internal-architecture/runtime-layer.md`: document native support installation, extension initialization, callbacks, cleanup, and shared native state. -- [ ] `docs/maintainer/internal-architecture/ownership-tracking.md`: document ownership - facts, transfer, borrowing, alias storage, destruction, writeback, and setter - exposure. - [ ] `docs/maintainer/internal-architecture/dependency-analysis.md`: document current source ordering, preprocessing dependency facts, generated build plans, and future automatic dependency discovery. @@ -347,6 +344,9 @@ primary placeholder queue. policy reference. - [x] `docs/maintainer/internal-architecture/pipeline-map.md`: maintained pipeline and concept-ownership map. +- [x] `docs/maintainer/internal-architecture/ownership-tracking.md`: maintained + ownership philosophy, completed policy vocabulary, supported lifetime triples, + pointer-policy boundary, validation order, source routes, and safety boundary. - [x] `docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md`: active implementation roadmap for semantic `.pyi` wrapper parity. diff --git a/prik/semantics/ownership.py b/prik/semantics/ownership.py index e0a3623b2..024b928a0 100644 --- a/prik/semantics/ownership.py +++ b/prik/semantics/ownership.py @@ -1,10 +1,38 @@ -"""Complete wrapper boundary and storage policy before codegen lowering. - -This module decides ownership, transfer, destruction, writeback, projection, -nullability, release responsibility, codegen action, and both contract-value -and boundary ``stack``/``heap``/``alias`` storage modes. Bridge and binding -generators consume these decisions through strict dispatch and do not -reconstruct policy from codegen datatypes. +"""Complete lifetime and boundary policy before wrapper planning and lowering. + +Ownership is represented as several independent questions instead of one +``owned`` or ``borrowed`` flag: + +* ``OwnershipOwner`` says who owns the represented storage. +* ``TransferMode`` says how the value or storage relationship crosses the + Python/native boundary. +* ``DestructionPolicy`` says who releases an owned resource. +* ``StorageMode`` says whether PRIK keeps a direct value, heap-backed value, or + alias for both the contract and ABI boundary representations. +* the action enums tell planning and lowering exactly which generated + mechanism to use. + +The separation matters because the Python object and its native storage can +have different owners. For example, +``PYTHON + COPY_RETURN + PYTHON_REFCOUNT`` describes an independent Python +copy, while ``NATIVE + BORROWED_VIEW + NATIVE_OWNER`` describes a live Python +view whose storage remains owned and released by native code. The resolver +accepts only implemented combinations and fails closed when the owner, +transfer, release responsibility, or required lifetime proof is missing. + +``OwnershipPolicyResolver`` first selects defaults from semantic storage and +use context, then applies explicit contract metadata, validates the completed +lifetime triple, and finally derives strict lowering actions. Post-IR policy +completion attaches the resulting immutable ``OwnershipDecision`` before +wrapper planning begins. Bridge and binding generators consume those +decisions; they must not reconstruct semantic policy from datatypes, source +``intent``, rank, alias flags, or local memory checks. + +For example, a normal scalar input commonly resolves to caller-owned, +call-local use with no wrapper release action, while an array result commonly +resolves to a Python-owned copy released by Python reference counting. See +``docs/maintainer/internal-architecture/ownership-tracking.md`` for the full +stage map, supported triples, pointer-policy boundary, and change routes. """ from __future__ import annotations @@ -27,6 +55,11 @@ OWNERSHIP_POLICY_METADATA = "ownership_policy" POINTER_POLICY_METADATA = "pointer_policy" +# PointerPolicy fields answer, in order: whether association may be absent, +# boundary use, target owner, lifetime proof, permitted release, shape source, +# layout guarantee, permitted association change, alias relationship, and +# mutability. String values remain contract facts until policy completion +# validates whether the current runtime implements the requested mechanism. POINTER_POLICY_FIELDS = ( "nullable", "transfer", @@ -52,6 +85,13 @@ class ObjectKind(str, Enum): ``OwnershipPolicyResolver`` chooses a kind before selecting lifetime and ABI actions. Strict lowering dispatchers consume this value as part of their completed-policy key. + + Values: + ``SCALAR`` is an ordinary scalar value or addressable scalar cell. + ``STRING`` is a Python string value or native character storage. + ``NUMPY_ARRAY`` is NumPy-compatible array storage, including native + descriptor-backed arrays. ``DERIVED_TYPE`` is an opaque native object + represented through a generated wrapper. """ SCALAR = "scalar" @@ -61,7 +101,22 @@ class ObjectKind(str, Enum): class OwnershipOwner(str, Enum): - """Name the party responsible for the represented object's storage.""" + """Name the party that owns the represented value or storage. + + Values: + ``PYTHON`` means Python, NumPy, or a Python-owned capsule owns the + value or buffer. ``CALLER`` means the supplied caller object retains + ownership across the call. ``NATIVE`` means an independent Fortran or + external native owner retains the storage. ``WRAPPER`` means a + generated wrapper or handle owns or controls the native resource. + ``TEMPORARY`` means generated storage exists only for the current + call. ``UNKNOWN`` records that no safe owner is known and is used by + fail-closed decisions. + + The owner does not by itself say whether a copy or view crosses the + boundary; read it together with ``TransferMode`` and + ``DestructionPolicy``. + """ PYTHON = "python" CALLER = "caller" @@ -72,7 +127,22 @@ class OwnershipOwner(str, Enum): class TransferMode(str, Enum): - """Describe how a value or storage reference crosses the wrapper boundary.""" + """Describe how a value or storage relationship crosses the boundary. + + Values: + ``BY_VALUE`` passes an independent scalar-like value. ``IN_PLACE`` + lets native code use caller-visible storage without replacement. + ``COPY_RETURN`` copies or converts native output into a fresh Python + result. ``SNAPSHOT_COPY`` detaches a copy of current persistent native + state. ``BORROWED_VIEW`` exposes storage owned elsewhere without + transferring ownership. ``CALL_LOCAL`` keeps storage or association + valid only for one wrapped call. ``WRAPPER_INSTANCE`` returns a + generated object that owns or controls a native instance. ``BLOCKED`` + states that no supported safe transfer exists. + + A transfer is an observable relationship, not a cleanup instruction; + cleanup comes from ``DestructionPolicy``. + """ BY_VALUE = "by_value" IN_PLACE = "in_place" @@ -85,7 +155,22 @@ class TransferMode(str, Enum): class DestructionPolicy(str, Enum): - """Describe who, if anyone, releases native or Python-side resources.""" + """Describe who releases any resource represented by the decision. + + Values: + ``PYTHON_REFCOUNT`` delegates release to Python, NumPy, or a + Python-owned capsule. ``CALLER`` leaves release responsibility with + the caller that supplied the object. ``WRAPPER_DEALLOC`` uses a + generated wrapper or handle deallocator. ``NATIVE_OWNER`` leaves + release to an independent native owner. ``CALL_LOCAL`` runs generated + cleanup before the wrapped call finishes. ``NONE`` means this boundary + value creates no resource that PRIK must release. ``BLOCKED`` means + release responsibility is unsafe, contradictory, or unimplemented. + + ``NONE`` does not claim that no storage exists. For example, an existing + wrapper-owned object passed call-locally still has storage, but that call + creates nothing new to destroy. + """ PYTHON_REFCOUNT = "python_refcount" CALLER = "caller" @@ -97,7 +182,17 @@ class DestructionPolicy(str, Enum): class StorageMode(str, Enum): - """Select stable storage for a contract value or ABI boundary representation.""" + """Select storage for a contract value or ABI boundary representation. + + Values: + ``STACK`` is a direct or call-frame value with no persistent heap + allocation. ``HEAP`` is storage whose lifetime extends beyond a native + stack value. ``ALIAS`` refers to existing storage without owning an + independent value at this location. + + ``OwnershipDecision.storage_mode`` describes the contract value; + ``boundary_storage_mode`` may separately describe the ABI-facing form. + """ STACK = "stack" HEAP = "heap" @@ -105,7 +200,22 @@ class StorageMode(str, Enum): class CodegenAction(str, Enum): - """Identify the completed lowering action for a supported ownership decision.""" + """Identify the completed general lowering mechanism. + + Values: + ``DIRECT_VALUE`` converts or returns an independent value. + ``CALL_LOCAL_INPUT`` prepares input storage valid for one call. + ``IN_PLACE_ARGUMENT`` passes caller-visible mutable storage. + ``IDENTITY_OUTPUT`` mutates and projects the same supplied object. + ``COPY_IN_OUT`` copies immutable Python input into mutable call storage + and returns its final value. ``COPY_OUT`` materializes native output as + a fresh Python result. ``SNAPSHOT_COPY`` materializes a detached copy + of persistent state. ``BORROWED_VIEW`` exposes owner-controlled + storage. ``WRAPPER_INSTANCE`` constructs or returns a generated native + object wrapper. ``BLOCKED`` rejects lowering. + + This action is derived only after the lifetime triple is validated. + """ DIRECT_VALUE = "direct_value" CALL_LOCAL_INPUT = "call_local_input" @@ -120,7 +230,18 @@ class CodegenAction(str, Enum): class PythonBarrierAction(str, Enum): - """Identify how a Python-visible argument crosses into wrapper storage.""" + """Identify how a Python-visible argument enters wrapper storage. + + Values: + ``SCALAR_VALUE`` reads an ordinary Python scalar. ``SCALAR_STORAGE`` + reads or creates addressable scalar storage. ``ARRAY_STORAGE`` + validates and uses NumPy-compatible array storage. ``STRING_VALUE`` + reads an immutable Python string. ``STRING_STORAGE`` uses mutable + character storage. ``RAW_ADDRESS`` accepts an explicit raw address. + ``WRAPPER_INSTANCE`` extracts an opaque instance or native descriptor + from a generated wrapper. ``NONE`` means there is no Python argument + for this value. ``BLOCKED`` rejects Python-boundary lowering. + """ SCALAR_VALUE = "scalar_value" SCALAR_STORAGE = "scalar_storage" @@ -134,7 +255,19 @@ class PythonBarrierAction(str, Enum): class NativeBarrierAction(str, Enum): - """Identify how wrapper storage crosses the native ABI boundary.""" + """Identify how wrapper storage crosses the native ABI boundary. + + Values: + ``PASS_VALUE`` passes a converted value directly. + ``PASS_CALL_LOCAL_ADDRESS`` passes wrapper-created call-local storage + by address. ``PASS_STORAGE_ADDRESS`` passes existing mutable storage by + address. ``PASS_RAW_ADDRESS`` forwards an explicit raw address. + ``PASS_ARRAY_BUFFER`` passes a validated array data buffer. + ``PASS_NATIVE_DESCRIPTOR`` passes an allocatable or pointer descriptor. + ``PASS_WRAPPER_ADDRESS`` passes an opaque address held by a generated + object wrapper. ``NONE`` means no native argument is required. + ``BLOCKED`` rejects native-boundary lowering. + """ PASS_VALUE = "pass_value" PASS_CALL_LOCAL_ADDRESS = "pass_call_local_address" @@ -148,7 +281,13 @@ class NativeBarrierAction(str, Enum): class AssignmentMode(str, Enum): - """Describe whether a setter copies a value, aliases storage, or is unavailable.""" + """Describe the native assignment mechanism selected for a setter. + + Values: + ``NONE`` emits no native assignment. ``VALUE_COPY`` copies the incoming + value into existing native storage. ``ALIAS`` associates the + destination with existing storage rather than copying it. + """ NONE = "none" VALUE_COPY = "value_copy" @@ -156,7 +295,13 @@ class AssignmentMode(str, Enum): class SetterAction(str, Enum): - """Describe the Python property setter behavior selected by policy completion.""" + """Describe the Python property setter behavior selected by policy. + + Values: + ``WRITE_THROUGH`` exposes a setter that updates native state. + ``REJECT_REPLACEMENT`` keeps the property readable but explicitly + rejects replacing its storage. ``OMIT`` exposes no Python setter. + """ WRITE_THROUGH = "write_through" REJECT_REPLACEMENT = "reject_replacement" @@ -395,6 +540,18 @@ class OwnershipContext: or module-variable cases. The resolver combines these flags with storage facts to select an ownership decision; callers do not need to infer a codegen action themselves. + + ``location`` is the diagnostic location label. ``reads_argument`` and + ``writes_argument`` describe native access to a supplied argument. + ``is_result``, ``is_argument``, ``is_field``, and ``is_module_variable`` + identify the semantic owner. ``projects_result`` says the value occupies a + declared Python result position, while ``python_visible`` says the caller + supplies it as a Python argument. + + For example, a hidden output dummy uses + ``OwnershipContext.argument(reads_argument=False, writes_argument=True, + projects_result=True, python_visible=False)``: native code writes it, the + wrapper returns it, and the Python caller does not supply it. """ location: str = "value" @@ -564,6 +721,24 @@ class OwnershipDecision: Policy completion stores this immutable record beside semantic values and wrapper planning projects it into backend-neutral records. A blocked decision carries its diagnostic in ``blocker`` and must not be lowered. + + ``kind`` selects the Python representation. ``owner``, ``transfer``, and + ``destruction`` form the validated lifetime triple. ``storage_mode`` and + ``boundary_storage_mode`` describe contract and ABI storage. The three + action fields select general, Python-barrier, and native-barrier lowering. + + ``nullable`` permits an absent value or descriptor. ``borrowed`` records a + non-owning relationship. ``mutates_native`` records observable native + mutation. ``projects_result`` and ``python_visible`` describe the Python + signature, while ``descriptor_boundary`` selects native descriptor + transport. ``assignment_mode`` and ``setter_action`` complete property + write behavior. ``blocker`` explains a fail-closed decision and ``reason`` + explains the selected supported policy. + + For example, a live module array normally has kind ``NUMPY_ARRAY``, owner + ``NATIVE``, transfer ``BORROWED_VIEW``, destruction ``NATIVE_OWNER``, and + alias storage. The resulting actions expose native-controlled storage + without giving Python permission to destroy it. """ kind: ObjectKind @@ -620,6 +795,29 @@ class _StorageFacts: metadata: Mapping[str, Any] | None = None +@dataclass(frozen=True) +class _OwnershipOverride: + """Hold one normalized explicit lifetime override before it is applied. + + The record contains the effective values selected from explicit metadata + and the resolver's default decision. Keeping this intermediate immutable + separates metadata parsing from decision replacement and safety + validation. + + For example, metadata requesting ``python/copy_return/python_refcount`` + becomes one record whose ``owner``, ``transfer``, and ``destruction`` are + the corresponding enums; the later application step also derives its + storage mode and blocked diagnostic. + """ + + owner: OwnershipOwner + transfer: TransferMode + destruction: DestructionPolicy + nullable: bool + borrowed: bool + reason: str + + Handler = Callable[[_StorageFacts, OwnershipContext], OwnershipDecision] @@ -1650,24 +1848,111 @@ def _apply_overrides( """Apply declared ownership metadata without bypassing later safety validation. Pointer container metadata stays separate from general ownership - overrides. Unsupported borrowed pointer views become blocked here so + overrides. Unsupported borrowed pointer views become blocked here so lower stages cannot fabricate target retention. + + For example, an array result default can be overridden with + ``python/copy_return/python_refcount``. The method selects that metadata, + normalizes it into ``_OwnershipOverride``, applies storage invariants, + and returns a new decision. A pointer requesting ``borrowed_view`` is + instead returned as an explicit blocked decision. + """ + # Select the applicable general or non-container pointer metadata. + raw = self._ownership_override_metadata(facts, context) + if raw is None: + return decision + + # Normalize the owner before pointer rejection to preserve metadata diagnostics. + owner = self._enum_value(OwnershipOwner, raw.get("owner"), decision.owner) + + # Normalize the transfer used to select the supported or blocked path. + transfer = self._enum_value(TransferMode, raw.get("transfer"), decision.transfer) + + # Fail closed before parsing later fields when pointer borrowing has no lifetime proof. + blocked = self._blocked_borrowed_pointer_override(decision, facts, raw, transfer) + if blocked is not None: + return blocked + + # Convert all remaining metadata into one immutable effective override. + override = self._ownership_override(decision, raw, owner, transfer) + + # Apply the normalized values while preserving pointer and allocatable storage invariants. + return self._decision_with_ownership_override(decision, facts, override) + + @staticmethod + def _ownership_override_metadata( + facts: _StorageFacts, + context: OwnershipContext, + ) -> Mapping[str, Any] | None: + """Select the explicit lifetime metadata that applies to one value. + + ``facts`` supplies the type metadata and pointer/storage category; + ``context`` identifies whether a pointer is a descriptor container. + General ``ownership_policy`` metadata is returned as-is. For a + non-container pointer, ``pointer_policy`` values override matching + general keys. Pointer-array containers keep the two contracts separate + because their descriptor ownership is fixed by their native parent. + + For example, a scalar pointer with general owner ``native`` and pointer + transfer ``call_local`` returns a merged mapping containing both + values. A pointer-array field returns only its general ownership mapping. """ metadata = facts.metadata or {} raw = metadata.get(OWNERSHIP_POLICY_METADATA) pointer_policy = metadata.get(POINTER_POLICY_METADATA) - pointer_container = ( - facts.pointer - and facts.rank > 0 - and (context.is_argument or context.is_field or context.is_module_variable or context.is_result) - ) + + # Identify descriptor containers whose parent fixes their ownership. + pointer_container = OwnershipPolicyResolver._is_pointer_container(facts, context) if facts.pointer and isinstance(pointer_policy, Mapping) and not pointer_container: raw = {**(raw if isinstance(raw, Mapping) else {}), **pointer_policy} if not isinstance(raw, Mapping): - return decision - owner = self._enum_value(OwnershipOwner, raw.get("owner"), decision.owner) - transfer = self._enum_value(TransferMode, raw.get("transfer"), decision.transfer) + return None + return raw + + @staticmethod + def _is_pointer_container(facts: _StorageFacts, context: OwnershipContext) -> bool: + """Return whether a pointer value is a native descriptor container. + + ``facts`` supplies pointer and rank information, while ``context`` says + whether the value belongs to an argument, field, module variable, or + result. A rank-positive pointer in one of those locations has native + descriptor identity whose container ownership must not be replaced by + target-oriented ``PointerPolicy`` metadata. + + For example, a pointer-array field returns ``True`` because its parent + wrapper owns the descriptor container. A scalar pointer or neutral + temporary returns ``False`` and may merge pointer-policy transfer facts + into the general override. + """ + owner_contexts = ( + context.is_argument, + context.is_field, + context.is_module_variable, + context.is_result, + ) + return bool(facts.pointer and facts.rank > 0 and any(owner_contexts)) + + @staticmethod + def _blocked_borrowed_pointer_override( + decision: OwnershipDecision, + facts: _StorageFacts, + raw: Mapping[str, Any], + transfer: TransferMode, + ) -> OwnershipDecision | None: + """Return the fail-closed decision for unsupported pointer borrowing. + + The default ``decision`` supplies unrelated completed fields, ``facts`` + identifies whether storage is a pointer, ``raw`` supplies explicit + nullability, and ``transfer`` is the already-normalized requested mode. + Non-pointer and non-borrowed requests return ``None`` so normal override + application can continue. + + For example, a pointer request with ``transfer='borrowed_view'`` returns + ``UNKNOWN/BLOCKED/BLOCKED`` and explains the missing owner retention and + stale-view invalidation mechanism. + """ if facts.pointer and transfer is TransferMode.BORROWED_VIEW: + # Preserve unrelated fields while replacing every unsafe lifetime axis. return replace( decision, owner=OwnershipOwner.UNKNOWN, @@ -1679,21 +1964,73 @@ def _apply_overrides( blocker="borrowed pointer views need native-owner retention and stale-view invalidation", reason="borrowed pointer views are not implemented", ) + return None + + def _ownership_override( + self, + decision: OwnershipDecision, + raw: Mapping[str, Any], + owner: OwnershipOwner, + transfer: TransferMode, + ) -> _OwnershipOverride: + """Normalize explicit metadata against an existing default decision. + + ``owner`` and ``transfer`` have already been validated because pointer + borrowing must be rejected before later metadata is interpreted. This + helper validates destruction, fills omitted Boolean and reason fields + from ``decision``, and returns an immutable value without applying it. + + For example, ``{'destruction': 'python_refcount'}`` combined with a + preselected ``PYTHON`` owner and ``COPY_RETURN`` transfer produces an + override with ``DestructionPolicy.PYTHON_REFCOUNT`` while preserving the + decision's nullability. + """ + # Normalize release responsibility only after pointer-specific rejection. destruction = self._enum_value(DestructionPolicy, raw.get("destruction"), decision.destruction) - storage_mode = self._storage_for_override(facts, transfer, decision.storage_mode) - nullable = bool(raw.get("nullable", decision.nullable)) - borrowed = transfer is TransferMode.BORROWED_VIEW or bool(raw.get("borrowed", decision.borrowed)) - blocker = None if transfer is not TransferMode.BLOCKED else decision.blocker or "blocked by ownership policy" - return replace( - decision, + # Package the effective fields without mutating the resolver's default decision. + return _OwnershipOverride( owner=owner, transfer=transfer, destruction=destruction, + nullable=bool(raw.get("nullable", decision.nullable)), + borrowed=transfer is TransferMode.BORROWED_VIEW or bool(raw.get("borrowed", decision.borrowed)), + reason=str(raw.get("reason", "explicit ownership policy metadata")), + ) + + def _decision_with_ownership_override( + self, + decision: OwnershipDecision, + facts: _StorageFacts, + override: _OwnershipOverride, + ) -> OwnershipDecision: + """Apply one normalized override while preserving storage invariants. + + ``decision`` is the resolver default, ``facts`` supplies pointer, + allocatable, and array storage constraints, and ``override`` supplies + the effective lifetime fields. The result is a new decision; later + resolver steps still validate the triple and derive lowering actions. + + For example, an allocatable result overridden to ``snapshot_copy`` + remains heap-backed, while a borrowed ordinary array is forced to alias + storage. An explicit ``blocked`` transfer also gains a stable blocker + when the default decision had none. + """ + # Derive storage from the normalized transfer without violating native storage facts. + storage_mode = self._storage_for_override(facts, override.transfer, decision.storage_mode) + blocker = ( + None if override.transfer is not TransferMode.BLOCKED else decision.blocker or "blocked by ownership policy" + ) + # Return a new decision for the resolver's later validation and action derivation. + return replace( + decision, + owner=override.owner, + transfer=override.transfer, + destruction=override.destruction, storage_mode=storage_mode, - nullable=nullable, - borrowed=borrowed, + nullable=override.nullable, + borrowed=override.borrowed, blocker=blocker, - reason=str(raw.get("reason", "explicit ownership policy metadata")), + reason=override.reason, ) @staticmethod @@ -2045,6 +2382,10 @@ def _enum_value(enum_type: type[Enum], value: object, default: Any) -> Any: Invalid present values raise ``ValueError`` listing the accepted enum values so malformed contracts fail during policy completion. + + For example, ``_enum_value(TransferMode, 'copy_return', default)`` + returns ``TransferMode.COPY_RETURN``, while a ``None`` value returns the + supplied default unchanged. """ if value is None: return default @@ -2060,7 +2401,17 @@ def _storage_for_override( transfer: TransferMode, default: StorageMode, ) -> StorageMode: - """Choose storage implied by an override while preserving pointer/allocatable invariants.""" + """Choose override storage without violating native storage invariants. + + ``facts`` identifies pointer, allocatable, and array storage; + ``transfer`` is the normalized override and ``default`` is the storage + chosen by the normal resolver branch. Pointers remain aliases, + allocatables remain heap-backed, borrowed ordinary arrays become + aliases, and all other values keep ``default``. + + For example, an allocatable with ``transfer=SNAPSHOT_COPY`` returns + ``HEAP``, while an ordinary borrowed array returns ``ALIAS``. + """ if facts.pointer: return StorageMode.ALIAS if facts.allocatable: diff --git a/tests/fortran/infrastructure/semantics/test_ownership.py b/tests/fortran/infrastructure/semantics/test_ownership.py index b964c1f4b..ffc22ab34 100644 --- a/tests/fortran/infrastructure/semantics/test_ownership.py +++ b/tests/fortran/infrastructure/semantics/test_ownership.py @@ -22,6 +22,7 @@ StorageMode, TransferMode, default_ownership_policy, + set_ownership_metadata, ) from tests.fortran._support.ownership_policy import ( _address_type, @@ -234,6 +235,57 @@ def native_scalar_handler(_facts, _context): assert array.transfer is TransferMode.WRAPPER_INSTANCE +def test_explicit_ownership_override_preserves_normalized_fields_and_storage_invariants(): + metadata: dict[str, object] = {} + set_ownership_metadata( + metadata, + owner="python", + transfer="snapshot_copy", + destruction="python_refcount", + ) + metadata["ownership_policy"].update( + nullable=True, + borrowed=True, + reason="test override", + ) + + decision = default_ownership_policy.decide_semantic_type( + _array_type(allocatable=True, metadata=metadata), + OwnershipContext.result(), + ) + + assert decision.owner is OwnershipOwner.PYTHON + assert decision.transfer is TransferMode.SNAPSHOT_COPY + assert decision.destruction is DestructionPolicy.PYTHON_REFCOUNT + assert decision.storage_mode is StorageMode.HEAP + assert decision.nullable is True + assert decision.borrowed is True + assert decision.reason == "test override" + + +def test_borrowed_pointer_override_blocks_before_unrelated_destruction_validation(): + metadata = { + "ownership_policy": { + "owner": "native", + "transfer": "borrowed_view", + "destruction": "not_a_destruction_policy", + "nullable": False, + } + } + + decision = default_ownership_policy.decide_semantic_type( + _array_type(pointer=True, metadata=metadata), + OwnershipContext.result(), + ) + + assert decision.owner is OwnershipOwner.UNKNOWN + assert decision.transfer is TransferMode.BLOCKED + assert decision.destruction is DestructionPolicy.BLOCKED + assert decision.storage_mode is StorageMode.ALIAS + assert decision.nullable is False + assert decision.blocker == ("borrowed pointer views need native-owner retention and stale-view invalidation") + + def test_codegen_action_dispatcher_routes_policy_actions_to_named_methods(): class FakeVar: rank = 1 From 01bcd0547ec08aaa3ffbac8dffbc00a7e7b2d3a2 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 07:48:59 +0100 Subject: [PATCH 14/22] add prik/semantics/wrapper_policy_models.py where we extract the model from wrapper_policy.py --- docs/developer/source-map.md | 4 +- .../internal-architecture/pipeline-map.md | 6 +- .../wrapper-generation-pipeline.md | 8 + prik/codegen/c/binding.py | 2 +- prik/codegen/c/python_surface.py | 2 +- prik/codegen/docstrings.py | 2 +- prik/codegen/fortran/bridge.py | 2 +- prik/codegen/generator.py | 6 +- prik/codegen/plan.py | 2 +- prik/codegen/planner.py | 10 +- prik/semantics/policy_completion.py | 10 +- prik/semantics/wrapper_policy.py | 1285 ++--------------- prik/semantics/wrapper_policy_models.py | 1206 ++++++++++++++++ .../policy/test_allocatable_result_policy.py | 2 +- .../codegen/test_array_buffer_lowering.py | 2 +- .../codegen/test_array_output_identity.py | 2 +- .../codegen/test_array_result_lowering.py | 2 +- .../test_dense_array_shape_lowering.py | 2 +- .../codegen/test_specialized_array_roles.py | 2 +- .../arrays/policy/test_array_shape_policy.py | 2 +- .../codegen/test_callback_planning.py | 2 +- .../callbacks/policy/test_callback_policy.py | 4 +- .../test_default_logical_scalar_lowering.py | 2 +- .../test_primitive_scalar_result_lowering.py | 2 +- .../codegen/test_derived_lowering.py | 2 +- .../codegen/test_scalar_actual_dummy_plan.py | 2 +- .../policy/test_derived_accessor_policy.py | 2 +- .../codegen/test_status_error_lowering.py | 2 +- .../codegen/test_scalar_function_writeback.py | 2 +- .../policy/test_function_result_policy.py | 4 +- .../codegen/test_overload_dispatch_plan.py | 2 +- .../infrastructure/codegen/test_generator.py | 2 +- .../semantics/test_wrapper_policy.py | 5 +- .../codegen/test_native_handle_planning.py | 2 +- .../test_scalar_module_variable_lowering.py | 2 +- .../policy/test_module_variable_policy.py | 2 +- .../codegen/test_optional_lowering.py | 2 +- .../policy/test_optional_policy.py | 4 +- .../pointers/codegen/test_pointer_lowering.py | 2 +- .../codegen/test_call_and_result_lowering.py | 2 +- .../policy/test_class_surface_policy.py | 2 +- .../codegen/test_raw_array_lowering.py | 2 +- .../codegen/test_scalar_address_lowering.py | 2 +- .../codegen/test_string_address_lowering.py | 2 +- .../policy/test_raw_address_policy.py | 2 +- .../codegen/test_character_array_lowering.py | 2 +- .../test_fixed_string_result_lowering.py | 2 +- .../codegen/test_fixed_string_writeback.py | 2 +- .../codegen/test_string_input_lowering.py | 2 +- .../policy/test_string_wrapper_policy.py | 2 +- ..._scalar_subroutine_writeback_validation.py | 2 +- 51 files changed, 1385 insertions(+), 1245 deletions(-) create mode 100644 prik/semantics/wrapper_policy_models.py diff --git a/docs/developer/source-map.md b/docs/developer/source-map.md index 710b6a813..479ddff4a 100644 --- a/docs/developer/source-map.md +++ b/docs/developer/source-map.md @@ -46,8 +46,8 @@ change crosses ownership boundaries. | Wrapper-planning errors and support claims | `prik/semantics/policy_completion.py`, `prik/codegen/planner.py` | `docs/user/reference/diagnostic-codes.md`, `docs/user/language-support/feature-matrix.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | | Source-driven Fortran wrapper orchestration | `prik/pipeline/build.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py` | | Semantic `.pyi` wrapper orchestration from native artifacts | `prik/pipeline/build.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/pyi_contracts/exports_and_modules/`, `tests/fortran/pyi_contracts/functions_and_classes/` | -| Ownership, lifetime, output projection, and unsupported wrapper policy | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py`, `prik/codegen/planner.py` | `docs/user/guide/memory-management.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | -| Immediate callback policy, typed adapters, and trampolines | `prik/semantics/wrapper_policy.py`, `prik/semantics/policy_completion.py`, `prik/codegen/plan.py`, `prik/codegen/planner.py`, `prik/codegen/c/binding.py`, `prik/codegen/fortran/bridge.py` | `docs/user/guide/callbacks.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/callbacks/` | +| Ownership, lifetime, output projection, and unsupported wrapper policy | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py`, `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/codegen/planner.py` | `docs/user/guide/memory-management.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | +| Immediate callback policy, typed adapters, and trampolines | `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/semantics/policy_completion.py`, `prik/codegen/plan.py`, `prik/codegen/planner.py`, `prik/codegen/c/binding.py`, `prik/codegen/fortran/bridge.py` | `docs/user/guide/callbacks.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/callbacks/` | | Native compilation, binding support, and shared-library linking | `prik/pipeline/build.py`, `prik/compiling/compilers.py`, `prik/compiling/native_support.py` | `docs/user/reference/fortran-wrapper.md`, `docs/developer/build-system.md` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | | Public Python exports | `prik/__init__.py` | `README.md`, `docs/user/reference/python-api.md` | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` | | Reference BLAS source ownership, inventory, and numerical validation | `examples/blas/routine_inventory.py`, `examples/blas/tests/test_routine_coverage.py` | `examples/blas/README.md`, `docs/user/examples/blas-wrapper.md` | `examples/blas/tests/test_*.py`, `examples/blas/ci/full_surface.py`, dedicated real-libraries workflow | diff --git a/docs/maintainer/internal-architecture/pipeline-map.md b/docs/maintainer/internal-architecture/pipeline-map.md index 37fbed643..a8164be45 100644 --- a/docs/maintainer/internal-architecture/pipeline-map.md +++ b/docs/maintainer/internal-architecture/pipeline-map.md @@ -76,7 +76,7 @@ cross-cutting infrastructure. | Concept family | Owner | What belongs there | What must stay out | | --- | --- | --- | --- | | Parser facts | parser packages | Source syntax, native declaration structure, source locations, and parser diagnostics | Wrapper policy, Python API projection, generated names, and compile/link decisions | -| Semantic policy completion and ownership | `prik/semantics/policy_completion.py` and `prik/semantics/ownership.py` | Completed policy choices for ownership, lifetime, output projection, replacement, and ABI safety | Raw parser syntax, backend-specific statement trees, and hidden lowering-time policy decisions | +| Semantic policy completion and ownership | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py`, `prik/semantics/wrapper_policy_models.py`, and `prik/semantics/wrapper_policy.py` | Completed policy choices for ownership, lifetime, output projection, replacement, and ABI safety; immutable wrapper-policy vocabulary is separate from its construction rules | Raw parser syntax, backend-specific statement trees, and hidden lowering-time policy decisions | | Typed wrapper plan | `prik/codegen/plan.py` and `prik/codegen/planner.py` | A validated, backend-neutral implementation plan projected from completed semantic decisions | Source-contract authority, policy inference, and target-language statement details | | Printers and compilation | `prik/codegen/printers/`, `prik/compiling/`, and wrapper orchestration | Text emission, generated artifact layout, compiler commands, native objects, libraries, include directories, and link inputs | Semantic support decisions and plan rewriting policy | @@ -157,8 +157,8 @@ PRIK_C_DOCS_END --> | CLI and output routing | `prik/cli.py`, parser CLI helpers | `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` | | Source loading and preprocessing | `prik/pipeline/preprocessing.py` | `docs/developer/source-map.md`, parser references | | Editable semantic contracts | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/codegen/printers/pyi_printer.py` | `docs/user/reference/semantic-pyi-format.md` | -| Semantic and wrapper-planning errors | `prik/semantics/fortran2ir.py`, `prik/semantics/policy_completion.py`, `prik/semantics/wrapper_policy.py`, `prik/codegen/planner.py` | `docs/user/guide/error-handling.md` | -| Wrapper policy and lowering | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py`, `prik/codegen/planner.py`, `prik/codegen/generator.py` | `docs/user/reference/fortran-wrapper.md`, ownership docs | +| Semantic and wrapper-planning errors | `prik/semantics/fortran2ir.py`, `prik/semantics/policy_completion.py`, `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/codegen/planner.py` | `docs/user/guide/error-handling.md` | +| Wrapper policy and lowering | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py`, `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/codegen/planner.py`, `prik/codegen/generator.py` | `docs/user/reference/fortran-wrapper.md`, ownership docs | | Native build | `prik/pipeline/build.py`, `prik/compiling/compilers.py`, `prik/compiling/native_support.py` | compiling package README and build-system docs | Do not move semantic ownership or projection policy into printers. Do not infer @@ -942,7 +942,7 @@ PRIK_C_DOCS_END --> - `prik/semantics/fortran2ir.py` maps Fortran procedures, derived types, module variables, kinds, shapes, storage contracts, visibility, imported references, and compile-time values. -- `prik/codegen/printers/pyi_printer.py` emits editable user contracts. +- `prik/printers/pyi.py` emits editable user contracts. - `prik/parsers/pyi/parser.py` parses edited contracts to Python AST. - `prik/pipeline/pyi.py` converts edited contract text, files, and path sets. - `prik/semantics/pyi2ir.py` converts parsed `.pyi` AST back into semantic IR. @@ -951,7 +951,7 @@ PRIK_C_DOCS_END --> - Named data bindings keep role-specific semantic types: `SemanticVariable` for module variables and constants, `SemanticArgument` for callable parameters, and `SemanticField` for Fortran derived-type components. -- `prik/semantics/policy_completion.py` completes semantic policies after +- `prik/policy/completion.py` completes semantic policies after Fortran or `.pyi` conversion and before wrapper planning or lowering. precision is stored on the semantic type. C and Fortran enums lower to unscoped module-level integer constants; enum names are metadata, not semantic datatypes. -- `prik/semantics/policy_completion.py` completes semantic policies after +- `prik/policy/completion.py` completes semantic policies after C/Fortran/`.pyi` conversion and before wrapper planning or lowering. PRIK_C_DOCS_END --> @@ -1238,7 +1238,7 @@ Example target: add a new `Annotated[...]` metadata item or projection helper. `prik/parsers/pyi/parser.py` only when the raw Python AST parsing boundary changes. 3. Add printer tests in `tests/fortran/semantic_pyi_format/pipeline/`. -4. Update `prik/codegen/printers/pyi_printer.py`. +4. Update `prik/printers/pyi.py`. 5. Update semantic models in `prik/semantics/models.py` only if the IR needs a new field or constraint. 6. Update policy completion or wrapper planning if the syntax changes a diff --git a/docs/developer/feature-to-code-map.md b/docs/developer/feature-to-code-map.md index 944595f5d..f090a836f 100644 --- a/docs/developer/feature-to-code-map.md +++ b/docs/developer/feature-to-code-map.md @@ -18,11 +18,11 @@ before documentation may call the behavior supported. | Feature or behavior | Public docs | Main implementation files | Focused tests | Support evidence | | --- | --- | --- | --- | --- | | Fortran parse output | `docs/developer/fortran-parser-reference.md` | `prik/parsers/fortran/parser.py`, `models.py`, `lexer.py`, `type_resolver.py` | `tests/fortran/source_parsing/parsing/`, `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` | Parser facts and diagnostics match fixtures | -| Semantic `.pyi` generation | `docs/user/reference/semantic-pyi-format.md` | `prik/codegen/printers/pyi_printer.py` | `tests/fortran/semantic_pyi_format/pipeline/`, `tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py` | Printed `.pyi` round-trips or matches fixtures | +| Semantic `.pyi` generation | `docs/user/reference/semantic-pyi-format.md` | `prik/printers/pyi.py` | `tests/fortran/semantic_pyi_format/pipeline/`, `tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py` | Printed `.pyi` round-trips or matches fixtures | | Semantic `.pyi` conversion and editing | `docs/user/reference/pyi-contracts/index.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/examples/recipes/semantic-pyi-contracts.md` | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `models.py` | `tests/fortran/semantic_pyi_format/` | Edited contracts parse to Python AST, then become semantic IR with preserved native facts | -| Semantic and wrapper-planning errors | `docs/user/guide/error-handling.md`, `docs/user/reference/diagnostic-codes.md` | `prik/semantics/fortran2ir.py`, `prik/semantics/policy_completion.py`, `prik/codegen/planner.py` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and feature-local `codegen/` tests | Each owning stage rejects unsupported or incomplete contracts | +| Semantic and wrapper-planning errors | `docs/user/guide/error-handling.md`, `docs/user/reference/diagnostic-codes.md` | `prik/semantics/fortran2ir.py`, `prik/policy/completion.py`, `prik/planning/planner.py` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and feature-local `codegen/` tests | Each owning stage rejects unsupported or incomplete contracts | | Fortran wrapper orchestration | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md` | `prik/pipeline/build.py` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, multi-source wrapper tests | Builds report artifacts and compile/link as documented | -| Completed semantic policy to wrapper artifacts | `docs/user/reference/fortran-wrapper.md` | `prik/semantics/policy_completion.py`, `prik/codegen/plan.py`, `planner.py`, `generator.py` | `tests/fortran/infrastructure/semantics/`, `tests/fortran/infrastructure/codegen/`, and feature-local policy/codegen tests | Runtime policy is explicit, the typed plan is complete, and generated artifacts compile and run | +| Completed semantic policy to generated wrapper | `docs/user/reference/fortran-wrapper.md` | `prik/policy/completion.py`, `prik/planning/models.py`, `prik/planning/planner.py`, `prik/codegen/docstrings.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/semantics/`, `tests/fortran/infrastructure/codegen/`, and feature-local policy/codegen tests | Runtime policy is explicit, the typed plan is complete, and the generated wrapper compiles and runs | | Native compilation and binding support | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md`, `docs/developer/build-system.md`, `docs/developer/quality-assurance.md` | `prik/compiling/`, `prik/binding_support/` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, build-mode tests | Generated sources compile, link, import, and clean up correctly | | Source documentation structure | `docs/developer/source-map.md` | `docs/`, package README files, `tests/docs/test_reference_and_source_map.py` | documentation metadata, navigation, source-map, and example tests | Pages have metadata, audience separation, and source coverage checks | @@ -31,8 +31,8 @@ before documentation may call the behavior supported. | Compiler preprocessing | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/compiler-preprocessing.md`, parser references | `prik/pipeline/preprocessing.py`, parser CLI helpers | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py`, C preprocessing tests | Preprocessed input and dependency facts are stable | | C parse output | `docs/developer/c-parser-reference.md`, `docs/user/examples/recipes/inspect-c-api.md` | `prik/parsers/c/parser.py`, `models.py`, `lexer.py`, `type_resolver.py` | `tests/c/parsing/test_c_declarations_and_declarators.py`, `tests/c/parsing/test_c_fixture_suite.py` | Parser facts and diagnostics match fixtures | | Semantic IR | `docs/user/reference/semantic-ir.md` | `prik/semantics/models.py`, `fortran2ir.py`, `c2ir.py` | `tests/fortran/semantic_ir/semantics/`, `tests/c/semantics/conversion/` | Source facts lower without losing wrapper-relevant meaning | -| Generated Fortran bridge | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/fortran/bridge.py`, `prik/codegen/printers/source_printers.py` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | Generated bridge compiles and preserves native calling contract | -| Generated CPython binding | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/codegen/printers/source_printers.py` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | Extension imports, validates Python inputs, dispatches overloads in C, and installs the derived-class Python facade | +| Generated Fortran bridge | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/fortran/bridge.py`, `prik/printers/fortran.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | Generated bridge compiles and preserves native calling contract | +| Generated CPython binding | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/printers/c.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | Extension imports, validates Python inputs, dispatches overloads in C, and installs the derived-class Python facade | | Public API exports | `README.md`, `docs/user/reference/python-api.md` | `prik/__init__.py` | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py`, C public API tests | Import paths are intentional and documented | PRIK_C_DOCS_END --> @@ -55,7 +55,7 @@ this routing page tied to the source hotspots and package README files. | User workflow | Start in code | Do not mark supported until | | --- | --- | --- | -| Wrapping functions and subroutines | `prik/semantics/fortran2ir.py`, policy completion, `prik/codegen/planner.py`, bridge and binding generators | Runtime tests compile, import, call, and verify return and failure behavior | +| Wrapping functions and subroutines | `prik/semantics/fortran2ir.py`, policy completion, `prik/planning/planner.py`, bridge and binding generators | Runtime tests compile, import, call, and verify return and failure behavior | | Wrapping modules and module variables | parser module facts, semantic module conversion, naming policy, wrapper generators | Python-visible names, accessors, and unsupported module constructs are tested | | Arrays and allocatables | semantic array contracts, ownership policy, typed wrapper plans, bridge/binding array handlers | dtype, shape, rank, contiguity, mutation, returned arrays, and failure paths are tested; ordinary NumPy array actuals validate and extract their buffer directly in the C binding, descriptor handles use the planned runtime-handle path, and strided contracts carry a dense-actual role for zero-copy fast-path selection | | Pointer arguments | semantic metadata, ownership policy, bridge/binding pointer handlers | Owner, lifetime, association, and blocked cases are explicit and tested | diff --git a/docs/developer/source-map.md b/docs/developer/source-map.md index 479ddff4a..ecb2ff648 100644 --- a/docs/developer/source-map.md +++ b/docs/developer/source-map.md @@ -20,7 +20,7 @@ current Python package layout. | `prik/cli.py` | User CLI, stage selection, output routing, diagnostics, wrapper-build option validation | parser frontends, semantic conversion, wrapper planning, `prik/pipeline/build.py` | | `prik/pipeline/build.py` | End-to-end Fortran source and semantic `.pyi` extension builds | preprocessing, parser, probes, completed semantic policy, wrapper planning and generation, compilation | | `prik/__init__.py` | Public Python exports | parser public-entrypoint tests and user examples | -| `prik/semantics/ownership.py` | Central ownership, transfer, destruction, and generated-action policy | policy completion and typed wrapper planning | +| `prik/policy/ownership.py` | Central ownership, transfer, destruction, and generated-action policy | policy completion and typed wrapper planning | | `prik/probes/fortran_types.py` | Fortran kind/storage facts and cache | semantic Fortran conversion and wrapper builds | | `prik/probes/report.py` | Generated target datatype mapping examples | documentation example tests | | `examples/blas/` | Complete Reference BLAS correctness example, source inventory, build fixtures, and the authoritative native source set | dedicated BLAS/LAPACK workflow and full-library integration | @@ -42,12 +42,12 @@ change crosses ownership boundaries. | CLI flags, stage selection, output formatting, diagnostics | `prik/cli.py` | `docs/user/reference/cli-commands.md`, `docs/user/getting-started/beginner-workflow.md` | `tests/fortran/command_line_interface/pipeline/`, `tests/docs/test_examples.py` | | Compiler preprocessing, include paths, macros, and target flags | `prik/pipeline/preprocessing.py` | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/compiler-preprocessing.md`, `docs/developer/fortran-parser-reference.md` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` | | Fortran parser facts and diagnostics | `prik/parsers/fortran/parser.py` | `docs/developer/fortran-parser-reference.md`, `docs/user/examples/recipes/inspect-fortran-api.md` | `tests/fortran/source_parsing/parsing/` | -| Semantic `.pyi` parsing, conversion, printing, package generation, and round-trip behavior | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/codegen/printers/pyi_printer.py` | `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/pyi-contracts/index.md`, `docs/user/examples/recipes/semantic-pyi-contracts.md` | `tests/fortran/semantic_pyi_format/`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/semantic_pyi_format/pipeline/` | -| Wrapper-planning errors and support claims | `prik/semantics/policy_completion.py`, `prik/codegen/planner.py` | `docs/user/reference/diagnostic-codes.md`, `docs/user/language-support/feature-matrix.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | +| Semantic `.pyi` parsing, conversion, printing, package generation, and round-trip behavior | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/printers/pyi.py` | `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/pyi-contracts/index.md`, `docs/user/examples/recipes/semantic-pyi-contracts.md` | `tests/fortran/semantic_pyi_format/`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/semantic_pyi_format/pipeline/` | +| Wrapper-planning errors and support claims | `prik/policy/completion.py`, `prik/planning/planner.py` | `docs/user/reference/diagnostic-codes.md`, `docs/user/language-support/feature-matrix.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | | Source-driven Fortran wrapper orchestration | `prik/pipeline/build.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py` | | Semantic `.pyi` wrapper orchestration from native artifacts | `prik/pipeline/build.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/pyi_contracts/exports_and_modules/`, `tests/fortran/pyi_contracts/functions_and_classes/` | -| Ownership, lifetime, output projection, and unsupported wrapper policy | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py`, `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/codegen/planner.py` | `docs/user/guide/memory-management.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | -| Immediate callback policy, typed adapters, and trampolines | `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/semantics/policy_completion.py`, `prik/codegen/plan.py`, `prik/codegen/planner.py`, `prik/codegen/c/binding.py`, `prik/codegen/fortran/bridge.py` | `docs/user/guide/callbacks.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/callbacks/` | +| Ownership, lifetime, output projection, and unsupported wrapper policy | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py` | `docs/user/guide/memory-management.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | +| Immediate callback policy, typed adapters, and trampolines | `prik/policy/models.py`, `prik/policy/construction.py`, `prik/policy/completion.py`, `prik/planning/models.py`, `prik/planning/planner.py`, `prik/codegen/c/binding.py`, `prik/codegen/fortran/bridge.py` | `docs/user/guide/callbacks.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/callbacks/` | | Native compilation, binding support, and shared-library linking | `prik/pipeline/build.py`, `prik/compiling/compilers.py`, `prik/compiling/native_support.py` | `docs/user/reference/fortran-wrapper.md`, `docs/developer/build-system.md` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | | Public Python exports | `prik/__init__.py` | `README.md`, `docs/user/reference/python-api.md` | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` | | Reference BLAS source ownership, inventory, and numerical validation | `examples/blas/routine_inventory.py`, `examples/blas/tests/test_routine_coverage.py` | `examples/blas/README.md`, `docs/user/examples/blas-wrapper.md` | `examples/blas/tests/test_*.py`, `examples/blas/ci/full_surface.py`, dedicated real-libraries workflow | @@ -63,8 +63,8 @@ PRIK_C_DOCS_END --> ## Package Map @@ -72,12 +72,17 @@ PRIK_C_DOCS_END --> | Package | Purpose | Main files | Primary tests and docs | | --- | --- | --- | --- | | `prik/contracts/` | Public semantic `.pyi` contract vocabulary | `__init__.py` | `tests/fortran/semantic_pyi_format/`, semantic `.pyi` reference | -| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, and wrapper build orchestration | `preprocessing.py`, `pyi.py`, `build.py` | preprocessing, `.pyi`, and wrapper build tests | +| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, plan-to-source wrapper generation, and native build orchestration | `preprocessing.py`, `pyi.py`, `wrapper.py`, `build.py` | preprocessing, `.pyi`, wrapper generation, and build tests | | `prik/probes/` | Compiler-derived target facts plus mapping reports | `fortran_types.py`, `report.py` | target probe and type mapping report tests | | `prik/runtime/` | Python runtime objects consumed by generated extensions | `handles.py` | runtime handle and wrapper runtime tests | | `prik/types/` | Semantic-to-Python ecosystem type mappings | `numpy.py` | `tests/fortran/infrastructure/types/test_numpy.py` | | `prik/parsers/` | Public namespace for language and semantic `.pyi` frontends | child parser packages | `tests/fortran/source_parsing/parsing/`, `tests/c/parsing/`, `tests/fortran/semantic_pyi_format/parsing/` | | `prik/parsers/fortran/` | Fortran lexer, recursive parser, models, type resolver, and parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `type_resolver.py`, `cli.py` | `tests/fortran/source_parsing/parsing/`, `docs/developer/fortran-parser-reference.md` | +| `prik/semantics/` | Language-neutral semantic IR, source-to-IR conversion, `.pyi` conversion, and raw ownership or descriptor metadata | `models.py`, `fortran2ir.py`, `c2ir.py`, `pyi2ir.py`, `ownership_metadata.py`, `native_array_handles.py` | `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/` | +| `prik/policy/` | Post-IR ownership, export, wrapper-policy construction, immutable policy models, descriptor-handle policy, and ordered completion | `ownership.py`, `exports.py`, `models.py`, `native_array_handles.py`, `construction.py`, `completion.py` | infrastructure semantics and feature-local policy tests | +| `prik/planning/` | Editable backend-neutral wrapper-plan records and mechanical policy projection | `models.py`, `planner.py` | infrastructure and feature-local codegen tests | +| `prik/codegen/` | Plan-driven docstrings and direct lowering into C and Fortran syntax nodes | `docstrings.py`, `nodes.py`, `c/`, `fortran/` | infrastructure codegen, feature-local codegen, and end-to-end tests | +| `prik/printers/` | Language-specific serialization of C nodes, Fortran nodes, and semantic IR | `c.py`, `fortran.py`, `pyi.py` | source-printer and semantic-contract printer tests | | `prik/compiling/` | Native compile objects, compiler command execution, shared-library linking, and native support installation; wrapper build orchestration lives in `prik/pipeline/build.py` | `objects.py`, `compilers.py`, `compiler_profiles.py`, `native_support.py` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py` | | `prik/binding_support/` | Bundled header-only native binding support copied into generated wrapper builds | support header | wrapper build tests | | `prik/utilities/` | Small shared Python utilities | `strings.py`, `visitor.py` | `tests/fortran/infrastructure/utilities/` and tests that exercise callers | @@ -86,9 +91,7 @@ PRIK_C_DOCS_END --> | `prik/probes/c_types.py` | Compiler-derived target ABI facts for C inspection workflows | `c_types.py` | C target probe tests | | `prik/parsers/c/` | C lexer, parser, models, preprocessing metadata, and C parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `preprocessor.py`, `type_resolver.py`, `cli.py` | `tests/c/fixtures/parser/`, `docs/developer/c-parser-reference.md` | | `prik/parsers/pyi/` | Semantic `.pyi` text/file parsing to Python AST. | `parser.py` | `tests/fortran/semantic_pyi_format/parsing/`, `docs/user/reference/semantic-pyi-format.md` | -| `prik/semantics/` | Language-neutral semantic IR, source-to-IR conversion, `.pyi` AST conversion, and policy completion | `models.py`, `fortran2ir.py`, `c2ir.py`, `pyi2ir.py`, `policy_completion.py` | `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/`, `docs/user/reference/semantic-ir.md`, `docs/user/reference/semantic-pyi-format.md` | -| `prik/codegen/` | Canonical wrapper planning, C/Fortran generation, source printing, and semantic `.pyi` printing | `plan.py`, `planner.py`, `generator.py`, `printers/` | `tests/fortran/infrastructure/codegen/`, feature-local `codegen/` and `end_to_end/` tests, `docs/user/reference/fortran-wrapper.md` | -| `prik/naming/` | Unified public-name and generated-symbol policy for Python, C, and Fortran targets | `policy.py` | naming, visibility, and wrapper runtime tests | +| `prik/naming/` | Unified public-name and generated-symbol policy for Python, C, and Fortran targets | `policy.py`, `native_symbols.py` | naming, visibility, and wrapper runtime tests | PRIK_C_DOCS_END --> ## Hotspot Index @@ -104,7 +107,10 @@ update this table, the package README files, and the mechanical checks in | `prik/pipeline/build.py` | End-to-end source and `.pyi` wrapper build orchestration. | | `prik/pipeline/preprocessing.py` | Compiler-backed source preprocessing and dependency facts. | | `prik/probes/fortran_types.py` | Fortran kind and storage probing. | -| `prik/semantics/ownership.py` | Central ownership, transfer, destruction, and generated-action policy. | +| `prik/semantics/ownership_metadata.py` | Raw ownership and pointer-contract metadata keys and normalized semantic setters. | +| `prik/semantics/native_array_handles.py` | Raw semantic descriptor-handle facts attached before policy completion. | +| `prik/policy/ownership.py` | Central ownership, transfer, destruction, and generated-action policy. | +| `prik/policy/exports.py` | Completed Python namespace and export-name policy. | | `prik/parsers/fortran/parser.py` | Fortran parser project model and diagnostics. | | `prik/parsers/fortran/cli.py` | Fortran parser report formatting. | | `prik/semantics/metadata.py` | Cross-stage semantic metadata keys that survive parser, policy, printer, and lowering boundaries. | @@ -113,16 +119,22 @@ update this table, the package README files, and the mechanical checks in | `prik/parsers/pyi/parser.py` | Minimal `.pyi` text/file parsing to Python AST. | | `prik/pipeline/pyi.py` | Semantic `.pyi` text/file/path-set conversion and external-type reconciliation. | | `prik/semantics/pyi2ir.py` | Semantic `.pyi` AST conversion and validation. | -| `prik/semantics/policy_completion.py` | Post-IR semantic policy completion before wrapper planning. | -| `prik/codegen/plan.py` | Typed, policy-complete wrapper plan records. | -| `prik/codegen/planner.py` | Semantic policy to wrapper-plan conversion. | -| `prik/codegen/generator.py` | Ordered direct bridge, binding, header, and source generation. | +| `prik/policy/models.py` | Immutable backend-neutral completed wrapper-policy vocabulary. | +| `prik/policy/construction.py` | Wrapper-policy construction rules and completed-policy accessors. | +| `prik/policy/completion.py` | Ordered post-IR semantic policy completion before wrapper planning. | +| `prik/policy/native_array_handles.py` | Completed descriptor-handle policy and build requirements. | +| `prik/planning/models.py` | Typed, policy-complete wrapper plan records. | +| `prik/planning/planner.py` | Semantic policy to wrapper-plan conversion. | +| `prik/naming/native_symbols.py` | Stable generated native-symbol construction shared by planning and code generation. | +| `prik/codegen/docstrings.py` | Plan-driven Python-facing documentation generation. | +| `prik/pipeline/wrapper.py` | Single plan-to-rendered-wrapper orchestration and generated-wrapper result records. | | `prik/codegen/fortran/bridge.py` | Direct Fortran bridge lowering from typed plans. | | `prik/codegen/c/binding.py` | Direct Python-extension binding lowering from typed plans. | | `prik/codegen/c/python_surface.py` | Executable derived-class facade and thin class-overload forwarding source. | | `prik/codegen/c/naming.py` | Shared symbols referenced by generated C and the embedded Python facade. | -| `prik/codegen/printers/source_printers.py` | Native binding, header, and Fortran source printing. | -| `prik/codegen/printers/pyi_printer.py` | Semantic `.pyi` printing. | +| `prik/printers/c.py` | C binding and header node serialization. | +| `prik/printers/fortran.py` | Fortran bridge node serialization. | +| `prik/printers/pyi.py` | Semantic IR serialization as editable `.pyi`. | | `prik/compiling/objects.py` | Native compile object model. | | `prik/compiling/compilers.py` | Compiler command execution and tool lookup. | | `prik/compiling/native_support.py` | Native binding support installation for generated wrappers. | @@ -148,9 +160,9 @@ prik/cli.py -> prik/parsers/fortran/parser.py -> prik/probes/fortran_types.py -> prik/semantics/fortran2ir.py - -> prik/semantics/policy_completion.py - -> prik/codegen/planner.py - -> prik/codegen/generator.py + -> prik/policy/completion.py + -> prik/planning/planner.py + -> prik/pipeline/wrapper.py -> prik/codegen/fortran/bridge.py -> prik/codegen/c/binding.py -> prik/compiling/compilers.py @@ -164,9 +176,9 @@ For semantic `.pyi` builds, the parser branch is replaced by: prik/parsers/pyi/parser.py -> prik/pipeline/pyi.py -> prik/semantics/pyi2ir.py - -> prik/semantics/policy_completion.py - -> prik/codegen/planner.py - -> prik/codegen/generator.py + -> prik/policy/completion.py + -> prik/planning/planner.py + -> prik/pipeline/wrapper.py ``` diff --git a/docs/developer/testing-strategy.md b/docs/developer/testing-strategy.md index b9df372b5..ddfd04d6f 100644 --- a/docs/developer/testing-strategy.md +++ b/docs/developer/testing-strategy.md @@ -188,7 +188,7 @@ For example, semantic-policy internals use `infrastructure/semantics/test_ownership.py` and `test_policy_completion.py`; wrapper internals use `infrastructure/codegen/test_plan.py`, `test_planner.py`, and -`test_generator.py`. Other internal owners mirror `prik/compiling/`, +`tests/fortran/infrastructure/pipeline/test_wrapper_generator.py`. Other internal owners mirror `prik/compiling/`, `prik/contracts/`, `prik/pipeline/`, `prik/runtime/`, and the remaining source packages when they have real internal tests. Do not create empty mirror directories or combine multiple production owners in generic backend or policy diff --git a/docs/maintainer/internal-architecture/ownership-tracking.md b/docs/maintainer/internal-architecture/ownership-tracking.md index dd6edeb7d..ab8de29cc 100644 --- a/docs/maintainer/internal-architecture/ownership-tracking.md +++ b/docs/maintainer/internal-architecture/ownership-tracking.md @@ -259,19 +259,20 @@ different implementation. The main source owners are: -- `prik/semantics/ownership.py`: vocabulary, defaults, overrides, validation, +- `prik/policy/ownership.py`: vocabulary, defaults, overrides, validation, and completed actions; -- `prik/semantics/policy_completion.py`: attachment of decisions to semantic +- `prik/policy/completion.py`: attachment of decisions to semantic variables, functions, fields, classes, and module state; -- `prik/semantics/wrapper_policy.py`: completed wrapper-policy records and +- `prik/policy/construction.py`: completed wrapper-policy records and cross-feature validation; -- `prik/codegen/planner.py`: projection into the immutable wrapper plan; +- `prik/policy/models.py`: immutable feature-specific completed-policy records; +- `prik/planning/planner.py`: projection into the editable wrapper plan; - `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py`: strict dispatch from planned actions into emitted mechanisms. The enums documented on this page are the shared ownership vocabulary defined in `ownership.py`. Feature-specific completed policies also define narrower -mechanical enums in `wrapper_policy.py`, such as derived-object owner +mechanical enums in `models.py`, such as derived-object owner retention/release and native-array descriptor ownership/release. Those values refine an already completed ownership decision for one implementation family; they do not form another competing ownership system. diff --git a/docs/maintainer/internal-architecture/pipeline-map.md b/docs/maintainer/internal-architecture/pipeline-map.md index a8164be45..7b8b37c44 100644 --- a/docs/maintainer/internal-architecture/pipeline-map.md +++ b/docs/maintainer/internal-architecture/pipeline-map.md @@ -38,15 +38,16 @@ PRIK_C_DOCS_END --> | Stage | Main source | Input | Output | Primary evidence | | --- | --- | --- | --- | --- | | CLI request | `prik/cli.py` | source paths and stage flags | selected stage or wrapper build options | `tests/fortran/command_line_interface/pipeline/` | -| Build orchestration | `prik/pipeline/build.py` | ordered Fortran sources or `.pyi` contracts plus explicit native artifacts | `WrapperBuildResult`, `NativeBuildPlan`, and generated artifact plan | wrapper build-mode tests | +| Build orchestration | `prik/pipeline/build.py` | ordered Fortran sources or `.pyi` contracts plus explicit native artifacts | `WrapperBuildResult`, `NativeBuildPlan`, and `GeneratedWrapper` | wrapper build-mode tests | | Preprocessing | `prik/pipeline/preprocessing.py` | source path, compiler config | preprocessed source and dependency facts | preprocessing tests | | Parser project model | `prik/parsers/fortran/parser.py` (`_SourceUnitScanner` for structural boundaries/regions; `FortranParser` for scopes and model construction) | preprocessed Fortran source | parser project with modules, procedures, types, visibility | Fortran parser fixture tests | | Target probes | `prik/probes/fortran_types.py` | semantic type requirements and compiler flags | resolved kind/storage facts | Fortran type probe tests | | Semantic IR | `prik/semantics/fortran2ir.py` | parser project and target facts | `SemanticModule` objects | semantic Fortran tests | -| Semantic policy completion | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py` | full semantic modules with signatures and `.pyi` overrides | semantic modules annotated with every ownership, transfer, destruction, mutability, storage, accessor, and projection decision needed by wrapper generation | ownership-policy tests | -| Wrapper planning | `prik/codegen/planner.py`, `prik/codegen/plan.py` | policy-completed semantic modules | typed wrapper plans consuming completed decisions without a separate support-analysis traversal | `tests/fortran/infrastructure/codegen/`, wrapper tests | -| Direct bridge and binding lowering | `prik/codegen/fortran/bridge.py`, `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/codegen/generator.py` | validated typed wrapper plans | Fortran, C, header syntax nodes, and the embedded derived-class facade | `tests/fortran/infrastructure/codegen/`, wrapper tests | -| Wrapper and semantic-contract printing | `prik/codegen/printers/` | wrapper syntax nodes or semantic IR | wrapper source files or semantic `.pyi` text | printer, generated-contract, and wrapper artifact tests | +| Semantic policy completion | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/construction.py` | full semantic modules with signatures and `.pyi` overrides | semantic modules annotated with every ownership, transfer, destruction, mutability, storage, accessor, and projection decision needed by wrapper generation | ownership-policy tests | +| Wrapper planning | `prik/planning/planner.py`, `prik/planning/models.py` | policy-completed semantic modules | typed wrapper plans consuming completed decisions without rendering output text | `tests/fortran/infrastructure/codegen/`, wrapper tests | +| Direct documentation, bridge, and binding generation | `prik/codegen/docstrings.py`, `prik/codegen/fortran/bridge.py`, `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/pipeline/wrapper.py` | validated typed wrapper plans | completed public docstrings, Fortran, C, header syntax nodes, and the embedded derived-class facade | `tests/fortran/infrastructure/codegen/`, wrapper tests | +| Language printing | `prik/printers/c.py`, `prik/printers/fortran.py`, `prik/printers/pyi.py` | C or Fortran syntax nodes, or semantic IR | C, Fortran, header, or semantic `.pyi` text | printer and generated-contract tests | +| Wrapper generation pipeline | `prik/pipeline/wrapper.py` | editable completed wrapper plan | one generated wrapper containing rendered sources and build metadata | wrapper-generation and golden tests | | Compile and link | `prik/compiling/`, `prik/pipeline/build.py` | dependency-batched native objects, generated bridge and binding objects, compiler-process limit, and ordered link inputs | shared library | wrapper runtime and build-mode tests | | --- | --- | --- | | CLI and output routing | `prik/cli.py`, parser CLI helpers | `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` | | Source loading and preprocessing | `prik/pipeline/preprocessing.py` | `docs/developer/source-map.md`, parser references | -| Editable semantic contracts | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/codegen/printers/pyi_printer.py` | `docs/user/reference/semantic-pyi-format.md` | -| Semantic and wrapper-planning errors | `prik/semantics/fortran2ir.py`, `prik/semantics/policy_completion.py`, `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/codegen/planner.py` | `docs/user/guide/error-handling.md` | -| Wrapper policy and lowering | `prik/semantics/policy_completion.py`, `prik/semantics/ownership.py`, `prik/semantics/wrapper_policy_models.py`, `prik/semantics/wrapper_policy.py`, `prik/codegen/planner.py`, `prik/codegen/generator.py` | `docs/user/reference/fortran-wrapper.md`, ownership docs | +| Editable semantic contracts | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/printers/pyi.py` | `docs/user/reference/semantic-pyi-format.md` | +| Semantic and wrapper-planning errors | `prik/semantics/fortran2ir.py`, `prik/policy/completion.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py` | `docs/user/guide/error-handling.md` | +| Wrapper policy and lowering | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py`, `prik/pipeline/wrapper.py` | `docs/user/reference/fortran-wrapper.md`, ownership docs | | Native build | `prik/pipeline/build.py`, `prik/compiling/compilers.py`, `prik/compiling/native_support.py` | compiling package README and build-system docs | @@ -274,7 +275,7 @@ PRIK_C_DOCS_END --> | Source cannot be preprocessed | `prik/pipeline/preprocessing.py` | | Source syntax cannot be represented by prik's parser model | parser package | | Source facts cannot form a semantic contract | semantic conversion | -| Ownership, lifetime, ABI, projection, or wrapper support decision is unsafe | `prik/semantics/ownership.py` or policy completion | +| Ownership, lifetime, ABI, projection, or wrapper support decision is unsafe | `prik/policy/ownership.py` or policy completion | | A completed policy is internally inconsistent while being projected | wrapper planner at the owner being projected | | Native-language validity does not affect prik's contract | Fortran or C compiler | | Generated code cannot represent a supported plan | bridge or binding generator with focused tests | diff --git a/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md b/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md index 8dfa9a50d..17a5e2171 100644 --- a/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md +++ b/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md @@ -28,12 +28,18 @@ they do not reconstruct policy from datatype, `intent`, shape, alias flags, or local memory checks. The immutable backend-neutral vocabulary consumed across this boundary lives -in `prik/semantics/wrapper_policy_models.py`. It owns completed action enums, -policy records, and stable cross-stage reason constants, but no construction -rules. `prik/semantics/wrapper_policy.py` owns the semantic rules that build and -validate those records. Planning and lowering import completed model types from -the model module; only post-IR completion and planner accessors depend on the -construction module. +in `prik/policy/models.py`. It owns completed action enums, policy records, and +stable cross-stage reason constants, but no construction rules. +`prik/policy/construction.py` owns the semantic rules that build and validate +those records, and `prik/policy/completion.py` owns their ordered attachment to +the semantic IR. Raw contract metadata remains in +`prik/semantics/ownership_metadata.py`; ownership resolution and completed +ownership vocabulary live in `prik/policy/ownership.py`. + +Planning is a separate package. `prik/planning/models.py` owns the editable, +backend-neutral wrapper-plan records and `prik/planning/planner.py` mechanically +projects completed policy into those records. Planning does not render C, +Fortran, Python, or documentation text. Native-source `intent` may be consumed while importing a source declaration to propose default Python argument/result positions. It is not retained in the @@ -73,16 +79,21 @@ The public direct-generation boundary is: ```python complete_semantic_policies(module) plan = WrapperPlanner().build(module) -artifacts = WrapperCodeGenerator().generate(plan) +generated = WrapperGenerator().generate(plan) ``` -`WrapperCodeGenerator.generate()` freezes the plan, runs the shared validator, -runs both backend preflight checks, lowers recursively to C and Fortran syntax -nodes, and asks the source printers to render those nodes. Build integration -compiles the rendered sources; it does not own datatype transfer policy. -Wrapper C/Fortran source printers and the semantic `.pyi` printer share -`prik/codegen/printers/`; no compatibility printer remains under the -legacy codegen package. +`WrapperGenerator.generate()` in `prik/pipeline/wrapper.py` is the one wrapper +orchestrator. It completes plan-driven documentation, freezes and validates the +plan, runs both backend preflight checks, asks the C binding and Fortran bridge +generators for syntax nodes, renders those nodes through the language printers, +assigns their stable filenames, and returns one `GeneratedWrapper`. Build +integration writes or compiles that result; it does not own datatype transfer +policy. + +The language printers are the inverse-facing companions of the language +parsers. `prik/printers/c.py`, `prik/printers/fortran.py`, and +`prik/printers/pyi.py` serialize already-formed C nodes, Fortran nodes, or +semantic IR respectively. They do not plan wrappers or coordinate builds. Wrapper builds have no legacy route or fallback. An unsupported completed plan fails with its exact owner path before either backend emits source. @@ -122,12 +133,14 @@ value. They own export names, call order, result order, runtime/GIL envelopes, and aggregation, but not datatype policy. -Python-facing documentation is also a plan projection. The shared docstring -builder consumes completed namespace, module-variable, class, overload, -argument, result, and lifecycle records and stores the rendered text on the -owning plan nodes. C method-table emission and generated Python class assembly -only attach that text; neither backend infers signatures, ownership, mutation, -nullability, or exception behavior while rendering source. +Python-facing documentation is generated from the completed wrapper plan. +`prik/codegen/docstrings.py` owns this presentation step and is invoked by the +wrapper pipeline after planning and before the plan is frozen. It fills +only unresolved documentation fields, preserving explicit editable-plan +overrides, and renders child documentation before class and namespace +summaries. C method-table emission and generated Python class assembly only +attach the completed text; neither backend infers signatures, ownership, +mutation, nullability, or exception behavior while rendering source. `OverloadPlan` stores candidates, exact argument-match records, receiver conventions, and one unique integer candidate ID per overload set. The C @@ -219,7 +232,7 @@ whether the transfer itself is a scalar, string, or array. Inspect the real records directly with normal Python prints. The primary path is `complete_semantic_policies()` -> `WrapperPlanner.build()` -> -`WrapperCodeGenerator.generate()`. Generated artifacts from real passing +`WrapperGenerator.generate()`. Generated wrappers from real passing feature-local `tests/fortran/*/end_to_end/` cases are the behavioral oracle; plan unit tests cover action and graph invariants. Production source and semantic-`.pyi` builds both use this one path; unsupported completed policy diff --git a/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md b/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md index b2ca7faf2..80727ffb9 100644 --- a/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md +++ b/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md @@ -415,12 +415,13 @@ PRIK_C_DOCS_END --> - [x] Ownership, transfer, and destruction policy is completed after full signatures are known and before wrapper planning. The shared post-IR entrypoint is `complete_semantic_policies(...)` in - `prik/semantics/policy_completion.py`; direct ownership subpasses stay behind + `prik/policy/completion.py`; direct ownership subpasses stay behind that entrypoint. Planning and lowering consume completed policy metadata instead of recomputing policy from raw datatypes. Evidence: - `tests/semantics/policy/`, - `tests/codegen/`, - `tests/semantics/policy/`, + `tests/fortran/infrastructure/semantics/test_policy_completion.py`, + `tests/fortran/infrastructure/semantics/test_ownership.py`, + feature-local `tests/fortran/*/policy/`, + `tests/fortran/infrastructure/codegen/`, and `prik/semantics/README.md`. - [x] `.pyi` parsing and `.pyi` semantic conversion are separate stages: `prik/parsers/pyi/parser.py` parses text/files to Python AST, and @@ -550,7 +551,7 @@ PRIK_C_DOCS_END --> implemented. Remaining rank, datatype, `is_alias`, and storage checks in bridge and binding code are local emitted-code, ABI, documentation, or object-model mechanics rather than semantic policy selection. Evidence: - `prik/semantics/ownership.py`, + `prik/policy/ownership.py`, `prik/codegen/fortran/bridge.py`, `prik/codegen/c/binding.py`, `tests/semantics/policy/`, diff --git a/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md b/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md index fefb44ae3..de7631354 100644 --- a/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md +++ b/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md @@ -22,12 +22,12 @@ Semantic IR -> post-IR policy completion -> WrapperPlanner.build(module) -> editable ModulePlan - -> WrapperCodeGenerator.generate(plan) + -> WrapperGenerator.generate(plan) -> freeze and validate the received plan -> recursively synthesize C binding nodes -> recursively synthesize Fortran bridge nodes -> print backend nodes - -> RenderedGeneratedWrapperArtifacts + -> GeneratedWrapper -> existing build/link orchestration ``` @@ -41,10 +41,10 @@ The public generation boundary is deliberately small: ```python complete_semantic_policies(module) plan = WrapperPlanner().build(module) -artifacts = WrapperCodeGenerator().generate(plan) +generated_wrapper = WrapperGenerator().generate(plan) ``` -`WrapperCodeGenerator.generate` accepts `ModulePlan` only. It does not accept +`WrapperGenerator.generate` accepts `ModulePlan` only. It does not accept semantic modules, build a plan itself, select an alternate lowering route, or retry a prior route after direct generation begins. @@ -241,12 +241,12 @@ rather than in a backend exception. Every consumer freezes the exact object it receives: ```text -editable ModulePlan -- WrapperCodeGenerator --> frozen ModulePlan +editable ModulePlan -- WrapperGenerator --> frozen ModulePlan editable backend modules -- source printers --> frozen backend modules -editable generated artifacts -- build integration --> frozen artifacts +editable GeneratedWrapper -- build integration --> frozen GeneratedWrapper ``` -At the start of `WrapperCodeGenerator.generate(plan)`, the generator must: +At the start of `WrapperGenerator.generate(plan)`, the generator must: 1. recursively freeze that exact plan object; 2. run the complete binding/bridge plan-consistency validation on the final @@ -256,16 +256,16 @@ At the start of `WrapperCodeGenerator.generate(plan)`, the generator must: consume those nodes. Later mutation of the received plan raises `FrozenStageRecordError`. Backend -nodes remain editable until their printer consumes them. Generated artifacts -remain editable until `_build_rendered_wrapper_extension(...)` consumes them. +nodes remain editable until their printer consumes them. The generated wrapper +remains editable until `_build_generated_wrapper_extension(...)` consumes it. `WrapperPlanner` does not validate its output. It mechanically projects an editable plan, which may temporarily be inconsistent while a maintainer edits -it. `WrapperCodeGenerator` owns the private structured validation methods and +it. `WrapperGenerator` owns the private structured validation methods and is the only validation consumer. There is no standalone validator class or public validation operation. -`WrapperCodeGenerator._validate_plan()` is the single plan-consistency gate. +`WrapperGenerator._validate_plan()` is the single plan-consistency gate. It validates the complete binding/bridge graph after the editable plan has been frozen and before either backend preflight or visitor runs. The gate stays small by composing `_plan_diagnostics()` from typed private diagnostics for @@ -302,7 +302,7 @@ Structural validation preserves these invariants: ## Direct Recursive Lowering -`WrapperCodeGenerator` owns two private backend visitors: +`WrapperGenerator` owns two private backend visitors: ```python c_module, c_header = CBindingGenerator().visit(plan) @@ -335,8 +335,8 @@ nodes. Do not introduce another wrapper-specific transport model. The public orchestration stays visibly direct: ```python -class WrapperCodeGenerator: - def generate(self, plan: ModulePlan) -> RenderedGeneratedWrapperArtifacts: +class WrapperGenerator: + def generate(self, plan: ModulePlan) -> GeneratedWrapper: plan.freeze() self._validate_plan(plan) self._c_generator.require_supported(plan) @@ -348,7 +348,7 @@ class WrapperCodeGenerator: c_source = self._c_printer.doprint(c_module) c_header_source = self._c_printer.doprint(c_header) fortran_source = self._fortran_printer.doprint(fortran_module) - return self._rendered_artifacts( + return self._generated_wrapper( plan.owner_path, c_source, c_header_source, @@ -356,8 +356,8 @@ class WrapperCodeGenerator: ) ``` -The generator constructs `RenderedGeneratedWrapperArtifacts` directly from the -printed source plus artifact metadata. It does not duplicate native build plans, +The generator constructs `GeneratedWrapper` directly from the +printed source plus build metadata. It does not duplicate native build plans, compiler selection, link ordering, native-support installation, or compilation policy; those remain in existing build/link orchestration. @@ -480,7 +480,7 @@ bridge = FortranBridgeGenerator() print(function.arguments[0].binding.optional_mode) print(function.arguments[0].bridge.optional_mode) -artifacts = WrapperCodeGenerator( +generated_wrapper = WrapperGenerator( c_generator=binding, fortran_generator=bridge, ).generate(plan) @@ -501,7 +501,7 @@ Focused tests must prove: - `WrapperPlanner.build(module)` returns a directly mutable plan; - direct edits to binding and bridge views change the relevant generated C and Fortran source; -- `WrapperCodeGenerator.generate(plan)` freezes the exact consumed plan; +- `WrapperGenerator.generate(plan)` freezes the exact consumed plan; - module visitors recursively include generated function nodes; - function visitors recursively include argument, result, and lifecycle nodes; - directly named backend lowering methods cover every supported plan action; @@ -579,7 +579,7 @@ new row here before later implementation starts. Statuses have the meanings defined above: `legacy` still uses the current `semantic_ir_to_codegen_ast()` route, `dual-route` runs the same generation unit and runtime assertions through both implementations, `wrapper-plan` uses -only `WrapperPlan -> WrapperCodeGenerator`, `not-applicable` does not generate +only `WrapperPlan -> WrapperGenerator`, `not-applicable` does not generate a runtime wrapper, and `deferred-real-library` is reserved for the full BLAS and LAPACK corpus until Phase 12. @@ -921,13 +921,13 @@ the product contract. plans, including module/function/result/lifecycle scope as well as arguments. - [x] Remove plan-owned method names and handler registries; add typed datatype-family facts required by direct lowering. -- [x] Keep structural plan validation private to `WrapperCodeGenerator`, with +- [x] Keep structural plan validation private to `WrapperGenerator`, with no planner-time validation or standalone validator class, and verify every listed invariant after direct plan edits. ### Direct generator boundary -- [x] Change `WrapperCodeGenerator.generate` to consume only `ModulePlan`, +- [x] Change `WrapperGenerator.generate` to consume only `ModulePlan`, freeze it, validate it, validate lowering support, recursively generate backend nodes, print them, and return artifacts directly. - [x] Implement recursive `CBindingGenerator` synthesis of complete C modules, @@ -1189,7 +1189,7 @@ bridge-owned null-terminated storage that the binding converts and frees after the GIL is reacquired. Generated symbol spelling differs from the legacy artifacts, but the same source and edited-`.pyi` concurrency, exception, cleanup, and runtime assertions pass. Production cutover also reused the -existing rendered-artifact build path for inferred native module include +existing generated-wrapper build path for inferred native module include directories, native library directories, `.pyi` manifests, verbose timing, and scalar external explicit interfaces; no legacy retry was added. @@ -2288,7 +2288,7 @@ sources of truth: - `prik/semantics/native_array_handles.py` defines `NativeArrayHandlePolicy`, `ArrayInteropPolicy`, handle facts, descriptor kinds, and completed build requirements. -- `prik/semantics/policy_completion.py` completes handle kind, origin, owner, +- `prik/policy/completion.py` completes handle kind, origin, owner, owner retention, descriptor ownership, getter/setter behavior, output projection, release, target lifetime, destruction, extraction, interop, nullability, storage mode, operations, and blockers before `ir2ast.py`. @@ -3110,18 +3110,18 @@ copied native-array-handle extraction. Use the current implementation as an oracle, not as permission to preserve its architecture: -- `prik/semantics/ownership.py` already names `DERIVED_TYPE`, +- `prik/policy/ownership.py` already names `DERIVED_TYPE`, `PASS_WRAPPER_ADDRESS`, `WRAPPER_INSTANCE`, and `BORROWED_VIEW`, and contains the current argument/result/module/field owner defaults. Remove the obsolete derived whole-object snapshot action without disturbing ordinary result copies, scalar descriptor value copies, or explicit non-object uses of `snapshot_copy` transfer policy. -- `prik/semantics/policy_completion.py` is the only allowed owner of origin, +- `prik/policy/completion.py` is the only allowed owner of origin, ownership, transfer, destruction, mutability, nullability, projection, release, storage, getter/setter, owner-retention, module-object handoff, and field decisions. It must complete module-proxy policy for plain module objects and direct-address borrowed policy for `Aliased` module objects. -- `prik/semantics/wrapper_policy.py` must gain a derived-specific policy branch. +- `prik/policy/construction.py` must gain a derived-specific policy branch. Derived values must not continue through primitive-scalar blockers, primitive result checks, or primitive bridge data-action selection. - `prik/semantics/ir2ast.py` and the legacy generators remain the generated @@ -4751,7 +4751,7 @@ declared before an earlier array dummy without reordering the native call. Cutover contract: source builds, semantic-`.pyi` builds, Makefile generation, manifest replay, and strict-name validation all use completed policy -> -`WrapperPlan` -> `WrapperCodeGenerator`. The build API has no route selector, +`WrapperPlan` -> `WrapperGenerator`. The build API has no route selector, rollback flag, or silent fallback; an unsupported owner path fails before any backend or legacy lowering runs. @@ -4780,7 +4780,7 @@ backend or legacy lowering runs. package during migration. After final cutover, remove the legacy package pieces proven unused and keep `prik.codegen` as the canonical generator rather than performing a second package rename. -- [x] Keep semantic `.pyi` emission under `prik.codegen.printers` and +- [x] Keep semantic `.pyi` emission under `prik.printers` and retire focused tests of the old semantic AST, bridge, binding, and printer implementation before deleting the legacy package. - [x] Remove the temporary legacy route and its route diagnostics after every @@ -4833,7 +4833,7 @@ maintainability reports, and `git diff --check` all passed. - [x] The final report for each lane names the plan actions added, the binding and bridge handlers they dispatch to, and the handoff specs validated. - [x] No unsupported wrapper lane uses old lowering/codegen; focused tests now - target completed policy, `WrapperPlan`, `WrapperCodeGenerator`, or compiled + target completed policy, `WrapperPlan`, `WrapperGenerator`, or compiled public behavior rather than `ir2ast.py` and `prik.codegen` internals. - [x] The final cutover report includes the completed `tests/wrapper` migration matrix and confirms every wrapper-generating row uses the wrapper-plan route. diff --git a/docs/old_docs/developper_guide.md b/docs/old_docs/developper_guide.md index 5c6ae2284..a93dc685e 100644 --- a/docs/old_docs/developper_guide.md +++ b/docs/old_docs/developper_guide.md @@ -204,10 +204,10 @@ implementation files. | Generated target datatype mapping examples | `prik/type_mapping_report.py` | `tests/tools/test_type_mapping_report.py`, `tests/tools/test_documentation_examples.py` | | Fortran to semantic IR | `prik/semantics/fortran2ir.py`, `prik/semantics/models.py` | `tests/semantics/test_fortran2ir.py` | | C to semantic IR | `prik/semantics/c2ir.py`, `prik/semantics/models.py` | `tests/semantics/test_c2ir.py` | -| `.pyi` printing | `prik/codegen/printers/pyi_printer.py` | `tests/semantics/test_pyi_printer.py`, `tests/semantics/test_pyi_printer_modern_example.py` | +| `.pyi` printing | `prik/printers/pyi.py` | `tests/semantics/test_pyi_printer.py`, `tests/semantics/test_pyi_printer_modern_example.py` | | `.pyi` loading/editing | `prik/pyi_parser/parser.py` | `tests/pyi/test_pyi_to_ir.py`, `tests/pyi/test_pyi_fixture_suite.py` | | Fortran wrapper orchestration | `prik/wrapping.py` | `tests/wrapper/fortran/native_build/test_build_modes.py`, `tests/wrapper/fortran/multi_source/test_multi_source_builds.py` | -| Wrapper planning and owner-local errors | `prik/codegen/planner.py` | `tests/codegen/` | +| Wrapper planning and owner-local errors | `prik/planning/planner.py` | `tests/codegen/` | | Semantic IR to codegen AST | `prik/semantics/ir2ast.py` | `tests/semantics/test_ir2ast.py`, `tests/wrapper/` | | Fortran-to-C bridge and CPython binding | `prik/codegen/bridges/fortran_to_c.py`, `prik/codegen/bindings/c_to_python.py` | `tests/wrapper/` subject suites | | Native compilation and binding support | `prik/compiling/`, `prik/binding_support/` | `tests/wrapper/fortran/native_build/test_runtime_abi.py`, `tests/wrapper/fortran/native_build/test_build_modes.py` | @@ -258,7 +258,7 @@ module-level function only to preserve an old internal call path. ### `.pyi` Contract Internals User-visible `.pyi` syntax is parsed by `prik/pyi_parser/parser.py` and printed -by `prik/codegen/printers/pyi_printer.py`. Both operate on `prik/semantics/models.py`. +by `prik/printers/pyi.py`. Both operate on `prik/semantics/models.py`. Important implementation rules: @@ -715,7 +715,7 @@ The main ownership boundaries are: - `prik/codegen/bridges/fortran_to_c.py`: Fortran-to-C ABI adaptation; - `prik/codegen/bindings/c_to_python.py`: Python argument/result conversion, reference handling, and CPython wrapper construction; -- `prik/codegen/printers/{fcode,ccode,cpythoncode}.py`: source rendering only; +- `prik/printers/{c,fortran,pyi}.py`: language source rendering only; - `prik/compiling/`: compiler commands and shared-library linking; and - `prik/binding_support/`: native binding support copied into each build. @@ -784,9 +784,9 @@ from `prik/semantics/models.py`. reserved home for local variables or local constants if a frontend later promotes them into semantic IR; local bindings are not emitted into `.pyi` or treated as wrapper interface items by default. -- `prik/codegen/printers/pyi_printer.py` emits editable user contracts. +- `prik/printers/pyi.py` emits editable user contracts. - `prik/pyi_parser/parser.py` loads edited contracts back into semantic IR. -- `prik/semantics/policy_completion.py` completes the decisions required for +- `prik/policy/completion.py` completes the decisions required for wrapping. Keep semantic IR stable where possible. If a parser change does not affect the @@ -990,7 +990,7 @@ Example target: add a new `Annotated[...]` metadata item or projection helper. 1. Add loader tests in `tests/pyi/test_pyi_to_ir.py`. 2. Update `prik/pyi_parser/parser.py`. 3. Add printer tests in `tests/semantics/test_pyi_printer.py`. -4. Update `prik/codegen/printers/pyi_printer.py`. +4. Update `prik/printers/pyi.py`. 5. Update semantic models in `prik/semantics/models.py` only if the IR needs a new field or constraint. 6. Update policy completion or wrapper planning if the syntax changes a diff --git a/docs/user/reference/fortran-wrapper.md b/docs/user/reference/fortran-wrapper.md index c3813d890..1810afc38 100644 --- a/docs/user/reference/fortran-wrapper.md +++ b/docs/user/reference/fortran-wrapper.md @@ -663,7 +663,7 @@ an independent lifetime. ### Policy Overrides In Semantic `.pyi` Files -Ownership decisions are centralized in `prik.semantics.ownership`. Semantic +Ownership decisions are centralized in `prik.policy.ownership`. Semantic lowering and both bridge layers consume that resolved decision; low-level printers do not invent ownership behavior. diff --git a/prik/README.md b/prik/README.md index 4229f4de6..0a5e3162b 100644 --- a/prik/README.md +++ b/prik/README.md @@ -10,13 +10,16 @@ jumping directly into generated-code internals. | --- | --- | | `cli.py` | User CLI stages, output routing, diagnostics, and wrapper option validation. | | `contracts/` | Public names used by semantic `.pyi` contracts. | -| `pipeline/` | Preprocessing, semantic `.pyi` loading, and end-to-end wrapper builds. | +| `pipeline/` | Preprocessing, semantic `.pyi` loading, wrapper generation orchestration, and end-to-end builds. | | `probes/` | C ABI facts, Fortran kind/storage facts, and type mapping reports. | | `runtime/` | Python runtime objects used by generated extensions. | | `types/` | Semantic-to-Python ecosystem type mappings. | | `parsers/` | Parser namespace containing the `c`, `fortran`, and semantic `.pyi` frontends. | -| `semantics/` | Language-neutral semantic IR, declaration-expression provenance, policy completion, and `.pyi` conversion. | -| `codegen/` | Canonical wrapper plans, direct native bridge/binding generation, and source printers. | +| `semantics/` | Language-neutral semantic IR, declaration-expression provenance, and `.pyi` conversion. | +| `policy/` | Completed ownership and interoperability policy. | +| `planning/` | Editable backend-neutral wrapper implementation plans. | +| `codegen/` | Plan-driven documentation and direct C/Fortran syntax-node lowering. | +| `printers/` | C, Fortran, and semantic `.pyi` serialization. | | `compiling/` | Native compiler objects, wrapper compilation, native support installation, and linking. | | `utilities/` | Shared parsing, normalization, rendering, evaluation, and visitor helpers. | @@ -29,9 +32,9 @@ is part of semantic `.pyi` syntax. Parser-specific imports use the public Array declaration expressions cross three source packages in a fixed order: `utilities/declaration_expressions.py` parses and normalizes expression text, -`semantics/` records native callable provenance and completes support policy, -and `codegen/` consumes only the completed result while rendering generated -artifacts. Follow that order when changing an expression feature; source +`semantics/` records native callable provenance, `policy/` completes support, +and `codegen/` consumes only the completed plan while lowering generated +nodes. Follow that order when changing an expression feature; language printers, bridges, and bindings must not infer missing expression semantics. ## Source Navigation Docs diff --git a/prik/__init__.py b/prik/__init__.py index 99894f3b1..e6cec4a14 100644 --- a/prik/__init__.py +++ b/prik/__init__.py @@ -39,8 +39,13 @@ c_type_to_semantic_type, ) from prik.semantics.pyi2ir import convert_pyi_to_ir -from prik.pipeline.pyi import pyi_file_to_semantic_module, pyi_paths_to_semantic_modules, pyi_text_to_semantic_module -from prik.codegen.printers import emit_module_stubs, opaque_dependency_modules +from prik.pipeline.pyi import ( + emit_module_stubs, + opaque_dependency_modules, + pyi_file_to_semantic_module, + pyi_paths_to_semantic_modules, + pyi_text_to_semantic_module, +) from prik.runtime.handles import AllocatableArray, NativeArrayHandleBase, PointerArray __version__ = _distribution_version("prik") diff --git a/prik/cli.py b/prik/cli.py index 7aa99a06b..ee784d8c6 100644 --- a/prik/cli.py +++ b/prik/cli.py @@ -586,7 +586,7 @@ def _convert_fortran_semantic_sources( def _semantic_payload_for_converted_files(converted_files) -> dict[str, dict]: - from prik.codegen.printers import emit_module_stubs + from prik.pipeline.pyi import emit_module_stubs out: dict[str, dict] = {} available_modules = [module for _p, modules in converted_files for module in modules] @@ -613,7 +613,7 @@ def _is_fortran_semantic_file(modules) -> bool: def _fortran_contract_payload(path: Path, modules, available_modules) -> dict[str, object]: - from prik.codegen.printers import emit_module_stubs + from prik.pipeline.pyi import emit_module_stubs native_modules = [module for module in modules if module.origin.source_kind == "module"] external_modules = [module for module in modules if module.origin.source_kind != "module"] diff --git a/prik/codegen/__init__.py b/prik/codegen/__init__.py index 0e245c52b..692ab4f20 100644 --- a/prik/codegen/__init__.py +++ b/prik/codegen/__init__.py @@ -1,10 +1,9 @@ -"""Direct wrapper-plan generation infrastructure.""" +"""Wrapper backend nodes and direct C/Fortran lowering.""" from __future__ import annotations from .c.binding import CBindingGenerator from .fortran.bridge import FortranBridgeGenerator -from .generator import WrapperCodeGenerator from .nodes import ( BackendScalarType, CAllowThreadsBegin, @@ -44,65 +43,11 @@ FortranSelectCase, FortranUse, ) -from .plan import ( - ArgumentTransferPlan, - ArrayHandoffPlan, - BindingArgumentPlan, - BindingFunctionPlan, - BindingLifecyclePlan, - BindingModulePlan, - BindingModuleVariablePlan, - BindingResultPlan, - BindingStatusErrorPlan, - BridgeArgumentPlan, - BridgeFunctionPlan, - BridgeLifecyclePlan, - BridgeModulePlan, - BridgeModuleVariablePlan, - BridgeResultPlan, - DatatypeFamily, - DerivedFieldPlan, - DerivedHandoffPlan, - DerivedMemberPathPlan, - DerivedModuleObjectPlan, - DerivedTypePlan, - FunctionPlan, - LifecycleActionPlan, - ModulePlan, - ModuleVariablePlan, - NativeArrayActualPlan, - NativeArrayDefaultHandlePlan, - NativeArrayHandlePlan, - NativeCallSlotPlan, - NativeDescriptorHandoffPlan, - NamespacePlan, - ResultPlan, - ScalarDescriptorResultPlan, - TransformationPlan, - WrapperPlanDiagnostic, -) -from .planner import WrapperPlanner from .primitive_scalar_types import PrimitiveScalarTypeRegistry -from .printers import CSourcePrinter, FortranSourcePrinter from .visitor import ClassVisitor, UnsupportedWrapperCodegenNodeError __all__ = ( - "ArgumentTransferPlan", - "ArrayHandoffPlan", "BackendScalarType", - "BindingArgumentPlan", - "BindingFunctionPlan", - "BindingLifecyclePlan", - "BindingModulePlan", - "BindingModuleVariablePlan", - "BindingResultPlan", - "BindingStatusErrorPlan", - "BridgeArgumentPlan", - "BridgeFunctionPlan", - "BridgeLifecyclePlan", - "BridgeModulePlan", - "BridgeModuleVariablePlan", - "BridgeResultPlan", "CAllowThreadsBegin", "CAllowThreadsEnd", "CBindingGenerator", @@ -123,16 +68,9 @@ "CModulePropertySupport", "CParameter", "CReturn", - "CSourcePrinter", "CSwitch", "ClassVisitor", "CodeExpression", - "DatatypeFamily", - "DerivedFieldPlan", - "DerivedHandoffPlan", - "DerivedMemberPathPlan", - "DerivedModuleObjectPlan", - "DerivedTypePlan", "FortranAllocate", "FortranAssignment", "FortranBridgeGenerator", @@ -148,24 +86,7 @@ "FortranParameter", "FortranPointerAssignment", "FortranSelectCase", - "FortranSourcePrinter", "FortranUse", - "FunctionPlan", - "LifecycleActionPlan", - "ModulePlan", - "ModuleVariablePlan", - "NamespacePlan", - "NativeArrayActualPlan", - "NativeArrayDefaultHandlePlan", - "NativeArrayHandlePlan", - "NativeCallSlotPlan", - "NativeDescriptorHandoffPlan", "PrimitiveScalarTypeRegistry", - "ResultPlan", - "ScalarDescriptorResultPlan", - "TransformationPlan", "UnsupportedWrapperCodegenNodeError", - "WrapperCodeGenerator", - "WrapperPlanDiagnostic", - "WrapperPlanner", ) diff --git a/prik/codegen/c/binding.py b/prik/codegen/c/binding.py index 1a89ab2ae..7023304d0 100644 --- a/prik/codegen/c/binding.py +++ b/prik/codegen/c/binding.py @@ -14,13 +14,13 @@ import re from prik.utilities.declaration_expressions import declaration_extent_uses_power, render_declaration_extent -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, ObjectKind, PythonBarrierAction, SetterAction, ) -from prik.semantics.wrapper_policy_models import ( +from prik.policy.models import ( ArgumentHandoffMode, CallbackABIKind, CallbackResultAction, @@ -79,9 +79,9 @@ CSwitch, CodeExpression, ) -from prik.codegen.naming import NativeSymbolNames +from prik.naming.native_symbols import NativeSymbolNames from prik.codegen.overloads import OverloadPlanQueries -from prik.codegen.plan import ( +from prik.planning.models import ( ArrayHandoffPlan, ArgumentTransferPlan, CallbackHandoffPlan, @@ -11439,7 +11439,7 @@ def _overload_required_argument_condition( return f"PyUnicode_Check({value})" if match.kind is OverloadMatchKind.NUMPY_SCALAR: predicate = f"PyArray_IsScalar({value}, {self._overload_numpy_scalar_kind(match.semantic_type_name)})" - if match.accept_builtin_scalar: + if match.builtin_scalar_family is not None: predicate = f"({predicate} || {self._overload_builtin_scalar_condition(match, value)})" return predicate raise ValueError(f"Unsupported overload match kind: {match.kind.value}") @@ -11467,15 +11467,15 @@ def _overload_numpy_scalar_kind(semantic_type_name: str) -> str: @staticmethod def _overload_builtin_scalar_condition(match: OverloadArgumentMatchPlan, value: str) -> str: """Return the exact builtin predicate allowed for reflected dispatch.""" - if is_boolean_semantic_type_name(match.semantic_type_name): + if match.builtin_scalar_family == "bool": return f"PyBool_Check({value})" - if match.semantic_type_name.startswith("Int"): + if match.builtin_scalar_family == "int": return f"PyLong_CheckExact({value})" - if match.semantic_type_name.startswith("Float"): + if match.builtin_scalar_family == "float": return f"PyFloat_CheckExact({value})" - if match.semantic_type_name.startswith("Complex"): + if match.builtin_scalar_family == "complex": return f"PyComplex_CheckExact({value})" - raise ValueError(f"Unsupported reflected overload scalar {match.semantic_type_name!r}") + raise ValueError(f"Unsupported reflected overload scalar family {match.builtin_scalar_family!r}") def _overload_candidate_case( self, @@ -11541,7 +11541,7 @@ def _overload_candidate_keyword_nodes( ) ] coerced_name = None - if match.accept_builtin_scalar: + if match.builtin_scalar_family is not None: coerced_name = f"candidate_coerced_{index}" nodes.append(CDeclaration(coerced_name, "PyObject *", CodeExpression("NULL"))) nodes.append(self._overload_builtin_coercion_node(match, value_name, coerced_name)) @@ -12230,9 +12230,9 @@ def _namespace_module_name(self, module: ModulePlan, namespace: NamespacePlan) - if __name__ == "__main__": - from prik.codegen.planner import WrapperPlanner + from prik.planning.planner import WrapperPlanner from prik.semantics.models import SemanticArgument, SemanticFunction, SemanticModule, SemanticType - from prik.semantics.policy_completion import complete_semantic_policies + from prik.policy.completion import complete_semantic_policies module = SemanticModule( name="binding_demo", diff --git a/prik/codegen/c/naming.py b/prik/codegen/c/naming.py index 557f0a301..26cfec65c 100644 --- a/prik/codegen/c/naming.py +++ b/prik/codegen/c/naming.py @@ -2,8 +2,8 @@ from __future__ import annotations -from prik.codegen.naming import NativeSymbolNames -from prik.codegen.plan import ( +from prik.naming.native_symbols import NativeSymbolNames +from prik.planning.models import ( ClassSurfacePlan, DerivedFieldPlan, DerivedMemberPathPlan, diff --git a/prik/codegen/c/python_surface.py b/prik/codegen/c/python_surface.py index 3fed9f508..2b63379a5 100644 --- a/prik/codegen/c/python_surface.py +++ b/prik/codegen/c/python_surface.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from prik.codegen.c.naming import CBindingNames -from prik.codegen.plan import ( +from prik.planning.models import ( ArgumentTransferPlan, ClassMethodPlan, ClassSurfacePlan, @@ -18,8 +18,8 @@ OverloadPlan, ) from prik.codegen.visitor import ClassVisitor -from prik.semantics.ownership import SetterAction -from prik.semantics.wrapper_policy_models import ( +from prik.policy.ownership import SetterAction +from prik.policy.models import ( ClassConstructorKind, ClassMethodKind, ModuleObjectAccessMechanism, diff --git a/prik/codegen/checks.py b/prik/codegen/checks.py index 464fa6367..2876a1296 100644 --- a/prik/codegen/checks.py +++ b/prik/codegen/checks.py @@ -1,4 +1,4 @@ -"""Static contracts for the isolated wrapper-plan generator package.""" +"""Static contracts for wrapper lowering, printing, and orchestration.""" from __future__ import annotations @@ -17,8 +17,8 @@ INFRASTRUCTURE_MODULES = frozenset({"__init__.py", "checks.py", "visitor.py"}) -SEMANTIC_PRINTER_MODULE = "pyi_printer.py" -SEMANTIC_PRINTER_FUNCTIONS = frozenset({"emit_module", "emit_module_stubs", "opaque_dependency_modules"}) +SEMANTIC_PRINTER_MODULE = "pyi.py" +SEMANTIC_PRINTER_FUNCTIONS = frozenset({"emit_module"}) VISITOR_CLASS_SUFFIXES = ("Analyzer", "Emitter", "Generator", "Planner", "Validator") REGISTRY_SUFFIXES = ("_DISPATCHER", "_HANDLERS", "_REGISTRY") HANDLER_PREFIXES = ("_convert_", "_emit_", "_handle_", "_visit_") @@ -35,7 +35,7 @@ { "CBindingGenerator", "FortranBridgeGenerator", - "WrapperCodeGenerator", + "WrapperGenerator", } ) @@ -76,9 +76,17 @@ def check_codegen_package( *, config: WrapperCodegenCheckConfig | None = None, ) -> tuple[WrapperCodegenViolation, ...]: - """Check every Python module in the isolated wrapper-codegen package.""" - root = package_root or Path(__file__).resolve().parent - return check_codegen_paths(sorted(root.rglob("*.py")), config=config) + """Check the wrapper generation, printer, and orchestration modules.""" + if package_root is not None: + paths = sorted(package_root.rglob("*.py")) + else: + source_root = Path(__file__).resolve().parents[1] + paths = [ + *sorted((source_root / "codegen").rglob("*.py")), + *sorted((source_root / "printers").glob("*.py")), + source_root / "pipeline" / "wrapper.py", + ] + return check_codegen_paths(paths, config=config) def check_codegen_paths( @@ -124,7 +132,7 @@ def _visitor_class_violations(path: Path, tree: ast.Module) -> list[WrapperCodeg for node in tree.body if isinstance(node, ast.ClassDef) and node.name.endswith(VISITOR_CLASS_SUFFIXES) - and node.name != "WrapperCodeGenerator" + and node.name != "WrapperGenerator" and not _inherits_class_visitor(node) ] diff --git a/prik/codegen/docstrings.py b/prik/codegen/docstrings.py index 68f925353..37d78bd58 100644 --- a/prik/codegen/docstrings.py +++ b/prik/codegen/docstrings.py @@ -9,25 +9,28 @@ from __future__ import annotations -from prik.semantics.ownership import OwnershipOwner, SetterAction, TransferMode -from prik.semantics.wrapper_policy_models import ( +from prik.policy.ownership import OwnershipOwner, SetterAction, TransferMode +from prik.policy.models import ( ClassConstructorKind, ModuleGetterAction, NativeArrayDescriptorKind, OptionalMode, ) -from prik.codegen.plan import ( +from prik.planning.models import ( ArgumentTransferPlan, ArrayHandoffPlan, BindingStatusErrorPlan, CallbackHandoffPlan, CallbackTransferPlan, ClassMethodPlan, + ClassSurfacePlan, ConstructorPlan, DatatypeFamily, DerivedFieldPlan, FunctionPlan, + ModulePlan, ModuleVariablePlan, + NamespacePlan, OverloadPlan, ResultPlan, ) @@ -53,14 +56,111 @@ class WrapperDocstringBuilder: """Build compact, public NumPy-style documentation from completed plans. - Use the public entrypoints while ``WrapperPlanner`` constructs namespace, - callable, class, and attribute plan records. The builder preserves the - completed plan's public visibility, ordering, ownership, and transfer - facts, returning plain strings that generators later attach to public - Python surfaces. Its sections cover summaries, callable documentation, - constructors and attributes, then shared formatting helpers. + :meth:`render` is called by ``WrapperGenerator`` after planning. It + fills unresolved documentation fields in dependency order while preserving + explicit editable-plan strings, including an intentionally empty string. + The remaining public methods render individual plan records without + changing policy or transfer facts. """ + def render(self, plan: ModulePlan) -> ModulePlan: + """Fill unresolved Python-facing documentation on one editable plan. + + Child callables, fields, overloads, constructors, and variables are + rendered before their class and namespace summaries. Existing strings + are explicit plan overrides and remain unchanged. The same plan is + returned for generation-stage chaining. + """ + for namespace in plan.namespaces: + self._render_namespace(plan.owner_path, namespace) + return plan + + def _render_namespace(self, module_name: str, namespace: NamespacePlan) -> None: + """Render one namespace's children before its aggregate summary.""" + for function in namespace.functions: + self._render_function(function) + for derived_type in namespace.derived_types: + for field in derived_type.fields: + self._render_field(field) + for variable in namespace.variables: + self._render_module_variable(variable) + for overload in namespace.overloads: + self._render_overload(overload) + + derived_types = {item.type_identity: item for item in namespace.derived_types} + for surface in namespace.classes: + derived_type = derived_types.get(surface.type_identity) + self._render_class_surface(surface, () if derived_type is None else derived_type.fields) + + if namespace.docstring is None: + namespace.docstring = self.namespace( + module_name, + namespace.python_path, + namespace.functions, + namespace.variables, + namespace.classes, + namespace.overloads, + ) + + def _render_function(self, function: FunctionPlan) -> None: + """Render one ordinary callable unless the editable plan overrides it.""" + if function.binding.docstring is None: + function.binding.docstring = self.function( + function.binding.python_name, + function.arguments, + function.results, + status_error=function.binding.status_error, + ) + + def _render_field(self, field: DerivedFieldPlan) -> None: + """Render one class field unless the editable plan overrides it.""" + if field.docstring is None: + field.docstring = self.field(field) + + def _render_module_variable(self, variable: ModuleVariablePlan) -> None: + """Render one module attribute unless the editable plan overrides it.""" + if variable.docstring is None: + variable.docstring = self.module_variable(variable) + + def _render_overload(self, overload: OverloadPlan) -> None: + """Render overload candidates before their public dispatcher summary.""" + for candidate in overload.candidates: + self._render_function(candidate) + if overload.docstring is None: + overload.docstring = self.overload(overload) + + def _render_class_surface( + self, + surface: ClassSurfacePlan, + fields: tuple[DerivedFieldPlan, ...], + ) -> None: + """Render one class's dependent records before its aggregate summary.""" + for field in fields: + self._render_field(field) + for method in surface.methods: + self._render_function(method.function) + if method.docstring is None: + method.docstring = self.method(method) + for overload in surface.overloads: + self._render_overload(overload) + + constructor = surface.constructor + if constructor.target is not None: + self._render_function(constructor.target) + if constructor.overload is not None: + self._render_overload(constructor.overload) + if constructor.docstring is None: + constructor.docstring = self.constructor(surface.python_names[0], constructor, fields) + if surface.docstring is None: + surface.docstring = self.class_surface( + surface.python_names[0], + surface.type_identity[1], + constructor, + fields, + surface.methods, + surface.overloads, + ) + # Public entrypoints: namespace and class summaries. def namespace( self, @@ -406,7 +506,7 @@ def _append_section(lines: list[str], heading: str, body: tuple[str, ...]) -> No lines.extend(("", heading, "-" * len(heading), *body)) @staticmethod - def _first_line(docstring: str) -> str: + def _first_line(docstring: str | None) -> str: """Return the first summary line from rendered text or an empty string. Namespace and class summaries use this to embed a callable's compact @@ -891,6 +991,8 @@ def _module_variable_summary_lines(self, variable: ModuleVariablePlan) -> tuple[ without rebuilding getter/setter policy. A nonstandard first line is returned unchanged as one summary line. """ + if variable.docstring is None: + raise ValueError(f"Module variable {variable.owner_path!r} has no rendered documentation") first, *details = variable.docstring.splitlines() _name, separator, type_name = first.partition(" : ") if not separator: @@ -926,9 +1028,9 @@ def _constructor_field_lines(self, field: DerivedFieldPlan) -> tuple[str, ...]: if __name__ == "__main__": - from prik.codegen.planner import WrapperPlanner + from prik.planning.planner import WrapperPlanner from prik.semantics.models import SemanticArgument, SemanticFunction, SemanticModule, SemanticType - from prik.semantics.policy_completion import complete_semantic_policies + from prik.policy.completion import complete_semantic_policies module = SemanticModule( name="docstring_demo", @@ -942,12 +1044,8 @@ def _constructor_field_lines(self, field: DerivedFieldPlan) -> tuple[str, ...]: ], ) complete_semantic_policies(module) - function = WrapperPlanner().build(module).namespaces[0].functions[0] - docstring = WrapperDocstringBuilder().function( - function.binding.python_name, - function.arguments, - function.results, - status_error=function.binding.status_error, - ) + plan = WrapperPlanner().build(module) + WrapperDocstringBuilder().render(plan) + docstring = plan.namespaces[0].functions[0].binding.docstring print(docstring) diff --git a/prik/codegen/fortran/bridge.py b/prik/codegen/fortran/bridge.py index a7b350c13..a80c94d4f 100644 --- a/prik/codegen/fortran/bridge.py +++ b/prik/codegen/fortran/bridge.py @@ -12,7 +12,7 @@ import re from prik.utilities.declaration_expressions import render_declaration_extent -from prik.semantics.ownership import ( +from prik.policy.ownership import ( AssignmentMode, CodegenAction, NativeBarrierAction, @@ -21,7 +21,7 @@ SetterAction, ) from prik.semantics.metadata import SCALAR_STORAGE_CATEGORY -from prik.semantics.wrapper_policy_models import ( +from prik.policy.models import ( ArgumentHandoffMode, ArrayLogicalABI, ArrayWritebackABI, @@ -74,8 +74,8 @@ FortranTypeDefinition, FortranUse, ) -from prik.codegen.naming import NativeSymbolNames -from prik.codegen.plan import ( +from prik.naming.native_symbols import NativeSymbolNames +from prik.planning.models import ( ArrayHandoffPlan, ArgumentTransferPlan, CallbackHandoffPlan, @@ -7972,9 +7972,9 @@ def _uses_derived_interop_symbols(self, plan: ModulePlan) -> bool: if __name__ == "__main__": - from prik.codegen.planner import WrapperPlanner + from prik.planning.planner import WrapperPlanner from prik.semantics.models import SemanticArgument, SemanticFunction, SemanticModule, SemanticType - from prik.semantics.policy_completion import complete_semantic_policies + from prik.policy.completion import complete_semantic_policies module = SemanticModule( name="bridge_demo", diff --git a/prik/codegen/overloads.py b/prik/codegen/overloads.py index f882ff913..d07c9c2f4 100644 --- a/prik/codegen/overloads.py +++ b/prik/codegen/overloads.py @@ -2,7 +2,7 @@ from __future__ import annotations -from prik.codegen.plan import FunctionPlan +from prik.planning.models import FunctionPlan class OverloadPlanQueries: diff --git a/prik/codegen/printers/__init__.py b/prik/codegen/printers/__init__.py deleted file mode 100644 index 0f9a398f0..000000000 --- a/prik/codegen/printers/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Canonical source and semantic-contract printers.""" - -from .pyi_printer import PyiPrinter, emit_module, emit_module_stubs, opaque_dependency_modules -from .source_printers import CSourcePrinter, FortranSourcePrinter - -__all__ = ( - "CSourcePrinter", - "FortranSourcePrinter", - "PyiPrinter", - "emit_module", - "emit_module_stubs", - "opaque_dependency_modules", -) diff --git a/prik/codegen/naming.py b/prik/naming/native_symbols.py similarity index 87% rename from prik/codegen/naming.py rename to prik/naming/native_symbols.py index bb8d8812e..f1cf97a2a 100644 --- a/prik/codegen/naming.py +++ b/prik/naming/native_symbols.py @@ -1,4 +1,4 @@ -"""Stable backend symbols whose spelling stays within native compiler limits.""" +"""Stable backend symbols shared by planning and native code generation.""" from __future__ import annotations diff --git a/prik/parsers/fortran/cli.py b/prik/parsers/fortran/cli.py index 983f51617..f537feb4e 100644 --- a/prik/parsers/fortran/cli.py +++ b/prik/parsers/fortran/cli.py @@ -81,7 +81,7 @@ def _parse_paths(paths: list[str]) -> dict[str, dict]: def _semantic_report(paths: list[str]) -> dict[str, dict]: """Generate semantic IR and pyi text per parsed file.""" from prik.semantics.fortran2ir import fortran_module_to_semantic_module - from prik.codegen.printers import emit_module + from prik.printers import emit_module parsed = _parse_paths(paths) semantic_out: dict[str, dict] = {} diff --git a/prik/pipeline/README.md b/prik/pipeline/README.md new file mode 100644 index 000000000..6e5c106e8 --- /dev/null +++ b/prik/pipeline/README.md @@ -0,0 +1,17 @@ +# Pipeline Package + +This package coordinates complete workflows without taking ownership of +language semantics, policy rules, backend lowering, language printing, or +native compiler mechanisms. + +| File | Owns | +| --- | --- | +| `preprocessing.py` | Compiler preprocessing recipes and source mappings. | +| `pyi.py` | Semantic `.pyi` loading, package assembly, and reference reconciliation. | +| `wrapper.py` | One completed-plan-to-rendered-wrapper generation workflow. | +| `build.py` | Generated-source output, native compilation, linking, and extension results. | + +`WrapperGenerator` in `wrapper.py` completes plan-driven documentation, +validates the editable plan, invokes the C and Fortran node generators, prints +their results through `../printers/`, assigns stable filenames, and returns one +`GeneratedWrapper`. It does not write files or invoke a compiler. diff --git a/prik/pipeline/build.py b/prik/pipeline/build.py index d80cad739..862ae7866 100644 --- a/prik/pipeline/build.py +++ b/prik/pipeline/build.py @@ -24,7 +24,7 @@ resolve_fortran_logical_storage_types, ) from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source -from prik.pipeline.wrapper_artifacts import GeneratedSourceFile, RenderedGeneratedWrapperArtifacts +from prik.pipeline.wrapper import GeneratedSource, GeneratedWrapper, WrapperGenerator from prik.semantics.fortran2ir import ( collect_fortran_type_storage_requirements, collect_semantic_compile_time_requirements, @@ -43,14 +43,14 @@ _iter_module_semantic_types, ) from prik.semantics.native_contract import NATIVE_CONTRACT_PREPARED_METADATA, validate_pyi_native_contract -from prik.semantics.native_array_handles import ( +from prik.policy.native_array_handles import ( NativeArrayBuildRequirements, native_array_handle_build_requirements, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from prik.pipeline.pyi import _PyiSemanticModuleCache from prik.semantics.pyi_metadata import PYI_LOADED_METADATA -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.planning import WrapperPlanner from prik.types.numpy import boolean_storage_bits, is_boolean_semantic_type_name @@ -61,7 +61,7 @@ _C_SOURCE_SUFFIXES = {".c"} _NATIVE_PATH_LINK_KINDS = frozenset({"object", "archive", "shared_library"}) _NATIVE_LINK_KINDS = frozenset({*_NATIVE_PATH_LINK_KINDS, "named_library", "linker_argument"}) -_RENDERED_WRAPPER_SOURCE_LANGUAGES = { +_GENERATED_WRAPPER_SOURCE_LANGUAGES = { ".c": "c", ".f": "fortran", ".f03": "fortran", @@ -72,7 +72,7 @@ ".for": "fortran", ".ftn": "fortran", } -_RENDERED_WRAPPER_NATIVE_SUPPORT_IMPORTS = { +_GENERATED_WRAPPER_NATIVE_SUPPORT_IMPORTS = { "binding_support": ("binding_support/prik_binding",), } @@ -497,7 +497,7 @@ def _validated_wrapper_module_name(requested_name: str | None, default_name: str return module_name -# Rendered wrapper artifacts and native compilation +# Generated wrapper materialization and native compilation def _expected_generated_files( @@ -530,80 +530,80 @@ def _expected_generated_files( return tuple(path for path in candidates if path.exists()) -def _rendered_artifact_output_path(output_dir: Path, path: Path) -> Path: - """Return the output path for one rendered wrapper artifact.""" +def _generated_source_output_path(output_dir: Path, path: Path) -> Path: + """Return the output path for one generated wrapper source.""" if path.is_absolute() or ".." in path.parts: - raise ValueError(f"Rendered wrapper artifact path must stay inside the build directory: {path}") + raise ValueError(f"Generated wrapper source path must stay inside the build directory: {path}") return output_dir / path -def _write_rendered_wrapper_sources( - rendered: RenderedGeneratedWrapperArtifacts, +def _write_generated_wrapper_sources( + rendered: GeneratedWrapper, output_dir: Path, *, verbose: bool | int = False, ) -> tuple[Path, ...]: - """Write rendered wrapper-plan sources into one build directory.""" + """Write one generated wrapper's sources into a build directory.""" written = [] for source in rendered.sources: - path = _rendered_artifact_output_path(output_dir, source.path) - _print_verbose_step(verbose, f"{_rendered_source_write_label(rendered, source.path)}: {path}") + path = _generated_source_output_path(output_dir, source.path) + _print_verbose_step(verbose, f"{_generated_source_write_label(rendered, source.path)}: {path}") path.parent.mkdir(parents=True, exist_ok=True) path.write_text(source.text, encoding="utf-8") written.append(path) return tuple(written) -def _rendered_source_payloads( - rendered: RenderedGeneratedWrapperArtifacts, -) -> dict[Path, GeneratedSourceFile]: - """Return rendered payloads keyed by generated artifact path.""" +def _generated_source_payloads( + rendered: GeneratedWrapper, +) -> dict[Path, GeneratedSource]: + """Return source payloads keyed by generated wrapper path.""" return {Path(source.path): source for source in rendered.sources} -def _rendered_source_write_label(rendered: RenderedGeneratedWrapperArtifacts, source_path: Path) -> str: - """Return the verbose write label for one generated artifact.""" - if source_path in rendered.artifacts.bridge_sources: +def _generated_source_write_label(rendered: GeneratedWrapper, source_path: Path) -> str: + """Return the verbose write label for one generated source.""" + if source_path in rendered.bridge_sources: return "Write bridge source" - if source_path in rendered.artifacts.binding_sources: + if source_path in rendered.binding_sources: return "Write binding source" - if source_path in rendered.artifacts.header_files: + if source_path in rendered.headers: return "Write binding header" - return "Write wrapper artifact" + return "Write generated source" -def _rendered_wrapper_compile_source_paths( - rendered: RenderedGeneratedWrapperArtifacts, +def _generated_wrapper_compile_source_paths( + rendered: GeneratedWrapper, ) -> tuple[Path, ...]: - """Return rendered wrapper-plan source paths in compile order.""" - source_paths = (*rendered.artifacts.bridge_sources, *rendered.artifacts.binding_sources) - payloads = _rendered_source_payloads(rendered) + """Return generated wrapper source paths in compile order.""" + source_paths = rendered.compile_sources + payloads = _generated_source_payloads(rendered) missing = tuple(path for path in source_paths if path not in payloads) if missing: - raise ValueError(f"Rendered wrapper artifacts are missing source payloads: {missing!r}") + raise ValueError(f"Generated wrapper is missing source payloads: {missing!r}") return source_paths -def _rendered_wrapper_source_language(path: Path) -> str: - """Return the compiler language for one rendered wrapper source.""" +def _generated_wrapper_source_language(path: Path) -> str: + """Return the compiler language for one generated wrapper source.""" try: - return _RENDERED_WRAPPER_SOURCE_LANGUAGES[path.suffix.lower()] + return _GENERATED_WRAPPER_SOURCE_LANGUAGES[path.suffix.lower()] except KeyError: - raise ValueError(f"Unsupported rendered wrapper source suffix: {path}") from None + raise ValueError(f"Unsupported generated wrapper source suffix: {path}") from None -def _rendered_wrapper_native_support_imports(native_support_keys: Iterable[str]) -> tuple[str, ...]: +def _generated_wrapper_native_support_imports(native_support_keys: Iterable[str]) -> tuple[str, ...]: """Return native-support import keys consumed by the support installer.""" imports: list[str] = [] for key in native_support_keys: try: - imports.extend(_RENDERED_WRAPPER_NATIVE_SUPPORT_IMPORTS[key]) + imports.extend(_GENERATED_WRAPPER_NATIVE_SUPPORT_IMPORTS[key]) except KeyError: raise ValueError(f"Unsupported wrapper native support key: {key!r}") from None return tuple(imports) -def _rendered_wrapper_object_file( +def _generated_wrapper_object_file( source_path: Path, output_dir: Path, *, @@ -611,8 +611,8 @@ def _rendered_wrapper_object_file( include_dirs: tuple[Path, ...], language: str, ) -> ObjectFile: - """Return one explicit object-file input for a rendered wrapper source.""" - source = _rendered_artifact_output_path(output_dir, source_path) + """Return one explicit object-file input for a generated wrapper source.""" + source = _generated_source_output_path(output_dir, source_path) return ObjectFile( source=source, object_path=source.with_suffix(".o"), @@ -623,8 +623,8 @@ def _rendered_wrapper_object_file( ) -def _rendered_wrapper_object_stages( - rendered: RenderedGeneratedWrapperArtifacts, +def _generated_wrapper_object_stages( + rendered: GeneratedWrapper, output_dir: Path, *, wrapper_fortran_flags: tuple[str, ...], @@ -632,41 +632,41 @@ def _rendered_wrapper_object_stages( native_module_dirs: tuple[Path, ...], ) -> tuple[tuple[ObjectFile, ...], tuple[ObjectFile, ...]]: """Return bridge and binding objects in their required compile order.""" - source_paths = _rendered_wrapper_compile_source_paths(rendered) - bridge_source_paths = source_paths[: len(rendered.artifacts.bridge_sources)] + source_paths = _generated_wrapper_compile_source_paths(rendered) + bridge_source_paths = source_paths[: len(rendered.bridge_sources)] binding_source_paths = source_paths[len(bridge_source_paths) :] bridge_objects = tuple( - _rendered_wrapper_object_file( + _generated_wrapper_object_file( source_path, output_dir, flags=wrapper_fortran_flags, include_dirs=native_module_dirs, - language=_rendered_wrapper_source_language(source_path), + language=_generated_wrapper_source_language(source_path), ) for source_path in bridge_source_paths ) binding_objects = tuple( - _rendered_wrapper_object_file( + _generated_wrapper_object_file( source_path, output_dir, flags=wrapper_c_flags, include_dirs=native_module_dirs, - language=_rendered_wrapper_source_language(source_path), + language=_generated_wrapper_source_language(source_path), ) for source_path in binding_source_paths ) return bridge_objects, binding_objects -def _rendered_wrapper_link_language( +def _generated_wrapper_link_language( bridge_objects: tuple[ObjectFile, ...], binding_objects: tuple[ObjectFile, ...], ) -> str: - """Return the linker language for rendered wrapper-plan sources.""" + """Return the linker language for generated wrapper sources.""" if bridge_objects: return "fortran" if not binding_objects: - raise ValueError("Rendered wrapper artifacts must include at least one binding source") + raise ValueError("Generated wrapper must include at least one binding source") return binding_objects[-1].language @@ -788,8 +788,8 @@ def _compile_extension_objects( _finish_object_stage(binding_futures, label="Compile binding source", verbose=verbose) -def _build_rendered_wrapper_extension( - rendered: RenderedGeneratedWrapperArtifacts, +def _build_generated_wrapper_extension( + rendered: GeneratedWrapper, *, output_dir: str | Path, shared_library_output_dir: str | Path | None = None, @@ -804,19 +804,19 @@ def _build_rendered_wrapper_extension( compile_jobs: int | None = None, verbose: bool | int = False, ) -> WrapperBuildResult: - """Build one extension from rendered wrapper-plan artifacts.""" + """Write, compile, and link one complete generated wrapper.""" # Materialize the canonical wrapper output before creating compiler inputs. rendered.freeze() output_path = Path(output_dir) output_path.mkdir(parents=True, exist_ok=True) shared_output_path = Path(shared_library_output_dir) if shared_library_output_dir is not None else output_path shared_output_path.mkdir(parents=True, exist_ok=True) - _write_rendered_wrapper_sources(rendered, output_path, verbose=verbose) + _write_generated_wrapper_sources(rendered, output_path, verbose=verbose) # Prepare generated-object inputs and their native support files. compiler = compiler or _new_compiler() resolved_native_build_plan = native_build_plan or NativeBuildPlan() - bridge_objects, binding_objects = _rendered_wrapper_object_stages( + bridge_objects, binding_objects = _generated_wrapper_object_stages( rendered, output_path, wrapper_fortran_flags=_compiler_flags(wrapper_fortran_flags), @@ -828,7 +828,7 @@ def _build_rendered_wrapper_extension( ) ), ) - native_support_imports = _rendered_wrapper_native_support_imports(rendered.artifacts.native_support_keys) + native_support_imports = _generated_wrapper_native_support_imports(rendered.native_support_keys) install_native_support( native_support_imports, prik_dirpath=str(output_path), @@ -848,9 +848,9 @@ def _build_rendered_wrapper_extension( # Link the generated and caller-supplied objects into the extension. linking_started = time.perf_counter() shared_library = compiler.link_extension( - module_name=rendered.artifacts.module_name, + module_name=rendered.module_name, output_dir=shared_output_path, - language=_rendered_wrapper_link_language(bridge_objects, binding_objects), + language=_generated_wrapper_link_language(bridge_objects, binding_objects), objects=(*tuple(native_dependencies), *bridge_objects, *binding_objects), link_args=tuple(native_link_args), library_dirs=resolved_native_build_plan.library_dirs, @@ -859,14 +859,12 @@ def _build_rendered_wrapper_extension( ) _print_verbose_timing(verbose, time.perf_counter() - linking_started) generated_source_paths = tuple( - path - for path in rendered.artifacts.generated_files - if _rendered_artifact_output_path(output_path, path).exists() + path for path in rendered.generated_files if _generated_source_output_path(output_path, path).exists() ) - generated_sources = tuple(_rendered_artifact_output_path(output_path, path) for path in generated_source_paths) + generated_sources = tuple(_generated_source_output_path(output_path, path) for path in generated_source_paths) return WrapperBuildResult( sources=tuple(Path(source) for source in sources), - module_name=rendered.artifacts.module_name, + module_name=rendered.module_name, output_dir=output_path, shared_library=shared_library, build_makefile=None, @@ -875,7 +873,7 @@ def _build_rendered_wrapper_extension( generated_files=_expected_generated_files( source_objects=tuple(native_dependencies), output_dir=output_path, - module_name=rendered.artifacts.module_name, + module_name=rendered.module_name, shared_library=shared_library, ), native_build_plan=resolved_native_build_plan, @@ -936,18 +934,18 @@ def _render_wrapper_plan( module: SemanticModule, *, progress: Callable[[str, float | None], None] | None = None, -) -> RenderedGeneratedWrapperArtifacts: +) -> GeneratedWrapper: """Render one policy-completed module through the canonical generator.""" plan = WrapperPlanner().build(module) - return WrapperCodeGenerator().generate(plan, progress=progress) + return WrapperGenerator().generate(plan, progress=progress) -def _generated_wrapper_plan_artifacts( +def _generate_wrapper( module: SemanticModule, *, strict_wrapper_names: bool, verbose: bool | int = False, -) -> RenderedGeneratedWrapperArtifacts: +) -> GeneratedWrapper: """Complete policy and generate the one production wrapper representation.""" _print_verbose_step(verbose, "Complete wrapper policies") policy_started = time.perf_counter() @@ -1710,8 +1708,8 @@ def _native_link_args(link_items: Iterable[NativeLinkItem]) -> tuple[str, ...]: return tuple(args) -def _rendered_wrapper_native_link_args(plan: NativeBuildPlan) -> tuple[str, ...]: - """Return link arguments not already supplied as rendered-wrapper dependencies.""" +def _generated_wrapper_native_link_args(plan: NativeBuildPlan) -> tuple[str, ...]: + """Return link arguments not already supplied as generated-wrapper dependencies.""" produced_objects = {_path_key(path) for path in plan.produced_objects} return _native_link_args( item @@ -2803,8 +2801,8 @@ def build_fortran_extension( refresh_fortran_type_probe=refresh_fortran_type_probe, ) - # 3. Complete wrapper policy and render the canonical artifacts. - rendered_wrapper_plan = _generated_wrapper_plan_artifacts( + # 3. Complete wrapper policy and generate the canonical wrapper. + generated_wrapper = _generate_wrapper( module, strict_wrapper_names=strict_wrapper_names, verbose=verbose, @@ -2822,15 +2820,15 @@ def build_fortran_extension( native_compile_batches = _project_compile_batches(parsed, native_source_objects) # 5. Build the extension, or retain the generated source/Makefile plan. - result = _build_rendered_wrapper_extension( - rendered_wrapper_plan, + result = _build_generated_wrapper_extension( + generated_wrapper, output_dir=output_path, shared_library_output_dir=shared_library_output_path, sources=source_paths, native_build_plan=native_build_plan, native_dependencies=native_source_objects, native_compile_batches=native_compile_batches, - native_link_args=_rendered_wrapper_native_link_args(native_build_plan), + native_link_args=_generated_wrapper_native_link_args(native_build_plan), wrapper_fortran_flags=wrapper_fortran_flags, wrapper_c_flags=wrapper_c_flags, compiler=compiler, @@ -2963,7 +2961,7 @@ def build_pyi_extension( wrapper_fortran_flags = _compiler_flags(wrapper_fortran_flags) wrapper_c_flags = _compiler_flags(wrapper_c_flags) - # 2. Assemble semantic IR, complete policy, and render wrapper artifacts. + # 2. Assemble semantic IR, complete policy, and generate the wrapper. modules = list(bundle.modules) _complete_pyi_fortran_boolean_types( modules, @@ -2972,7 +2970,7 @@ def build_pyi_extension( ) module_name = _validated_wrapper_module_name(output_name, _bundle_output_name(bundle)) module = _merge_wrapper_modules(modules, name=module_name) - rendered_wrapper_plan = _generated_wrapper_plan_artifacts( + generated_wrapper = _generate_wrapper( module, strict_wrapper_names=strict_wrapper_names, verbose=verbose, @@ -2988,15 +2986,15 @@ def build_pyi_extension( native_array_build_requirements = native_array_handle_build_requirements(module) # 4. Build the extension and attach its replayable manifest data. - result = _build_rendered_wrapper_extension( - rendered_wrapper_plan, + result = _build_generated_wrapper_extension( + generated_wrapper, output_dir=output_path, shared_library_output_dir=shared_library_output_path, sources=bundle.paths, native_build_plan=native_build_plan, native_dependencies=native_source_objects, native_compile_batches=_serial_compile_batches(native_source_objects), - native_link_args=_rendered_wrapper_native_link_args(native_build_plan), + native_link_args=_generated_wrapper_native_link_args(native_build_plan), wrapper_fortran_flags=wrapper_fortran_flags, wrapper_c_flags=wrapper_c_flags, compiler=compiler, diff --git a/prik/pipeline/pyi.py b/prik/pipeline/pyi.py index 662982d43..06ba35719 100644 --- a/prik/pipeline/pyi.py +++ b/prik/pipeline/pyi.py @@ -3,15 +3,132 @@ from __future__ import annotations from collections.abc import Iterable +from copy import deepcopy from dataclasses import dataclass, field from pathlib import Path from prik.parsers.pyi import parse_pyi_text -from prik.semantics.models import SemanticModule +from prik.policy.completion import complete_semantic_policies +from prik.printers.pyi import emit_module +from prik.semantics.models import EXTERNAL_TYPE_REF_METADATA, SemanticClass, SemanticModule, _iter_module_semantic_types from prik.semantics.pyi_metadata import PYI_LOADED_METADATA from prik.semantics.pyi2ir import convert_pyi_to_ir, reconcile_external_type_refs -__all__ = ("pyi_file_to_semantic_module", "pyi_paths_to_semantic_modules", "pyi_text_to_semantic_module") +__all__ = ( + "emit_module_stubs", + "opaque_dependency_modules", + "pyi_file_to_semantic_module", + "pyi_paths_to_semantic_modules", + "pyi_text_to_semantic_module", +) + + +def _module_list(modules: SemanticModule | Iterable[SemanticModule] | None) -> list[SemanticModule]: + """Normalize one semantic module or an iterable to a list.""" + if modules is None: + return [] + if isinstance(modules, SemanticModule): + return [modules] + return list(modules) + + +def _opaque_dependency_class(type_name: str, c_kind: str | None) -> SemanticClass: + """Build the semantic placeholder for one missing opaque dependency.""" + base_classes: list[str] = [] + metadata: dict[str, object] = {"representation": "opaque"} + if c_kind == "struct": + base_classes.append("CStruct") + metadata["c_kind"] = "struct" + elif c_kind == "union": + base_classes.append("CUnion") + metadata["c_kind"] = "union" + base_classes.append("Opaque") + return SemanticClass( + name=type_name, + native_name=type_name, + base_classes=base_classes, + metadata=metadata, + ) + + +def opaque_dependency_modules( + modules: SemanticModule | Iterable[SemanticModule], + *, + available_modules: Iterable[SemanticModule] | None = None, +) -> list[SemanticModule]: + """Build semantic modules for opaque types referenced but not supplied. + + Use this before package emission when a contract refers to C opaque types + from absent modules. The input modules are inspected but not mutated; the + returned list is ordered deterministically by module and type name. + """ + source_modules = _module_list(modules) + known_modules = _module_list(available_modules) if available_modules is not None else source_modules + known_classes = { + (module.name, cls.name) for module in known_modules for cls in module.classes if isinstance(cls, SemanticClass) + } + dependencies: dict[str, dict[str, str | None]] = {} + for module in source_modules: + for semantic_type in _iter_module_semantic_types(module): + ref = semantic_type.metadata.get(EXTERNAL_TYPE_REF_METADATA) + if not isinstance(ref, dict) or ref.get("representation") != "opaque": + continue + origin_module = ref.get("origin_module") + type_name = ref.get("name") + if not isinstance(origin_module, str) or not isinstance(type_name, str): + continue + if (origin_module, type_name) in known_classes: + continue + c_kind = semantic_type.metadata.get("c_kind") + dependencies.setdefault(origin_module, {}).setdefault( + type_name, + c_kind if c_kind in {"struct", "union"} else None, + ) + return [ + SemanticModule( + name=module_name, + classes=[_opaque_dependency_class(type_name, c_kind) for type_name, c_kind in sorted(type_kinds.items())], + ) + for module_name, type_kinds in sorted(dependencies.items()) + ] + + +def emit_module_stubs( + modules: SemanticModule | Iterable[SemanticModule], + *, + available_modules: Iterable[SemanticModule] | None = None, + normalize_fortran_public_names: bool = False, +) -> dict[str, str]: + """Complete and render semantic modules plus opaque dependencies. + + Inputs are deep-copied before dependency insertion and policy completion, + so callers retain their original semantic modules. The returned mapping is + keyed by module name and is normally written into a generated contract + package by a pipeline stage. + """ + source_modules = _module_list(modules) + emitted_modules: dict[str, SemanticModule] = {} + for module in source_modules: + if module.name in emitted_modules: + raise ValueError(f"Cannot emit duplicate semantic module '{module.name}'") + emitted_modules[module.name] = deepcopy(module) + + for dependency in opaque_dependency_modules( + source_modules, + available_modules=available_modules, + ): + target = emitted_modules.setdefault(dependency.name, SemanticModule(name=dependency.name)) + existing = {cls.name for cls in target.classes} + target.classes.extend(cls for cls in dependency.classes if cls.name not in existing) + + complete_semantic_policies(emitted_modules.values()) + return { + module_name: emit_module( + module, + normalize_fortran_public_names=normalize_fortran_public_names, + ).strip() + for module_name, module in emitted_modules.items() + } @dataclass diff --git a/prik/codegen/generator.py b/prik/pipeline/wrapper.py similarity index 98% rename from prik/codegen/generator.py rename to prik/pipeline/wrapper.py index 3c972076f..9110d1db9 100644 --- a/prik/codegen/generator.py +++ b/prik/pipeline/wrapper.py @@ -1,26 +1,22 @@ -"""Freeze, validate, lower, and render editable wrapper plans. - -``WrapperCodeGenerator`` is the final consumer of an editable ``ModulePlan``. -It validates cross-view plan consistency, asks each backend to preflight its -own lowering capability, renders the resulting C, Fortran, and header nodes, -and returns source-bearing wrapper artifacts for build integration. Semantic -policy remains upstream: this module validates and lowers completed decisions -without selecting replacements. +"""Generate complete rendered wrappers from editable wrapper plans. + +``WrapperGenerator`` is the pipeline boundary between an editable +``ModulePlan`` and source-bearing ``GeneratedWrapper``. It validates cross-view +plan consistency, asks each backend to preflight and lower its completed plan, +prints the resulting C, Fortran, and header nodes, assigns stable filenames, +and returns the complete handoff consumed by build integration. """ from __future__ import annotations from collections import Counter from collections.abc import Callable +from dataclasses import dataclass from pathlib import Path import time -from prik.pipeline.wrapper_artifacts import ( - GeneratedSourceFile, - GeneratedWrapperArtifacts, - RenderedGeneratedWrapperArtifacts, -) -from prik.semantics.ownership import ( +from prik.stage_values import StageRecord +from prik.policy.ownership import ( AssignmentMode, CodegenAction, DestructionPolicy, @@ -33,7 +29,7 @@ TransferMode, ) from prik.semantics.metadata import SCALAR_STORAGE_CATEGORY -from prik.semantics.wrapper_policy_models import ( +from prik.policy.models import ( ArgumentHandoffMode, ArrayLogicalABI, ArrayWritebackABI, @@ -93,11 +89,11 @@ TransformationLayer, WritebackPhase, ) -from prik.semantics.native_array_handles import NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER -from prik.semantics.wrapper_policy import overload_builtin_scalar_family +from prik.policy.native_array_handles import NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER from prik.codegen.c.binding import CBindingGenerator +from prik.codegen.docstrings import WrapperDocstringBuilder from prik.codegen.fortran.bridge import FortranBridgeGenerator -from prik.codegen.plan import ( +from prik.planning.models import ( ArrayHandoffPlan, ArgumentTransferPlan, CallbackHandoffPlan, @@ -119,18 +115,57 @@ ResultPlan, WrapperPlanDiagnostic, ) -from prik.codegen.printers import CSourcePrinter, FortranSourcePrinter +from prik.printers import CSourcePrinter, FortranSourcePrinter + +__all__ = ("GeneratedSource", "GeneratedWrapper", "WrapperGenerator") + + +@dataclass +class GeneratedSource(StageRecord): + """One generated source payload before it is written to disk.""" + + path: Path + text: str + + +@dataclass +class GeneratedWrapper(StageRecord): + """Rendered wrapper sources and the metadata required by build integration.""" + + module_name: str + sources: tuple[GeneratedSource, ...] + bridge_sources: tuple[Path, ...] + binding_sources: tuple[Path, ...] + headers: tuple[Path, ...] + native_support_keys: tuple[str, ...] + required_headers: tuple[str, ...] + extension_init_name: str + + @property + def source_paths(self) -> tuple[Path, ...]: + """Return generated payload paths in stable write order.""" + return tuple(source.path for source in self.sources) + + @property + def compile_sources(self) -> tuple[Path, ...]: + """Return bridge and binding source paths in compiler order.""" + return (*self.bridge_sources, *self.binding_sources) + + @property + def generated_files(self) -> tuple[Path, ...]: + """Return all generated wrapper paths, including headers.""" + return (*self.compile_sources, *self.headers) -class WrapperCodeGenerator: - """Turn one editable ``ModulePlan`` into rendered wrapper artifacts. +class WrapperGenerator: + """Turn one editable ``ModulePlan`` into one complete generated wrapper. Use :meth:`generate` after ``WrapperPlanner.build`` and before build integration writes or compiles sources. This class owns the plan's final freeze and cross-backend consistency validation, then delegates backend node construction and source printing to the injected or default C and Fortran components. Its private sections cover the generation entrypoint, - typed plan diagnostics, and artifact assembly. + typed plan diagnostics, and generated-wrapper assembly. """ def __init__( @@ -138,18 +173,21 @@ def __init__( *, c_generator: CBindingGenerator | None = None, fortran_generator: FortranBridgeGenerator | None = None, + docstring_builder: WrapperDocstringBuilder | None = None, c_printer: CSourcePrinter | None = None, fortran_printer: FortranSourcePrinter | None = None, ): """Create a generator with default or explicitly supplied backend components. - Supplying a generator or printer is useful when an established caller - needs to observe or substitute a backend implementation. Omitted - components use the standard direct-lowering and printing paths; no - plan policy is stored or inferred during initialization. + Supplying a docstring builder, backend generator, or printer is useful + when an established caller needs to observe or substitute one + generation component. Omitted components use the standard plan-driven + documentation, direct-lowering, and printing paths; no semantic policy + is stored or inferred during initialization. """ self._c_generator = c_generator or CBindingGenerator() self._fortran_generator = fortran_generator or FortranBridgeGenerator() + self._docstring_builder = docstring_builder or WrapperDocstringBuilder() self._c_printer = c_printer or CSourcePrinter() self._fortran_printer = fortran_printer or FortranSourcePrinter() @@ -159,8 +197,8 @@ def generate( plan: ModulePlan, *, progress: Callable[[str, float | None], None] | None = None, - ) -> RenderedGeneratedWrapperArtifacts: - """Render one editable plan into C, Fortran, header, and build artifacts. + ) -> GeneratedWrapper: + """Render one editable plan into a complete generated wrapper. The received ``plan`` is frozen before validation, so later mutation raises the stage-record error. ``progress``, when provided, receives @@ -172,6 +210,9 @@ def generate( ValueError: If the frozen plan is inconsistent or a selected backend cannot lower one of its completed actions. """ + # Complete presentation from the editable plan before consuming and freezing it. + self._docstring_builder.render(plan) + # Freeze the exact editable handoff, then validate cross-backend plan facts. plan.freeze() self._validate_plan(plan) @@ -208,7 +249,7 @@ def generate( progress("Generate binding header", time.perf_counter() - started) # Assemble source text with the stable filenames consumed by build integration. - return self._rendered_artifacts( + return self._generated_wrapper( plan.owner_path, c_sources, c_header_source, @@ -259,7 +300,7 @@ def _plan_diagnostics(self, plan: ModulePlan) -> tuple[WrapperPlanDiagnostic, .. for overload in namespace.overloads: diagnostics.extend(self._overload_diagnostics(overload, functions)) - # Validate graph-wide ordering, generated spellings, and artifact dependencies. + # Validate graph-wide ordering, generated spellings, and header dependencies. diagnostics.extend(self._class_graph_diagnostics(plan)) diagnostics.extend(self._generated_symbol_diagnostics(plan)) diagnostics.extend(self._required_header_diagnostics(plan)) @@ -502,11 +543,7 @@ def _overload_builtin_signature(matches: tuple) -> tuple: ( match.kind, match.optional, - ( - overload_builtin_scalar_family(match.semantic_type_name) - if match.accept_builtin_scalar - else match.semantic_type_name - ), + match.builtin_scalar_family or match.semantic_type_name, match.rank, match.derived_type_identity, ) @@ -1672,7 +1709,7 @@ def _prototype_argument_matches_transfer( argument.passed_by_value, argument.intent, argument.character_length, - WrapperCodeGenerator._prototype_array_shape(argument.array), + WrapperGenerator._prototype_array_shape(argument.array), argument.derived_type_identity, argument.derived_backend_symbol, ) == ( @@ -1682,7 +1719,7 @@ def _prototype_argument_matches_transfer( transfer.passed_by_value, transfer.intent, transfer.character_length, - WrapperCodeGenerator._prototype_array_shape(transfer.array), + WrapperGenerator._prototype_array_shape(transfer.array), transfer.derived_type_identity, transfer.derived_backend_symbol, ) @@ -1697,14 +1734,14 @@ def _prototype_result_matches_transfer( result.semantic_type_name, result.rank, result.character_length, - WrapperCodeGenerator._prototype_array_shape(result.array), + WrapperGenerator._prototype_array_shape(result.array), result.derived_type_identity, result.derived_backend_symbol, ) == ( transfer.semantic_type_name, transfer.rank, transfer.character_length, - WrapperCodeGenerator._prototype_array_shape(transfer.array), + WrapperGenerator._prototype_array_shape(transfer.array), transfer.derived_type_identity, transfer.derived_backend_symbol, ) @@ -5039,7 +5076,7 @@ def _argument_extent_roles(arguments: tuple[ArgumentTransferPlan, ...]) -> tuple for role in (argument.array.extent_roles if argument.array is not None else ()) ) - # Diagnostic formatting and rendered-artifact assembly. + # Diagnostic formatting and generated-wrapper assembly. def _diagnostic(self, owner_path: str, code: str, detail: object) -> WrapperPlanDiagnostic: """Create one normalized diagnostic from an owner, stable code, and detail. @@ -5059,7 +5096,7 @@ def _diagnostic_summary(self, diagnostics: tuple[WrapperPlanDiagnostic, ...]) -> details = "; ".join(f"{item.owner_path}:{item.code}:{item.message}" for item in diagnostics) return f"Invalid edited wrapper plan before generation: {details}" - def _rendered_artifacts( + def _generated_wrapper( self, module_name: str, c_sources: tuple[str, ...], @@ -5067,47 +5104,43 @@ def _rendered_artifacts( fortran_source: str, native_support_keys: tuple[str, ...], required_headers: tuple[str, ...], - ) -> RenderedGeneratedWrapperArtifacts: + ) -> GeneratedWrapper: """Package rendered source text with the filenames owned by build integration. Binding translation-unit paths preserve the primary file followed by - zero-padded worker shards. The returned artifacts place bridge, C + zero-padded worker shards. The returned wrapper places bridge, C sources, and header text in that stable order; this helper does not - write files or freeze the newly assembled artifact records. + write files or freeze the newly assembled source records. """ # Name bridge, binding, and header files before pairing each with rendered text. binding_sources = ( Path(f"{module_name}_wrapper.c"), *(Path(f"{module_name}_wrapper_{index:03d}.c") for index in range(1, len(c_sources))), ) - artifacts = GeneratedWrapperArtifacts( - module_name=module_name, - bridge_sources=(Path(f"bind_c_{module_name}_wrapper.f90"),), - binding_sources=binding_sources, - header_files=(Path(f"{module_name}_wrapper.h"),), - native_support_keys=native_support_keys, - required_headers=required_headers, - ) + bridge_sources = (Path(f"bind_c_{module_name}_wrapper.f90"),) + headers = (Path(f"{module_name}_wrapper.h"),) # Preserve build-consumed source ordering: bridge, binding units, then header. - return RenderedGeneratedWrapperArtifacts( - artifacts=artifacts, + return GeneratedWrapper( + module_name=module_name, extension_init_name=f"PyInit_{module_name}", sources=( - GeneratedSourceFile(artifacts.bridge_sources[0], fortran_source), - *( - GeneratedSourceFile(path, source) - for path, source in zip(artifacts.binding_sources, c_sources, strict=True) - ), - GeneratedSourceFile(artifacts.header_files[0], c_header), + GeneratedSource(bridge_sources[0], fortran_source), + *(GeneratedSource(path, source) for path, source in zip(binding_sources, c_sources, strict=True)), + GeneratedSource(headers[0], c_header), ), + bridge_sources=bridge_sources, + binding_sources=binding_sources, + headers=headers, + native_support_keys=native_support_keys, + required_headers=required_headers, ) if __name__ == "__main__": - from prik.codegen.planner import WrapperPlanner + from prik.planning.planner import WrapperPlanner from prik.semantics.models import SemanticArgument, SemanticFunction, SemanticModule, SemanticType - from prik.semantics.policy_completion import complete_semantic_policies + from prik.policy.completion import complete_semantic_policies module = SemanticModule( name="generator_demo", @@ -5121,8 +5154,8 @@ def _rendered_artifacts( ], ) complete_semantic_policies(module) - rendered = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + rendered = WrapperGenerator().generate(WrapperPlanner().build(module)) print(f"Extension initializer: {rendered.extension_init_name}") print("Rendered sources:", ", ".join(source.path.name for source in rendered.sources)) - print("Native support:", ", ".join(rendered.artifacts.native_support_keys) or "none") + print("Native support:", ", ".join(rendered.native_support_keys) or "none") diff --git a/prik/pipeline/wrapper_artifacts.py b/prik/pipeline/wrapper_artifacts.py deleted file mode 100644 index c9cdc2a45..000000000 --- a/prik/pipeline/wrapper_artifacts.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Generated-wrapper artifact handoff shared by wrapper build routes.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path - -from prik.stage_values import StageRecord - -__all__ = ( - "GeneratedSourceFile", - "GeneratedWrapperArtifacts", - "RenderedGeneratedWrapperArtifacts", -) - - -@dataclass -class GeneratedSourceFile(StageRecord): - """One rendered generated source payload before it is written to disk.""" - - path: Path - text: str - - -@dataclass -class GeneratedWrapperArtifacts(StageRecord): - """Generated wrapper files produced before compile/link orchestration.""" - - module_name: str - bridge_sources: tuple[Path, ...] = () - binding_sources: tuple[Path, ...] = () - header_files: tuple[Path, ...] = () - native_support_keys: tuple[str, ...] = () - required_headers: tuple[str, ...] = () - - @property - def source_files(self) -> tuple[Path, ...]: - """Return all generated wrapper sources in compile order.""" - return (*self.bridge_sources, *self.binding_sources) - - @property - def generated_files(self) -> tuple[Path, ...]: - """Return all generated wrapper files, including headers.""" - return (*self.source_files, *self.header_files) - - -@dataclass -class RenderedGeneratedWrapperArtifacts(StageRecord): - """Rendered generated sources plus compile/link metadata.""" - - artifacts: GeneratedWrapperArtifacts - sources: tuple[GeneratedSourceFile, ...] - extension_init_name: str - - @property - def source_paths(self) -> tuple[Path, ...]: - """Return rendered source payload paths in write order.""" - return tuple(source.path for source in self.sources) diff --git a/prik/planning/README.md b/prik/planning/README.md new file mode 100644 index 000000000..588a084f2 --- /dev/null +++ b/prik/planning/README.md @@ -0,0 +1,13 @@ +# Planning Package + +This package projects policy-completed semantic IR into one editable, +backend-neutral wrapper plan. + +| File | Owns | +| --- | --- | +| `models.py` | Typed wrapper-plan records shared by all generated backends. | +| `planner.py` | Mechanical projection from completed policy into those records. | + +Planning must not infer semantic policy or render output text. Python-facing +docstrings, C, Fortran, headers, and the generated Python class facade are +rendered by `../codegen/` from the completed plan. diff --git a/prik/planning/__init__.py b/prik/planning/__init__.py new file mode 100644 index 000000000..84dd6a18b --- /dev/null +++ b/prik/planning/__init__.py @@ -0,0 +1,79 @@ +"""Backend-neutral wrapper-plan models and policy projection.""" + +from .models import ( + ArgumentTransferPlan, + ArrayHandoffPlan, + BindingArgumentPlan, + BindingFunctionPlan, + BindingLifecyclePlan, + BindingModulePlan, + BindingModuleVariablePlan, + BindingResultPlan, + BindingStatusErrorPlan, + BridgeArgumentPlan, + BridgeFunctionPlan, + BridgeLifecyclePlan, + BridgeModulePlan, + BridgeModuleVariablePlan, + BridgeResultPlan, + DatatypeFamily, + DerivedFieldPlan, + DerivedHandoffPlan, + DerivedMemberPathPlan, + DerivedModuleObjectPlan, + DerivedTypePlan, + FunctionPlan, + LifecycleActionPlan, + ModulePlan, + ModuleVariablePlan, + NativeArrayActualPlan, + NativeArrayDefaultHandlePlan, + NativeArrayHandlePlan, + NativeCallSlotPlan, + NativeDescriptorHandoffPlan, + NamespacePlan, + ResultPlan, + ScalarDescriptorResultPlan, + TransformationPlan, + WrapperPlanDiagnostic, +) +from .planner import WrapperPlanner + +__all__ = ( + "ArgumentTransferPlan", + "ArrayHandoffPlan", + "BindingArgumentPlan", + "BindingFunctionPlan", + "BindingLifecyclePlan", + "BindingModulePlan", + "BindingModuleVariablePlan", + "BindingResultPlan", + "BindingStatusErrorPlan", + "BridgeArgumentPlan", + "BridgeFunctionPlan", + "BridgeLifecyclePlan", + "BridgeModulePlan", + "BridgeModuleVariablePlan", + "BridgeResultPlan", + "DatatypeFamily", + "DerivedFieldPlan", + "DerivedHandoffPlan", + "DerivedMemberPathPlan", + "DerivedModuleObjectPlan", + "DerivedTypePlan", + "FunctionPlan", + "LifecycleActionPlan", + "ModulePlan", + "ModuleVariablePlan", + "NamespacePlan", + "NativeArrayActualPlan", + "NativeArrayDefaultHandlePlan", + "NativeArrayHandlePlan", + "NativeCallSlotPlan", + "NativeDescriptorHandoffPlan", + "ResultPlan", + "ScalarDescriptorResultPlan", + "TransformationPlan", + "WrapperPlanDiagnostic", + "WrapperPlanner", +) diff --git a/prik/codegen/plan.py b/prik/planning/models.py similarity index 97% rename from prik/codegen/plan.py rename to prik/planning/models.py index a2be16769..6c63cb44e 100644 --- a/prik/codegen/plan.py +++ b/prik/planning/models.py @@ -9,7 +9,7 @@ Records appear in plan-tree order: shared vocabulary; derived and class surfaces; array and descriptor facets; module and procedure views; callback and transfer records; then module-level orchestration. Consumers normally use -the ``ModulePlan`` root returned by :class:`prik.codegen.WrapperPlanner`. +the ``ModulePlan`` root returned by :class:`prik.planning.WrapperPlanner`. """ from __future__ import annotations @@ -18,7 +18,7 @@ from enum import Enum from typing import Any -from prik.semantics.ownership import ( +from prik.policy.ownership import ( AssignmentMode, CodegenAction, DestructionPolicy, @@ -30,7 +30,7 @@ StorageMode, TransferMode, ) -from prik.semantics.wrapper_policy_models import ( +from prik.policy.models import ( ArgumentConversionPhase, ArgumentHandoffMode, ArrayLogicalABI, @@ -206,7 +206,7 @@ class DerivedFieldPlan(StageRecord): array: ArrayHandoffPlan | None = None native_array_handle: NativeArrayHandlePlan | None = None derived: DerivedHandoffPlan | None = None - docstring: str = "" + docstring: str | None = None @dataclass @@ -272,7 +272,7 @@ class ConstructorPlan(StageRecord): rejection_message: str | None = None target: FunctionPlan | None = None overload: OverloadPlan | None = None - docstring: str = "" + docstring: str | None = None @dataclass @@ -289,12 +289,17 @@ class ClassMethodPlan(StageRecord): passed_object_position: int | None public: bool function: FunctionPlan - docstring: str = "" + docstring: str | None = None @dataclass class OverloadArgumentMatchPlan(StageRecord): - """Store one exact argument predicate selected for overload dispatch.""" + """Store one exact argument predicate selected for overload dispatch. + + A non-null ``builtin_scalar_family`` is the completed reflected-operator + exception to the otherwise exact NumPy scalar match. Lowering dispatches + that family directly and does not classify it again from the semantic type. + """ python_name: str kind: OverloadMatchKind @@ -302,7 +307,7 @@ class OverloadArgumentMatchPlan(StageRecord): semantic_type_name: str rank: int derived_type_identity: tuple[str, str] | None - accept_builtin_scalar: bool = False + builtin_scalar_family: str | None = None @dataclass @@ -323,7 +328,7 @@ class OverloadPlan(StageRecord): candidate_passed_objects: tuple[bool, ...] unsupported_extra_argument_message: str | None = None identity_receiver_shortcut: bool = False - docstring: str = "" + docstring: str | None = None @dataclass @@ -342,7 +347,7 @@ class ClassSurfacePlan(StageRecord): methods: tuple[ClassMethodPlan, ...] overloads: tuple[OverloadPlan, ...] registration: tuple[ClassRegistrationAction, ...] - docstring: str = "" + docstring: str | None = None @dataclass @@ -601,19 +606,20 @@ class ModuleVariablePlan(StageRecord): array: ArrayHandoffPlan | None native_array_handle: NativeArrayHandlePlan | None derived: DerivedModuleObjectPlan | None = None - docstring: str = "" + docstring: str | None = None @dataclass class BindingFunctionPlan(StageRecord): """Store Python-visible call behavior for one generated binding function. - The planner fixes public naming, documentation, GIL handling, optional - status projection, and argument-conversion order before generation. + The planner fixes public naming, GIL handling, optional status projection, + and argument-conversion order. Code generation fills an unresolved + docstring while preserving any explicit editable-plan value. """ python_name: str - docstring: str + docstring: str | None release_gil: bool status_error: BindingStatusErrorPlan | None argument_conversion_order: tuple[str, ...] @@ -1066,7 +1072,7 @@ class NamespacePlan(StageRecord): derived_types: tuple[DerivedTypePlan, ...] = () classes: tuple[ClassSurfacePlan, ...] = () overloads: tuple[OverloadPlan, ...] = () - docstring: str = "" + docstring: str | None = None @dataclass @@ -1075,7 +1081,7 @@ class ModulePlan(StageRecord): Constructed by ``WrapperPlanner.build()``, this root joins binding and bridge module views with an explicit namespace tree and required headers. - Pass it to ``WrapperCodeGenerator.generate()``; generation validates then + Pass it to ``WrapperGenerator.generate()``; generation validates then freezes the graph before it renders artifacts. """ diff --git a/prik/codegen/planner.py b/prik/planning/planner.py similarity index 97% rename from prik/codegen/planner.py rename to prik/planning/planner.py index 16f68cf70..9e35cd22b 100644 --- a/prik/codegen/planner.py +++ b/prik/planning/planner.py @@ -16,8 +16,8 @@ from types import MappingProxyType from prik.semantics import models -from prik.semantics.native_array_handles import NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER -from prik.semantics.wrapper_policy_models import ( +from prik.policy.native_array_handles import NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER +from prik.policy.models import ( ArgumentConversionPhase, ArgumentHandoffMode, ArrayHandoffPolicy, @@ -58,16 +58,15 @@ TransformationPolicy, WritebackPhase, ) -from prik.semantics.wrapper_policy import ( +from prik.policy.construction import ( completed_class_surface_policy, completed_derived_type_policy, completed_function_wrapper_policy, completed_module_variable_policy, ) -from prik.semantics.wrapper_exports import PythonExportPolicy -from prik.semantics.ownership import NativeBarrierAction, SetterAction -from prik.codegen.docstrings import WrapperDocstringBuilder -from prik.codegen.plan import ( +from prik.policy.exports import PythonExportPolicy +from prik.policy.ownership import NativeBarrierAction, SetterAction +from prik.planning.models import ( ArrayHandoffPlan, ArgumentTransferPlan, BindingArgumentPlan, @@ -121,9 +120,9 @@ ScalarDescriptorResultPlan, TransformationPlan, ) -from prik.codegen.naming import NativeSymbolNames -from prik.codegen.visitor import ClassVisitor +from prik.naming.native_symbols import NativeSymbolNames from prik.types.numpy import BOOLEAN_SEMANTIC_TYPE_NAMES +from prik.utilities.visitor import ClassVisitor _DATATYPE_FAMILIES = { @@ -245,14 +244,14 @@ class WrapperPlanner(ClassVisitor): code generator validates and freezes it. """ - def __init__(self): - """Initialize visitor state and the shared plan-docstring builder. + def visit(self, node, *args, **kwargs): + """Project one completed policy record through its named handler.""" + return self._visit(node, *args, **kwargs) - Per-module caches are reset at the beginning of :meth:`build`, so a - planner instance can safely project more than one semantic module. - """ - super().__init__() - self.docstrings = WrapperDocstringBuilder() + @staticmethod + def _visit_not_supported(node): + """Reject inputs outside the completed semantic-policy vocabulary.""" + raise TypeError(f"WrapperPlanner does not support completed policy {type(node).__name__}") def build(self, module: models.SemanticModule) -> ModulePlan: """Build an editable wrapper plan from one policy-completed module. @@ -260,7 +259,7 @@ def build(self, module: models.SemanticModule) -> ModulePlan: Call this after post-IR policy completion. ``module`` supplies all ownership, transfer, ABI, export, and lifecycle decisions; this method does not infer or replace them. The returned ``ModulePlan`` is the - normal input to ``WrapperCodeGenerator.generate``. + normal input to ``WrapperGenerator.generate``. Raises: ValueError: If completed policy is missing, inconsistent, or has @@ -377,7 +376,7 @@ def _namespace_plan( classes: tuple[ClassSurfacePlan, ...], overloads: tuple[OverloadPlan, ...], ) -> NamespacePlan: - """Freeze one namespace with stable public documentation.""" + """Create one namespace after its generated symbols are complete.""" return NamespacePlan( owner_path=self._namespace_owner_path(module_name, path), python_path=path, @@ -386,14 +385,6 @@ def _namespace_plan( derived_types=derived_types, classes=classes, overloads=overloads, - docstring=self.docstrings.namespace( - module_name, - path, - functions, - variables, - classes, - overloads, - ), ) def _complete_derived_backend_symbols(self, module: models.SemanticModule) -> None: @@ -516,14 +507,11 @@ def _class_surface_plan( entry, overloads_by_name, ) - fields = tuple(self._derived_field_plan(field) for field in policy.effective_fields) constructor = self._constructor_plan( module_name, namespace, entry, overloads_by_name, - python_name=python_names[0], - fields=fields, ) return ClassSurfacePlan( owner_path=policy.owner_path, @@ -534,14 +522,6 @@ def _class_surface_plan( methods=methods, overloads=overloads, registration=policy.registration, - docstring=self.docstrings.class_surface( - python_names[0], - policy.type_identity[1], - constructor, - fields, - methods, - overloads, - ), ) def _class_method_plans( @@ -601,9 +581,6 @@ def _constructor_plan( namespace: tuple[str, ...], entry: _ClassPolicyEntry, overloads_by_name: dict, - *, - python_name: str, - fields: tuple[DerivedFieldPlan, ...], ) -> ConstructorPlan: """Link one completed constructor to its target and lifecycle records.""" policy = entry.surface_policy @@ -619,7 +596,7 @@ def _constructor_plan( entry, overloads_by_name, ) - plan = ConstructorPlan( + return ConstructorPlan( kind=constructor.kind, fields=tuple(self._constructor_field_plan(field) for field in constructor.fields), target_owner_path=constructor.target_owner_path, @@ -629,8 +606,6 @@ def _constructor_plan( target=target, overload=overload, ) - plan.docstring = self.docstrings.constructor(python_name, plan, fields) - return plan def _bound_constructor_target_plan( self, @@ -705,7 +680,7 @@ def _class_method_plan( module_name, public=False, ) - plan = ClassMethodPlan( + return ClassMethodPlan( owner_path=policy.owner_path, python_name=policy.python_name, kind=policy.kind, @@ -713,8 +688,6 @@ def _class_method_plan( public=policy.public, function=function, ) - plan.docstring = self.docstrings.method(plan) - return plan def _overload_plan( self, @@ -738,7 +711,7 @@ def _overload_plan( ) for index, candidate in enumerate(policy.candidates) ) - plan = OverloadPlan( + return OverloadPlan( owner_path=policy.owner_path, python_name=policy.python_name, kind=policy.kind, @@ -753,7 +726,7 @@ def _overload_plan( semantic_type_name=argument.semantic_type_name, rank=argument.rank, derived_type_identity=argument.derived_type_identity, - accept_builtin_scalar=argument.accept_builtin_scalar, + builtin_scalar_family=argument.builtin_scalar_family, ) for argument in candidate.arguments ) @@ -763,8 +736,6 @@ def _overload_plan( unsupported_extra_argument_message=policy.unsupported_extra_argument_message, identity_receiver_shortcut=policy.identity_receiver_shortcut, ) - plan.docstring = self.docstrings.overload(plan) - return plan def _class_callable_name(self, type_identity: tuple[str, str], name: str) -> str: """Return one private callable export fixed during plan construction.""" @@ -816,9 +787,7 @@ def _derived_field_plan(self, policy: DerivedFieldPolicy) -> DerivedFieldPlan: array=array, ), derived=self._derived_handoff_plan(policy.derived), - docstring="", ) - plan.docstring = self.docstrings.field(plan) self._derived_field_plans[policy.owner_path] = plan return plan @@ -986,7 +955,7 @@ def _module_variable_plan( # Roles are present only where the completed accessor policy requires them. getter_role = self._module_getter_role(policy) setter_role = f"{policy.owner_path}:setter" if policy.setter_action is SetterAction.WRITE_THROUGH else None - plan = ModuleVariablePlan( + return ModuleVariablePlan( owner_path=self._export_owner_path(module_name, namespace, python_names[0]), symbol_name=policy.native_name.casefold(), semantic_type_name=policy.semantic_type_name, @@ -1031,10 +1000,7 @@ def _module_variable_plan( if policy.derived is not None else None ), - docstring="", ) - plan.docstring = self.docstrings.module_variable(plan) - return plan @staticmethod def _module_getter_role(policy: ModuleVariablePolicy) -> str | None: @@ -1077,12 +1043,7 @@ def _function_plan( symbol_name=export.name.casefold(), binding=BindingFunctionPlan( python_name=export.name, - docstring=self.docstrings.function( - export.name, - arguments, - results, - status_error=status_error, - ), + docstring=None, release_gil=policy.release_gil, status_error=status_error, argument_conversion_order=self._binding_argument_conversion_order(arguments), @@ -2319,7 +2280,7 @@ def _public_result_for_slot( if __name__ == "__main__": from prik.semantics.models import SemanticArgument, SemanticFunction, SemanticModule, SemanticType - from prik.semantics.policy_completion import complete_semantic_policies + from prik.policy.completion import complete_semantic_policies module = SemanticModule( name="planner_demo", diff --git a/prik/policy/README.md b/prik/policy/README.md new file mode 100644 index 000000000..cc38c59c5 --- /dev/null +++ b/prik/policy/README.md @@ -0,0 +1,18 @@ +# Policy Package + +This package owns every post-IR semantic decision required before wrapper +planning begins. It may consume semantic IR and raw contract metadata, but it +must not construct wrapper plans or render backend output. + +| File | Owns | +| --- | --- | +| `models.py` | Immutable backend-neutral completed-policy vocabulary. | +| `ownership.py` | Ownership, transfer, destruction, storage, and strict lowering-action resolution. | +| `exports.py` | Completed Python export policy. | +| `construction.py` | Wrapper-policy construction rules and completed-policy accessors. | +| `completion.py` | Ordered completion and attachment of policy to semantic IR. | +| `native_array_handles.py` | Completed descriptor-handle policy, ABI dispatch records, and selected build requirements. | + +Raw ownership and pointer-contract metadata belongs to +`../semantics/ownership_metadata.py`. Planning consumes completed records from +this package through `../planning/planner.py`. diff --git a/prik/policy/__init__.py b/prik/policy/__init__.py new file mode 100644 index 000000000..3b47dd0e8 --- /dev/null +++ b/prik/policy/__init__.py @@ -0,0 +1,5 @@ +"""Post-IR semantic policy completion and immutable policy vocabulary.""" + +from .completion import complete_semantic_policies + +__all__ = ("complete_semantic_policies",) diff --git a/prik/semantics/policy_completion.py b/prik/policy/completion.py similarity index 98% rename from prik/semantics/policy_completion.py rename to prik/policy/completion.py index df4e5cb6d..95a6a5d45 100644 --- a/prik/semantics/policy_completion.py +++ b/prik/policy/completion.py @@ -14,10 +14,8 @@ from collections.abc import Iterable from prik.types.numpy import SEMANTIC_SCALAR_TYPE_NAMES -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, - OWNERSHIP_POLICY_METADATA, - POINTER_POLICY_METADATA, OwnershipDecision, OwnershipContext, ObjectKind, @@ -25,6 +23,7 @@ default_ownership_policy, ownership_context_for_argument, ) +from prik.semantics.ownership_metadata import OWNERSHIP_POLICY_METADATA, POINTER_POLICY_METADATA from prik.semantics.metadata import ( ADDRESS_ROLE_METADATA, ADDRESS_ROLE_PROJECTION, @@ -36,8 +35,9 @@ SCALAR_STORAGE_CATEGORY, ) from prik.semantics import models -from prik.semantics.native_array_handles import NativeArrayHandlePolicy, native_array_descriptor_kind -from prik.semantics.wrapper_policy_models import ( +from prik.policy.native_array_handles import NativeArrayHandlePolicy +from prik.semantics.native_array_handles import native_array_descriptor_kind +from prik.policy.models import ( ArgumentPolicy, OverloadArgumentPolicy, OverloadMatchKind, @@ -54,7 +54,7 @@ OptionalMode, PythonExceptionKind, ) -from prik.semantics.wrapper_policy import ( +from prik.policy.construction import ( build_callback_handoff_policy, build_class_surface_policy, build_derived_field_policy, @@ -65,7 +65,7 @@ derived_member_path_policies, overload_builtin_scalar_family, ) -from prik.semantics.wrapper_exports import complete_python_export_policy +from prik.policy.exports import complete_python_export_policy __all__ = ("complete_semantic_policies",) @@ -778,11 +778,7 @@ def _overload_candidate_builtin_signature(arguments: tuple[OverloadArgumentPolic ( argument.kind, argument.optional, - ( - overload_builtin_scalar_family(argument.semantic_type_name) - if argument.accept_builtin_scalar - else argument.semantic_type_name - ), + (argument.builtin_scalar_family or argument.semantic_type_name), argument.rank, argument.derived_type_identity, ) @@ -838,10 +834,26 @@ def _overload_argument_match( semantic_type_name=argument.semantic_type_name, rank=argument.rank, derived_type_identity=derived_identity, - accept_builtin_scalar=accept_builtin_scalar and match_kind is OverloadMatchKind.NUMPY_SCALAR, + builtin_scalar_family=_accepted_builtin_scalar_family( + argument.semantic_type_name, + match_kind=match_kind, + accept_builtin_scalar=accept_builtin_scalar, + ), ) +def _accepted_builtin_scalar_family( + semantic_type_name: str, + *, + match_kind: OverloadMatchKind, + accept_builtin_scalar: bool, +) -> str | None: + """Complete the optional reflected-dispatch scalar family.""" + if not accept_builtin_scalar or match_kind is not OverloadMatchKind.NUMPY_SCALAR: + return None + return overload_builtin_scalar_family(semantic_type_name) + + def _iter_semantic_classes(classes: list[models.SemanticClass]): """Yield one stable depth-first class sequence for policy completion.""" for semantic_class in classes: diff --git a/prik/semantics/wrapper_policy.py b/prik/policy/construction.py similarity index 99% rename from prik/semantics/wrapper_policy.py rename to prik/policy/construction.py index 5d20a45b4..f5c16ba08 100644 --- a/prik/semantics/wrapper_policy.py +++ b/prik/policy/construction.py @@ -1,7 +1,7 @@ """Project completed semantic decisions into backend-neutral wrapper policies. This module consumes semantic signatures and ownership decisions completed by -``policy_completion``. It produces immutable records for wrapper planning: +``completion``. It produces immutable records for wrapper planning: Python/native boundaries, ordered call slots, result projections, lifecycle, module and derived-object access, and fail-closed support blockers. Planners and backend generators consume these records without inferring replacement @@ -25,13 +25,13 @@ SCALAR_STORAGE_CATEGORY, SUPPRESS_DEFAULT_CONSTRUCTOR_METADATA, ) -from prik.semantics.native_array_handles import ( +from prik.policy.native_array_handles import ( NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER, NativeArrayHandlePolicy as CompletedNativeArrayHandlePolicy, - native_array_descriptor_kind, ) -from prik.semantics.wrapper_exports import PythonExportPolicy, completed_python_exports -from prik.semantics.ownership import ( +from prik.semantics.native_array_handles import native_array_descriptor_kind +from prik.policy.exports import PythonExportPolicy, completed_python_exports +from prik.policy.ownership import ( AssignmentMode, CodegenAction, DestructionPolicy, @@ -44,7 +44,7 @@ StorageMode, TransferMode, ) -from prik.semantics.wrapper_policy_models import ( +from prik.policy.models import ( FIXED_STRING_RESULT_COPY_REASON, ORDINARY_ARRAY_RESULT_COPY_REASON, OWNED_NATIVE_ARRAY_HANDLE_COPY_REASON, diff --git a/prik/semantics/wrapper_exports.py b/prik/policy/exports.py similarity index 100% rename from prik/semantics/wrapper_exports.py rename to prik/policy/exports.py diff --git a/prik/semantics/wrapper_policy_models.py b/prik/policy/models.py similarity index 98% rename from prik/semantics/wrapper_policy_models.py rename to prik/policy/models.py index f35110a7b..91f0bf0b1 100644 --- a/prik/semantics/wrapper_policy_models.py +++ b/prik/policy/models.py @@ -3,7 +3,7 @@ This module owns the stable enums, frozen policy records, and cross-stage reason constants produced by post-IR policy construction and consumed by wrapper planning and lowering. It contains no semantic policy construction: -those rules remain in :mod:`prik.semantics.wrapper_policy`. +those rules remain in :mod:`prik.policy.construction`. """ from __future__ import annotations @@ -12,7 +12,7 @@ from enum import Enum from typing import Any -from prik.semantics.ownership import ( +from prik.policy.ownership import ( AssignmentMode, CodegenAction, NativeBarrierAction, @@ -23,7 +23,7 @@ SetterAction, StorageMode, ) -from prik.semantics.wrapper_exports import PythonExportPolicy +from prik.policy.exports import PythonExportPolicy FIXED_STRING_RESULT_COPY_REASON = "copy fixed-length Fortran character output into C-owned null-terminated storage" @@ -569,7 +569,12 @@ class ClassMethodPolicy: @dataclass(frozen=True) class OverloadArgumentPolicy: - """One completed exact-type predicate in an overload signature.""" + """One completed exact-type predicate in an overload signature. + + ``builtin_scalar_family`` is ``bool``, ``int``, ``float``, or ``complex`` + only when reflected dispatch admits that exact Python builtin beside the + recorded NumPy scalar type. ``None`` keeps dispatch NumPy-exact. + """ python_name: str kind: OverloadMatchKind @@ -577,7 +582,7 @@ class OverloadArgumentPolicy: semantic_type_name: str rank: int derived_type_identity: tuple[str, str] | None - accept_builtin_scalar: bool = False + builtin_scalar_family: str | None = None @dataclass(frozen=True) diff --git a/prik/policy/native_array_handles.py b/prik/policy/native_array_handles.py new file mode 100644 index 000000000..1308e5ecf --- /dev/null +++ b/prik/policy/native_array_handles.py @@ -0,0 +1,324 @@ +from __future__ import annotations + +from collections.abc import Iterable, Mapping +from dataclasses import dataclass +from typing import Any + +from prik.semantics.models import ( + RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA, + ProcedureOverloadSet, + SemanticClass, + SemanticFunction, + SemanticModule, + SemanticType, + SemanticVariable, +) +from prik.semantics.native_array_handles import native_array_descriptor_kind + + +NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER = "ISO_Fortran_binding.h" + + +@dataclass(frozen=True) +class NativeArrayHandlePolicy: + """Completed post-IR policy for a native allocatable or pointer array handle.""" + + descriptor_kind: str + handle_kind: str + origin: str + owner: str + owner_retention: str + descriptor_ownership: str + borrowed: bool + getter_behavior: str + python_setter: str + native_setter: str + output_projection: str + result_allocation: str + release: str + target_lifetime: str + destroy_behavior: str + to_numpy: str + descriptor_interop: str + nullable: bool + optional_absent: bool + storage_mode: str + operations: tuple[str, ...] = () + blocker: str | None = None + default_construction: str = "none" + default_descriptor_ownership: str = "unknown" + default_release: str = "none" + default_destroy_behavior: str = "none" + default_operations: tuple[str, ...] = () + + @property + def is_blocked(self) -> bool: + """Return whether this completed policy blocks wrapper generation.""" + return self.handle_kind == "unsupported" or self.blocker is not None + + def allows(self, operation: str) -> bool: + """Return whether a descriptor operation is explicitly permitted.""" + return operation in self.operations + + @property + def requires_pointer_c_descriptor_interop(self) -> bool: + """Return whether this handle path needs TS 29113 C descriptor interop.""" + return self.descriptor_interop == "pointer_c_descriptor" + + @property + def requires_c_descriptor_interop(self) -> bool: + """Return whether generated code needs standard C descriptor support.""" + return self.descriptor_interop in { + "module_allocatable_c_descriptor", + "owned_allocatable_c_descriptor", + "pointer_c_descriptor", + } + + +@dataclass(frozen=True) +class ArrayInteropPolicy: + """Completed selector for the ABI lane used by an array-like boundary.""" + + abi: str + owner: str + descriptor_kind: str | None = None + handle_kind: str | None = None + + @property + def is_data_buffer(self) -> bool: + """Return whether this boundary uses ordinary data-pointer array ABI.""" + return self.abi == "data_buffer" + + @property + def is_descriptor(self) -> bool: + """Return whether this boundary uses native descriptor-handle ABI.""" + return self.abi == "descriptor" + + +@dataclass(frozen=True) +class ArrayInteropPolicyDispatcher: + """Dispatch array-like bridge/binding work from the completed ABI selector.""" + + handlers: Mapping[tuple[str, str], str] + + def handler_name_for_policy(self, policy: ArrayInteropPolicy, context: str, name: str) -> str: + key = (context, policy.abi) + try: + return self.handlers[key] + except KeyError: + raise ValueError(f"No array interop codegen handler for {name!r}: {context}/{policy.abi}") from None + + def dispatch( + self, + target: Any, + subject: Any, + policy: ArrayInteropPolicy, + context: str, + *args: Any, + **kwargs: Any, + ) -> Any: + name = str(getattr(subject, "name", getattr(subject, "python_name", type(subject).__name__))) + handler = getattr(target, self.handler_name_for_policy(policy, context, name)) + return handler(subject, policy, *args, **kwargs) + + +@dataclass(frozen=True) +class NativeArrayHandlePolicyDispatcher: + """Dispatch generated handle work from completed native-array policy.""" + + handlers: Mapping[tuple[str, str], str] + + def handler_name_for_policy(self, policy: NativeArrayHandlePolicy, name: str) -> str: + key = (policy.descriptor_kind, policy.handle_kind) + try: + return self.handlers[key] + except KeyError: + descriptor_kind, handle_kind = key + raise ValueError( + f"No native-array-handle codegen handler for {name!r}: {descriptor_kind}/{handle_kind}" + ) from None + + def dispatch( + self, + target: Any, + subject: Any, + policy: NativeArrayHandlePolicy, + *args: Any, + **kwargs: Any, + ) -> Any: + name = str(getattr(subject, "name", getattr(subject, "python_name", type(subject).__name__))) + handler = getattr(target, self.handler_name_for_policy(policy, name)) + return handler(subject, policy, *args, **kwargs) + + +@dataclass(frozen=True) +class NativeArrayOutputProjectionDispatcher: + """Dispatch handle boundary work from completed output projection.""" + + handlers: Mapping[str, str] + + def dispatch( + self, + target: Any, + subject: Any, + policy: NativeArrayHandlePolicy, + *args: Any, + **kwargs: Any, + ) -> Any: + try: + handler_name = self.handlers[policy.output_projection] + except KeyError: + name = str(getattr(subject, "name", getattr(subject, "python_name", type(subject).__name__))) + raise ValueError( + f"No native-array output-projection handler for {name!r}: {policy.output_projection}" + ) from None + return getattr(target, handler_name)(subject, policy, *args, **kwargs) + + +@dataclass(frozen=True) +class NativeArrayBuildRequirement: + """One build requirement selected by a completed native-array handle policy.""" + + owner: str + item: str + descriptor_kind: str + handle_kind: str + descriptor_interop: str + headers: tuple[str, ...] + + +@dataclass(frozen=True) +class NativeArrayBuildRequirements: + """Build requirements selected by all completed native-array handle policies.""" + + pointer_c_descriptor_interop: bool + headers: tuple[str, ...] + items: tuple[NativeArrayBuildRequirement, ...] + + @property + def requires_iso_fortran_binding(self) -> bool: + """Return whether generated wrapper C code needs ISO_Fortran_binding.h.""" + return NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER in self.headers + + +def array_interop_policy( + semantic_type: SemanticType | None, + *, + owner: str, + native_array_handle_policy: NativeArrayHandlePolicy | None = None, +) -> ArrayInteropPolicy | None: + """Return the completed ABI selector for an array-like boundary.""" + if native_array_handle_policy is not None: + return ArrayInteropPolicy( + abi="descriptor", + owner=owner, + descriptor_kind=native_array_handle_policy.descriptor_kind, + handle_kind=native_array_handle_policy.handle_kind, + ) + if semantic_type is None: + return None + storage = semantic_type.storage + if semantic_type.rank > 0 and storage is not None and storage.array is not None: + return ArrayInteropPolicy(abi="data_buffer", owner=owner) + return None + + +def native_array_handle_build_requirements( + semantic_ir: SemanticModule | Iterable[SemanticModule], +) -> NativeArrayBuildRequirements: + """Return build requirements selected by completed native-array handle policies.""" + modules = [semantic_ir] if isinstance(semantic_ir, SemanticModule) else list(semantic_ir) + requirements = tuple( + _c_descriptor_requirement(owner, item, policy) + for owner, item, policy in _iter_native_array_handle_policies(modules) + if policy.requires_c_descriptor_interop + ) + headers = (NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER,) if requirements else () + return NativeArrayBuildRequirements( + pointer_c_descriptor_interop=any( + requirement.descriptor_interop == "pointer_c_descriptor" for requirement in requirements + ), + headers=headers, + items=requirements, + ) + + +def _c_descriptor_requirement( + owner: str, + item: str, + policy: NativeArrayHandlePolicy, +) -> NativeArrayBuildRequirement: + return NativeArrayBuildRequirement( + owner=owner, + item=item, + descriptor_kind=policy.descriptor_kind, + handle_kind=policy.handle_kind, + descriptor_interop=policy.descriptor_interop, + headers=(NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER,), + ) + + +def _iter_native_array_handle_policies(modules: Iterable[SemanticModule]): + for module in modules: + for variable in module.variables: + yield from _variable_native_array_policy(variable, owner=f"{module.name}.{variable.name}") + for semantic_class in module.classes: + yield from _iter_class_native_array_policies(semantic_class, owner=f"{module.name}.{semantic_class.name}") + for function in module.functions: + yield from _iter_function_native_array_policies(function, owner=f"{module.name}.{function.name}") + for overload_set in module.overload_sets: + yield from _iter_overload_native_array_policies(overload_set, owner=f"{module.name}.{overload_set.name}") + + +def _iter_class_native_array_policies(semantic_class: SemanticClass, *, owner: str): + for field in semantic_class.fields: + yield from _variable_native_array_policy(field, owner=f"{owner}.{field.name}") + for nested in semantic_class.classes: + yield from _iter_class_native_array_policies(nested, owner=f"{owner}.{nested.name}") + for method in semantic_class.methods: + yield from _iter_function_native_array_policies(method, owner=f"{owner}.{method.name}") + for overload_set in semantic_class.overload_sets: + yield from _iter_overload_native_array_policies(overload_set, owner=f"{owner}.{overload_set.name}") + + +def _iter_overload_native_array_policies(overload_set: ProcedureOverloadSet, *, owner: str): + for procedure in overload_set.procedures: + yield from _iter_function_native_array_policies(procedure, owner=owner) + + +def _iter_function_native_array_policies(function: SemanticFunction, *, owner: str): + for argument in function.arguments: + yield from _variable_native_array_policy(argument, owner=f"{owner}.{argument.name}") + if native_array_descriptor_kind(function.return_type) is not None: + policy = function.metadata.get(RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA) + if policy is None: + raise ValueError( + f"Native array handle {owner}.return is missing completed policy; " + "run complete_semantic_policies before collecting build requirements" + ) + yield f"{owner}.return", "return", policy + + +def _variable_native_array_policy(variable: SemanticVariable, *, owner: str): + if native_array_descriptor_kind(variable.semantic_type) is None: + return + policy = variable.metadata.get(RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA) + if policy is None: + raise ValueError( + f"Native array handle {owner} is missing completed policy; " + "run complete_semantic_policies before collecting build requirements" + ) + yield owner, variable.name, policy + + +__all__ = ( + "NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER", + "ArrayInteropPolicy", + "ArrayInteropPolicyDispatcher", + "NativeArrayBuildRequirement", + "NativeArrayBuildRequirements", + "NativeArrayHandlePolicy", + "NativeArrayHandlePolicyDispatcher", + "array_interop_policy", + "native_array_handle_build_requirements", +) diff --git a/prik/semantics/ownership.py b/prik/policy/ownership.py similarity index 97% rename from prik/semantics/ownership.py rename to prik/policy/ownership.py index 024b928a0..429b791c9 100644 --- a/prik/semantics/ownership.py +++ b/prik/policy/ownership.py @@ -50,32 +50,11 @@ PROJECTED_OUTPUT_METADATA, SCALAR_STORAGE_CATEGORY, ) +from prik.semantics.models import PYTHON_VALUE_IMMUTABLE, PYTHON_VALUE_MUTABILITY_METADATA +from prik.semantics.ownership_metadata import OWNERSHIP_POLICY_METADATA, POINTER_POLICY_METADATA from prik.types.numpy import BOOLEAN_SEMANTIC_TYPE_NAMES -OWNERSHIP_POLICY_METADATA = "ownership_policy" -POINTER_POLICY_METADATA = "pointer_policy" -# PointerPolicy fields answer, in order: whether association may be absent, -# boundary use, target owner, lifetime proof, permitted release, shape source, -# layout guarantee, permitted association change, alias relationship, and -# mutability. String values remain contract facts until policy completion -# validates whether the current runtime implements the requested mechanism. -POINTER_POLICY_FIELDS = ( - "nullable", - "transfer", - "target_owner", - "lifetime", - "deallocation", - "shape_source", - "contiguity", - "reassociation", - "aliasing", - "mutability", -) -PYTHON_VALUE_MUTABILITY_METADATA = "python_value_mutability" -PYTHON_VALUE_IMMUTABLE = "immutable" - - # Completed policy vocabulary @@ -2484,59 +2463,6 @@ def _semantic_variable_context(variable: Any) -> OwnershipContext: return OwnershipContext(location="value") -# Contract metadata and lowering gates - - -def set_ownership_metadata( - metadata: dict[str, Any], - *, - owner: str | None = None, - transfer: str | None = None, - destruction: str | None = None, -) -> None: - """Store validated owner, transfer, and destruction metadata on a semantic mapping. - - Use this when constructing or editing a semantic contract. Provided - values are normalized through their enums; an existing non-dictionary - ownership policy raises ``ValueError`` rather than being overwritten. - """ - policy = metadata.setdefault(OWNERSHIP_POLICY_METADATA, {}) - if not isinstance(policy, dict): - raise ValueError(f"{OWNERSHIP_POLICY_METADATA!r} metadata must be a dictionary") - if owner is not None: - policy["owner"] = OwnershipOwner(owner).value - if transfer is not None: - policy["transfer"] = TransferMode(transfer).value - if destruction is not None: - policy["destruction"] = DestructionPolicy(destruction).value - - -def set_pointer_policy_metadata(metadata: dict[str, Any], **policy_values: Any) -> None: - """Store a complete semantic pointer policy after validating its shape. - - Callers must provide exactly ``POINTER_POLICY_FIELDS``. The helper mutates - ``metadata`` with the checked policy and its ``fortran_pointer`` marker; - malformed values raise ``ValueError`` before policy resolution. - """ - missing = [name for name in POINTER_POLICY_FIELDS if name not in policy_values] - extra = [name for name in policy_values if name not in POINTER_POLICY_FIELDS] - if missing or extra: - details = [] - if missing: - details.append(f"missing: {', '.join(missing)}") - if extra: - details.append(f"unexpected: {', '.join(extra)}") - raise ValueError(f"PointerPolicy requires exactly {', '.join(POINTER_POLICY_FIELDS)} ({'; '.join(details)})") - if not isinstance(policy_values["nullable"], bool): - raise ValueError("PointerPolicy nullable must be a boolean") - for name in POINTER_POLICY_FIELDS[1:]: - if not isinstance(policy_values[name], str) or not policy_values[name]: - raise ValueError(f"PointerPolicy {name} must be a non-empty string") - TransferMode(policy_values["transfer"]) - metadata[POINTER_POLICY_METADATA] = dict(policy_values) - metadata["fortran_pointer"] = True - - default_ownership_policy = OwnershipPolicyResolver() diff --git a/prik/printers/README.md b/prik/printers/README.md new file mode 100644 index 000000000..2af05c66f --- /dev/null +++ b/prik/printers/README.md @@ -0,0 +1,16 @@ +# Printers Package + +This package serializes already-formed language representations into text. It +is the output-side counterpart of `../parsers/` and owns no semantic policy, +wrapper planning, cross-language orchestration, filenames, or build behavior. + +| File | Owns | +| --- | --- | +| `c.py` | C translation-unit and header node serialization. | +| `fortran.py` | Fortran bridge node serialization and free-form line wrapping. | +| `pyi.py` | Semantic IR serialization as editable semantic `.pyi`. | + +`../pipeline/wrapper.py` coordinates C and Fortran node generation, calls the +two native source printers, assigns stable wrapper filenames, and returns one +generated-wrapper result. `../pipeline/build.py` writes or compiles that +result. diff --git a/prik/printers/__init__.py b/prik/printers/__init__.py new file mode 100644 index 000000000..03bf1af23 --- /dev/null +++ b/prik/printers/__init__.py @@ -0,0 +1,12 @@ +"""Canonical C, Fortran, and semantic-contract printers.""" + +from .c import CSourcePrinter +from .fortran import FortranSourcePrinter +from .pyi import PyiPrinter, emit_module + +__all__ = ( + "CSourcePrinter", + "FortranSourcePrinter", + "PyiPrinter", + "emit_module", +) diff --git a/prik/printers/c.py b/prik/printers/c.py new file mode 100644 index 000000000..d212d193b --- /dev/null +++ b/prik/printers/c.py @@ -0,0 +1,359 @@ +"""Render lowered C backend nodes into compilable source text. + +This module is the final text-rendering boundary for generated wrapper source. +It consumes only backend syntax nodes; semantic policy and wrapper planning are +completed by earlier stages. +""" + +from __future__ import annotations + + +from prik.codegen.nodes import ( + CAllowThreadsBegin, + CAllowThreadsEnd, + CComment, + CDeclaration, + CExpressionStatement, + CFor, + CBreak, + CCase, + CFunction, + CFunctionPointerType, + CFunctionPrototype, + CHeader, + CIf, + CInclude, + CMacroDefinition, + CMethodDefEntry, + CMethodDefTable, + CModuleDef, + CModule, + CModulePropertyEntry, + CModulePropertySupport, + CParameter, + CReturn, + CStructDefinition, + CSwitch, +) +from prik.stage_values import StageRecord +from prik.codegen.visitor import ClassVisitor + + +class CSourcePrinter(ClassVisitor): + """Render lowered C backend nodes into source or header text. + + Use this printer after the binding generator has produced C syntax nodes. + It accepts individual nodes, C translation units, and headers, then returns + their source representation. Rendering a stage record freezes that record, + matching the immutable handoff used by the rest of code generation. + """ + + def doprint(self, node: object) -> str: + """Render one C backend node and return its source text. + + Use this public entrypoint for C headers, translation units, and their + constituent nodes. A StageRecord is frozen before visitor dispatch; + unsupported node types retain the visitor's existing exception. + """ + if isinstance(node, StageRecord): + node.freeze() + return self.visit(node) + + def _visit_CModule(self, node: CModule) -> str: + """Render one C translation unit in compiler-required source order.""" + # Definitions must precede includes, declarations, and function bodies. + parts = [self.visit(define) for define in node.defines] + parts.extend(self.visit(include) for include in node.includes) + parts.extend(self.visit(declaration) for declaration in node.declarations) + parts.extend(self.visit(function) for function in node.functions) + return "\n\n".join(part for part in parts if part) + + def _visit_CHeader(self, node: CHeader) -> str: + """Render one guarded C header from its includes and prototypes.""" + lines = [f"#ifndef {node.guard}", f"#define {node.guard}"] + lines.extend(self.visit(include) for include in node.includes) + lines.extend(self.visit(prototype) for prototype in node.prototypes) + lines.append(f"#endif /* {node.guard} */") + return "\n".join(lines) + + def _visit_CInclude(self, node: CInclude) -> str: + """Render one C include directive, preserving the system-header mode.""" + if node.system: + return f"#include <{node.header}>" + return f'#include "{node.header}"' + + def _visit_CMacroDefinition(self, node: CMacroDefinition) -> str: + """Render one C macro, omitting its value when the node has none.""" + if node.value is None: + return f"#define {node.name}" + return f"#define {node.name} {node.value}" + + def _visit_CComment(self, node: CComment) -> str: + """Render one generated C line comment from the node text.""" + return f"// {node.text}" + + def _visit_CFunction(self, node: CFunction) -> str: + """Render one C function definition with each body statement indented.""" + prefix = f"{node.storage} " if node.storage else "" + body = "\n".join(self._indented(self.visit(statement)) for statement in node.body) + return f"{prefix}{self._signature(node.return_type, node.name, node.parameters)} {{\n{body}\n}}" + + def _visit_CFunctionPrototype(self, node: CFunctionPrototype) -> str: + """Render one C prototype using the shared signature renderer.""" + prefix = f"{node.storage} " if node.storage else "" + return f"{prefix}{self._signature(node.return_type, node.name, node.parameters)};" + + def _visit_CFunctionPointerType(self, node: CFunctionPointerType) -> str: + """Render one typed function-pointer alias with explicit void parameters.""" + parameters = ", ".join(node.parameter_types) or "void" + return f"typedef {node.return_type} (*{node.name})({parameters});" + + def _visit_CStructDefinition(self, node: CStructDefinition) -> str: + """Render one C struct definition and preserve field declaration order.""" + lines = [f"typedef struct {node.name} {{"] + lines.extend(f" {self.visit(field)};" for field in node.fields) + lines.append(f"}} {node.name};") + return "\n".join(lines) + + def _visit_CMethodDefTable(self, node: CMethodDefTable) -> str: + """Render one CPython method table and append its required sentinel.""" + lines = [f"static PyMethodDef {node.name}[] = {{"] + lines.extend(f" {self.visit(entry)}," for entry in node.entries) + lines.extend((" {NULL, NULL, 0, NULL}", "};")) + return "\n".join(lines) + + def _visit_CMethodDefEntry(self, node: CMethodDefEntry) -> str: + """Render one CPython method-table entry with safely quoted strings.""" + return ( + f"{{{self._c_string_literal(node.python_name)}, " + f"(PyCFunction){node.wrapper_name}, {node.flags}, {self._c_string_literal(node.docstring)}}}" + ) + + def _visit_CModuleDef(self, node: CModuleDef) -> str: + """Render one CPython module-definition initializer from its node fields.""" + return "\n".join( + ( + f"static struct PyModuleDef {node.name} = {{", + " PyModuleDef_HEAD_INIT,", + f" {self._c_string_literal(node.module_name)},", + f" {self._c_string_literal(node.docstring)},", + f" {node.state_size},", + f" {node.methods_name},", + "};", + ) + ) + + def _visit_CModulePropertySupport(self, node: CModulePropertySupport) -> str: + """Render all generated module-property routing support in stable order. + + The node supplies getter and setter entries plus the heap subtype name. + This method returns the three dependent C definitions: attribute getter, + attribute setter, and module-type installer. + """ + return "\n\n".join( + ( + self._module_getattro_source(node), + self._module_setattro_source(node), + self._module_property_type_source(node), + ) + ) + + def _module_getattro_source(self, node: CModulePropertySupport) -> str: + """Build the module attribute getter for every declared property entry. + + The returned function compares only Unicode attribute names, delegates + matching names to generated getters, and preserves the base module + fallback for all other attributes. + """ + lines = [f"static PyObject *{node.name}_getattro(PyObject *self, PyObject *name)", "{"] + lines.append(" if (PyUnicode_Check(name)) {") + for entry in node.entries: + lines.extend(self._module_getter_entry_source(entry)) + lines.extend((" }", " return PyModule_Type.tp_getattro(self, name);", "}")) + return "\n".join(lines) + + def _module_getter_entry_source(self, node: CModulePropertyEntry) -> tuple[str, ...]: + """Build one getter dispatch branch from a property entry. + + The tuple is inserted into the enclosing Unicode-name guard. It returns + NULL on comparison failure and calls exactly the getter named by the + supplied entry when its Python name matches. + """ + name = self._c_string_literal(node.python_name) + return ( + " {", + f" int comparison = PyUnicode_CompareWithASCIIString(name, {name});", + " if (comparison == -1 && PyErr_Occurred()) return NULL;", + f" if (comparison == 0) return {node.getter_name}();", + " }", + ) + + def _module_setattro_source(self, node: CModulePropertySupport) -> str: + """Build the module attribute setter for every declared property entry. + + The returned function dispatches writable properties to their generated + setters and keeps the base module setter as the nonmatching fallback. + """ + lines = [f"static int {node.name}_setattro(PyObject *self, PyObject *name, PyObject *value)", "{"] + lines.append(" if (PyUnicode_Check(name)) {") + for entry in node.entries: + lines.extend(self._module_setter_entry_source(entry)) + lines.extend((" }", " return PyModule_Type.tp_setattro(self, name, value);", "}")) + return "\n".join(lines) + + def _module_setter_entry_source(self, node: CModulePropertyEntry) -> tuple[str, ...]: + """Build one setter dispatch branch and its node-selected error path. + + The tuple rejects replacement for read-only entries. Writable entries + reject deletion before calling their generated setter with the supplied + value; those rules are already encoded by the backend node. + """ + name = self._c_string_literal(node.python_name) + lines = [ + " {", + f" int comparison = PyUnicode_CompareWithASCIIString(name, {name});", + " if (comparison == -1 && PyErr_Occurred()) return -1;", + " if (comparison == 0) {", + ] + if node.reject_replacement: + lines.extend( + ( + f' PyErr_SetString(PyExc_AttributeError, "module variable {node.python_name} is read-only");', + " return -1;", + ) + ) + else: + lines.extend( + ( + " if (value == NULL) {", + f' PyErr_SetString(PyExc_AttributeError, "module variable {node.python_name} cannot be deleted");', + " return -1;", + " }", + f" return {node.setter_name}(value);", + ) + ) + lines.extend((" }", " }")) + return tuple(lines) + + def _module_property_type_source(self, node: CModulePropertySupport) -> str: + """Build C slots, type spec, and installer for module property support. + + The returned definitions are ordered so the installer can reference the + generated slots and type spec without forward declarations. The node's + name is reused consistently for all emitted symbols. + """ + return "\n".join( + ( + f"static PyType_Slot {node.name}_slots[] = {{", + f" {{Py_tp_getattro, (void *){node.name}_getattro}},", + f" {{Py_tp_setattro, (void *){node.name}_setattro}},", + " {0, NULL}", + "};", + f"static PyType_Spec {node.name}_spec = {{", + f" {self._c_string_literal(f'{node.module_name}.__prik_module_type')},", + " 0,", + " 0,", + " Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,", + f" {node.name}_slots", + "};", + f"static int {node.name}(PyObject *module)", + "{", + " PyObject *bases = PyTuple_Pack(1, (PyObject *)&PyModule_Type);", + " if (bases == NULL) return -1;", + f" PyObject *module_type = PyType_FromSpecWithBases(&{node.name}_spec, bases);", + " Py_DECREF(bases);", + " if (module_type == NULL) return -1;", + ' int status = PyObject_SetAttrString(module, "__class__", module_type);', + " Py_DECREF(module_type);", + " return status;", + "}", + ) + ) + + def _visit_CParameter(self, node: CParameter) -> str: + """Render one C parameter, including typed callback parameters.""" + if node.function_parameters is not None: + parameters = ", ".join(node.function_parameters) or "void" + return f"{node.type_name} (*{node.name})({parameters})" + return f"{node.type_name} {node.name}" + + def _visit_CDeclaration(self, node: CDeclaration) -> str: + """Render one C declaration and optional initializer expression.""" + if node.initializer is None: + return f"{node.type_name} {node.name};" + return f"{node.type_name} {node.name} = {node.initializer.text};" + + def _visit_CExpressionStatement(self, node: CExpressionStatement) -> str: + """Render one C expression statement and add its terminating semicolon.""" + return f"{node.expression.text};" + + def _visit_CAllowThreadsBegin(self, _node: CAllowThreadsBegin) -> str: + """Render the opening CPython thread-release macro without a semicolon.""" + return "Py_BEGIN_ALLOW_THREADS" + + def _visit_CAllowThreadsEnd(self, _node: CAllowThreadsEnd) -> str: + """Render the closing CPython thread-release macro without a semicolon.""" + return "Py_END_ALLOW_THREADS" + + def _visit_CIf(self, node: CIf) -> str: + """Render one C conditional and preserve optional else-body ordering.""" + lines = [f"if ({node.condition.text}) {{"] + lines.extend(self._indented(self.visit(statement)) for statement in node.body) + if node.else_body: + lines.append("} else {") + lines.extend(self._indented(self.visit(statement)) for statement in node.else_body) + lines.append("}") + return "\n".join(lines) + + def _visit_CFor(self, node: CFor) -> str: + """Render one C for-loop with each generated statement indented.""" + lines = [f"for ({node.initializer}; {node.condition.text}; {node.increment.text}) {{"] + lines.extend(self._indented(self.visit(statement)) for statement in node.body) + lines.append("}") + return "\n".join(lines) + + def _visit_CBreak(self, _node: CBreak) -> str: + """Render one C loop-break statement.""" + return "break;" + + def _visit_CCase(self, node: CCase) -> str: + """Render one switch case with an explicit terminating branch body.""" + label = "default: {" if node.value is None else f"case {node.value.text}: {{" + lines = [label] + lines.extend(self._indented(self.visit(statement)) for statement in node.body) + lines.append("}") + return "\n".join(lines) + + def _visit_CSwitch(self, node: CSwitch) -> str: + """Render one integer-key switch and its ordered cases.""" + lines = [f"switch ({node.expression.text}) {{"] + lines.extend(self._indented(self.visit(case)) for case in node.cases) + lines.append("}") + return "\n".join(lines) + + def _visit_CReturn(self, node: CReturn) -> str: + """Render one C return with or without the node expression.""" + if node.expression is None: + return "return;" + return f"return {node.expression.text};" + + def _signature(self, return_type: str, name: str, parameters: tuple[CParameter, ...]) -> str: + """Render a C signature from its return type, name, and parameters. + + Empty parameter tuples become void so both function declarations and + definitions retain C's explicit no-argument form. + """ + rendered = ", ".join(self.visit(parameter) for parameter in parameters) or "void" + return f"{return_type} {name}({rendered})" + + def _c_string_literal(self, value: str) -> str: + """Escape one Python string into the C literal used by generated tables.""" + escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + return f'"{escaped}"' + + def _indented(self, text: str) -> str: + """Indent every line of rendered C text for a containing block.""" + return "\n".join(f" {line}" for line in text.splitlines()) + + +# Fortran source rendering diff --git a/prik/codegen/printers/source_printers.py b/prik/printers/fortran.py similarity index 64% rename from prik/codegen/printers/source_printers.py rename to prik/printers/fortran.py index 1d9aab02a..fba8e0701 100644 --- a/prik/codegen/printers/source_printers.py +++ b/prik/printers/fortran.py @@ -1,4 +1,4 @@ -"""Render lowered C and Fortran backend nodes into compilable source text. +"""Render lowered Fortran backend nodes into compilable source text. This module is the final text-rendering boundary for generated wrapper source. It consumes only backend syntax nodes; semantic policy and wrapper planning are @@ -10,31 +10,6 @@ import re from prik.codegen.nodes import ( - CAllowThreadsBegin, - CAllowThreadsEnd, - CComment, - CDeclaration, - CExpressionStatement, - CFor, - CBreak, - CCase, - CFunction, - CFunctionPointerType, - CFunctionPrototype, - CHeader, - CIf, - CInclude, - CMacroDefinition, - CMethodDefEntry, - CMethodDefTable, - CModuleDef, - CModule, - CModulePropertyEntry, - CModulePropertySupport, - CParameter, - CReturn, - CStructDefinition, - CSwitch, FortranAllocate, FortranAssignment, FortranCall, @@ -56,329 +31,6 @@ from prik.codegen.visitor import ClassVisitor -# C source rendering - - -class CSourcePrinter(ClassVisitor): - """Render lowered C backend nodes into source or header text. - - Use this printer after the binding generator has produced C syntax nodes. - It accepts individual nodes, C translation units, and headers, then returns - their source representation. Rendering a stage record freezes that record, - matching the immutable handoff used by the rest of code generation. - """ - - def doprint(self, node: object) -> str: - """Render one C backend node and return its source text. - - Use this public entrypoint for C headers, translation units, and their - constituent nodes. A StageRecord is frozen before visitor dispatch; - unsupported node types retain the visitor's existing exception. - """ - if isinstance(node, StageRecord): - node.freeze() - return self.visit(node) - - def _visit_CModule(self, node: CModule) -> str: - """Render one C translation unit in compiler-required source order.""" - # Definitions must precede includes, declarations, and function bodies. - parts = [self.visit(define) for define in node.defines] - parts.extend(self.visit(include) for include in node.includes) - parts.extend(self.visit(declaration) for declaration in node.declarations) - parts.extend(self.visit(function) for function in node.functions) - return "\n\n".join(part for part in parts if part) - - def _visit_CHeader(self, node: CHeader) -> str: - """Render one guarded C header from its includes and prototypes.""" - lines = [f"#ifndef {node.guard}", f"#define {node.guard}"] - lines.extend(self.visit(include) for include in node.includes) - lines.extend(self.visit(prototype) for prototype in node.prototypes) - lines.append(f"#endif /* {node.guard} */") - return "\n".join(lines) - - def _visit_CInclude(self, node: CInclude) -> str: - """Render one C include directive, preserving the system-header mode.""" - if node.system: - return f"#include <{node.header}>" - return f'#include "{node.header}"' - - def _visit_CMacroDefinition(self, node: CMacroDefinition) -> str: - """Render one C macro, omitting its value when the node has none.""" - if node.value is None: - return f"#define {node.name}" - return f"#define {node.name} {node.value}" - - def _visit_CComment(self, node: CComment) -> str: - """Render one generated C line comment from the node text.""" - return f"// {node.text}" - - def _visit_CFunction(self, node: CFunction) -> str: - """Render one C function definition with each body statement indented.""" - prefix = f"{node.storage} " if node.storage else "" - body = "\n".join(self._indented(self.visit(statement)) for statement in node.body) - return f"{prefix}{self._signature(node.return_type, node.name, node.parameters)} {{\n{body}\n}}" - - def _visit_CFunctionPrototype(self, node: CFunctionPrototype) -> str: - """Render one C prototype using the shared signature renderer.""" - prefix = f"{node.storage} " if node.storage else "" - return f"{prefix}{self._signature(node.return_type, node.name, node.parameters)};" - - def _visit_CFunctionPointerType(self, node: CFunctionPointerType) -> str: - """Render one typed function-pointer alias with explicit void parameters.""" - parameters = ", ".join(node.parameter_types) or "void" - return f"typedef {node.return_type} (*{node.name})({parameters});" - - def _visit_CStructDefinition(self, node: CStructDefinition) -> str: - """Render one C struct definition and preserve field declaration order.""" - lines = [f"typedef struct {node.name} {{"] - lines.extend(f" {self.visit(field)};" for field in node.fields) - lines.append(f"}} {node.name};") - return "\n".join(lines) - - def _visit_CMethodDefTable(self, node: CMethodDefTable) -> str: - """Render one CPython method table and append its required sentinel.""" - lines = [f"static PyMethodDef {node.name}[] = {{"] - lines.extend(f" {self.visit(entry)}," for entry in node.entries) - lines.extend((" {NULL, NULL, 0, NULL}", "};")) - return "\n".join(lines) - - def _visit_CMethodDefEntry(self, node: CMethodDefEntry) -> str: - """Render one CPython method-table entry with safely quoted strings.""" - return ( - f"{{{self._c_string_literal(node.python_name)}, " - f"(PyCFunction){node.wrapper_name}, {node.flags}, {self._c_string_literal(node.docstring)}}}" - ) - - def _visit_CModuleDef(self, node: CModuleDef) -> str: - """Render one CPython module-definition initializer from its node fields.""" - return "\n".join( - ( - f"static struct PyModuleDef {node.name} = {{", - " PyModuleDef_HEAD_INIT,", - f" {self._c_string_literal(node.module_name)},", - f" {self._c_string_literal(node.docstring)},", - f" {node.state_size},", - f" {node.methods_name},", - "};", - ) - ) - - def _visit_CModulePropertySupport(self, node: CModulePropertySupport) -> str: - """Render all generated module-property routing support in stable order. - - The node supplies getter and setter entries plus the heap subtype name. - This method returns the three dependent C definitions: attribute getter, - attribute setter, and module-type installer. - """ - return "\n\n".join( - ( - self._module_getattro_source(node), - self._module_setattro_source(node), - self._module_property_type_source(node), - ) - ) - - def _module_getattro_source(self, node: CModulePropertySupport) -> str: - """Build the module attribute getter for every declared property entry. - - The returned function compares only Unicode attribute names, delegates - matching names to generated getters, and preserves the base module - fallback for all other attributes. - """ - lines = [f"static PyObject *{node.name}_getattro(PyObject *self, PyObject *name)", "{"] - lines.append(" if (PyUnicode_Check(name)) {") - for entry in node.entries: - lines.extend(self._module_getter_entry_source(entry)) - lines.extend((" }", " return PyModule_Type.tp_getattro(self, name);", "}")) - return "\n".join(lines) - - def _module_getter_entry_source(self, node: CModulePropertyEntry) -> tuple[str, ...]: - """Build one getter dispatch branch from a property entry. - - The tuple is inserted into the enclosing Unicode-name guard. It returns - NULL on comparison failure and calls exactly the getter named by the - supplied entry when its Python name matches. - """ - name = self._c_string_literal(node.python_name) - return ( - " {", - f" int comparison = PyUnicode_CompareWithASCIIString(name, {name});", - " if (comparison == -1 && PyErr_Occurred()) return NULL;", - f" if (comparison == 0) return {node.getter_name}();", - " }", - ) - - def _module_setattro_source(self, node: CModulePropertySupport) -> str: - """Build the module attribute setter for every declared property entry. - - The returned function dispatches writable properties to their generated - setters and keeps the base module setter as the nonmatching fallback. - """ - lines = [f"static int {node.name}_setattro(PyObject *self, PyObject *name, PyObject *value)", "{"] - lines.append(" if (PyUnicode_Check(name)) {") - for entry in node.entries: - lines.extend(self._module_setter_entry_source(entry)) - lines.extend((" }", " return PyModule_Type.tp_setattro(self, name, value);", "}")) - return "\n".join(lines) - - def _module_setter_entry_source(self, node: CModulePropertyEntry) -> tuple[str, ...]: - """Build one setter dispatch branch and its node-selected error path. - - The tuple rejects replacement for read-only entries. Writable entries - reject deletion before calling their generated setter with the supplied - value; those rules are already encoded by the backend node. - """ - name = self._c_string_literal(node.python_name) - lines = [ - " {", - f" int comparison = PyUnicode_CompareWithASCIIString(name, {name});", - " if (comparison == -1 && PyErr_Occurred()) return -1;", - " if (comparison == 0) {", - ] - if node.reject_replacement: - lines.extend( - ( - f' PyErr_SetString(PyExc_AttributeError, "module variable {node.python_name} is read-only");', - " return -1;", - ) - ) - else: - lines.extend( - ( - " if (value == NULL) {", - f' PyErr_SetString(PyExc_AttributeError, "module variable {node.python_name} cannot be deleted");', - " return -1;", - " }", - f" return {node.setter_name}(value);", - ) - ) - lines.extend((" }", " }")) - return tuple(lines) - - def _module_property_type_source(self, node: CModulePropertySupport) -> str: - """Build C slots, type spec, and installer for module property support. - - The returned definitions are ordered so the installer can reference the - generated slots and type spec without forward declarations. The node's - name is reused consistently for all emitted symbols. - """ - return "\n".join( - ( - f"static PyType_Slot {node.name}_slots[] = {{", - f" {{Py_tp_getattro, (void *){node.name}_getattro}},", - f" {{Py_tp_setattro, (void *){node.name}_setattro}},", - " {0, NULL}", - "};", - f"static PyType_Spec {node.name}_spec = {{", - f" {self._c_string_literal(f'{node.module_name}.__prik_module_type')},", - " 0,", - " 0,", - " Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,", - f" {node.name}_slots", - "};", - f"static int {node.name}(PyObject *module)", - "{", - " PyObject *bases = PyTuple_Pack(1, (PyObject *)&PyModule_Type);", - " if (bases == NULL) return -1;", - f" PyObject *module_type = PyType_FromSpecWithBases(&{node.name}_spec, bases);", - " Py_DECREF(bases);", - " if (module_type == NULL) return -1;", - ' int status = PyObject_SetAttrString(module, "__class__", module_type);', - " Py_DECREF(module_type);", - " return status;", - "}", - ) - ) - - def _visit_CParameter(self, node: CParameter) -> str: - """Render one C parameter, including typed callback parameters.""" - if node.function_parameters is not None: - parameters = ", ".join(node.function_parameters) or "void" - return f"{node.type_name} (*{node.name})({parameters})" - return f"{node.type_name} {node.name}" - - def _visit_CDeclaration(self, node: CDeclaration) -> str: - """Render one C declaration and optional initializer expression.""" - if node.initializer is None: - return f"{node.type_name} {node.name};" - return f"{node.type_name} {node.name} = {node.initializer.text};" - - def _visit_CExpressionStatement(self, node: CExpressionStatement) -> str: - """Render one C expression statement and add its terminating semicolon.""" - return f"{node.expression.text};" - - def _visit_CAllowThreadsBegin(self, _node: CAllowThreadsBegin) -> str: - """Render the opening CPython thread-release macro without a semicolon.""" - return "Py_BEGIN_ALLOW_THREADS" - - def _visit_CAllowThreadsEnd(self, _node: CAllowThreadsEnd) -> str: - """Render the closing CPython thread-release macro without a semicolon.""" - return "Py_END_ALLOW_THREADS" - - def _visit_CIf(self, node: CIf) -> str: - """Render one C conditional and preserve optional else-body ordering.""" - lines = [f"if ({node.condition.text}) {{"] - lines.extend(self._indented(self.visit(statement)) for statement in node.body) - if node.else_body: - lines.append("} else {") - lines.extend(self._indented(self.visit(statement)) for statement in node.else_body) - lines.append("}") - return "\n".join(lines) - - def _visit_CFor(self, node: CFor) -> str: - """Render one C for-loop with each generated statement indented.""" - lines = [f"for ({node.initializer}; {node.condition.text}; {node.increment.text}) {{"] - lines.extend(self._indented(self.visit(statement)) for statement in node.body) - lines.append("}") - return "\n".join(lines) - - def _visit_CBreak(self, _node: CBreak) -> str: - """Render one C loop-break statement.""" - return "break;" - - def _visit_CCase(self, node: CCase) -> str: - """Render one switch case with an explicit terminating branch body.""" - label = "default: {" if node.value is None else f"case {node.value.text}: {{" - lines = [label] - lines.extend(self._indented(self.visit(statement)) for statement in node.body) - lines.append("}") - return "\n".join(lines) - - def _visit_CSwitch(self, node: CSwitch) -> str: - """Render one integer-key switch and its ordered cases.""" - lines = [f"switch ({node.expression.text}) {{"] - lines.extend(self._indented(self.visit(case)) for case in node.cases) - lines.append("}") - return "\n".join(lines) - - def _visit_CReturn(self, node: CReturn) -> str: - """Render one C return with or without the node expression.""" - if node.expression is None: - return "return;" - return f"return {node.expression.text};" - - def _signature(self, return_type: str, name: str, parameters: tuple[CParameter, ...]) -> str: - """Render a C signature from its return type, name, and parameters. - - Empty parameter tuples become void so both function declarations and - definitions retain C's explicit no-argument form. - """ - rendered = ", ".join(self.visit(parameter) for parameter in parameters) or "void" - return f"{return_type} {name}({rendered})" - - def _c_string_literal(self, value: str) -> str: - """Escape one Python string into the C literal used by generated tables.""" - escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") - return f'"{escaped}"' - - def _indented(self, text: str) -> str: - """Indent every line of rendered C text for a containing block.""" - return "\n".join(f" {line}" for line in text.splitlines()) - - -# Fortran source rendering - - class FortranSourcePrinter(ClassVisitor): """Render lowered Fortran backend nodes into free-form source text. diff --git a/prik/codegen/printers/pyi_printer.py b/prik/printers/pyi.py similarity index 95% rename from prik/codegen/printers/pyi_printer.py rename to prik/printers/pyi.py index c3565bf7c..b19450db9 100644 --- a/prik/codegen/printers/pyi_printer.py +++ b/prik/printers/pyi.py @@ -18,7 +18,11 @@ from prik.contracts import CONTRACT_SYMBOLS, CONTRACT_TYPE_NAMES from prik.naming import NamingPolicy -from prik.semantics.ownership import OWNERSHIP_POLICY_METADATA, POINTER_POLICY_FIELDS, POINTER_POLICY_METADATA +from prik.semantics.ownership_metadata import ( + OWNERSHIP_POLICY_METADATA, + POINTER_POLICY_FIELDS, + POINTER_POLICY_METADATA, +) from prik.types.numpy import SEMANTIC_DTYPE_TO_NUMPY_DTYPE, SEMANTIC_SCALAR_TYPE_NAMES from prik.semantics.metadata import ( ADDRESS_ROLE_METADATA, @@ -2338,34 +2342,6 @@ def _parameter_target(name: str) -> str: target = f"{target}_" return target - @staticmethod - def _opaque_dependency_class(type_name: str, c_kind: str | None) -> SemanticClass: - """Build the semantic placeholder for one missing opaque dependency.""" - base_classes: list[str] = [] - metadata: dict[str, object] = {"representation": "opaque"} - if c_kind == "struct": - base_classes.append("CStruct") - metadata["c_kind"] = "struct" - elif c_kind == "union": - base_classes.append("CUnion") - metadata["c_kind"] = "union" - base_classes.append("Opaque") - return SemanticClass( - name=type_name, - native_name=type_name, - base_classes=base_classes, - metadata=metadata, - ) - - @staticmethod - def _module_list(modules: SemanticModule | Iterable[SemanticModule] | None) -> list[SemanticModule]: - """Normalize one semantic module or an iterable to a list.""" - if modules is None: - return [] - if isinstance(modules, SemanticModule): - return [modules] - return list(modules) - _DEFAULT_PRINTER = PyiPrinter() @@ -2382,91 +2358,6 @@ def emit_module(module: SemanticModule, *, normalize_fortran_public_names: bool return _DEFAULT_PRINTER.emit(module) -def opaque_dependency_modules( - modules: SemanticModule | Iterable[SemanticModule], - *, - available_modules: Iterable[SemanticModule] | None = None, -) -> list[SemanticModule]: - """Build semantic modules for opaque types referenced but not supplied. - - Use this before package emission when a contract refers to C opaque types - from absent modules. The input modules are inspected but not mutated; the - returned list is ordered deterministically by module and type name. - """ - source_modules = PyiPrinter._module_list(modules) - known_modules = PyiPrinter._module_list(available_modules) if available_modules is not None else source_modules - known_classes = { - (module.name, cls.name) for module in known_modules for cls in module.classes if isinstance(cls, SemanticClass) - } - dependencies: dict[str, dict[str, str | None]] = {} - for module in source_modules: - for semantic_type in _iter_module_semantic_types(module): - ref = semantic_type.metadata.get(EXTERNAL_TYPE_REF_METADATA) - if not isinstance(ref, dict) or ref.get("representation") != "opaque": - continue - origin_module = ref.get("origin_module") - type_name = ref.get("name") - if not isinstance(origin_module, str) or not isinstance(type_name, str): - continue - if (origin_module, type_name) in known_classes: - continue - c_kind = semantic_type.metadata.get("c_kind") - dependencies.setdefault(origin_module, {}).setdefault( - type_name, - c_kind if c_kind in {"struct", "union"} else None, - ) - return [ - SemanticModule( - name=module_name, - classes=[ - PyiPrinter._opaque_dependency_class(type_name, c_kind) - for type_name, c_kind in sorted(type_kinds.items()) - ], - ) - for module_name, type_kinds in sorted(dependencies.items()) - ] - - -def emit_module_stubs( - modules: SemanticModule | Iterable[SemanticModule], - *, - available_modules: Iterable[SemanticModule] | None = None, - normalize_fortran_public_names: bool = False, -) -> dict[str, str]: - """Render complete stub text for semantic modules and opaque dependencies. - - Inputs are deep-copied before dependency insertion and policy completion, - so callers retain their original semantic modules. The returned mapping is - keyed by module name and is normally written into a generated contract - package by a pipeline stage. - """ - from prik.semantics.policy_completion import complete_semantic_policies - - source_modules = PyiPrinter._module_list(modules) - emitted_modules: dict[str, SemanticModule] = {} - for module in source_modules: - if module.name in emitted_modules: - raise ValueError(f"Cannot emit duplicate semantic module '{module.name}'") - emitted_modules[module.name] = deepcopy(module) - - for dependency in opaque_dependency_modules( - source_modules, - available_modules=available_modules, - ): - target = emitted_modules.setdefault(dependency.name, SemanticModule(name=dependency.name)) - existing = {cls.name for cls in target.classes} - target.classes.extend(cls for cls in dependency.classes if cls.name not in existing) - - complete_semantic_policies(emitted_modules.values()) - return { - module_name: emit_module( - module, - normalize_fortran_public_names=normalize_fortran_public_names, - ).strip() - for module_name, module in emitted_modules.items() - } - - if __name__ == "__main__": module = SemanticModule( name="printer_demo", diff --git a/prik/semantics/README.md b/prik/semantics/README.md index 525b32dbf..f1c7cf458 100644 --- a/prik/semantics/README.md +++ b/prik/semantics/README.md @@ -1,7 +1,8 @@ # Semantics Package -This package owns the language-neutral contract between native parser facts, -editable `.pyi` files, policy completion, and wrapper code generation. +This package owns the language-neutral contract between native parser facts +and editable `.pyi` files. Post-IR decisions live in `../policy/`; typed wrapper +implementation plans live in `../planning/`. ## Entry Points @@ -15,8 +16,10 @@ editable `.pyi` files, policy completion, and wrapper code generation. | `../pipeline/pyi.py` | Combined `.pyi` text/file/path-set conversion and external-type reconciliation. | | `pyi_metadata.py` | Semantic `.pyi` loader workflow metadata. | | `native_contract.py` | Source-free native ABI and placement validation. | -| `policy_completion.py` | Complete ownership, transfer, destruction, mutability/writeback, projection, nullability, release, storage, Python-barrier, native-barrier, and accessor decisions after full signatures are known. | -| `../codegen/planner.py` | Converts completed semantic policy into the typed wrapper plan consumed by code generation. | +| `native_array_handles.py` | Semantic descriptor marking, normalized data facets, and native-array facts. | +| `ownership_metadata.py` | Raw ownership and pointer-contract metadata keys and normalized semantic setters. | +| `../policy/completion.py` | Complete ownership, transfer, destruction, mutability/writeback, projection, nullability, release, storage, Python-barrier, native-barrier, and accessor decisions after full signatures are known. | +| `../planning/planner.py` | Converts completed semantic policy into the typed wrapper plan consumed by code generation. | ## Declaration Expressions @@ -49,12 +52,12 @@ C parser facts, Fortran parser facts, or parsed .pyi AST -> typed wrapper planning ``` -`../codegen/planner.py` is the boundary where semantic contracts become typed +`../planning/planner.py` is the boundary where semantic contracts become typed wrapper implementation plans. Object kind, ownership, transfer, destruction, mutability/writeback, result projection, nullability, release responsibility, contract/boundary storage modes, Python-barrier action, and native-barrier -action must be completed before this boundary by `policy_completion.py` using -`prik/semantics/ownership.py`. Getter result, native setter assignment, and Python +action must be completed before this boundary by `../policy/completion.py` +using `prik/policy/ownership.py`. Getter result, native setter assignment, and Python setter exposure policies are completed there as well. The Python barrier and native barrier are separate policy decisions. The Python diff --git a/prik/semantics/__init__.py b/prik/semantics/__init__.py index 09a7f7ddb..0925d8a04 100644 --- a/prik/semantics/__init__.py +++ b/prik/semantics/__init__.py @@ -17,7 +17,6 @@ c_type_to_semantic_type, ) from .pyi2ir import convert_pyi_to_ir -from .policy_completion import complete_semantic_policies __all__ = ( "CToIRConverter", @@ -30,7 +29,6 @@ "c_struct_to_semantic_class", "c_type_to_semantic_type", "collect_semantic_compile_time_requirements", - "complete_semantic_policies", "convert_pyi_to_ir", "fortran_file_to_semantic_modules", "fortran_module_to_semantic_module", diff --git a/prik/semantics/fortran2ir.py b/prik/semantics/fortran2ir.py index fc5324864..107a24d2e 100644 --- a/prik/semantics/fortran2ir.py +++ b/prik/semantics/fortran2ir.py @@ -39,7 +39,7 @@ split_dimension_bounds, split_top_level_expression, ) -from prik.semantics.ownership import set_ownership_metadata +from prik.semantics.ownership_metadata import set_ownership_metadata from prik.semantics.metadata import BIND_TARGET_METADATA, PROJECTED_OUTPUT_METADATA, SCALAR_STORAGE_CATEGORY from prik.types.numpy import BOOLEAN_STORAGE_BITS, SEMANTIC_SCALAR_TYPE_NAMES, is_boolean_semantic_type_name from prik.utilities.visitor import ClassVisitor diff --git a/prik/semantics/native_array_handles.py b/prik/semantics/native_array_handles.py index 891117c7d..86986be28 100644 --- a/prik/semantics/native_array_handles.py +++ b/prik/semantics/native_array_handles.py @@ -1,30 +1,18 @@ +"""Semantic facts for native allocatable and pointer array descriptors.""" + from __future__ import annotations -from collections.abc import Iterable, Mapping from copy import deepcopy from dataclasses import dataclass -from typing import Any -from prik.semantics.ownership import OWNERSHIP_POLICY_METADATA, POINTER_POLICY_METADATA from prik.semantics.metadata import ( MAYBE_UNALLOCATED_METADATA, NATIVE_ARRAY_DESCRIPTOR_METADATA, NATIVE_ARRAY_HANDLE_POLICY_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, ) -from prik.semantics.models import ( - PYTHON_VALUE_MUTABILITY_METADATA, - RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA, - ProcedureOverloadSet, - SemanticClass, - SemanticFunction, - SemanticModule, - SemanticType, - SemanticVariable, -) - - -NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER = "ISO_Fortran_binding.h" +from prik.semantics.models import PYTHON_VALUE_MUTABILITY_METADATA, SemanticType +from prik.semantics.ownership_metadata import OWNERSHIP_POLICY_METADATA, POINTER_POLICY_METADATA _HANDLE_ONLY_METADATA = ( @@ -42,162 +30,6 @@ ) -@dataclass(frozen=True) -class NativeArrayHandlePolicy: - """Completed post-IR policy for a native allocatable or pointer array handle.""" - - descriptor_kind: str - handle_kind: str - origin: str - owner: str - owner_retention: str - descriptor_ownership: str - borrowed: bool - getter_behavior: str - python_setter: str - native_setter: str - output_projection: str - result_allocation: str - release: str - target_lifetime: str - destroy_behavior: str - to_numpy: str - descriptor_interop: str - nullable: bool - optional_absent: bool - storage_mode: str - operations: tuple[str, ...] = () - blocker: str | None = None - default_construction: str = "none" - default_descriptor_ownership: str = "unknown" - default_release: str = "none" - default_destroy_behavior: str = "none" - default_operations: tuple[str, ...] = () - - @property - def is_blocked(self) -> bool: - """Return whether this completed policy blocks wrapper generation.""" - return self.handle_kind == "unsupported" or self.blocker is not None - - def allows(self, operation: str) -> bool: - """Return whether a descriptor operation is explicitly permitted.""" - return operation in self.operations - - @property - def requires_pointer_c_descriptor_interop(self) -> bool: - """Return whether this handle path needs TS 29113 C descriptor interop.""" - return self.descriptor_interop == "pointer_c_descriptor" - - @property - def requires_c_descriptor_interop(self) -> bool: - """Return whether generated code needs standard C descriptor support.""" - return self.descriptor_interop in { - "module_allocatable_c_descriptor", - "owned_allocatable_c_descriptor", - "pointer_c_descriptor", - } - - -@dataclass(frozen=True) -class ArrayInteropPolicy: - """Completed selector for the ABI lane used by an array-like boundary.""" - - abi: str - owner: str - descriptor_kind: str | None = None - handle_kind: str | None = None - - @property - def is_data_buffer(self) -> bool: - """Return whether this boundary uses ordinary data-pointer array ABI.""" - return self.abi == "data_buffer" - - @property - def is_descriptor(self) -> bool: - """Return whether this boundary uses native descriptor-handle ABI.""" - return self.abi == "descriptor" - - -@dataclass(frozen=True) -class ArrayInteropPolicyDispatcher: - """Dispatch array-like bridge/binding work from the completed ABI selector.""" - - handlers: Mapping[tuple[str, str], str] - - def handler_name_for_policy(self, policy: ArrayInteropPolicy, context: str, name: str) -> str: - key = (context, policy.abi) - try: - return self.handlers[key] - except KeyError: - raise ValueError(f"No array interop codegen handler for {name!r}: {context}/{policy.abi}") from None - - def dispatch( - self, - target: Any, - subject: Any, - policy: ArrayInteropPolicy, - context: str, - *args: Any, - **kwargs: Any, - ) -> Any: - name = str(getattr(subject, "name", getattr(subject, "python_name", type(subject).__name__))) - handler = getattr(target, self.handler_name_for_policy(policy, context, name)) - return handler(subject, policy, *args, **kwargs) - - -@dataclass(frozen=True) -class NativeArrayHandlePolicyDispatcher: - """Dispatch generated handle work from completed native-array policy.""" - - handlers: Mapping[tuple[str, str], str] - - def handler_name_for_policy(self, policy: NativeArrayHandlePolicy, name: str) -> str: - key = (policy.descriptor_kind, policy.handle_kind) - try: - return self.handlers[key] - except KeyError: - descriptor_kind, handle_kind = key - raise ValueError( - f"No native-array-handle codegen handler for {name!r}: {descriptor_kind}/{handle_kind}" - ) from None - - def dispatch( - self, - target: Any, - subject: Any, - policy: NativeArrayHandlePolicy, - *args: Any, - **kwargs: Any, - ) -> Any: - name = str(getattr(subject, "name", getattr(subject, "python_name", type(subject).__name__))) - handler = getattr(target, self.handler_name_for_policy(policy, name)) - return handler(subject, policy, *args, **kwargs) - - -@dataclass(frozen=True) -class NativeArrayOutputProjectionDispatcher: - """Dispatch handle boundary work from completed output projection.""" - - handlers: Mapping[str, str] - - def dispatch( - self, - target: Any, - subject: Any, - policy: NativeArrayHandlePolicy, - *args: Any, - **kwargs: Any, - ) -> Any: - try: - handler_name = self.handlers[policy.output_projection] - except KeyError: - name = str(getattr(subject, "name", getattr(subject, "python_name", type(subject).__name__))) - raise ValueError( - f"No native-array output-projection handler for {name!r}: {policy.output_projection}" - ) from None - return getattr(target, handler_name)(subject, policy, *args, **kwargs) - - @dataclass(frozen=True) class NativeArrayHandleFacts: """Common semantic facts carried by any native array descriptor handle.""" @@ -211,32 +43,6 @@ class NativeArrayHandleFacts: fortran_character_length: object | None = None -@dataclass(frozen=True) -class NativeArrayBuildRequirement: - """One build requirement selected by a completed native-array handle policy.""" - - owner: str - item: str - descriptor_kind: str - handle_kind: str - descriptor_interop: str - headers: tuple[str, ...] - - -@dataclass(frozen=True) -class NativeArrayBuildRequirements: - """Build requirements selected by all completed native-array handle policies.""" - - pointer_c_descriptor_interop: bool - headers: tuple[str, ...] - items: tuple[NativeArrayBuildRequirement, ...] - - @property - def requires_iso_fortran_binding(self) -> bool: - """Return whether generated wrapper C code needs ISO_Fortran_binding.h.""" - return NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER in self.headers - - def native_array_descriptor_kind(semantic_type: SemanticType | None) -> str | None: """Return the native descriptor kind for an array handle type.""" if semantic_type is None: @@ -313,130 +119,11 @@ def native_array_handle_facts(semantic_type: SemanticType) -> NativeArrayHandleF ) -def array_interop_policy( - semantic_type: SemanticType | None, - *, - owner: str, - native_array_handle_policy: NativeArrayHandlePolicy | None = None, -) -> ArrayInteropPolicy | None: - """Return the completed ABI selector for an array-like boundary.""" - if native_array_handle_policy is not None: - return ArrayInteropPolicy( - abi="descriptor", - owner=owner, - descriptor_kind=native_array_handle_policy.descriptor_kind, - handle_kind=native_array_handle_policy.handle_kind, - ) - if semantic_type is None: - return None - storage = semantic_type.storage - if semantic_type.rank > 0 and storage is not None and storage.array is not None: - return ArrayInteropPolicy(abi="data_buffer", owner=owner) - return None - - -def native_array_handle_build_requirements( - semantic_ir: SemanticModule | Iterable[SemanticModule], -) -> NativeArrayBuildRequirements: - """Return build requirements selected by completed native-array handle policies.""" - modules = [semantic_ir] if isinstance(semantic_ir, SemanticModule) else list(semantic_ir) - requirements = tuple( - _c_descriptor_requirement(owner, item, policy) - for owner, item, policy in _iter_native_array_handle_policies(modules) - if policy.requires_c_descriptor_interop - ) - headers = (NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER,) if requirements else () - return NativeArrayBuildRequirements( - pointer_c_descriptor_interop=any( - requirement.descriptor_interop == "pointer_c_descriptor" for requirement in requirements - ), - headers=headers, - items=requirements, - ) - - -def _c_descriptor_requirement( - owner: str, - item: str, - policy: NativeArrayHandlePolicy, -) -> NativeArrayBuildRequirement: - return NativeArrayBuildRequirement( - owner=owner, - item=item, - descriptor_kind=policy.descriptor_kind, - handle_kind=policy.handle_kind, - descriptor_interop=policy.descriptor_interop, - headers=(NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER,), - ) - - -def _iter_native_array_handle_policies(modules: Iterable[SemanticModule]): - for module in modules: - for variable in module.variables: - yield from _variable_native_array_policy(variable, owner=f"{module.name}.{variable.name}") - for semantic_class in module.classes: - yield from _iter_class_native_array_policies(semantic_class, owner=f"{module.name}.{semantic_class.name}") - for function in module.functions: - yield from _iter_function_native_array_policies(function, owner=f"{module.name}.{function.name}") - for overload_set in module.overload_sets: - yield from _iter_overload_native_array_policies(overload_set, owner=f"{module.name}.{overload_set.name}") - - -def _iter_class_native_array_policies(semantic_class: SemanticClass, *, owner: str): - for field in semantic_class.fields: - yield from _variable_native_array_policy(field, owner=f"{owner}.{field.name}") - for nested in semantic_class.classes: - yield from _iter_class_native_array_policies(nested, owner=f"{owner}.{nested.name}") - for method in semantic_class.methods: - yield from _iter_function_native_array_policies(method, owner=f"{owner}.{method.name}") - for overload_set in semantic_class.overload_sets: - yield from _iter_overload_native_array_policies(overload_set, owner=f"{owner}.{overload_set.name}") - - -def _iter_overload_native_array_policies(overload_set: ProcedureOverloadSet, *, owner: str): - for procedure in overload_set.procedures: - yield from _iter_function_native_array_policies(procedure, owner=owner) - - -def _iter_function_native_array_policies(function: SemanticFunction, *, owner: str): - for argument in function.arguments: - yield from _variable_native_array_policy(argument, owner=f"{owner}.{argument.name}") - if native_array_descriptor_kind(function.return_type) is not None: - policy = function.metadata.get(RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA) - if policy is None: - raise ValueError( - f"Native array handle {owner}.return is missing completed policy; " - "run complete_semantic_policies before collecting build requirements" - ) - yield f"{owner}.return", "return", policy - - -def _variable_native_array_policy(variable: SemanticVariable, *, owner: str): - if native_array_descriptor_kind(variable.semantic_type) is None: - return - policy = variable.metadata.get(RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA) - if policy is None: - raise ValueError( - f"Native array handle {owner} is missing completed policy; " - "run complete_semantic_policies before collecting build requirements" - ) - yield owner, variable.name, policy - - __all__ = ( - "NATIVE_ARRAY_POINTER_C_DESCRIPTOR_HEADER", - "ArrayInteropPolicy", - "ArrayInteropPolicyDispatcher", - "NativeArrayBuildRequirement", - "NativeArrayBuildRequirements", "NativeArrayHandleFacts", - "NativeArrayHandlePolicy", - "NativeArrayHandlePolicyDispatcher", - "array_interop_policy", "is_native_array_handle", "mark_native_array_handle", "native_array_data_type", "native_array_descriptor_kind", - "native_array_handle_build_requirements", "native_array_handle_facts", ) diff --git a/prik/semantics/ownership_metadata.py b/prik/semantics/ownership_metadata.py new file mode 100644 index 000000000..dfbd83764 --- /dev/null +++ b/prik/semantics/ownership_metadata.py @@ -0,0 +1,106 @@ +"""Raw ownership and pointer-policy metadata stored on semantic contracts. + +This module owns only contract keys and normalized metadata setters used +during semantic IR construction. Completed ownership vocabulary, resolution, +and lowering actions belong to :mod:`prik.policy.ownership`. +""" + +from __future__ import annotations + +from enum import Enum +from typing import Any + + +OWNERSHIP_POLICY_METADATA = "ownership_policy" +POINTER_POLICY_METADATA = "pointer_policy" +POINTER_POLICY_FIELDS = ( + "nullable", + "transfer", + "target_owner", + "lifetime", + "deallocation", + "shape_source", + "contiguity", + "reassociation", + "aliasing", + "mutability", +) + + +class _OwnershipOwner(str, Enum): + PYTHON = "python" + CALLER = "caller" + NATIVE = "native" + WRAPPER = "wrapper" + TEMPORARY = "temporary" + UNKNOWN = "unknown" + + +class _TransferMode(str, Enum): + BY_VALUE = "by_value" + IN_PLACE = "in_place" + COPY_RETURN = "copy_return" + SNAPSHOT_COPY = "snapshot_copy" + BORROWED_VIEW = "borrowed_view" + CALL_LOCAL = "call_local" + WRAPPER_INSTANCE = "wrapper_instance" + BLOCKED = "blocked" + + +class _DestructionPolicy(str, Enum): + PYTHON_REFCOUNT = "python_refcount" + CALLER = "caller" + WRAPPER_DEALLOC = "wrapper_dealloc" + NATIVE_OWNER = "native_owner" + CALL_LOCAL = "call_local" + NONE = "none" + BLOCKED = "blocked" + + +def set_ownership_metadata( + metadata: dict[str, Any], + *, + owner: str | None = None, + transfer: str | None = None, + destruction: str | None = None, +) -> None: + """Store normalized owner, transfer, and destruction contract metadata.""" + policy = metadata.setdefault(OWNERSHIP_POLICY_METADATA, {}) + if not isinstance(policy, dict): + raise ValueError(f"{OWNERSHIP_POLICY_METADATA!r} metadata must be a dictionary") + if owner is not None: + policy["owner"] = _OwnershipOwner(owner).value + if transfer is not None: + policy["transfer"] = _TransferMode(transfer).value + if destruction is not None: + policy["destruction"] = _DestructionPolicy(destruction).value + + +def set_pointer_policy_metadata(metadata: dict[str, Any], **policy_values: Any) -> None: + """Store a complete semantic pointer policy after validating its shape.""" + missing = [name for name in POINTER_POLICY_FIELDS if name not in policy_values] + extra = [name for name in policy_values if name not in POINTER_POLICY_FIELDS] + if missing or extra: + details = [] + if missing: + details.append(f"missing: {', '.join(missing)}") + if extra: + details.append(f"unexpected: {', '.join(extra)}") + raise ValueError(f"PointerPolicy requires exactly {', '.join(POINTER_POLICY_FIELDS)} ({'; '.join(details)})") + if not isinstance(policy_values["nullable"], bool): + raise ValueError("PointerPolicy nullable must be a boolean") + for name in POINTER_POLICY_FIELDS[1:]: + if not isinstance(policy_values[name], str) or not policy_values[name]: + raise ValueError(f"PointerPolicy {name} must be a non-empty string") + _TransferMode(policy_values["transfer"]) + metadata[POINTER_POLICY_METADATA] = dict(policy_values) + metadata["fortran_pointer"] = True + + +__all__ = ( + "OWNERSHIP_POLICY_METADATA", + "POINTER_POLICY_FIELDS", + "POINTER_POLICY_METADATA", + "set_ownership_metadata", + "set_pointer_policy_metadata", +) diff --git a/prik/semantics/pyi2ir.py b/prik/semantics/pyi2ir.py index 54f041305..95d98ca45 100644 --- a/prik/semantics/pyi2ir.py +++ b/prik/semantics/pyi2ir.py @@ -21,7 +21,11 @@ is_public_declaration_expression, ) from prik.types.numpy import SEMANTIC_SCALAR_TYPE_NAMES -from prik.semantics.ownership import OWNERSHIP_POLICY_METADATA, set_ownership_metadata, set_pointer_policy_metadata +from prik.semantics.ownership_metadata import ( + OWNERSHIP_POLICY_METADATA, + set_ownership_metadata, + set_pointer_policy_metadata, +) from prik.semantics.metadata import ( ADDRESS_ROLE_METADATA, ADDRESS_ROLE_PROJECTION, diff --git a/tests/README.md b/tests/README.md index e9f036fc8..4676cdfc1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -54,9 +54,10 @@ Within one Fortran feature, use only the stages that own real evidence: | `preprocessing/` | Source processing, dependencies, and mappings are correct | | `semantics/` | Parser or `.pyi` facts become the intended semantic IR | | `policy/` | Ownership, lifetime, projection, mutation, nullability, storage, and accessor decisions are complete | -| `codegen/` | Completed policy selects a typed plan and named bridge/binding mechanisms | +| `codegen/` | Completed policy selects a typed plan and named bridge/binding node mechanisms | +| `printers/` | C, Fortran, and semantic `.pyi` representations serialize to exact text | | `compiling/` | Commands, objects, libraries, and link inputs are correct | -| `pipeline/` | Build stages and generated artifacts transition correctly | +| `pipeline/` | Wrapper orchestration, build stages, and generated results transition correctly | | `runtime/` | Runtime support mechanisms behave correctly without owning a complete feature journey | | `end_to_end/` | Source or intentional `.pyi` input produces an imported extension whose public behavior is called and verified | @@ -157,9 +158,11 @@ production package and module: tests/fortran/infrastructure//test_.py ``` -Thus `prik/semantics/ownership.py` uses +Thus `prik/policy/ownership.py` uses `infrastructure/semantics/test_ownership.py`, while -`prik/codegen/planner.py` uses `infrastructure/codegen/test_planner.py`. +`prik/planning/planner.py` uses `infrastructure/codegen/test_planner.py`; +language source printers use `infrastructure/printers/` and the wrapper +orchestrator uses `infrastructure/pipeline/test_wrapper_generator.py`. User-visible behavior does not move to infrastructure merely because it reaches those modules. Retained production `if __name__ == "__main__"` demonstrations are smoke-tested by the same dedicated module owner. diff --git a/tests/c/_support/fixture_outputs.py b/tests/c/_support/fixture_outputs.py index ac4c5b062..a4e4823bf 100644 --- a/tests/c/_support/fixture_outputs.py +++ b/tests/c/_support/fixture_outputs.py @@ -10,7 +10,7 @@ from prik.parsers.c.cli import attach_preprocessing_recipe from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source from prik.semantics.c2ir import c_project_to_semantic_module -from prik.codegen.printers import emit_module +from prik.printers import emit_module C_ROOT = Path(__file__).resolve().parents[1] diff --git a/tests/c/pipeline/test_c_pyi_contract_fixtures.py b/tests/c/pipeline/test_c_pyi_contract_fixtures.py index 7f89b4654..7e90584a3 100644 --- a/tests/c/pipeline/test_c_pyi_contract_fixtures.py +++ b/tests/c/pipeline/test_c_pyi_contract_fixtures.py @@ -11,7 +11,7 @@ iter_general_c_fixture_projects, ) from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text -from prik.codegen.printers import emit_module +from prik.printers import emit_module C_FIXTURE_PROJECTS = iter_general_c_fixture_projects() diff --git a/tests/c/semantics/conversion/test_projects_and_diagnostics.py b/tests/c/semantics/conversion/test_projects_and_diagnostics.py index a9edcd190..27ce02186 100644 --- a/tests/c/semantics/conversion/test_projects_and_diagnostics.py +++ b/tests/c/semantics/conversion/test_projects_and_diagnostics.py @@ -7,7 +7,7 @@ import pytest -from prik.codegen.printers import emit_module_stubs +from prik.pipeline.pyi import emit_module_stubs from prik.parsers.c import parse_c_file, parse_c_project from prik.parsers.c.models import ( CEnum, diff --git a/tests/c/semantics/conversion/test_records_and_enums.py b/tests/c/semantics/conversion/test_records_and_enums.py index 058efe8df..4cf8e216b 100644 --- a/tests/c/semantics/conversion/test_records_and_enums.py +++ b/tests/c/semantics/conversion/test_records_and_enums.py @@ -2,7 +2,8 @@ from dataclasses import asdict -from prik.codegen.printers import emit_module, emit_module_stubs +from prik.pipeline.pyi import emit_module_stubs +from prik.printers import emit_module from prik.parsers.c import parse_c_file, parse_c_project from prik.parsers.c.models import ( CArray, diff --git a/tests/docs/_structure_support.py b/tests/docs/_structure_support.py index dce3c5cd7..898540c26 100644 --- a/tests/docs/_structure_support.py +++ b/tests/docs/_structure_support.py @@ -259,6 +259,10 @@ "prik/parsers/fortran/README.md", "prik/parsers/pyi/README.md", "prik/semantics/README.md", + "prik/policy/README.md", + "prik/planning/README.md", + "prik/printers/README.md", + "prik/pipeline/README.md", "prik/compiling/README.md", ] SOURCE_NAVIGATION_HOTSPOTS = [ @@ -268,7 +272,9 @@ "prik/pipeline/preprocessing.py", "prik/probes/c_types.py", "prik/probes/fortran_types.py", - "prik/semantics/ownership.py", + "prik/semantics/ownership_metadata.py", + "prik/semantics/native_array_handles.py", + "prik/policy/ownership.py", "prik/parsers/c/parser.py", "prik/parsers/c/cli.py", "prik/parsers/fortran/parser.py", @@ -279,14 +285,21 @@ "prik/semantics/c2ir.py", "prik/semantics/pyi2ir.py", "prik/pipeline/pyi.py", - "prik/semantics/policy_completion.py", - "prik/codegen/plan.py", - "prik/codegen/planner.py", - "prik/codegen/generator.py", + "prik/policy/models.py", + "prik/policy/construction.py", + "prik/policy/exports.py", + "prik/policy/native_array_handles.py", + "prik/policy/completion.py", + "prik/planning/models.py", + "prik/planning/planner.py", + "prik/naming/native_symbols.py", + "prik/codegen/docstrings.py", + "prik/pipeline/wrapper.py", "prik/codegen/c/binding.py", "prik/codegen/fortran/bridge.py", - "prik/codegen/printers/pyi_printer.py", - "prik/codegen/printers/source_printers.py", + "prik/printers/pyi.py", + "prik/printers/c.py", + "prik/printers/fortran.py", "prik/compiling/objects.py", "prik/compiling/compilers.py", "prik/compiling/native_support.py", diff --git a/tests/fortran/README.md b/tests/fortran/README.md index 08fc412b1..d0d6b7537 100644 --- a/tests/fortran/README.md +++ b/tests/fortran/README.md @@ -72,7 +72,7 @@ evidence, not the ownership rule. | `infrastructure/semantics/` | Internal semantic ownership, policy completion, and completed wrapper-policy mechanics, with one test module per production module | | `infrastructure/codegen/` | Internal plan, planner, generator, binding, bridge, printer, docstring, check, and visitor mechanics, with one test module per production module | | `infrastructure/naming/` | Internal generated-name and public-name policy owned by `prik/naming/` | -| `infrastructure/pipeline/` | Internal wrapper-artifact transport owned by `prik/pipeline/` | +| `infrastructure/pipeline/` | Generated-wrapper orchestration and transport owned by `prik/pipeline/` | | `infrastructure/types/` | Internal NumPy type mapping and target mapping-report mechanics | | `infrastructure/utilities/` | Internal string and class-visitor helpers owned by `prik/utilities/` | diff --git a/tests/fortran/_support/ownership_policy.py b/tests/fortran/_support/ownership_policy.py index 1685bf361..6d9cb300f 100644 --- a/tests/fortran/_support/ownership_policy.py +++ b/tests/fortran/_support/ownership_policy.py @@ -8,7 +8,7 @@ ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( OwnershipContext, ) @@ -18,7 +18,7 @@ SemanticType, ) -from prik.semantics.native_array_handles import ( +from prik.policy.native_array_handles import ( NativeArrayHandlePolicy, ) diff --git a/tests/fortran/_support/printer_models.py b/tests/fortran/_support/printer_models.py index a86917908..264c2e176 100644 --- a/tests/fortran/_support/printer_models.py +++ b/tests/fortran/_support/printer_models.py @@ -12,16 +12,17 @@ from prik.pipeline.pyi import pyi_text_to_semantic_module as _parse_pyi_text -from prik.codegen.printers import ( +from prik.printers import ( emit_module, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner from prik.semantics.models import ( SemanticModule, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies OPERATOR_F90_SOURCE = ( Path(__file__).parents[1] / "generic_interfaces" / "end_to_end" / "fixtures" / "foperators_f90.f90" @@ -44,10 +45,10 @@ def generate_pyi(source: str) -> str: return emit_module(smod) -def generate_wrapper_artifacts(module: SemanticModule): - """Generate wrapper sources through the canonical plan implementation.""" +def generate_wrapper(module: SemanticModule): + """Generate one rendered wrapper through the canonical pipeline.""" complete_semantic_policies(module) - return WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + return WrapperGenerator().generate(WrapperPlanner().build(module)) def rendered_source(artifacts, suffix: str) -> str: diff --git a/tests/fortran/_support/wrapper_build.py b/tests/fortran/_support/wrapper_build.py index 547e3cbf3..c2bc120bc 100644 --- a/tests/fortran/_support/wrapper_build.py +++ b/tests/fortran/_support/wrapper_build.py @@ -25,7 +25,7 @@ from prik.pipeline.build import ( NativeBuildPlan, _apply_source_python_exports, - _build_rendered_wrapper_extension, + _build_generated_wrapper_extension, _fortran_source_for_pipeline, _merge_wrapper_modules, _new_compiler, @@ -34,8 +34,9 @@ from prik.pipeline.build import build_fortran_extension from prik.runtime.handles import AllocatableArray from prik.semantics.fortran2ir import fortran_project_to_semantic_modules -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner REPO_ROOT = Path(__file__).resolve().parents[3] WRAPPER_TEST_ROOT = Path(__file__).resolve().parent @@ -349,13 +350,13 @@ def _build_source_wrapper_plan_and_import( complete_semantic_policies(module) plan = WrapperPlanner().build(module) - rendered = WrapperCodeGenerator().generate(plan) + rendered = WrapperGenerator().generate(plan) native_build_plan = NativeBuildPlan( produced_objects=(native_object,), module_dirs=(native_object.parent,), include_dirs=(native_object.parent,), ) - result = _build_rendered_wrapper_extension( + result = _build_generated_wrapper_extension( rendered, output_dir=workdir / "wrapper_plan_build", sources=(source,), diff --git a/tests/fortran/allocatables/codegen/test_allocatable_lowering.py b/tests/fortran/allocatables/codegen/test_allocatable_lowering.py index f4b2e6eba..883b0ad5e 100644 --- a/tests/fortran/allocatables/codegen/test_allocatable_lowering.py +++ b/tests/fortran/allocatables/codegen/test_allocatable_lowering.py @@ -3,8 +3,9 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _allocatable_plan(): @@ -38,7 +39,7 @@ def _module_allocatable_plan(): def test_plain_module_allocatable_uses_standard_descriptor_callback_without_copy(): - artifacts = WrapperCodeGenerator().generate(_module_allocatable_plan()) + artifacts = WrapperGenerator().generate(_module_allocatable_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -56,7 +57,7 @@ def test_plain_module_allocatable_uses_standard_descriptor_callback_without_copy def test_allocated_direct_result_assigns_then_moves_into_owned_descriptor(): bridge_source = next( source.text - for source in WrapperCodeGenerator().generate(_allocatable_plan()).sources + for source in WrapperGenerator().generate(_allocatable_plan()).sources if source.path.suffix == ".f90" ) start = bridge_source.index("subroutine bind_c_make(") @@ -84,7 +85,7 @@ def test_allocated_direct_result_assigns_then_moves_into_owned_descriptor(): def test_maybe_unallocated_direct_result_uses_collector_without_assignment(): bridge_source = next( source.text - for source in WrapperCodeGenerator().generate(_allocatable_plan()).sources + for source in WrapperGenerator().generate(_allocatable_plan()).sources if source.path.suffix == ".f90" ) start = bridge_source.index("subroutine bind_c_maybe_make(") diff --git a/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py b/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py index 16ca692b3..0c559061a 100644 --- a/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py +++ b/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py @@ -1,6 +1,6 @@ """Generated contract surface for allocatable outputs and results.""" -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik import parse_fortran_file as parse_fortran_source diff --git a/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py b/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py index d3b2bf369..3ab6ab7b9 100644 --- a/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py +++ b/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py @@ -1,6 +1,6 @@ """Generated and reparsed allocatable module and field declarations.""" -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik import parse_fortran_file as parse_fortran_source from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text diff --git a/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py b/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py index c74098806..bd7133586 100644 --- a/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py +++ b/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py @@ -1,6 +1,6 @@ """Generated contract surface for optional allocatable outputs.""" -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik import parse_fortran_file as parse_fortran_source diff --git a/tests/fortran/allocatables/policy/test_allocatable_handle_policy.py b/tests/fortran/allocatables/policy/test_allocatable_handle_policy.py index 05f9b78a6..4184c58c1 100644 --- a/tests/fortran/allocatables/policy/test_allocatable_handle_policy.py +++ b/tests/fortran/allocatables/policy/test_allocatable_handle_policy.py @@ -4,11 +4,11 @@ RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA, RESOLVED_OWNERSHIP_POLICY_METADATA, ) -from prik.semantics.native_array_handles import ( +from prik.policy.native_array_handles import ( NativeArrayBuildRequirement, native_array_handle_build_requirements, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -18,7 +18,7 @@ TransferMode, default_ownership_policy, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.ownership_policy import ( _array_type, parse_pyi_text, diff --git a/tests/fortran/allocatables/policy/test_allocatable_result_policy.py b/tests/fortran/allocatables/policy/test_allocatable_result_policy.py index 3a35e53a1..72b381740 100644 --- a/tests/fortran/allocatables/policy/test_allocatable_result_policy.py +++ b/tests/fortran/allocatables/policy/test_allocatable_result_policy.py @@ -11,11 +11,11 @@ RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, RESOLVED_OWNERSHIP_POLICY_METADATA, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( NativeBarrierAction, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( FunctionWrapperPolicy, NativeArrayDescriptorKind, NativeDescriptorHandoffABI, diff --git a/tests/fortran/arrays/codegen/test_array_buffer_lowering.py b/tests/fortran/arrays/codegen/test_array_buffer_lowering.py index 1851bf710..e7461fd8f 100644 --- a/tests/fortran/arrays/codegen/test_array_buffer_lowering.py +++ b/tests/fortran/arrays/codegen/test_array_buffer_lowering.py @@ -5,7 +5,7 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -15,10 +15,11 @@ StorageMode, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ArgumentHandoffMode, BridgeDataAction -from prik.codegen import ArrayHandoffPlan, WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import DatatypeFamily +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ArgumentHandoffMode, BridgeDataAction +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import ArrayHandoffPlan, WrapperPlanner +from prik.planning.models import DatatypeFamily def _array_module(): @@ -68,7 +69,7 @@ def test_required_array_buffer_has_one_printable_editable_handoff_plan(): def test_required_array_buffer_dispatches_through_named_binding_and_bridge_methods(): - artifacts = WrapperCodeGenerator().generate(_array_plan()) + artifacts = WrapperGenerator().generate(_array_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -117,4 +118,4 @@ def test_array_handoff_plan_edits_fail_before_backend_lowering(edit: str, diagno argument.bridge.data_action = BridgeDataAction.DIRECT_TRANSFER with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/arrays/codegen/test_array_output_identity.py b/tests/fortran/arrays/codegen/test_array_output_identity.py index b5fe5fc04..9080447a5 100644 --- a/tests/fortran/arrays/codegen/test_array_output_identity.py +++ b/tests/fortran/arrays/codegen/test_array_output_identity.py @@ -5,11 +5,12 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, ObjectKind, OwnershipOwner, TransferMode -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ArrayWritebackABI -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import WritebackPhase +from prik.policy.ownership import CodegenAction, ObjectKind, OwnershipOwner, TransferMode +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ArrayWritebackABI +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.planning.models import WritebackPhase def _output_plan(): @@ -73,7 +74,7 @@ def test_projected_array_identity_uses_one_completed_in_place_copy_out_action(): def test_projected_array_lowering_increfs_original_objects_and_reuses_tuple_aggregation(): - artifacts = WrapperCodeGenerator().generate(_output_plan()) + artifacts = WrapperGenerator().generate(_output_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") assert "PyObject * result_obj = bound_values_obj;" in c_source @@ -93,7 +94,7 @@ def test_mutable_bool_array_writeback_normalizes_the_aliased_numpy_buffer_in_pla assert values.array_writeback_abi is ArrayWritebackABI.LOGICAL_LOW_BIT_INT8 assert out.array_writeback_abi is ArrayWritebackABI.LOGICAL_LOW_BIT_INT8 - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") assert "integer(c_int8_t), pointer, dimension(:) :: out_logical_bytes" in bridge_source @@ -103,7 +104,7 @@ def test_mutable_bool_array_writeback_normalizes_the_aliased_numpy_buffer_in_pla def test_high_rank_bool_array_writeback_wraps_the_flattened_shape_product(): - artifacts = WrapperCodeGenerator().generate(_high_rank_logical_output_plan()) + artifacts = WrapperGenerator().generate(_high_rank_logical_output_plan()) bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") assert "values_extent_0 * &" in bridge_source @@ -116,4 +117,4 @@ def test_generator_rejects_a_non_normalized_mutable_bool_array_writeback_abi(): plan.namespaces[0].functions[0].arguments[-1].array_writeback_abi = ArrayWritebackABI.NATIVE_ARRAY with pytest.raises(ValueError, match="invalid-array-writeback-abi"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/arrays/codegen/test_array_result_lowering.py b/tests/fortran/arrays/codegen/test_array_result_lowering.py index e407848d5..e94e112ee 100644 --- a/tests/fortran/arrays/codegen/test_array_result_lowering.py +++ b/tests/fortran/arrays/codegen/test_array_result_lowering.py @@ -5,10 +5,11 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, NativeBarrierAction, ObjectKind, OwnershipOwner, TransferMode -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import BridgeDataAction, ORDINARY_ARRAY_RESULT_COPY_REASON -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.ownership import CodegenAction, NativeBarrierAction, ObjectKind, OwnershipOwner, TransferMode +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import BridgeDataAction, ORDINARY_ARRAY_RESULT_COPY_REASON +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _result_plan(): @@ -67,7 +68,7 @@ def test_array_results_record_producer_shape_copy_ownership_and_shared_hidden_sl def test_array_result_lowering_transfers_bridge_copy_to_capsule_owned_numpy_storage(): - artifacts = WrapperCodeGenerator().generate(_result_plan()) + artifacts = WrapperGenerator().generate(_result_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -106,7 +107,7 @@ def test_array_property_results_reuse_input_array_extent_roles_in_both_backends( assert flattened.results[0].array.shape == ("__prik_extent_values_0 * __prik_extent_values_1",) assert columns.results[0].array.shape == ("__prik_extent_values_1",) - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -144,4 +145,4 @@ def test_array_result_plan_edits_fail_before_backend_lowering(edit: str, diagnos hidden.native_call_slot.array = direct.array with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/arrays/codegen/test_dense_array_shape_lowering.py b/tests/fortran/arrays/codegen/test_dense_array_shape_lowering.py index a5e563700..9724259e0 100644 --- a/tests/fortran/arrays/codegen/test_dense_array_shape_lowering.py +++ b/tests/fortran/arrays/codegen/test_dense_array_shape_lowering.py @@ -5,13 +5,14 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( TransformationAction, TransformationLayer, WritebackPhase, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _dense_plan(): @@ -152,7 +153,7 @@ def test_dense_array_plan_records_extent_dependencies_flat_storage_and_order(): def test_dense_array_lowering_uses_planned_shape_checks_and_bridge_orientation(): - artifacts = WrapperCodeGenerator().generate(_dense_plan()) + artifacts = WrapperGenerator().generate(_dense_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -183,7 +184,7 @@ def test_dense_array_lowering_uses_planned_shape_checks_and_bridge_orientation() def test_external_interface_declares_late_extent_before_dependent_array(): plan = _late_extent_external_plan() function = plan.namespaces[0].functions[0] - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -207,7 +208,7 @@ def test_edited_binding_conversion_order_cannot_read_an_extent_late(): function.binding.argument_conversion_order = tuple(reversed(function.binding.argument_conversion_order)) with pytest.raises(ValueError, match="late-binding-extent-conversion"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_unavailable_dense_extent_role_fails_before_backend_lowering(): @@ -217,7 +218,7 @@ def test_unavailable_dense_extent_role_fails_before_backend_lowering(): array.extent_reference_roles = (("edited.missing:value",), array.extent_reference_roles[1]) with pytest.raises(ValueError, match="unavailable-array-extent-reference"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_copy_f_is_one_binding_owned_transformation_lifecycle(): @@ -240,7 +241,7 @@ def test_copy_f_is_one_binding_owned_transformation_lifecycle(): def test_copy_f_lowering_keeps_numpy_copy_in_and_copy_out_out_of_the_bridge(): - artifacts = WrapperCodeGenerator().generate(_copy_f_plan()) + artifacts = WrapperGenerator().generate(_copy_f_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -277,7 +278,7 @@ def test_copy_f_native_input_and_projected_identity_share_the_same_lifecycle_alg ) assert projected.projects_result is True - artifacts = WrapperCodeGenerator().generate(_copy_f_lifecycle_plan()) + artifacts = WrapperGenerator().generate(_copy_f_lifecycle_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -296,4 +297,4 @@ def test_copy_f_layer_edit_fails_central_validation(): argument.transformations[0].layer = TransformationLayer.BRIDGE with pytest.raises(ValueError, match="invalid-transformation-layer"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/arrays/codegen/test_specialized_array_roles.py b/tests/fortran/arrays/codegen/test_specialized_array_roles.py index a2d107e3f..9db98db77 100644 --- a/tests/fortran/arrays/codegen/test_specialized_array_roles.py +++ b/tests/fortran/arrays/codegen/test_specialized_array_roles.py @@ -4,9 +4,11 @@ from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import OptionalMode -from prik.codegen import CBindingGenerator, WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import OptionalMode +from prik.codegen import CBindingGenerator +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _later_array_plan(): @@ -58,7 +60,7 @@ def test_optional_assumed_rank_and_character_arrays_have_explicit_distinct_roles def test_optional_assumed_rank_and_character_lowering_follow_named_plan_fields(): - artifacts = WrapperCodeGenerator().generate(_later_array_plan()) + artifacts = WrapperGenerator().generate(_later_array_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") diff --git a/tests/fortran/arrays/codegen/test_strided_array_lowering.py b/tests/fortran/arrays/codegen/test_strided_array_lowering.py index 0a2c763e3..ef8e846cc 100644 --- a/tests/fortran/arrays/codegen/test_strided_array_lowering.py +++ b/tests/fortran/arrays/codegen/test_strided_array_lowering.py @@ -5,8 +5,9 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _strided_plan(rank: int = 2): @@ -43,7 +44,7 @@ def test_strided_array_plan_names_bounds_and_element_strides_explicitly(): def test_strided_array_lowering_validates_and_passes_one_explicit_bridge_slice(): - artifacts = WrapperCodeGenerator().generate(_strided_plan()) + artifacts = WrapperGenerator().generate(_strided_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -73,7 +74,7 @@ def test_strided_array_lowering_validates_and_passes_one_explicit_bridge_slice() def test_rank3_strided_array_pointer_sections_respect_free_form_line_limit(): - artifacts = WrapperCodeGenerator().generate(_strided_plan(rank=3)) + artifacts = WrapperGenerator().generate(_strided_plan(rank=3)) bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") assert "values => values_base(&" in bridge_source @@ -88,7 +89,7 @@ def test_strided_role_edit_fails_before_backend_lowering(): array.stride_roles = array.stride_roles[:1] with pytest.raises(ValueError, match="invalid-array-stride-roles"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_strided_dense_actual_role_edit_fails_before_backend_lowering(): @@ -98,4 +99,4 @@ def test_strided_dense_actual_role_edit_fails_before_backend_lowering(): array.dense_actual_role = None with pytest.raises(ValueError, match="invalid-array-dense-actual-role"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/arrays/policy/test_array_shape_policy.py b/tests/fortran/arrays/policy/test_array_shape_policy.py index 000142074..853c25105 100644 --- a/tests/fortran/arrays/policy/test_array_shape_policy.py +++ b/tests/fortran/arrays/policy/test_array_shape_policy.py @@ -10,8 +10,8 @@ RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, RESOLVED_MODULE_VARIABLE_POLICY_METADATA, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import DeclarationCallableAction +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import DeclarationCallableAction from prik import parse_fortran_file as parse_fortran_source diff --git a/tests/fortran/arrays/semantics/test_array_semantics.py b/tests/fortran/arrays/semantics/test_array_semantics.py index ca54a9d20..431490c57 100644 --- a/tests/fortran/arrays/semantics/test_array_semantics.py +++ b/tests/fortran/arrays/semantics/test_array_semantics.py @@ -9,7 +9,7 @@ get_function, ) from prik.semantics.models import SemanticExpressionCallable -from prik.codegen.printers import PyiPrinter +from prik.printers import PyiPrinter from prik import parse_fortran_file as parse_fortran_source from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text diff --git a/tests/fortran/building_shared_library/pipeline/test_rendered_wrapper_artifact_build.py b/tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py similarity index 82% rename from tests/fortran/building_shared_library/pipeline/test_rendered_wrapper_artifact_build.py rename to tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py index 4ee9eb402..72b5e0510 100644 --- a/tests/fortran/building_shared_library/pipeline/test_rendered_wrapper_artifact_build.py +++ b/tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py @@ -1,4 +1,4 @@ -"""Tests for building already-rendered wrapper-plan artifacts.""" +"""Tests for building the complete result returned by wrapper generation.""" from __future__ import annotations @@ -11,21 +11,17 @@ from prik.pipeline.build import ( NativeBuildPlan, NativeLinkItem, - _build_rendered_wrapper_extension, - _generated_wrapper_plan_artifacts, + _build_generated_wrapper_extension, + _generate_wrapper, ) -from prik.pipeline.wrapper_artifacts import ( - GeneratedSourceFile, - GeneratedWrapperArtifacts, - RenderedGeneratedWrapperArtifacts, +from prik.pipeline.wrapper import ( + GeneratedSource, + GeneratedWrapper, + WrapperGenerator, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from prik.stage_values import FrozenStageRecordError -from prik.codegen import ( - ModulePlan, - WrapperCodeGenerator, - WrapperPlanner, -) +from prik.planning import ModulePlan, WrapperPlanner class RecordingCompiler: @@ -82,8 +78,8 @@ def _module_plan(source: str, *, module_name: str) -> ModulePlan: return WrapperPlanner().build(module) -def _rendered_artifacts(source: str, *, module_name: str) -> RenderedGeneratedWrapperArtifacts: - return WrapperCodeGenerator().generate(_module_plan(source, module_name=module_name)) +def _generated_wrapper(source: str, *, module_name: str) -> GeneratedWrapper: + return WrapperGenerator().generate(_module_plan(source, module_name=module_name)) def test_real_wrapper_build_path_raises_the_completed_policy_error_directly(): @@ -96,11 +92,11 @@ def test_real_wrapper_build_path_raises_the_completed_policy_error_directly(): ValueError, match=r"Semantic variable 'labels\.label'.*module variable initializer requires a write-through native setter", ): - _generated_wrapper_plan_artifacts(module, strict_wrapper_names=False) + _generate_wrapper(module, strict_wrapper_names=False) -def test_build_rendered_wrapper_extension_writes_compiles_runtime_and_links(tmp_path: Path, capsys): - rendered = _rendered_artifacts( +def test_build_generated_wrapper_extension_writes_compiles_runtime_and_links(tmp_path: Path, capsys): + rendered = _generated_wrapper( """ @bind("SCALE") @native_call([Addr(Arg(0))]) @@ -127,7 +123,7 @@ def scale(x: Float64) -> Float64: ... ) compiler = RecordingCompiler() - result = _build_rendered_wrapper_extension( + result = _build_generated_wrapper_extension( rendered, output_dir=tmp_path / "build", shared_library_output_dir=tmp_path / "extension", @@ -199,19 +195,20 @@ def scale(x: Float64) -> Float64: ... ] -def test_build_rendered_wrapper_extension_rejects_unknown_native_support_key(tmp_path: Path): - rendered = RenderedGeneratedWrapperArtifacts( - artifacts=GeneratedWrapperArtifacts( - module_name="bad_runtime", - binding_sources=(Path("bad_runtime_wrapper.c"),), - native_support_keys=("unknown_native_support",), - ), - sources=(GeneratedSourceFile(Path("bad_runtime_wrapper.c"), "PyObject *unused;\n"),), +def test_build_generated_wrapper_extension_rejects_unknown_native_support_key(tmp_path: Path): + rendered = GeneratedWrapper( + module_name="bad_runtime", + sources=(GeneratedSource(Path("bad_runtime_wrapper.c"), "PyObject *unused;\n"),), + bridge_sources=(), + binding_sources=(Path("bad_runtime_wrapper.c"),), + headers=(), + native_support_keys=("unknown_native_support",), + required_headers=(), extension_init_name="PyInit_bad_runtime", ) with pytest.raises(ValueError, match="Unsupported wrapper native support key"): - _build_rendered_wrapper_extension( + _build_generated_wrapper_extension( rendered, output_dir=tmp_path, compiler=RecordingCompiler(), diff --git a/tests/fortran/callbacks/codegen/test_callback_planning.py b/tests/fortran/callbacks/codegen/test_callback_planning.py index a33eaa4d3..d22135534 100644 --- a/tests/fortran/callbacks/codegen/test_callback_planning.py +++ b/tests/fortran/callbacks/codegen/test_callback_planning.py @@ -6,9 +6,9 @@ from prik.pipeline.pyi import pyi_file_to_semantic_module, pyi_text_to_semantic_module from prik.semantics import models -from prik.semantics.ownership import PythonBarrierAction -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.ownership import PythonBarrierAction +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( CallbackABIKind, CallbackGILAction, CallbackLifecycleAction, @@ -16,8 +16,9 @@ CallbackThreadAction, CallbackTransferAction, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import DatatypeFamily +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.planning.models import DatatypeFamily CONTRACT_ROOT = Path(__file__).parents[1] / "end_to_end" / "fixtures" / "contracts" CONTRACT = CONTRACT_ROOT / "fcallback_all_f90" / "fcallback_all_f90.pyi" @@ -45,7 +46,7 @@ def _callback_argument(plan, function_name: str): def _sources(plan): - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") return c_source, bridge @@ -150,7 +151,7 @@ def test_callback_plan_edits_fail_central_validation_before_backend_emission(edi callback.trampoline_symbol = callback.adapter_symbol with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_callback_artifacts_use_linear_context_adapter_and_trampoline_paths(): diff --git a/tests/fortran/callbacks/pipeline/test_callback_contract_printing.py b/tests/fortran/callbacks/pipeline/test_callback_contract_printing.py index 7ec30b4a6..9d1c6ebe6 100644 --- a/tests/fortran/callbacks/pipeline/test_callback_contract_printing.py +++ b/tests/fortran/callbacks/pipeline/test_callback_contract_printing.py @@ -1,6 +1,6 @@ """Semantic callback prototype contract printing.""" -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.models import ( SemanticArgument, SemanticArrayContract, diff --git a/tests/fortran/callbacks/policy/test_callback_policy.py b/tests/fortran/callbacks/policy/test_callback_policy.py index 8539306d9..55f703ec7 100644 --- a/tests/fortran/callbacks/policy/test_callback_policy.py +++ b/tests/fortran/callbacks/policy/test_callback_policy.py @@ -10,13 +10,13 @@ from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( CallbackABIKind, CallbackTransferAction, FunctionWrapperPolicy, ) -from prik.semantics.wrapper_policy import completed_function_wrapper_policy +from prik.policy.construction import completed_function_wrapper_policy FIXTURES = Path(__file__).parents[1] / "end_to_end" / "fixtures" diff --git a/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py b/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py index 4483530f1..cb12b2699 100644 --- a/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py +++ b/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py @@ -3,7 +3,7 @@ from prik import ( parse_fortran_project, ) -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.fortran2ir import FortranToIRConverter from prik.semantics.native_contract import native_contract_issues from tests.fortran._support.semantic_conversion import get_function diff --git a/tests/fortran/callbacks/semantics/test_pyi_callback_semantics.py b/tests/fortran/callbacks/semantics/test_pyi_callback_semantics.py index 47769df40..a32446cc8 100644 --- a/tests/fortran/callbacks/semantics/test_pyi_callback_semantics.py +++ b/tests/fortran/callbacks/semantics/test_pyi_callback_semantics.py @@ -1,7 +1,7 @@ """Tests split by stable ownership concept from `test_python_ast_contracts.py`.""" import pytest -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.pyi_conversion import parse_pyi_text @@ -142,7 +142,8 @@ def declared(value: Int32) -> Int32: ... def test_imported_prototype_resolves_as_module_interface_definition(tmp_path): from prik.pipeline.pyi import pyi_paths_to_semantic_modules - from prik.codegen import WrapperCodeGenerator, WrapperPlanner + from prik.pipeline.wrapper import WrapperGenerator + from prik.planning import WrapperPlanner (tmp_path / "callback_shapes.pyi").write_text( """from prik.contracts import Float64, Int32, prototype @@ -171,7 +172,7 @@ def apply(callback: transform, count: Int32, values: Float64[count]) -> None: .. } complete_semantic_policies(api) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(api)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(api)) bridge = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") assert "abstract interface" in bridge assert "function prik_transform_" in bridge diff --git a/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py b/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py index 64a7e6e61..c773351ac 100644 --- a/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py +++ b/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py @@ -2,9 +2,10 @@ from prik import parse_fortran_file from prik.semantics.fortran2ir import fortran_module_to_semantic_module -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import BridgeDataAction, ScalarLogicalABI -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import BridgeDataAction, ScalarLogicalABI +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner SOURCE = """ @@ -44,7 +45,7 @@ def test_bridge_mechanically_lowers_completed_default_logical_kind_copies(): module_plan, _function = _logical_function_plan() bridge_source = next( - source.text for source in WrapperCodeGenerator().generate(module_plan).sources if source.path.suffix == ".f90" + source.text for source in WrapperGenerator().generate(module_plan).sources if source.path.suffix == ".f90" ) assert "logical(c_bool), value :: input" in bridge_source diff --git a/tests/fortran/data_types/codegen/test_primitive_scalar_input_lowering.py b/tests/fortran/data_types/codegen/test_primitive_scalar_input_lowering.py index 0531ec7df..f8929aea7 100644 --- a/tests/fortran/data_types/codegen/test_primitive_scalar_input_lowering.py +++ b/tests/fortran/data_types/codegen/test_primitive_scalar_input_lowering.py @@ -5,8 +5,9 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner @pytest.mark.parametrize( @@ -28,7 +29,7 @@ def test_scalar_input_registry_lowers_completed_type_into_the_native_support_api complete_semantic_policies(module) plan = WrapperPlanner().build(module) - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") assert f"{c_type} bound_x;" in c_source @@ -44,7 +45,7 @@ def test_binding_locals_are_isolated_from_identifiers_imported_by_c_headers(): c_source = next( source.text - for source in WrapperCodeGenerator().generate(WrapperPlanner().build(module)).sources + for source in WrapperGenerator().generate(WrapperPlanner().build(module)).sources if source.path.suffix == ".c" ) diff --git a/tests/fortran/data_types/codegen/test_primitive_scalar_result_lowering.py b/tests/fortran/data_types/codegen/test_primitive_scalar_result_lowering.py index acf4008f1..d25afc7bf 100644 --- a/tests/fortran/data_types/codegen/test_primitive_scalar_result_lowering.py +++ b/tests/fortran/data_types/codegen/test_primitive_scalar_result_lowering.py @@ -5,9 +5,10 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import DirectResultABI -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import DirectResultABI +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner @pytest.mark.parametrize( @@ -24,7 +25,7 @@ def test_direct_scalar_result_registry_projects_supported_type_facts(type_name, numpy_type, result_kind): module = parse_pyi_text(f"def identity(x: {type_name}) -> {type_name}: ...", module_name="scalar_result") complete_semantic_policies(module) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(module)) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") helper_suffix = numpy_type.casefold().removeprefix("npy_") @@ -43,7 +44,7 @@ def test_direct_bool_result_normalizes_the_fortran_truth_bit_before_c_conversion assert result.direct_result_abi is DirectResultABI.LOGICAL_LOW_BIT_INT8 - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") fortran_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -65,4 +66,4 @@ def test_generator_rejects_a_non_normalized_direct_bool_result_abi(): plan.namespaces[0].functions[0].results[0].direct_result_abi = DirectResultABI.NATIVE_SCALAR with pytest.raises(ValueError, match="invalid-direct-result-abi"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/derived_types/codegen/test_class_surfaces.py b/tests/fortran/derived_types/codegen/test_class_surfaces.py index 6c430e2ef..43b0ef678 100644 --- a/tests/fortran/derived_types/codegen/test_class_surfaces.py +++ b/tests/fortran/derived_types/codegen/test_class_surfaces.py @@ -5,8 +5,9 @@ import pytest from prik.pipeline.pyi import pyi_file_to_semantic_module -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner FIXTURES = Path(__file__).parents[1] / "end_to_end" / "fixtures" INHERITANCE = FIXTURES / "contracts" / "finheritance_f90" / "finheritance_f90.pyi" @@ -55,4 +56,4 @@ def test_invalid_class_graph_fails_before_emission(): _surface(plan, "circle").base_identities = (("missing", "base"),) with pytest.raises(ValueError, match="missing-or-late-class-base"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/derived_types/codegen/test_derived_lowering.py b/tests/fortran/derived_types/codegen/test_derived_lowering.py index 7137ee148..fdcaddbbc 100644 --- a/tests/fortran/derived_types/codegen/test_derived_lowering.py +++ b/tests/fortran/derived_types/codegen/test_derived_lowering.py @@ -8,8 +8,8 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.semantics.models import RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( BridgeDataAction, DerivedNativeHandoff, DerivedObjectOrigin, @@ -17,7 +17,8 @@ DerivedRelease, LifecycleOperation, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _value_module(*, attributes: tuple[str, ...] = ()): @@ -44,7 +45,7 @@ def _value_plan(): def _sources(plan): - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") return c_source, bridge_source @@ -101,7 +102,7 @@ def test_derived_plan_edits_fail_central_validation(edit: str, diagnostic: str): argument.bridge = replace(argument.bridge, data_action=BridgeDataAction.ASSOCIATE_VIEW) with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_owned_derived_result_has_explicit_failure_and_release_lifecycle(): @@ -127,7 +128,7 @@ def make_point() -> point: ... function.release_actions = () with pytest.raises(ValueError, match="derived-wrapper-release-count"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_hidden_returns_derived_output_reuses_owned_result_storage_and_lifecycle(): @@ -210,7 +211,7 @@ class point: variable.derived.handoff.release = DerivedRelease.WRAPPER_DESTROY with pytest.raises(ValueError, match="invalid-derived-module-release"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) @pytest.mark.parametrize( @@ -297,9 +298,7 @@ def update(value: point) -> tuple[Float64, Returns["value", point]]: ... assert function.results[0].result_position == 0 assert function.writeback_actions[2].result_position == 1 - c_source = next( - source.text for source in WrapperCodeGenerator().generate(plan).sources if source.path.suffix == ".c" - ) + c_source = next(source.text for source in WrapperGenerator().generate(plan).sources if source.path.suffix == ".c") assert "PyTuple_New(2)" in c_source assert "Py_DECREF(result_0_obj);" in c_source diff --git a/tests/fortran/derived_types/codegen/test_scalar_actual_dummy_plan.py b/tests/fortran/derived_types/codegen/test_scalar_actual_dummy_plan.py index 27409700c..c20b5d9ec 100644 --- a/tests/fortran/derived_types/codegen/test_scalar_actual_dummy_plan.py +++ b/tests/fortran/derived_types/codegen/test_scalar_actual_dummy_plan.py @@ -7,8 +7,8 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( DerivedActualAccess, DerivedCallAction, DerivedDummyCategory, @@ -16,7 +16,8 @@ DerivedOwnerRetention, DerivedRelease, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner CONTRACT = """ @@ -222,7 +223,7 @@ def read(self) -> Int32: ... complete_semantic_policies(module) bridge = next( source.text - for source in WrapperCodeGenerator().generate(WrapperPlanner().build(module)).sources + for source in WrapperGenerator().generate(WrapperPlanner().build(module)).sources if source.path.suffix == ".f90" ) @@ -240,7 +241,7 @@ def test_validation_rejects_a_pointer_holder_without_completed_target_ownership( ) with pytest.raises(ValueError, match="missing-derived-pointer-target-owner"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_validation_rejects_a_backend_invented_matrix_gap(): @@ -251,11 +252,11 @@ def test_validation_rejects_a_backend_invented_matrix_gap(): argument.derived_call = replace(argument.derived_call, cases=argument.derived_call.cases[:-1]) with pytest.raises(ValueError, match="incomplete-derived-call-matrix"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_artifacts_emit_shared_holders_typed_origin_operations_and_one_native_call(): - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(_module())) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(_module())) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") diff --git a/tests/fortran/derived_types/policy/test_derived_accessor_policy.py b/tests/fortran/derived_types/policy/test_derived_accessor_policy.py index f80de0beb..e21a68568 100644 --- a/tests/fortran/derived_types/policy/test_derived_accessor_policy.py +++ b/tests/fortran/derived_types/policy/test_derived_accessor_policy.py @@ -10,7 +10,7 @@ SemanticModule, SemanticVariable, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( AssignmentMode, CodegenAction, DestructionPolicy, @@ -19,9 +19,9 @@ SetterAction, StorageMode, TransferMode, - set_ownership_metadata, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.semantics.ownership_metadata import set_ownership_metadata +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.ownership_policy import ( _array_type, _derived_type, @@ -33,7 +33,7 @@ RESOLVED_DERIVED_TYPE_POLICY_METADATA, RESOLVED_MODULE_VARIABLE_POLICY_METADATA, ) -from prik.semantics.wrapper_policy_models import ModuleObjectAccessMechanism +from prik.policy.models import ModuleObjectAccessMechanism def test_abstract_type_and_deferred_binding_fail_in_completed_derived_policy(): diff --git a/tests/fortran/derived_types/policy/test_derived_policy_defaults.py b/tests/fortran/derived_types/policy/test_derived_policy_defaults.py index ecba1a345..010baa160 100644 --- a/tests/fortran/derived_types/policy/test_derived_policy_defaults.py +++ b/tests/fortran/derived_types/policy/test_derived_policy_defaults.py @@ -8,7 +8,7 @@ SemanticModule, SemanticVariable, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, OwnershipOwner, diff --git a/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py b/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py index 0568b8b52..debc6603f 100644 --- a/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py +++ b/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py @@ -1,7 +1,7 @@ """Tests split by stable ownership concept from `test_compile_time_values.py`.""" import pytest -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.parsers.fortran.models import ( FortranArgument, FortranDerivedType, diff --git a/tests/fortran/derived_types/semantics/test_pyi_class_semantics.py b/tests/fortran/derived_types/semantics/test_pyi_class_semantics.py index b8014a4f7..2dad4909c 100644 --- a/tests/fortran/derived_types/semantics/test_pyi_class_semantics.py +++ b/tests/fortran/derived_types/semantics/test_pyi_class_semantics.py @@ -1,6 +1,6 @@ """Core `.pyi` class semantics retained by Derived Types.""" -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.metadata import ( PROJECTED_OUTPUT_METADATA, SUPPRESS_DEFAULT_CONSTRUCTOR_METADATA, diff --git a/tests/fortran/enumerations/semantics/test_enum_semantics.py b/tests/fortran/enumerations/semantics/test_enum_semantics.py index 97fba37b0..9e979251b 100644 --- a/tests/fortran/enumerations/semantics/test_enum_semantics.py +++ b/tests/fortran/enumerations/semantics/test_enum_semantics.py @@ -4,7 +4,7 @@ from prik import parse_fortran_file as parse_fortran_source -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module ENUM_SOURCE = Path(__file__).parents[1] / "end_to_end" / "fixtures" / "fenums_f90.f90" diff --git a/tests/fortran/enumerations/semantics/test_pyi_enum_constants.py b/tests/fortran/enumerations/semantics/test_pyi_enum_constants.py index 90e1f51a5..77bac271a 100644 --- a/tests/fortran/enumerations/semantics/test_pyi_enum_constants.py +++ b/tests/fortran/enumerations/semantics/test_pyi_enum_constants.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_python_ast_contracts.py`.""" -from prik.codegen.printers import emit_module +from prik.printers import emit_module from tests.fortran._support.pyi_conversion import parse_pyi_text diff --git a/tests/fortran/error_handling/codegen/test_runtime_envelope_lowering.py b/tests/fortran/error_handling/codegen/test_runtime_envelope_lowering.py index a9b87fea5..cebc9c7b3 100644 --- a/tests/fortran/error_handling/codegen/test_runtime_envelope_lowering.py +++ b/tests/fortran/error_handling/codegen/test_runtime_envelope_lowering.py @@ -5,8 +5,9 @@ from pathlib import Path from prik.pipeline.pyi import pyi_file_to_semantic_module -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner RECURSION_CONTRACT = ( @@ -27,6 +28,6 @@ def test_recursive_runtime_contract_keeps_the_gil_by_default(): assert plan.namespaces[0].functions assert all(function.binding.release_gil is False for function in plan.namespaces[0].functions) - c_source = _rendered_source(WrapperCodeGenerator().generate(plan), ".c") + c_source = _rendered_source(WrapperGenerator().generate(plan), ".c") assert "Py_BEGIN_ALLOW_THREADS" not in c_source assert "Py_END_ALLOW_THREADS" not in c_source diff --git a/tests/fortran/error_handling/codegen/test_status_error_lowering.py b/tests/fortran/error_handling/codegen/test_status_error_lowering.py index ab3482939..543dadfff 100644 --- a/tests/fortran/error_handling/codegen/test_status_error_lowering.py +++ b/tests/fortran/error_handling/codegen/test_status_error_lowering.py @@ -8,9 +8,10 @@ import pytest from prik.pipeline.pyi import pyi_file_to_semantic_module -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import BridgeDataAction, PythonExceptionKind -from prik.codegen import DatatypeFamily, WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import BridgeDataAction, PythonExceptionKind +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import DatatypeFamily, WrapperPlanner RUNTIME_POLICY_CONTRACT = ( @@ -56,6 +57,7 @@ def test_planner_records_editable_native_runtime_and_status_error_facts(): assert solve.binding.status_error.exception_kind is PythonExceptionKind.RUNTIME_ERROR assert solve.binding.status_error.status_role == solve.native_call_slots[1].symbolic_role assert solve.binding.status_error.message_role == solve.native_call_slots[2].symbolic_role + WrapperGenerator().generate(plan) assert "Raises\n------" in solve.binding.docstring assert solve.binding.docstring.count("RuntimeError\n") == 1 assert "If native status differs from the success value 0." in solve.binding.docstring @@ -73,7 +75,7 @@ def test_planner_records_editable_native_runtime_and_status_error_facts(): def test_direct_binding_lowering_places_only_opted_in_native_call_outside_the_gil(): - artifacts = WrapperCodeGenerator().generate(_runtime_plan()) + artifacts = WrapperGenerator().generate(_runtime_plan()) c_source = _rendered_source(artifacts, ".c") released = _function_source(c_source, "pause_for_one_second", "pause_with_gil") held = _function_source(c_source, "pause_with_gil", "solve") @@ -92,7 +94,7 @@ def test_direct_binding_lowering_places_only_opted_in_native_call_outside_the_gi def test_direct_bridge_lowering_projects_status_and_copies_fixed_message(): - artifacts = WrapperCodeGenerator().generate(_runtime_plan()) + artifacts = WrapperGenerator().generate(_runtime_plan()) fortran_source = _rendered_source(artifacts, ".f90") assert "subroutine bind_c_solve(value, status, message)" in fortran_source @@ -119,7 +121,7 @@ def test_fixed_message_bridge_copy_requires_its_completed_reason(): ) with pytest.raises(ValueError, match="missing-bridge-copy-reason"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_runtime_plan_edits_dispatch_to_named_lowering_and_validate_roles(): @@ -129,7 +131,7 @@ def test_runtime_plan_edits_dispatch_to_named_lowering_and_validate_roles(): "pause_for_one_second", lambda function: replace(function, binding=replace(function.binding, release_gil=False)), ) - c_source = _rendered_source(WrapperCodeGenerator().generate(held), ".c") + c_source = _rendered_source(WrapperGenerator().generate(held), ".c") released = _function_source(c_source, "pause_for_one_second", "pause_with_gil") assert "Py_BEGIN_ALLOW_THREADS" not in released assert "Py_END_ALLOW_THREADS" not in released @@ -146,4 +148,4 @@ def test_runtime_plan_edits_dispatch_to_named_lowering_and_validate_roles(): ), ) with pytest.raises(ValueError, match="missing-status-result-role"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) diff --git a/tests/fortran/error_handling/semantics/test_status_contract_semantics.py b/tests/fortran/error_handling/semantics/test_status_contract_semantics.py index 4a257804c..28de90192 100644 --- a/tests/fortran/error_handling/semantics/test_status_contract_semantics.py +++ b/tests/fortran/error_handling/semantics/test_status_contract_semantics.py @@ -4,8 +4,8 @@ from prik.pipeline.pyi import pyi_text_to_semantic_module from prik.semantics.models import RUNTIME_RELEASE_GIL_METADATA, RUNTIME_STATUS_ERROR_METADATA -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen.printers import emit_module +from prik.policy.completion import complete_semantic_policies +from prik.printers import emit_module _CONTRACT_IMPORTS = """from prik.contracts import ( diff --git a/tests/fortran/functions/codegen/test_multiple_function_results.py b/tests/fortran/functions/codegen/test_multiple_function_results.py index 643f1cfc6..9a55ac6cd 100644 --- a/tests/fortran/functions/codegen/test_multiple_function_results.py +++ b/tests/fortran/functions/codegen/test_multiple_function_results.py @@ -7,9 +7,10 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, NativeBarrierAction -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.ownership import CodegenAction, NativeBarrierAction +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _multiple_result_plan(): @@ -44,7 +45,7 @@ def test_multiple_scalar_result_plan_has_ordered_binding_consumers_and_shared_hi def test_multiple_scalar_results_lower_to_binding_tuple_and_one_bridge_function_call(): - artifacts = WrapperCodeGenerator().generate(_multiple_result_plan()) + artifacts = WrapperGenerator().generate(_multiple_result_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -69,14 +70,14 @@ def test_multiple_scalar_result_validation_rejects_position_and_consumer_drift() hidden.result_position = 0 with pytest.raises(ValueError, match=r"duplicate-binding-result-position.*missing-binding-result-position"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) plan = _multiple_result_plan() function = plan.namespaces[0].functions[0] direct, _hidden = function.results function.results = (direct,) with pytest.raises(ValueError, match="unclaimed-native-result"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) plan = _multiple_result_plan() function = plan.namespaces[0].functions[0] @@ -84,11 +85,11 @@ def test_multiple_scalar_result_validation_rejects_position_and_consumer_drift() duplicate = replace(hidden, owner_path=f"{hidden.owner_path}.duplicate", result_position=2) function.results = (direct, hidden, duplicate) with pytest.raises(ValueError, match="multiple-native-result-consumers"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) plan = _multiple_result_plan() function = plan.namespaces[0].functions[0] _direct, hidden = function.results hidden.native_call_slot = replace(hidden.native_call_slot) with pytest.raises(ValueError, match="inconsistent-function-result-slot"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/functions/codegen/test_scalar_function_writeback.py b/tests/fortran/functions/codegen/test_scalar_function_writeback.py index 86d9a284f..e0b55f455 100644 --- a/tests/fortran/functions/codegen/test_scalar_function_writeback.py +++ b/tests/fortran/functions/codegen/test_scalar_function_writeback.py @@ -4,14 +4,15 @@ from dataclasses import replace from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import WritebackPhase -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import WritebackPhase +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _artifacts(module): complete_semantic_policies(module) - return WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + return WrapperGenerator().generate(WrapperPlanner().build(module)) def _source(artifacts, suffix: str) -> str: @@ -38,7 +39,7 @@ def test_scalar_writeback_is_an_explicit_binding_lifecycle_result(): assert actions[2].binding.python_result_role == "scalar_writeback.bump.value:python-result" assert actions[3].binding is not None - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = _source(artifacts, ".c") fortran_source = _source(artifacts, ".f90") diff --git a/tests/fortran/functions/policy/test_function_result_policy.py b/tests/fortran/functions/policy/test_function_result_policy.py index f94a11978..1b420f29b 100644 --- a/tests/fortran/functions/policy/test_function_result_policy.py +++ b/tests/fortran/functions/policy/test_function_result_policy.py @@ -7,15 +7,15 @@ from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules from prik.pipeline.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules -from prik.semantics.ownership import ( +from prik.policy.ownership import ( NativeBarrierAction, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( ArgumentConversionPhase, WritebackPhase, ) -from prik.semantics.wrapper_policy import completed_function_wrapper_policy +from prik.policy.construction import completed_function_wrapper_policy FMATH_CONTRACT = Path("tests/fortran/data_types/end_to_end/fixtures/baseline/contracts/fmath/__init__.pyi") diff --git a/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py b/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py index de34382d5..f3188d803 100644 --- a/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py +++ b/tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py @@ -1,16 +1,23 @@ """Exact generic dispatch evidence at the shared wrapper-plan boundary.""" from dataclasses import replace +from pathlib import Path import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import OverloadMatchKind -from prik.codegen import CBindingGenerator, WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.pyi import pyi_file_to_semantic_module +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import OverloadMatchKind +from prik.codegen import CBindingGenerator +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner from prik.codegen.c.naming import CBindingNames +DEFINED_OPERATORS = Path(__file__).parents[1] / "end_to_end/fixtures/contracts/foperators_f90/foperators_f90.pyi" + + def _plan(): module = parse_pyi_text( """ @@ -45,11 +52,33 @@ def test_plan_records_one_exact_numpy_scalar_predicate_per_candidate(): assert overload.candidate_ids == (0, 1) +def test_policy_completes_builtin_scalar_family_only_for_reflected_dispatch(): + module = pyi_file_to_semantic_module(DEFINED_OPERATORS) + complete_semantic_policies(module) + plan = WrapperPlanner().build(module) + vector = next( + surface + for namespace in plan.namespaces + for surface in namespace.classes + if surface.type_identity[1] == "vector" + ) + overloads = {overload.python_name: overload for overload in vector.overloads} + + assert [ + match.builtin_scalar_family for candidate in overloads["__radd__"].candidate_matches for match in candidate + ] == ["float"] + assert all( + match.builtin_scalar_family is None + for candidate in overloads["__add__"].candidate_matches + for match in candidate + ) + + def test_binding_lowers_public_overload_to_candidate_id_switch(): plan = _plan() overload = plan.namespaces[0].overloads[0] - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") dispatcher = CBindingNames.overload_dispatch_function(overload) @@ -87,7 +116,7 @@ def test_generator_rejects_ambiguous_edited_overload_plan_before_emission(): invalid = replace(plan, namespaces=(replace(namespace, overloads=(ambiguous,)),)) with pytest.raises(ValueError, match="ambiguous-overload"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_duplicate_candidate_ids_before_emission(): @@ -98,7 +127,7 @@ def test_generator_rejects_duplicate_candidate_ids_before_emission(): invalid = replace(plan, namespaces=(replace(namespace, overloads=(duplicate_ids,)),)) with pytest.raises(ValueError, match="duplicate-overload-candidate-id"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_candidate_id_reserved_for_no_match(): @@ -109,7 +138,7 @@ def test_generator_rejects_candidate_id_reserved_for_no_match(): invalid = replace(plan, namespaces=(replace(namespace, overloads=(invalid_ids,)),)) with pytest.raises(ValueError, match="invalid-overload-candidate-id"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generic_candidate_with_array_of_derived_values_is_blocked_before_lowering(): diff --git a/tests/fortran/generic_interfaces/policy/test_generic_policy.py b/tests/fortran/generic_interfaces/policy/test_generic_policy.py index 4ca184926..094424642 100644 --- a/tests/fortran/generic_interfaces/policy/test_generic_policy.py +++ b/tests/fortran/generic_interfaces/policy/test_generic_policy.py @@ -10,7 +10,7 @@ from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies FMATH_CONTRACT = Path("tests/fortran/data_types/end_to_end/fixtures/baseline/contracts/fmath/__init__.pyi") diff --git a/tests/fortran/infrastructure/codegen/test_docstrings.py b/tests/fortran/infrastructure/codegen/test_docstrings.py index bb011d089..ae6cf2952 100644 --- a/tests/fortran/infrastructure/codegen/test_docstrings.py +++ b/tests/fortran/infrastructure/codegen/test_docstrings.py @@ -4,6 +4,47 @@ import subprocess import sys +from tests.fortran._support.ownership_policy import parse_pyi_text +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.policy.completion import complete_semantic_policies + + +def _function_plan(): + """Return an editable scalar plan before documentation generation.""" + module = parse_pyi_text("def scale(value: Float64) -> Float64: ...", module_name="docstrings") + complete_semantic_policies(module) + return WrapperPlanner().build(module) + + +def test_codegen_renders_unresolved_plan_docstrings_before_freezing(): + plan = _function_plan() + namespace = plan.namespaces[0] + function = namespace.functions[0] + + assert namespace.docstring is None + assert function.binding.docstring is None + + WrapperGenerator().generate(plan) + + assert namespace.docstring is not None + assert namespace.docstring.startswith("docstrings") + assert function.binding.docstring is not None + assert function.binding.docstring.startswith("scale(value) -> float64") + + +def test_codegen_preserves_explicit_plan_docstring_overrides(): + plan = _function_plan() + namespace = plan.namespaces[0] + function = namespace.functions[0] + namespace.docstring = "" + function.binding.docstring = "Custom scale documentation." + + WrapperGenerator().generate(plan) + + assert namespace.docstring == "" + assert function.binding.docstring == "Custom scale documentation." + def test_docstrings_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] diff --git a/tests/fortran/infrastructure/codegen/test_package.py b/tests/fortran/infrastructure/codegen/test_package.py index 42b2517f1..1f23d2499 100644 --- a/tests/fortran/infrastructure/codegen/test_package.py +++ b/tests/fortran/infrastructure/codegen/test_package.py @@ -13,6 +13,10 @@ SOURCE_ROOT = REPO_ROOT / "prik" CODEGEN_ROOT = SOURCE_ROOT / "codegen" +PRINTERS_ROOT = SOURCE_ROOT / "printers" +PLANNING_ROOT = SOURCE_ROOT / "planning" +POLICY_ROOT = SOURCE_ROOT / "policy" +SEMANTICS_ROOT = SOURCE_ROOT / "semantics" def _imported_modules(path: Path) -> set[str]: @@ -30,6 +34,10 @@ def _imports_under(imports: set[str], package: str) -> bool: return any(name == package or name.startswith(f"{package}.") for name in imports) +def _package_imports(root: Path) -> set[str]: + return set().union(*(_imported_modules(path) for path in root.rglob("*.py"))) + + def _write_module(root: Path, relative_path: str, source: str) -> Path: path = root / relative_path path.parent.mkdir(parents=True, exist_ok=True) @@ -47,10 +55,9 @@ def _check_source(tmp_path: Path, source: str, *, filename: str = "bad.py") -> s def test_canonical_printers_share_one_package(): - printers = CODEGEN_ROOT / "printers" - - assert (printers / "pyi_printer.py").is_file() - assert (printers / "source_printers.py").is_file() + assert (PRINTERS_ROOT / "c.py").is_file() + assert (PRINTERS_ROOT / "fortran.py").is_file() + assert (PRINTERS_ROOT / "pyi.py").is_file() def test_backend_generators_do_not_import_each_other(): @@ -61,7 +68,32 @@ def test_backend_generators_do_not_import_each_other(): assert not _imports_under(bridge_imports, "prik.codegen.c") -def test_wrapper_build_pipeline_imports_canonical_generator(): +def test_wrapper_stage_packages_follow_the_documented_dependency_direction(): + semantic_imports = _package_imports(SEMANTICS_ROOT) + policy_imports = _package_imports(POLICY_ROOT) + planning_imports = _package_imports(PLANNING_ROOT) + codegen_imports = _package_imports(CODEGEN_ROOT) + printer_imports = _package_imports(PRINTERS_ROOT) + + assert not _imports_under(semantic_imports, "prik.policy") + assert not _imports_under(semantic_imports, "prik.planning") + assert not _imports_under(semantic_imports, "prik.codegen") + assert not _imports_under(policy_imports, "prik.planning") + assert not _imports_under(policy_imports, "prik.codegen") + assert not _imports_under(planning_imports, "prik.codegen") + assert not _imports_under(codegen_imports, "prik.printers") + assert not _imports_under(codegen_imports, "prik.pipeline") + assert not _imports_under(printer_imports, "prik.policy") + assert not _imports_under(printer_imports, "prik.planning") + assert not _imports_under(printer_imports, "prik.pipeline") + + +def test_wrapper_build_pipeline_imports_canonical_wrapper_stages(): imports = _imported_modules(SOURCE_ROOT / "pipeline" / "build.py") + wrapper_imports = _imported_modules(SOURCE_ROOT / "pipeline" / "wrapper.py") - assert _imports_under(imports, "prik.codegen") + assert _imports_under(imports, "prik.policy") + assert _imports_under(imports, "prik.planning") + assert _imports_under(imports, "prik.pipeline.wrapper") + assert _imports_under(wrapper_imports, "prik.codegen") + assert _imports_under(wrapper_imports, "prik.printers") diff --git a/tests/fortran/infrastructure/codegen/test_plan.py b/tests/fortran/infrastructure/codegen/test_plan.py index cf1c25d4d..dd5c18a1c 100644 --- a/tests/fortran/infrastructure/codegen/test_plan.py +++ b/tests/fortran/infrastructure/codegen/test_plan.py @@ -8,7 +8,7 @@ def test_plan_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, str(repository_root / "prik/codegen/plan.py")], + [sys.executable, str(repository_root / "prik/planning/models.py")], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/codegen/test_planner.py b/tests/fortran/infrastructure/codegen/test_planner.py index aa37b185a..09c413f52 100644 --- a/tests/fortran/infrastructure/codegen/test_planner.py +++ b/tests/fortran/infrastructure/codegen/test_planner.py @@ -12,12 +12,10 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.semantics.models import PYTHON_EXPORTS_METADATA -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen.planner import _ClassPolicyCatalog -from prik.codegen import ( - WrapperCodeGenerator, - WrapperPlanner, -) +from prik.policy.completion import complete_semantic_policies +from prik.planning.planner import _ClassPolicyCatalog +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _plan(source: str, *, module_name: str = "fmath"): @@ -88,7 +86,7 @@ def right_value(x: Int32) -> Int32: ... module.functions[0].metadata[PYTHON_EXPORTS_METADATA] = [{"namespace": ("left",), "name": "shared_value"}] module.functions[1].metadata[PYTHON_EXPORTS_METADATA] = [{"namespace": ("right",), "name": "shared_value"}] complete_semantic_policies(module) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(module)) c_source = next(source.text for source in artifacts.sources if source.path.name.endswith(".c")) assert "PyModule_Create(&namespaced_left_module)" in c_source @@ -215,7 +213,7 @@ def test_planner_reports_an_unsupported_completed_module_policy_at_its_owner_pat def test_planner_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, str(repository_root / "prik/codegen/planner.py")], + [sys.executable, str(repository_root / "prik/planning/planner.py")], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/codegen/test_pyi_printer.py b/tests/fortran/infrastructure/codegen/test_pyi_printer.py index f57ee6b9e..60d17b34c 100644 --- a/tests/fortran/infrastructure/codegen/test_pyi_printer.py +++ b/tests/fortran/infrastructure/codegen/test_pyi_printer.py @@ -8,7 +8,7 @@ def test_pyi_printer_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, str(repository_root / "prik/codegen/printers/pyi_printer.py")], + [sys.executable, str(repository_root / "prik/printers/pyi.py")], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py b/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py index c64a6ca4e..ed15fe979 100644 --- a/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py +++ b/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py @@ -19,10 +19,11 @@ import pytest from prik import parse_fortran_project -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.printers import emit_module_stubs +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.pipeline.pyi import emit_module_stubs from prik.semantics.fortran2ir import fortran_project_to_semantic_modules -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies _FIXTURE_DIR = Path(__file__).parent / "fixtures" / "refactoring_goldens" @@ -86,7 +87,7 @@ def refactoring_golden_outputs() -> _RefactoringGoldenOutputs: complete_semantic_policies(semantic_modules) plan = WrapperPlanner().build(semantic_modules[0]) - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) return _RefactoringGoldenOutputs( parser_json=parser_json, diff --git a/tests/fortran/infrastructure/pipeline/test_wrapper.py b/tests/fortran/infrastructure/pipeline/test_wrapper.py new file mode 100644 index 000000000..3a3b6ea24 --- /dev/null +++ b/tests/fortran/infrastructure/pipeline/test_wrapper.py @@ -0,0 +1,39 @@ +"""Internal generated-wrapper handoff contracts.""" + +from __future__ import annotations + +from dataclasses import fields +from pathlib import Path + +from prik.pipeline.wrapper import GeneratedSource, GeneratedWrapper + + +def test_generated_wrapper_keeps_compile_and_link_ownership_out_of_the_handoff(): + wrapper = GeneratedWrapper( + module_name="demo", + sources=( + GeneratedSource(Path("bind_c_demo.f90"), "module bind_c_demo\nend module bind_c_demo\n"), + GeneratedSource(Path("demo.c"), "PyObject *demo;\n"), + GeneratedSource(Path("demo.h"), "#pragma once\n"), + ), + bridge_sources=(Path("bind_c_demo.f90"),), + binding_sources=(Path("demo.c"),), + headers=(Path("demo.h"),), + native_support_keys=("binding_support",), + required_headers=(), + extension_init_name="PyInit_demo", + ) + + assert wrapper.compile_sources == (Path("bind_c_demo.f90"), Path("demo.c")) + assert wrapper.generated_files == (Path("bind_c_demo.f90"), Path("demo.c"), Path("demo.h")) + assert wrapper.source_paths == wrapper.generated_files + assert {field.name for field in fields(GeneratedWrapper)} == { + "module_name", + "sources", + "bridge_sources", + "binding_sources", + "headers", + "native_support_keys", + "required_headers", + "extension_init_name", + } diff --git a/tests/fortran/infrastructure/pipeline/test_wrapper_artifacts.py b/tests/fortran/infrastructure/pipeline/test_wrapper_artifacts.py deleted file mode 100644 index f50a8ef6b..000000000 --- a/tests/fortran/infrastructure/pipeline/test_wrapper_artifacts.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Internal rendered-wrapper artifact handoff contracts.""" - -from __future__ import annotations - -import ast -from dataclasses import fields -from pathlib import Path - -from tests.fortran._support.wrapper_build import REPO_ROOT -from prik.pipeline.wrapper_artifacts import GeneratedWrapperArtifacts -from prik.codegen.checks import ( - WrapperCodegenCheckConfig, - check_codegen_paths, -) - -SOURCE_ROOT = REPO_ROOT / "prik" -CODEGEN_ROOT = SOURCE_ROOT / "codegen" - - -def _imported_modules(path: Path) -> set[str]: - tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) - imported = set() - for node in ast.walk(tree): - if isinstance(node, ast.Import): - imported.update(alias.name for alias in node.names) - if isinstance(node, ast.ImportFrom) and node.module: - imported.add(node.module) - return imported - - -def _imports_under(imports: set[str], package: str) -> bool: - return any(name == package or name.startswith(f"{package}.") for name in imports) - - -def _write_module(root: Path, relative_path: str, source: str) -> Path: - path = root / relative_path - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(source, encoding="utf-8") - return path - - -def _check_source(tmp_path: Path, source: str, *, filename: str = "bad.py") -> set[str]: - path = _write_module(tmp_path, filename, source) - violations = check_codegen_paths( - [path], - config=WrapperCodegenCheckConfig(max_complexity=3, max_statements=4, max_nesting=2), - ) - return {violation.code for violation in violations} - - -def test_generated_wrapper_artifacts_keep_compile_and_link_ownership_out_of_the_handoff(): - artifacts = GeneratedWrapperArtifacts( - module_name="demo", - bridge_sources=(Path("bind_c_demo.f90"),), - binding_sources=(Path("demo.c"),), - header_files=(Path("demo.h"),), - native_support_keys=("binding_support",), - ) - - assert artifacts.source_files == (Path("bind_c_demo.f90"), Path("demo.c")) - assert artifacts.generated_files == (Path("bind_c_demo.f90"), Path("demo.c"), Path("demo.h")) - assert artifacts.required_headers == () - assert {field.name for field in fields(GeneratedWrapperArtifacts)} == { - "module_name", - "bridge_sources", - "binding_sources", - "header_files", - "native_support_keys", - "required_headers", - } diff --git a/tests/fortran/infrastructure/codegen/test_generator.py b/tests/fortran/infrastructure/pipeline/test_wrapper_generator.py similarity index 85% rename from tests/fortran/infrastructure/codegen/test_generator.py rename to tests/fortran/infrastructure/pipeline/test_wrapper_generator.py index 484b407dc..80927562f 100644 --- a/tests/fortran/infrastructure/codegen/test_generator.py +++ b/tests/fortran/infrastructure/pipeline/test_wrapper_generator.py @@ -10,23 +10,22 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.ownership import CodegenAction, NativeBarrierAction, ObjectKind -from prik.semantics.wrapper_policy_models import ArgumentHandoffMode, BridgeDataAction +from prik.policy.completion import complete_semantic_policies +from prik.policy.ownership import CodegenAction, NativeBarrierAction, ObjectKind +from prik.policy.models import ArgumentHandoffMode, BridgeDataAction from prik.stage_values import FrozenStageRecordError from prik.codegen import ( CBindingGenerator, - CSourcePrinter, FortranBridgeGenerator, - FortranSourcePrinter, - NamespacePlan, - WrapperCodeGenerator, - WrapperPlanner, ) +from prik.codegen.docstrings import WrapperDocstringBuilder +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import NamespacePlan, WrapperPlanner +from prik.printers import CSourcePrinter, FortranSourcePrinter -def _rendered_source(artifacts, suffix: str) -> str: - return next(source.text for source in artifacts.sources if source.path.name.endswith(suffix)) +def _rendered_source(generated_wrapper, suffix: str) -> str: + return next(source.text for source in generated_wrapper.sources if source.path.name.endswith(suffix)) def _plan(source: str, *, module_name: str = "fmath"): @@ -79,7 +78,7 @@ def hidden_storage_result() -> Float64[()]: ... return WrapperPlanner().build(module) -def test_public_generator_directly_returns_complete_rendered_artifacts(): +def test_public_generator_directly_returns_one_complete_generated_wrapper(): plan = _plan( """ @nogil @@ -91,18 +90,18 @@ def swap_args(x: Float64, y: Float64) -> Float64: ... module_name="render_demo", ) - artifacts = WrapperCodeGenerator().generate(plan) - c_source = _rendered_source(artifacts, ".c") - c_header = _rendered_source(artifacts, ".h") - fortran_source = _rendered_source(artifacts, ".f90") + generated_wrapper = WrapperGenerator().generate(plan) + c_source = _rendered_source(generated_wrapper, ".c") + c_header = _rendered_source(generated_wrapper, ".h") + fortran_source = _rendered_source(generated_wrapper, ".f90") - assert artifacts.artifacts.module_name == "render_demo" - assert artifacts.source_paths == ( + assert generated_wrapper.module_name == "render_demo" + assert generated_wrapper.source_paths == ( Path("bind_c_render_demo_wrapper.f90"), Path("render_demo_wrapper.c"), Path("render_demo_wrapper.h"), ) - assert artifacts.extension_init_name == "PyInit_render_demo" + assert generated_wrapper.extension_init_name == "PyInit_render_demo" assert "double bind_c_swap_args(double * y, double * x);" in c_source assert 'static char * kwlist[] = {"x", "y", NULL};' in c_source assert 'PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &bound_x_obj, &bound_y_obj)' in c_source @@ -122,7 +121,7 @@ def test_public_generator_reports_each_rendering_operation_in_execution_order(): plan = _plan("def value(x: Float64) -> Float64: ...", module_name="render_progress") progress = [] - WrapperCodeGenerator().generate(plan, progress=lambda label, elapsed: progress.append((label, elapsed))) + WrapperGenerator().generate(plan, progress=lambda label, elapsed: progress.append((label, elapsed))) assert [label for label, _ in progress] == [ "Generate binding source", @@ -138,11 +137,13 @@ def test_public_generator_reports_each_rendering_operation_in_execution_order(): def test_large_procedure_only_binding_is_split_into_balanced_compile_units(): declarations = "\n".join(f"def value_{index:03d}(x: Float64) -> Float64: ..." for index in range(128)) - artifacts = WrapperCodeGenerator().generate(_plan(declarations, module_name="large_binding")) - binding_sources = [source for source in artifacts.sources if source.path.name.startswith("large_binding_wrapper")] + generated_wrapper = WrapperGenerator().generate(_plan(declarations, module_name="large_binding")) + binding_sources = [ + source for source in generated_wrapper.sources if source.path.name.startswith("large_binding_wrapper") + ] main_source, *worker_sources, header_source = binding_sources - assert artifacts.artifacts.binding_sources == ( + assert generated_wrapper.binding_sources == ( Path("large_binding_wrapper.c"), Path("large_binding_wrapper_001.c"), Path("large_binding_wrapper_002.c"), @@ -162,9 +163,9 @@ def test_large_procedure_only_binding_is_split_into_balanced_compile_units(): def test_procedure_only_binding_below_sharding_threshold_keeps_one_compile_unit(): declarations = "\n".join(f"def value_{index:03d}(x: Float64) -> Float64: ..." for index in range(127)) - artifacts = WrapperCodeGenerator().generate(_plan(declarations, module_name="unsharded_binding")) + generated_wrapper = WrapperGenerator().generate(_plan(declarations, module_name="unsharded_binding")) - assert artifacts.artifacts.binding_sources == (Path("unsharded_binding_wrapper.c"),) + assert generated_wrapper.binding_sources == (Path("unsharded_binding_wrapper.c"),) @pytest.mark.parametrize( @@ -199,10 +200,10 @@ def hidden_value(x: Float64) -> Float64: ... ], ) def test_supported_function_actions_select_their_backend_behavior(source, c_fragment, fortran_fragment): - artifacts = WrapperCodeGenerator().generate(_plan(source, module_name="action_dispatch")) + generated_wrapper = WrapperGenerator().generate(_plan(source, module_name="action_dispatch")) - assert c_fragment in _rendered_source(artifacts, ".c") - assert fortran_fragment in _rendered_source(artifacts, ".f90") + assert c_fragment in _rendered_source(generated_wrapper, ".c") + assert fortran_fragment in _rendered_source(generated_wrapper, ".f90") def test_direct_plan_edits_change_binding_and_bridge_generation_then_freeze_plan(): @@ -220,10 +221,10 @@ def calculate(x: Float64, y: Float64) -> Float64: ... function.owner_path = "editable_plan.subtract" function.bridge.native_name = "SUB_R8" - artifacts = WrapperCodeGenerator().generate(plan) + generated_wrapper = WrapperGenerator().generate(plan) - assert '"subtract", (PyCFunction)wrap_calculate' in _rendered_source(artifacts, ".c") - assert "result = SUB_R8(x, y)" in _rendered_source(artifacts, ".f90") + assert '"subtract", (PyCFunction)wrap_calculate' in _rendered_source(generated_wrapper, ".c") + assert "result = SUB_R8(x, y)" in _rendered_source(generated_wrapper, ".f90") with pytest.raises(FrozenStageRecordError): function.bridge.native_name = "ADD_R8" @@ -239,6 +240,7 @@ def scale(x: Float64) -> Float64: ... ) c_generator = CBindingGenerator() fortran_generator = FortranBridgeGenerator() + WrapperDocstringBuilder().render(plan) c_generator.require_supported(plan) fortran_generator.require_supported(plan) @@ -276,7 +278,7 @@ def scale(x: Float64) -> Float64: ... ) with pytest.raises(ValueError, match="Unsupported C argument optional mode"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_hidden_result_native_action_disagreement(): @@ -297,7 +299,7 @@ def test_generator_rejects_hidden_result_native_action_disagreement(): ) with pytest.raises(ValueError, match="inconsistent-result-native-action"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_hidden_result_slot_codegen_action_disagreement(): @@ -318,7 +320,7 @@ def test_generator_rejects_hidden_result_slot_codegen_action_disagreement(): ) with pytest.raises(ValueError, match="inconsistent-result-slot-codegen-action"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_argument_native_slot_object_kind_disagreement(): @@ -327,7 +329,7 @@ def test_generator_rejects_argument_native_slot_object_kind_disagreement(): argument.native_call_slot.object_kind = ObjectKind.STRING with pytest.raises(ValueError, match="inconsistent-argument-object-kind"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_generator_rejects_result_native_slot_object_kind_disagreement(): @@ -336,7 +338,7 @@ def test_generator_rejects_result_native_slot_object_kind_disagreement(): result.native_call_slot.object_kind = ObjectKind.STRING with pytest.raises(ValueError, match="inconsistent-result-object-kind"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_generator_rejects_advertised_role_without_a_plan_producer(): @@ -346,7 +348,7 @@ def test_generator_rejects_advertised_role_without_a_plan_producer(): ) with pytest.raises(ValueError, match="inconsistent-available-roles"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_duplicate_python_exports_before_lowering(): @@ -357,7 +359,7 @@ def test_generator_rejects_duplicate_python_exports_before_lowering(): invalid = replace(plan, namespaces=(replace(root, functions=(function, duplicate)),)) with pytest.raises(ValueError, match="duplicate-python-export"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_duplicate_generated_symbols_before_lowering(): @@ -372,7 +374,7 @@ def test_generator_rejects_duplicate_generated_symbols_before_lowering(): invalid = replace(plan, namespaces=(replace(root, functions=(function, duplicate)),)) with pytest.raises(ValueError, match="duplicate-generated-symbol"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_colliding_generated_namespace_symbols(): @@ -386,7 +388,7 @@ def test_generator_rejects_colliding_generated_namespace_symbols(): ) with pytest.raises(ValueError, match="duplicate-generated-namespace-symbol"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) @pytest.mark.parametrize( @@ -437,7 +439,7 @@ def test_generator_revalidates_direct_plan_edits(mutate, expected_code): invalid = mutate(_scalar_plan()) with pytest.raises(ValueError, match=expected_code): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) @pytest.mark.parametrize( @@ -468,7 +470,7 @@ def test_scalar_address_handoff_plan_edits_fail_before_lowering(edit, diagnostic storage.array.rank = 1 with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) @pytest.mark.parametrize( @@ -490,7 +492,7 @@ def test_bridge_data_action_invariant_rejects_unjustified_or_blocked_plans(actio assert function.native_call_slots[storage.native_position] is storage.native_call_slot with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_scalar_copy_in_out_reuses_one_binding_local_without_bridge_copy(): @@ -504,9 +506,9 @@ def test_scalar_copy_in_out_reuses_one_binding_local_without_bridge_copy(): assert value.bridge.data_action is BridgeDataAction.DIRECT_TRANSFER assert value.bridge.copy_reason is None - artifacts = WrapperCodeGenerator().generate(plan) - c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") - bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") + generated_wrapper = WrapperGenerator().generate(plan) + c_source = next(source.text for source in generated_wrapper.sources if source.path.suffix == ".c") + bridge_source = next(source.text for source in generated_wrapper.sources if source.path.suffix == ".f90") assert c_source.count("int32_t bound_value;") == 1 assert "prik_int32_unpack_exact(bound_value_obj, &bound_value)" in c_source @@ -522,7 +524,7 @@ def test_scalar_copy_in_out_reuses_one_binding_local_without_bridge_copy(): def test_generator_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, str(repository_root / "prik/codegen/generator.py")], + [sys.executable, str(repository_root / "prik/pipeline/wrapper.py")], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/codegen/test_source_printers.py b/tests/fortran/infrastructure/printers/test_source_printers.py similarity index 91% rename from tests/fortran/infrastructure/codegen/test_source_printers.py rename to tests/fortran/infrastructure/printers/test_source_printers.py index 8b54fa0e9..c74256256 100644 --- a/tests/fortran/infrastructure/codegen/test_source_printers.py +++ b/tests/fortran/infrastructure/printers/test_source_printers.py @@ -14,8 +14,6 @@ from tests.fortran._support.wrapper_build import REPO_ROOT from prik.codegen import ( BackendScalarType, - BindingModulePlan, - BridgeModulePlan, CDeclaration, CExpressionStatement, CFunction, @@ -25,7 +23,6 @@ CModule, CParameter, CReturn, - CSourcePrinter, CodeExpression, FortranAssignment, FortranCall, @@ -34,12 +31,11 @@ FortranModule, FortranParameter, FortranPointerAssignment, - FortranSourcePrinter, FortranUse, - ModulePlan, - NamespacePlan, UnsupportedWrapperCodegenNodeError, ) +from prik.planning import BindingModulePlan, BridgeModulePlan, ModulePlan, NamespacePlan +from prik.printers import CSourcePrinter, FortranSourcePrinter def test_source_printers_render_complete_c_header_and_fortran_modules(): @@ -211,20 +207,22 @@ def test_fortran_source_printer_rejects_an_overlong_token_without_a_safe_break() def test_source_printers_do_not_import_wrapper_plan_models(): - path = REPO_ROOT / "prik" / "codegen" / "printers" / "source_printers.py" - imports = { - node.module - for node in ast.walk(ast.parse(Path(path).read_text(encoding="utf-8"))) - if isinstance(node, ast.ImportFrom) and node.module is not None - } + imports = set() + for filename in ("c.py", "fortran.py"): + path = REPO_ROOT / "prik" / "printers" / filename + imports.update( + node.module + for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"))) + if isinstance(node, ast.ImportFrom) and node.module is not None + ) - assert "prik.codegen.plan" not in imports + assert "prik.planning.models" not in imports -def test_source_printers_direct_example_is_runnable(): +def test_fortran_source_printer_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, str(repository_root / "prik/codegen/printers/source_printers.py")], + [sys.executable, str(repository_root / "prik/printers/fortran.py")], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/semantics/test_native_array_handles.py b/tests/fortran/infrastructure/semantics/test_native_array_handles.py index f655e0080..b071a068f 100644 --- a/tests/fortran/infrastructure/semantics/test_native_array_handles.py +++ b/tests/fortran/infrastructure/semantics/test_native_array_handles.py @@ -1,6 +1,6 @@ """Internal native-array handle policy dispatch contracts.""" -from prik.semantics.native_array_handles import ( +from prik.policy.native_array_handles import ( ArrayInteropPolicy, ArrayInteropPolicyDispatcher, ) diff --git a/tests/fortran/infrastructure/semantics/test_ownership.py b/tests/fortran/infrastructure/semantics/test_ownership.py index ffc22ab34..79e679fc1 100644 --- a/tests/fortran/infrastructure/semantics/test_ownership.py +++ b/tests/fortran/infrastructure/semantics/test_ownership.py @@ -6,7 +6,7 @@ import pytest from prik.semantics.metadata import ADDRESS_ROLE_PROJECTION -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -22,8 +22,8 @@ StorageMode, TransferMode, default_ownership_policy, - set_ownership_metadata, ) +from prik.semantics.ownership_metadata import set_ownership_metadata from tests.fortran._support.ownership_policy import ( _address_type, _array_type, @@ -384,7 +384,7 @@ def test_ownership_policy_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, "prik/semantics/ownership.py"], + [sys.executable, "prik/policy/ownership.py"], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/semantics/test_policy_completion.py b/tests/fortran/infrastructure/semantics/test_policy_completion.py index 3d10a43ad..df6fd83f7 100644 --- a/tests/fortran/infrastructure/semantics/test_policy_completion.py +++ b/tests/fortran/infrastructure/semantics/test_policy_completion.py @@ -19,7 +19,7 @@ SemanticVariable, ) from prik.semantics.native_array_handles import native_array_descriptor_kind -from prik.semantics.ownership import ( +from prik.policy.ownership import ( AssignmentMode, CodegenAction, NativeBarrierAction, @@ -28,7 +28,7 @@ StorageMode, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.ownership_policy import ( _scalar_type, parse_pyi_text, @@ -47,7 +47,7 @@ POLICY_COMPLETION_PREPARED_METADATA, ProjectionMapping, ) -from prik.semantics.ownership import OwnershipOwner +from prik.policy.ownership import OwnershipOwner from tests.fortran._support.ownership_policy import _array_type @@ -281,7 +281,7 @@ def test_policy_completion_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, "prik/semantics/policy_completion.py"], + [sys.executable, "prik/policy/completion.py"], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/semantics/test_wrapper_policy.py b/tests/fortran/infrastructure/semantics/test_wrapper_policy.py index 0459ce449..cc9c252ee 100644 --- a/tests/fortran/infrastructure/semantics/test_wrapper_policy.py +++ b/tests/fortran/infrastructure/semantics/test_wrapper_policy.py @@ -6,7 +6,7 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from tests.fortran._support.wrapper_build import wrapper_source -from prik.codegen import WrapperPlanner +from prik.planning import WrapperPlanner from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules from prik.pipeline.preprocessing import PreprocessingConfig @@ -18,15 +18,15 @@ SemanticFunction, SemanticType, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, NativeBarrierAction, ObjectKind, PythonBarrierAction, StorageMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( ArgumentConversionPhase, ArgumentHandoffMode, BridgeDataAction, @@ -36,7 +36,7 @@ OptionalMode, PythonExceptionKind, ) -from prik.semantics.wrapper_policy import build_function_wrapper_policy, completed_function_wrapper_policy +from prik.policy.construction import build_function_wrapper_policy, completed_function_wrapper_policy FMATH_CONTRACT = Path("tests/fortran/data_types/end_to_end/fixtures/baseline/contracts/fmath/__init__.pyi") @@ -626,7 +626,7 @@ def test_wrapper_policy_direct_example_is_runnable(): repository_root = Path(__file__).resolve().parents[4] result = subprocess.run( - [sys.executable, "prik/semantics/wrapper_policy.py"], + [sys.executable, "prik/policy/construction.py"], cwd=repository_root, capture_output=True, check=True, diff --git a/tests/fortran/infrastructure/utilities/test_class_visitor.py b/tests/fortran/infrastructure/utilities/test_class_visitor.py index 34a10a3b1..39e32e9f6 100644 --- a/tests/fortran/infrastructure/utilities/test_class_visitor.py +++ b/tests/fortran/infrastructure/utilities/test_class_visitor.py @@ -14,8 +14,8 @@ from prik.utilities.visitor import ClassVisitor as SemanticClassVisitor from prik.codegen.c.binding import CBindingGenerator from prik.codegen.fortran.bridge import FortranBridgeGenerator -from prik.codegen.planner import WrapperPlanner -from prik.codegen.printers import PyiPrinter +from prik.planning.planner import WrapperPlanner +from prik.printers import PyiPrinter from prik.codegen.visitor import ClassVisitor as WrapperClassVisitor @@ -27,9 +27,9 @@ _ClassBodyVisitor, _ModuleVisitor, PyiPrinter, + WrapperPlanner, ) WRAPPER_VISITORS = ( - WrapperPlanner, CBindingGenerator, FortranBridgeGenerator, ) @@ -38,10 +38,10 @@ REPO_ROOT / "prik" / "semantics" / "c2ir.py", REPO_ROOT / "prik" / "semantics" / "fortran2ir.py", REPO_ROOT / "prik" / "semantics" / "pyi2ir.py", - REPO_ROOT / "prik" / "codegen" / "planner.py", + REPO_ROOT / "prik" / "planning" / "planner.py", REPO_ROOT / "prik" / "codegen" / "c" / "binding.py", REPO_ROOT / "prik" / "codegen" / "fortran" / "bridge.py", - REPO_ROOT / "prik" / "codegen" / "printers" / "pyi_printer.py", + REPO_ROOT / "prik" / "printers" / "pyi.py", ) diff --git a/tests/fortran/memory_management/codegen/test_native_handle_planning.py b/tests/fortran/memory_management/codegen/test_native_handle_planning.py index f31260c9d..8fab159d6 100644 --- a/tests/fortran/memory_management/codegen/test_native_handle_planning.py +++ b/tests/fortran/memory_management/codegen/test_native_handle_planning.py @@ -5,9 +5,9 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, ObjectKind, PythonBarrierAction -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.ownership import CodegenAction, ObjectKind, PythonBarrierAction +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( ArgumentHandoffMode, NativeArrayDescriptorInterop, NativeArrayDescriptorKind, @@ -21,7 +21,8 @@ NativeArraySourceKind, NativeDescriptorHandoffABI, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _native_handle_plan(): @@ -328,7 +329,7 @@ def test_module_variables_use_borrowed_handle_plans_and_operation_sets(): def test_deferred_character_module_handles_use_runtime_element_length(): - artifacts = WrapperCodeGenerator().generate(_module_handle_plan()) + artifacts = WrapperGenerator().generate(_module_handle_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -339,11 +340,11 @@ def test_deferred_character_module_handles_use_runtime_element_length(): def test_generated_native_handle_artifacts_follow_one_typed_action_vocabulary(): - artifacts = WrapperCodeGenerator().generate(_native_handle_plan()) + artifacts = WrapperGenerator().generate(_native_handle_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") - assert artifacts.artifacts.required_headers == ("ISO_Fortran_binding.h",) + assert artifacts.required_headers == ("ISO_Fortran_binding.h",) assert "prik_array_actual_unpack(" in c_source assert '"_native_array_descriptor_argument_for_binding_positional"' in c_source assert '"_native_array_descriptor_handoff_for_binding_positional"' in c_source @@ -397,7 +398,7 @@ def test_generated_native_handle_artifacts_follow_one_typed_action_vocabulary(): def test_constant_owned_handle_operations_do_not_emit_unused_descriptor_locals(): - artifacts = WrapperCodeGenerator().generate(_native_handle_plan()) + artifacts = WrapperGenerator().generate(_native_handle_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") for operation in ("aligned", "descriptor", "destroy", "layout", "native_byte_order", "writeable"): @@ -469,7 +470,7 @@ def test_native_handle_plan_edits_fail_central_validation(edit: str, diagnostic: plan.required_headers = () with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_plain_module_descriptor_view_requires_matching_completed_interop(): @@ -480,4 +481,4 @@ def test_plain_module_descriptor_view_requires_matching_completed_interop(): plain.native_array_handle.required_headers = () with pytest.raises(ValueError, match="missing-module-allocatable-descriptor-interop"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/memory_management/policy/test_memory_ownership_policy.py b/tests/fortran/memory_management/policy/test_memory_ownership_policy.py index 1b37e2122..90f4971d3 100644 --- a/tests/fortran/memory_management/policy/test_memory_ownership_policy.py +++ b/tests/fortran/memory_management/policy/test_memory_ownership_policy.py @@ -2,7 +2,7 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.semantics.models import RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies def test_scalar_storage_rejects_incompatible_explicit_ownership_metadata(): diff --git a/tests/fortran/modules/codegen/test_scalar_module_variable_lowering.py b/tests/fortran/modules/codegen/test_scalar_module_variable_lowering.py index 85bdad0b0..c5f17d25d 100644 --- a/tests/fortran/modules/codegen/test_scalar_module_variable_lowering.py +++ b/tests/fortran/modules/codegen/test_scalar_module_variable_lowering.py @@ -11,10 +11,11 @@ from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _merge_wrapper_modules from prik.semantics.fortran2ir import fortran_project_to_semantic_modules -from prik.semantics.ownership import AssignmentMode, SetterAction -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ModuleGetterAction -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.ownership import AssignmentMode, SetterAction +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ModuleGetterAction +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner from prik.codegen.c.binding import CBindingGenerator from prik.codegen.fortran.bridge import FortranBridgeGenerator @@ -114,7 +115,7 @@ def test_symbolic_source_parameter_reuses_scalar_bridge_getter_for_module_initia assert computed.bridge.getter_role == "computed_constants.computed:getter" assert computed.binding.setter_action is SetterAction.OMIT - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = _source(artifacts, ".c") fortran_source = _source(artifacts, ".f90") assert "int32_t bind_c_get_computed(void);" in c_source @@ -141,7 +142,7 @@ def test_parameter_array_uses_one_immutable_python_owned_import_snapshot(): assert variable.array is not None assert variable.array.shape == ("3",) - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = _source(artifacts, ".c") fortran_source = _source(artifacts, ".f90") assert "void * bind_c_get_dpmpar(int64_t * extent_0);" in c_source @@ -217,7 +218,7 @@ def test_module_setter_assignment_mismatch_fails_before_backend_preflight_or_low fortran_generator = Mock(spec=FortranBridgeGenerator) c_printer = Mock() fortran_printer = Mock() - generator = WrapperCodeGenerator( + generator = WrapperGenerator( c_generator=c_generator, fortran_generator=fortran_generator, c_printer=c_printer, @@ -238,7 +239,7 @@ def test_module_setter_assignment_mismatch_fails_before_backend_preflight_or_low def test_module_variable_generators_dispatch_get_set_and_rejection_from_plan(): - artifacts = WrapperCodeGenerator().generate(_plan()) + artifacts = WrapperGenerator().generate(_plan()) c_source = _source(artifacts, ".c") fortran_source = _source(artifacts, ".f90") @@ -279,7 +280,7 @@ def test_generator_rejects_python_module_setter_without_bridge_handoff(): ) with pytest.raises(ValueError, match="missing-module-setter-role"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_binding_bridge_module_getter_disagreement(): @@ -304,4 +305,4 @@ def test_generator_rejects_binding_bridge_module_getter_disagreement(): ) with pytest.raises(ValueError, match="inconsistent-module-getter-action"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) diff --git a/tests/fortran/modules/policy/test_module_variable_policy.py b/tests/fortran/modules/policy/test_module_variable_policy.py index 3b4b6c399..a69b76a0b 100644 --- a/tests/fortran/modules/policy/test_module_variable_policy.py +++ b/tests/fortran/modules/policy/test_module_variable_policy.py @@ -1,15 +1,15 @@ """Completed policy for editable module-variable initialization.""" -from prik.semantics.ownership import SetterAction -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.ownership import SetterAction +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.ownership_policy import parse_pyi_text from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _merge_wrapper_modules -from prik.codegen.printers.pyi_printer import PyiPrinter +from prik.printers.pyi import PyiPrinter from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import RESOLVED_MODULE_VARIABLE_POLICY_METADATA -from prik.semantics.ownership import AssignmentMode -from prik.semantics.wrapper_policy_models import ModuleGetterAction, ModuleVariablePolicy +from prik.policy.ownership import AssignmentMode +from prik.policy.models import ModuleGetterAction, ModuleVariablePolicy def test_scalar_module_variable_policy_completes_access_and_storage_before_planning(): diff --git a/tests/fortran/optional_arguments/codegen/test_optional_lowering.py b/tests/fortran/optional_arguments/codegen/test_optional_lowering.py index e271bc53f..d7911cbd8 100644 --- a/tests/fortran/optional_arguments/codegen/test_optional_lowering.py +++ b/tests/fortran/optional_arguments/codegen/test_optional_lowering.py @@ -9,9 +9,10 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.pipeline.pyi import pyi_file_to_semantic_module -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import BridgeDataAction, OptionalMode -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import BridgeDataAction, OptionalMode +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner OPTIONAL_FIXED_CONTRACT = ( @@ -21,7 +22,7 @@ def _artifacts(module): complete_semantic_policies(module) - return WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + return WrapperGenerator().generate(WrapperPlanner().build(module)) def _source(artifacts, suffix: str) -> str: @@ -41,7 +42,7 @@ def test_optional_scalar_lowering_distinguishes_absent_or_none_from_value(): assert factor.binding.optional_mode is OptionalMode.NULLABLE_VALUE assert factor.bridge.optional_mode is OptionalMode.NULLABLE_VALUE - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = _source(artifacts, ".c") fortran_source = _source(artifacts, ".f90") @@ -71,7 +72,7 @@ def alloc_state(value: Annotated[Float64, Immutable] | None = ...) -> Int32: ... assert value.bridge.presence_role == "scalar_optional_descriptors.alloc_state.value:present" assert value.bridge.data_action is BridgeDataAction.COPY_REPRESENTATION assert value.bridge.copy_reason == "materialize owned Fortran allocatable scalar storage from the binding value" - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = _source(artifacts, ".c") fortran_source = _source(artifacts, ".f90") @@ -97,7 +98,7 @@ def optional_literal(value: Annotated[Float64, Immutable] | None = ...) -> Float complete_semantic_policies(module) with pytest.raises(ValueError, match="optional-native-literal-combination"): - WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + WrapperGenerator().generate(WrapperPlanner().build(module)) def test_required_descriptor_keeps_python_presence_separate_from_native_state_and_copyout(): @@ -117,7 +118,7 @@ def update(value: Float64 | None) -> Returns["value", Float64] | None: ... assert value.bridge.descriptor_output_role == f"{value.owner_path}:descriptor-output" assert value.bridge.descriptor_output_presence_role == f"{value.owner_path}:descriptor-output-present" - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = _source(artifacts, ".c") fortran_source = _source(artifacts, ".f90") @@ -148,4 +149,4 @@ def alloc_state(value: Annotated[Float64, Immutable] | None = ...) -> Int32: ... invalid = _replace_root_function(plan, replace(function, arguments=(invalid_argument,))) with pytest.raises(ValueError, match="missing-descriptor-presence-role"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) diff --git a/tests/fortran/optional_arguments/policy/test_optional_policy.py b/tests/fortran/optional_arguments/policy/test_optional_policy.py index 067a22fa0..1094c7b3a 100644 --- a/tests/fortran/optional_arguments/policy/test_optional_policy.py +++ b/tests/fortran/optional_arguments/policy/test_optional_policy.py @@ -11,14 +11,14 @@ from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( ArgumentHandoffMode, BridgeDataAction, FunctionWrapperPolicy, OptionalMode, ) -from prik.semantics.wrapper_policy import completed_function_wrapper_policy +from prik.policy.construction import completed_function_wrapper_policy FMATH_CONTRACT = Path("tests/fortran/data_types/end_to_end/fixtures/baseline/contracts/fmath/__init__.pyi") diff --git a/tests/fortran/pointers/codegen/test_pointer_lowering.py b/tests/fortran/pointers/codegen/test_pointer_lowering.py index 8c521cd00..a3019f155 100644 --- a/tests/fortran/pointers/codegen/test_pointer_lowering.py +++ b/tests/fortran/pointers/codegen/test_pointer_lowering.py @@ -1,15 +1,16 @@ """Pointer descriptor lowering from completed wrapper policy.""" from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( NativeArrayDescriptorKind, NativeArrayDescriptorOwnership, NativeArrayOperation, NativeArrayResultAllocation, NativeDescriptorHandoffABI, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _pointer_plan(): @@ -102,7 +103,7 @@ def test_pointer_plans_complete_descriptor_ownership_and_operations_before_lower def test_pointer_lowering_assigns_descriptors_without_target_deallocation(): - artifacts = WrapperCodeGenerator().generate(_pointer_plan()) + artifacts = WrapperGenerator().generate(_pointer_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") diff --git a/tests/fortran/pointers/pipeline/test_pointer_build_manifest.py b/tests/fortran/pointers/pipeline/test_pointer_build_manifest.py index 0b7cfb71b..09a4d414b 100644 --- a/tests/fortran/pointers/pipeline/test_pointer_build_manifest.py +++ b/tests/fortran/pointers/pipeline/test_pointer_build_manifest.py @@ -1,7 +1,7 @@ """Pointer descriptor build requirements in the generated manifest.""" from prik.pipeline.build import _manifest_native_array_requirements -from prik.semantics.native_array_handles import NativeArrayBuildRequirement, NativeArrayBuildRequirements +from prik.policy.native_array_handles import NativeArrayBuildRequirement, NativeArrayBuildRequirements def test_pyi_manifest_records_pointer_descriptor_interop_requirements(): diff --git a/tests/fortran/pointers/policy/test_pointer_ownership_policy.py b/tests/fortran/pointers/policy/test_pointer_ownership_policy.py index d4077805f..46121032a 100644 --- a/tests/fortran/pointers/policy/test_pointer_ownership_policy.py +++ b/tests/fortran/pointers/policy/test_pointer_ownership_policy.py @@ -1,17 +1,17 @@ """Completed pointer ownership and native-array handle policy.""" import pytest -from prik.codegen.printers import PyiPrinter +from prik.printers import PyiPrinter from prik.semantics.models import ( RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA, RESOLVED_OWNERSHIP_POLICY_METADATA, ) -from prik.semantics.native_array_handles import ( +from prik.policy.native_array_handles import ( NativeArrayBuildRequirement, NativeArrayHandlePolicyDispatcher, native_array_handle_build_requirements, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -19,9 +19,9 @@ OwnershipOwner, TransferMode, default_ownership_policy, - set_ownership_metadata, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.semantics.ownership_metadata import set_ownership_metadata +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.ownership_policy import ( _array_type, _derived_type, diff --git a/tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py b/tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py index 2f647f5a5..f8f64222f 100644 --- a/tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py +++ b/tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py @@ -5,10 +5,11 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, NativeBarrierAction, ObjectKind -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import WritebackPhase -from prik.codegen import DatatypeFamily, WrapperCodeGenerator, WrapperPlanner +from prik.policy.ownership import CodegenAction, NativeBarrierAction, ObjectKind +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import WritebackPhase +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import DatatypeFamily, WrapperPlanner def _plan(source: str, *, module_name: str): @@ -18,7 +19,7 @@ def _plan(source: str, *, module_name: str): def _rendered_c(plan) -> str: - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) return next(source.text for source in artifacts.sources if source.path.suffix == ".c") diff --git a/tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py b/tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py index bcd85e9d2..2f3ebe855 100644 --- a/tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py +++ b/tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py @@ -2,9 +2,9 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.semantics.models import RESOLVED_OWNERSHIP_POLICY_METADATA -from prik.semantics.ownership import CodegenAction, ObjectKind, StorageMode -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy import completed_function_wrapper_policy +from prik.policy.ownership import CodegenAction, ObjectKind, StorageMode +from prik.policy.completion import complete_semantic_policies +from prik.policy.construction import completed_function_wrapper_policy def _completed_policy(source: str): diff --git a/tests/fortran/pyi_contracts/exports_and_modules/codegen/test_module_initializer_lowering.py b/tests/fortran/pyi_contracts/exports_and_modules/codegen/test_module_initializer_lowering.py index 08d0aac07..bc6e3c86c 100644 --- a/tests/fortran/pyi_contracts/exports_and_modules/codegen/test_module_initializer_lowering.py +++ b/tests/fortran/pyi_contracts/exports_and_modules/codegen/test_module_initializer_lowering.py @@ -1,8 +1,9 @@ """Wrapper lowering selected by completed module initializer policy.""" from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def test_module_variable_literal_families_select_their_c_spelling(): @@ -17,7 +18,7 @@ def test_module_variable_literal_families_select_their_c_spelling(): ) complete_semantic_policies(module) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(module)) c_source = next(item.text for item in artifacts.sources if item.path.name.endswith(".c")) assert "bind_c_set_enabled(true);" in c_source diff --git a/tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py b/tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py index 0f8dcbb29..75d7c4de1 100644 --- a/tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py +++ b/tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py @@ -11,8 +11,8 @@ SemanticType, SemanticVariable, ) -from prik.semantics.ownership import SetterAction -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.ownership import SetterAction +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.ownership_policy import _scalar_type from prik.semantics.models import RESOLVED_MODULE_VARIABLE_POLICY_METADATA diff --git a/tests/fortran/pyi_contracts/functions_and_classes/codegen/test_constructor_lowering.py b/tests/fortran/pyi_contracts/functions_and_classes/codegen/test_constructor_lowering.py index 10a2e7a5f..d21f7497c 100644 --- a/tests/fortran/pyi_contracts/functions_and_classes/codegen/test_constructor_lowering.py +++ b/tests/fortran/pyi_contracts/functions_and_classes/codegen/test_constructor_lowering.py @@ -1,8 +1,9 @@ """Lowering selected for an edited direct native constructor.""" from prik.pipeline.pyi import pyi_text_to_semantic_module -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def test_bound_constructor_generates_one_initializer_without_keyword_default(): @@ -21,7 +22,7 @@ def __init__(self, seed: Addr(Int32)) -> None: ... ) complete_semantic_policies(module) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(module)) sources = {source.path.suffix: source.text for source in artifacts.sources} assert {path.name for path in artifacts.source_paths} == { diff --git a/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py b/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py index c56289223..48bc04150 100644 --- a/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py +++ b/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py @@ -6,9 +6,10 @@ from prik import pyi_text_to_semantic_module from prik.pipeline.pyi import pyi_file_to_semantic_module -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ClassInvocationKind, OverloadMatchKind -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ClassInvocationKind, OverloadMatchKind +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner FIXTURES = Path(__file__).parents[1] / "end_to_end" / "fixtures" / "edited_contracts" METHOD_AND_CONSTRUCTOR = FIXTURES / "method_and_constructor" / "fclasses_f90.pyi" @@ -72,8 +73,8 @@ def move(item: point, dx: Float64) -> None: ... assert private_method.class_call is not None assert private_method.class_call.invocation is ClassInvocationKind.MODULE_PROCEDURE assert all(function.binding.python_name != "move" for function in private_plan.namespaces[0].functions) - WrapperCodeGenerator().generate(public_plan) - WrapperCodeGenerator().generate(private_plan) + WrapperGenerator().generate(public_plan) + WrapperGenerator().generate(private_plan) def test_bound_constructor_and_method_reuse_completed_direct_function_plans(): @@ -93,6 +94,7 @@ def test_bound_constructor_and_method_reuse_completed_direct_function_plans(): assert method.class_call.passed_object_position == 1 assert method.class_call.invocation is ClassInvocationKind.MODULE_PROCEDURE assert constructor.bridge.native_name == method.bridge.native_name == "shift_vector" + WrapperGenerator().generate(plan) assert "Constructor\n-----------\nvector(dx, dy) -> vector" in surface.docstring assert "shift(dx, dy) -> None" in surface.methods[0].docstring @@ -123,7 +125,7 @@ def initialize_point(left: point, owner: point, right: point) -> None: ... ) assert module_initializer.binding.public is True assert module_initializer is not target - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan(): @@ -147,11 +149,15 @@ def test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan(): ) assert {candidate.bridge.native_name for candidate in overload.candidates} == {"add"} + WrapperGenerator().generate(plan) assert "add(value: int32) -> None" in method.docstring assert "add(value: float64) -> None" in method.docstring assert "accumulator(value: int32) -> accumulator" in surface.constructor.docstring assert "accumulator(value: float64) -> accumulator" in surface.constructor.docstring + plan = _plan(OVERLOADED_API) + surface = _surface(plan, "accumulator") + method = next(overload for overload in surface.overloads if overload.python_name == "add") method.candidate_matches = (method.candidate_matches[0], method.candidate_matches[0]) with pytest.raises(ValueError, match="ambiguous-overload"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py b/tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py index 493d2c671..dba670f83 100644 --- a/tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py +++ b/tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py @@ -3,7 +3,7 @@ import pytest import re from dataclasses import asdict -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.metadata import ( BIND_TARGET_METADATA, SUPPRESS_DEFAULT_CONSTRUCTOR_METADATA, diff --git a/tests/fortran/raw_addresses/codegen/test_raw_array_lowering.py b/tests/fortran/raw_addresses/codegen/test_raw_array_lowering.py index 263f4b128..ba5fd9717 100644 --- a/tests/fortran/raw_addresses/codegen/test_raw_array_lowering.py +++ b/tests/fortran/raw_addresses/codegen/test_raw_array_lowering.py @@ -5,7 +5,7 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -15,10 +15,11 @@ StorageMode, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ArgumentHandoffMode, BridgeDataAction -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import DatatypeFamily +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ArgumentHandoffMode, BridgeDataAction +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.planning.models import DatatypeFamily def _raw_array_module(): @@ -89,7 +90,7 @@ def test_raw_array_addresses_use_one_shared_transfer_and_shape_plan(): def test_raw_array_addresses_reuse_integer_extraction_and_named_array_bridge_association(): - artifacts = WrapperCodeGenerator().generate(_raw_array_plan()) + artifacts = WrapperGenerator().generate(_raw_array_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -175,4 +176,4 @@ def test_raw_array_plan_edits_fail_before_backend_lowering(edit: str, diagnostic argument.native_call_slot = function.native_call_slots[0] with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/raw_addresses/codegen/test_scalar_address_lowering.py b/tests/fortran/raw_addresses/codegen/test_scalar_address_lowering.py index 02dea335d..5a89115f7 100644 --- a/tests/fortran/raw_addresses/codegen/test_scalar_address_lowering.py +++ b/tests/fortran/raw_addresses/codegen/test_scalar_address_lowering.py @@ -4,10 +4,11 @@ from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, NativeBarrierAction, ObjectKind, PythonBarrierAction -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ArgumentHandoffMode, BridgeDataAction, DirectResultABI -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.ownership import CodegenAction, NativeBarrierAction, ObjectKind, PythonBarrierAction +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ArgumentHandoffMode, BridgeDataAction, DirectResultABI +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _scalar_boundary_plan(): @@ -69,7 +70,7 @@ def test_scalar_storage_and_raw_address_plans_keep_explicit_boundary_facts(): def test_scalar_storage_and_raw_address_lower_to_direct_named_paths(): - artifacts = WrapperCodeGenerator().generate(_scalar_boundary_plan()) + artifacts = WrapperGenerator().generate(_scalar_boundary_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") diff --git a/tests/fortran/raw_addresses/codegen/test_string_address_lowering.py b/tests/fortran/raw_addresses/codegen/test_string_address_lowering.py index 274eac39b..83aa998a4 100644 --- a/tests/fortran/raw_addresses/codegen/test_string_address_lowering.py +++ b/tests/fortran/raw_addresses/codegen/test_string_address_lowering.py @@ -5,7 +5,7 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -15,14 +15,15 @@ StorageMode, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( ArgumentHandoffMode, BridgeDataAction, RAW_STRING_ADDRESS_COPY_REASON, STRING_STORAGE_COPY_REASON, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _string_address_module(): @@ -78,7 +79,7 @@ def test_string_address_plans_keep_completed_ownership_length_and_copy_facts(): def test_string_addresses_dispatch_to_named_binding_and_bridge_lowering(): - artifacts = WrapperCodeGenerator().generate(_string_address_plan()) + artifacts = WrapperGenerator().generate(_string_address_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -147,4 +148,4 @@ def test_string_address_plan_edits_fail_before_backend_lowering(edit: str, diagn raw.result_position = 0 with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/raw_addresses/policy/test_raw_address_policy.py b/tests/fortran/raw_addresses/policy/test_raw_address_policy.py index ebd03b1df..de9e83736 100644 --- a/tests/fortran/raw_addresses/policy/test_raw_address_policy.py +++ b/tests/fortran/raw_addresses/policy/test_raw_address_policy.py @@ -10,7 +10,7 @@ from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -20,8 +20,8 @@ StorageMode, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( RAW_STRING_ADDRESS_COPY_REASON, STRING_STORAGE_COPY_REASON, ArgumentHandoffMode, diff --git a/tests/fortran/raw_addresses/semantics/test_raw_address_semantics.py b/tests/fortran/raw_addresses/semantics/test_raw_address_semantics.py index e938bf6a9..cacf30f25 100644 --- a/tests/fortran/raw_addresses/semantics/test_raw_address_semantics.py +++ b/tests/fortran/raw_addresses/semantics/test_raw_address_semantics.py @@ -1,12 +1,12 @@ """Tests split by stable ownership concept from `test_python_ast_contracts.py`.""" import pytest -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.semantics.metadata import ( ADDRESS_ROLE_METADATA, ADDRESS_ROLE_RAW, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from tests.fortran._support.pyi_conversion import parse_pyi_text diff --git a/tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py b/tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py index 406d4342b..f1044acca 100644 --- a/tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py +++ b/tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py @@ -6,7 +6,7 @@ import ast import pytest -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.contracts import CONTRACT_SYMBOLS from prik.semantics.models import ( ProjectionMapping, diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py b/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py index 7991bdea8..38e417d8c 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py @@ -2,7 +2,7 @@ import pytest from prik import parse_fortran_file as parse_fortran_source -from prik.codegen.printers import ( +from prik.printers import ( PyiPrinter, emit_module, ) @@ -20,7 +20,7 @@ ) from tests.fortran._support.printer_models import ( generate_pyi, - generate_wrapper_artifacts, + generate_wrapper, normalize, parse_pyi_text, rendered_source, @@ -436,7 +436,7 @@ def update(scale: Float64 | None = ..., target: Float64 | None = ...) -> None: . """, module_name="optional_scalar_descriptors", ) - artifacts = generate_wrapper_artifacts(loaded) + artifacts = generate_wrapper(loaded) bridge_source = rendered_source(artifacts, ".f90") c_wrapper = rendered_source(artifacts, ".c") diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py b/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py index f574153fa..4d3605527 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py @@ -2,11 +2,8 @@ import pytest from prik import parse_fortran_file as parse_fortran_source -from prik.codegen.printers import ( - PyiPrinter, - emit_module, - emit_module_stubs, -) +from prik.pipeline.pyi import emit_module_stubs +from prik.printers import PyiPrinter, emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik.semantics.models import ( ProjectionMapping, @@ -20,7 +17,7 @@ from tests.fortran._support.printer_models import ( OPERATOR_F90_SOURCE, generate_pyi, - generate_wrapper_artifacts, + generate_wrapper, normalize, parse_pyi_text, rendered_source, @@ -387,7 +384,7 @@ def test_defined_operator_pyi_generates_wrapper_sources_without_fortran_source() ) pyi = emit_module(semantic_module) loaded = parse_pyi_text(pyi, module_name=semantic_module.name) - generated = generate_wrapper_artifacts(loaded) + generated = generate_wrapper(loaded) assert [path.name for path in generated.source_paths] == [ "bind_c_foperators_f90_wrapper.f90", diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py b/tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py index bcf768a9d..ee534f976 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py @@ -9,7 +9,7 @@ from prik.pipeline import build as build_pipeline from prik.pipeline.build import _discover_pyi_imports, _pyi_contract_bundle, _pyi_dependency_path from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text -from prik.codegen.printers import emit_module +from prik.printers import emit_module FIXTURES = Path(__file__).parent / "fixtures" CONTRACT_FIXTURES = FIXTURES / "contracts" diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py b/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py index 09e02b1cb..d3af8deed 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py @@ -2,7 +2,7 @@ from prik import parse_fortran_file from prik.semantics.fortran2ir import fortran_module_to_semantic_module -from prik.codegen.printers import emit_module +from prik.printers import emit_module def test_modern_fortran_example_pyi_snapshot(): diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_conversion_smoke.py b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_conversion_smoke.py index 257d7c91c..63df217ea 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_conversion_smoke.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_conversion_smoke.py @@ -3,7 +3,7 @@ import pytest from prik.semantics.fortran2ir import fortran_module_to_semantic_module -from prik.codegen.printers import emit_module +from prik.printers import emit_module from tests.fortran._support.fixture_outputs import ( PARSER_FIXTURE_ROOT as TESTS_DIR, diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py index fc16e0f53..1cf49d52f 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py @@ -3,13 +3,15 @@ import prik import pytest from prik import parse_fortran_file as parse_fortran_source -from prik.codegen.printers import ( +from prik.printers import ( PyiPrinter, emit_module, +) +from prik.pipeline.pyi import ( emit_module_stubs, opaque_dependency_modules, + pyi_text_to_semantic_module as _parse_pyi_text, ) -from prik.pipeline.pyi import pyi_text_to_semantic_module as _parse_pyi_text from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik.semantics.models import ( SemanticArgument, diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py b/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py index f712d090b..7fffcab18 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py @@ -2,7 +2,7 @@ import pytest from prik import parse_fortran_file as parse_fortran_source -from prik.codegen.printers import ( +from prik.printers import ( PyiPrinter, emit_module, ) diff --git a/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py b/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py index 120dd5102..be7d7d446 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py @@ -4,7 +4,7 @@ import pytest from dataclasses import asdict from prik import parse_fortran_file -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.contracts import CONTRACT_SYMBOLS from prik.semantics.fortran2ir import fortran_file_to_semantic_modules from prik.semantics.metadata import ( @@ -20,7 +20,7 @@ SemanticModule, SemanticType, ) -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from prik.semantics.pyi2ir import _PyiAstParser from tests.fortran._support.pyi_conversion import parse_pyi_text diff --git a/tests/fortran/semantic_pyi_format/semantics/test_classes_and_overloads.py b/tests/fortran/semantic_pyi_format/semantics/test_classes_and_overloads.py index 5f5e30136..c335de986 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_classes_and_overloads.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_classes_and_overloads.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_python_ast_contracts.py`.""" -from prik.codegen.printers import emit_module +from prik.printers import emit_module from tests.fortran._support.pyi_conversion import parse_pyi_text diff --git a/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py b/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py index 6c2727910..56e463e61 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py @@ -3,7 +3,7 @@ import pytest from pathlib import Path from prik import parse_fortran_file -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.pipeline.pyi import ( pyi_file_to_semantic_module, pyi_paths_to_semantic_modules, diff --git a/tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py b/tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py index 19f28f216..13cae8693 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py @@ -5,7 +5,7 @@ given, strategies as st, ) -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text from prik.semantics.models import ( EXTERNAL_TYPE_REF_METADATA, diff --git a/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py b/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py index 741237cc3..95205da28 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py @@ -3,7 +3,7 @@ import ast import pytest from prik import parse_fortran_file -from prik.codegen.printers import emit_module +from prik.printers import emit_module from prik.pipeline.pyi import pyi_text_to_semantic_module from prik.semantics.fortran2ir import fortran_file_to_semantic_modules from prik.semantics.metadata import ( diff --git a/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py b/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py index 78768bbba..860dad4bc 100644 --- a/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py +++ b/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py @@ -7,7 +7,7 @@ FortranParseError, parse_fortran_file, ) -from prik.codegen.printers import emit_module_stubs +from prik.pipeline.pyi import emit_module_stubs from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text from prik.semantics.fortran2ir import fortran_file_to_semantic_modules from tests.fortran._support.parser_properties import ( diff --git a/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py b/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py index 1a6b106f4..60952bc81 100644 --- a/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py +++ b/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py @@ -7,7 +7,7 @@ import pytest from prik.semantics.fortran2ir import fortran_file_to_semantic_modules -from prik.codegen.printers import emit_module_stubs +from prik.pipeline.pyi import emit_module_stubs from prik import parse_fortran_file pytestmark = pytest.mark.skip(reason="Benchmarks are parked until benchmark adoption resumes.") diff --git a/tests/fortran/strings/codegen/test_character_array_lowering.py b/tests/fortran/strings/codegen/test_character_array_lowering.py index f179ee48f..a21dc8ffd 100644 --- a/tests/fortran/strings/codegen/test_character_array_lowering.py +++ b/tests/fortran/strings/codegen/test_character_array_lowering.py @@ -5,11 +5,12 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, NativeBarrierAction, ObjectKind -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import BridgeDataAction -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import DatatypeFamily +from prik.policy.ownership import CodegenAction, NativeBarrierAction, ObjectKind +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import BridgeDataAction +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.planning.models import DatatypeFamily def _later_array_plan(): @@ -48,7 +49,7 @@ def test_character_itemsize_edit_fails_before_backend_lowering(): character.itemsize_role = None with pytest.raises(ValueError, match="invalid-array-itemsize"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_fixed_width_character_array_results_reuse_the_ordinary_array_copy_plan(): @@ -70,7 +71,7 @@ def test_fixed_width_character_array_results_reuse_the_ordinary_array_copy_plan( def test_fixed_width_character_array_results_lower_itemsize_into_both_backends(): - artifacts = WrapperCodeGenerator().generate(_character_array_result_plan()) + artifacts = WrapperGenerator().generate(_character_array_result_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -99,4 +100,4 @@ def test_fixed_width_character_array_result_itemsize_edit_fails_before_lowering( result.array.itemsize = None with pytest.raises(ValueError, match="invalid-array-result-itemsize"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/strings/codegen/test_fixed_string_result_lowering.py b/tests/fortran/strings/codegen/test_fixed_string_result_lowering.py index 84cadaa3f..a7cba59ae 100644 --- a/tests/fortran/strings/codegen/test_fixed_string_result_lowering.py +++ b/tests/fortran/strings/codegen/test_fixed_string_result_lowering.py @@ -6,7 +6,7 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.semantics.models import RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -16,10 +16,11 @@ StorageMode, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import BridgeDataAction -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import DatatypeFamily +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import BridgeDataAction +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.planning.models import DatatypeFamily _COPY_REASON = "copy fixed-length Fortran character output into C-owned null-terminated storage" @@ -78,7 +79,7 @@ def test_fixed_strings_reuse_ordered_result_plans_with_completed_length_and_copy def test_fixed_string_results_dispatch_to_named_binding_and_bridge_copy_lowering(): - artifacts = WrapperCodeGenerator().generate(_fixed_string_plan()) + artifacts = WrapperGenerator().generate(_fixed_string_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -153,7 +154,7 @@ def test_fixed_string_result_plan_edits_fail_before_backend_lowering(edit: str, hidden.native_call_slot.character_length = 7 with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_fixed_string_result_policy_uses_ordered_mixed_result_cleanup(): @@ -174,9 +175,7 @@ def mixed() -> tuple[String[8], Int32]: ... function = plan.namespaces[0].functions[0] assert tuple(result.result_position for result in function.results) == (0, 1) - c_source = next( - source.text for source in WrapperCodeGenerator().generate(plan).sources if source.path.suffix == ".c" - ) + c_source = next(source.text for source in WrapperGenerator().generate(plan).sources if source.path.suffix == ".c") assert "free(result);" in c_source assert "PyTuple_New(2)" in c_source assert "Py_DECREF(result_0_obj);" in c_source diff --git a/tests/fortran/strings/codegen/test_fixed_string_writeback.py b/tests/fortran/strings/codegen/test_fixed_string_writeback.py index 36f747222..eb47886c7 100644 --- a/tests/fortran/strings/codegen/test_fixed_string_writeback.py +++ b/tests/fortran/strings/codegen/test_fixed_string_writeback.py @@ -7,7 +7,7 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, ObjectKind, @@ -15,16 +15,17 @@ StorageMode, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( BridgeDataAction, OptionalMode, PythonExceptionKind, STRING_REPLACEMENT_COPY_REASON, WritebackPhase, ) -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import BindingStatusErrorPlan, DatatypeFamily +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.planning.models import BindingStatusErrorPlan, DatatypeFamily def _fixed_writeback_module(): @@ -80,7 +81,7 @@ def test_fixed_replacement_projects_completed_argument_and_lifecycle_facts(): def test_fixed_string_writeback_dispatches_to_named_binding_and_bridge_lowering(): - artifacts = WrapperCodeGenerator().generate(_fixed_writeback_plan()) + artifacts = WrapperGenerator().generate(_fixed_writeback_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -113,7 +114,7 @@ def test_fixed_string_replacement_allocation_runs_after_other_argument_conversio module_name="fixed_string_cleanup_order", ) complete_semantic_policies(module) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(module)) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") assert c_source.index("prik_int32_unpack_exact(bound_count_obj, &bound_count)") < c_source.index( @@ -152,7 +153,7 @@ def optional_identity(label: String = ...) -> None: ... module_name="assumed_optional_string_writeback", ) complete_semantic_policies(module) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(module)) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -205,7 +206,7 @@ def test_fixed_string_writeback_plan_edits_fail_before_backend_lowering(edit: st argument.bridge.optional_mode = OptionalMode.DESCRIPTOR with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) def test_fixed_string_writeback_status_edit_fails_at_generator_validation(): @@ -222,4 +223,4 @@ def test_fixed_string_writeback_status_edit_fails_at_generator_validation(): ) with pytest.raises(ValueError, match="string-writeback-with-status-error"): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/strings/codegen/test_string_input_lowering.py b/tests/fortran/strings/codegen/test_string_input_lowering.py index 6d4eb6a7a..5fb29c13c 100644 --- a/tests/fortran/strings/codegen/test_string_input_lowering.py +++ b/tests/fortran/strings/codegen/test_string_input_lowering.py @@ -5,11 +5,12 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.ownership import CodegenAction, NativeBarrierAction, PythonBarrierAction -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ArgumentHandoffMode, BridgeDataAction -from prik.codegen import WrapperCodeGenerator, WrapperPlanner -from prik.codegen.plan import DatatypeFamily +from prik.policy.ownership import CodegenAction, NativeBarrierAction, PythonBarrierAction +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ArgumentHandoffMode, BridgeDataAction +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner +from prik.planning.models import DatatypeFamily def _string_input_module(): @@ -55,7 +56,7 @@ def test_required_string_values_reuse_argument_plan_with_character_handoff_facts def test_required_string_values_dispatch_to_named_binding_and_bridge_lowering(): - artifacts = WrapperCodeGenerator().generate(_string_input_plan()) + artifacts = WrapperGenerator().generate(_string_input_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -101,4 +102,4 @@ def test_string_handoff_plan_edits_fail_before_backend_lowering(edit: str, diagn argument.native_call_slot.bridge_copy_reason = None with pytest.raises(ValueError, match=diagnostic): - WrapperCodeGenerator().generate(plan) + WrapperGenerator().generate(plan) diff --git a/tests/fortran/strings/policy/test_string_wrapper_policy.py b/tests/fortran/strings/policy/test_string_wrapper_policy.py index d5dae5dd0..2cf1ffeeb 100644 --- a/tests/fortran/strings/policy/test_string_wrapper_policy.py +++ b/tests/fortran/strings/policy/test_string_wrapper_policy.py @@ -10,7 +10,7 @@ from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, ) -from prik.semantics.ownership import ( +from prik.policy.ownership import ( CodegenAction, DestructionPolicy, NativeBarrierAction, @@ -19,8 +19,8 @@ PythonBarrierAction, TransferMode, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import ( ArgumentConversionPhase, ArgumentHandoffMode, BridgeDataAction, diff --git a/tests/fortran/strings/semantics/test_string_pyi_semantics.py b/tests/fortran/strings/semantics/test_string_pyi_semantics.py index 776cb0b84..d6025e4d5 100644 --- a/tests/fortran/strings/semantics/test_string_pyi_semantics.py +++ b/tests/fortran/strings/semantics/test_string_pyi_semantics.py @@ -1,7 +1,7 @@ """Tests split by stable ownership concept from `test_python_ast_contracts.py`.""" import pytest -from prik.codegen.printers import emit_module +from prik.printers import emit_module from tests.fortran._support.pyi_conversion import parse_pyi_text diff --git a/tests/fortran/subroutines/codegen/test_hidden_scalar_outputs.py b/tests/fortran/subroutines/codegen/test_hidden_scalar_outputs.py index 33fd498e9..7796dabd4 100644 --- a/tests/fortran/subroutines/codegen/test_hidden_scalar_outputs.py +++ b/tests/fortran/subroutines/codegen/test_hidden_scalar_outputs.py @@ -3,8 +3,9 @@ from __future__ import annotations from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def test_hidden_scalar_result_is_one_bridge_output_and_one_python_result(): @@ -24,7 +25,7 @@ def scale(x: Float64) -> Float64: ... assert result.native_call_slot is function.native_call_slots[result.bridge.abi_position] - artifacts = WrapperCodeGenerator().generate(plan) + artifacts = WrapperGenerator().generate(plan) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") fortran_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") @@ -53,7 +54,7 @@ def scale( module_name="hidden_result_explicit_interface", ) complete_semantic_policies(module) - artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + artifacts = WrapperGenerator().generate(WrapperPlanner().build(module)) fortran_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") native_interface = fortran_source.split("subroutine SCALE_OUT(x, result, mode)", maxsplit=1)[1].split( diff --git a/tests/fortran/subroutines/codegen/test_scalar_subroutine_writeback_validation.py b/tests/fortran/subroutines/codegen/test_scalar_subroutine_writeback_validation.py index 0dfed7358..49921de15 100644 --- a/tests/fortran/subroutines/codegen/test_scalar_subroutine_writeback_validation.py +++ b/tests/fortran/subroutines/codegen/test_scalar_subroutine_writeback_validation.py @@ -6,14 +6,15 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy_models import WritebackPhase -from prik.codegen import WrapperCodeGenerator, WrapperPlanner +from prik.policy.completion import complete_semantic_policies +from prik.policy.models import WritebackPhase +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner def _artifacts(module): complete_semantic_policies(module) - return WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + return WrapperGenerator().generate(WrapperPlanner().build(module)) def _source(artifacts, suffix: str) -> str: @@ -39,7 +40,7 @@ def test_generator_rejects_incomplete_writeback_phase_group(): ) with pytest.raises(ValueError, match="missing-writeback-phase"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_writeback_without_python_result_target(): @@ -59,7 +60,7 @@ def test_generator_rejects_writeback_without_python_result_target(): invalid = _replace_root_function(plan, replace(function, writeback_actions=actions)) with pytest.raises(ValueError, match="missing-python-writeback-target"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) def test_generator_rejects_writeback_from_an_unavailable_handoff(): @@ -82,4 +83,4 @@ def test_generator_rejects_writeback_from_an_unavailable_handoff(): invalid = _replace_root_function(plan, replace(function, writeback_actions=actions)) with pytest.raises(ValueError, match=r"unavailable-.*-role"): - WrapperCodeGenerator().generate(invalid) + WrapperGenerator().generate(invalid) diff --git a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py index 77a77aafe..3370a1786 100644 --- a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py +++ b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py @@ -6,11 +6,11 @@ from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules from prik.pipeline.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules -from prik.semantics.ownership import ( +from prik.policy.ownership import ( NativeBarrierAction, ) -from prik.semantics.policy_completion import complete_semantic_policies -from prik.semantics.wrapper_policy import ( +from prik.policy.completion import complete_semantic_policies +from prik.policy.construction import ( completed_function_wrapper_policy, ) diff --git a/tools/check_codegen_complexity.py b/tools/check_codegen_complexity.py index 2f683bc70..069f98700 100644 --- a/tools/check_codegen_complexity.py +++ b/tools/check_codegen_complexity.py @@ -2,15 +2,12 @@ from __future__ import annotations -from pathlib import Path - from prik.codegen.checks import check_codegen_package def main() -> int: """Print checker violations and return a process status for automation.""" - package_root = Path(__file__).resolve().parents[1] / "prik" / "codegen" - violations = check_codegen_package(package_root) + violations = check_codegen_package() for violation in violations: print(violation.label) return int(bool(violations)) diff --git a/tools/wrapper_plan_staged_walkthrough.py b/tools/wrapper_plan_staged_walkthrough.py index bbb8ccffe..0b8bf412b 100644 --- a/tools/wrapper_plan_staged_walkthrough.py +++ b/tools/wrapper_plan_staged_walkthrough.py @@ -15,13 +15,13 @@ from prik.pipeline import build as pipeline from prik.pipeline.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules -from prik.semantics.policy_completion import complete_semantic_policies +from prik.policy.completion import complete_semantic_policies from prik.codegen import ( CBindingGenerator, FortranBridgeGenerator, - WrapperCodeGenerator, - WrapperPlanner, ) +from prik.pipeline.wrapper import WrapperGenerator +from prik.planning import WrapperPlanner # Choose one starting point. Both paths then use the same plan, generator, and build steps. @@ -125,7 +125,7 @@ def calculate(x: Float64, y: Float64) -> Float64: ... # 3. Editable plan -> generated C binding and Fortran bridge sources. print("\n== GENERATED ARTIFACTS ==") -artifacts = WrapperCodeGenerator( +artifacts = WrapperGenerator( c_generator=binding_generator, fortran_generator=bridge_generator, ).generate(plan) @@ -146,13 +146,13 @@ def calculate(x: Float64, y: Float64) -> Float64: ... module_dirs=(build_dir,), include_dirs=(build_dir,), ) -build = pipeline._build_rendered_wrapper_extension( +build = pipeline._build_generated_wrapper_extension( artifacts, output_dir=build_dir, sources=(source,) if ENTRY == "fortran" else (contract,), native_build_plan=native_build_plan, native_dependencies=(native_object,), - native_link_args=pipeline._rendered_wrapper_native_link_args(native_build_plan), + native_link_args=pipeline._generated_wrapper_native_link_args(native_build_plan), compiler=compiler, ) sys.path.insert(0, str(build.output_dir)) From 769201548734e976853ea0de5b797d1883941216 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 10:56:23 +0100 Subject: [PATCH 16/22] remove top-level prik/types/ package. create semantics/scalar_types.py and codegen/primitive_scalar_types.py --- CHANGELOG.md | 4 + docs/developer/development-workflow.md | 9 +- docs/developer/repository-structure.md | 9 +- docs/developer/source-map.md | 17 +- .../maintainer/internal-architecture/index.md | 4 +- .../internal-architecture/pipeline-map.md | 9 +- .../internal-architecture/type-system.md | 374 +++++++++++++++++- .../documentation-content-checklist.md | 7 +- docs/user/reference/python-api.md | 14 +- mkdocs.yml | 2 +- prik/README.md | 9 +- prik/__init__.py | 15 - prik/cli.py | 2 +- prik/codegen/c/binding.py | 2 +- prik/codegen/docstrings.py | 2 +- prik/codegen/fortran/bridge.py | 2 +- prik/codegen/primitive_scalar_types.py | 219 ++++++---- prik/contracts/__init__.py | 67 +++- prik/pipeline/README.md | 1 + prik/pipeline/build.py | 2 +- .../type_mapping_report.py} | 16 +- prik/planning/planner.py | 2 +- prik/policy/completion.py | 2 +- prik/policy/construction.py | 2 +- prik/policy/ownership.py | 2 +- prik/printers/pyi.py | 5 +- prik/semantics/README.md | 2 + prik/semantics/c2ir.py | 2 +- prik/semantics/fortran2ir.py | 6 +- prik/semantics/pyi2ir.py | 2 +- prik/semantics/scalar_types.py | 110 ++++++ prik/types/__init__.py | 1 - prik/types/numpy.py | 117 ------ tests/docs/_structure_support.py | 4 + .../test_primitive_scalar_type_catalogue.py | 43 ++ .../pipeline/test_type_mapping_report.py} | 4 +- .../test_contract_scalar_constructors.py | 2 + .../semantics/test_scalar_type_catalogue.py | 43 ++ .../infrastructure/types/test_numpy.py | 70 ---- 39 files changed, 827 insertions(+), 378 deletions(-) rename prik/{probes/report.py => pipeline/type_mapping_report.py} (96%) create mode 100644 prik/semantics/scalar_types.py delete mode 100644 prik/types/__init__.py delete mode 100644 prik/types/numpy.py create mode 100644 tests/fortran/data_types/codegen/test_primitive_scalar_type_catalogue.py rename tests/fortran/{infrastructure/types/test_mapping_report.py => data_types/pipeline/test_type_mapping_report.py} (97%) create mode 100644 tests/fortran/data_types/semantics/test_scalar_type_catalogue.py delete mode 100644 tests/fortran/infrastructure/types/test_numpy.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3d1c2c0..86307b75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ release tags add a leading `v` to the package version. ### Changed +- Replaced the public semantic-to-NumPy helper API with stage-owned semantic, + contract-runtime, and code-generation datatype catalogues, and documented the + complete internal datatype lifecycle from compiler probing to runtime + validation. - Separated post-IR policy and wrapper planning into `prik.policy` and `prik.planning`; code generation now renders plan-driven docstrings, and the former maintainer import paths were removed. diff --git a/docs/developer/development-workflow.md b/docs/developer/development-workflow.md index 5e2ec53b7..cce44b183 100644 --- a/docs/developer/development-workflow.md +++ b/docs/developer/development-workflow.md @@ -208,7 +208,7 @@ implementation files. | Fortran parse output | `prik/parsers/fortran/parser.py`, `prik/parsers/fortran/models.py`, `prik/parsers/fortran/lexer.py` | `tests/fortran/source_parsing/parsing/`, `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py`, `tests/fortran/source_parsing/parsing/test_error_handling.py` | | CLI stage selection and output | `prik/cli.py`, `prik/parsers/fortran/cli.py` | `tests/fortran/command_line_interface/pipeline/` | | Fortran target type probing and cache | `prik/probes/fortran_types.py` | `tests/fortran/data_types/probes/test_fortran_type_probes.py` | -| Generated target datatype mapping examples | `prik/probes/report.py` | `tests/fortran/infrastructure/types/test_mapping_report.py`, `tests/docs/test_examples.py` | +| Generated target datatype mapping examples | `prik/pipeline/type_mapping_report.py` | `tests/fortran/data_types/pipeline/test_type_mapping_report.py`, `tests/docs/test_examples.py` | | Fortran to semantic IR | `prik/semantics/fortran2ir.py`, `prik/semantics/models.py` | `tests/fortran/semantic_ir/semantics/` | | `.pyi` printing | `prik/printers/pyi.py` | `tests/fortran/semantic_pyi_format/pipeline/`, `tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py` | | `.pyi` parsing/loading/editing | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py` | `tests/fortran/semantic_pyi_format/` | @@ -338,7 +338,10 @@ Mapping happens during parser-to-IR conversion: `prik/semantics/fortran2ir.py`. - The shared dtype names and storage contracts live in `prik/semantics/models.py`. - Compiler-measured mapping snapshots are generated by - `prik/probes/report.py`. + `prik/pipeline/type_mapping_report.py`. + +The complete ownership and lookup boundaries are documented for maintainers in +`docs/maintainer/internal-architecture/type-system.md`. | Package | Purpose | Main files | Primary tests and docs | | --- | --- | --- | --- | | `prik/contracts/` | Public semantic `.pyi` contract vocabulary | `__init__.py` | `tests/fortran/semantic_pyi_format/`, semantic `.pyi` reference | -| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, plan-to-source wrapper generation, and native build orchestration | `preprocessing.py`, `pyi.py`, `wrapper.py`, `build.py` | preprocessing, `.pyi`, wrapper generation, and build tests | -| `prik/probes/` | Compiler-derived target facts plus mapping reports | `fortran_types.py`, `report.py` | target probe and type mapping report tests | +| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, cross-stage datatype reporting, plan-to-source wrapper generation, and native build orchestration | `preprocessing.py`, `pyi.py`, `type_mapping_report.py`, `wrapper.py`, `build.py` | preprocessing, `.pyi`, datatype report, wrapper generation, and build tests | +| `prik/probes/` | Compiler-derived target facts | `fortran_types.py` | target probe tests | | `prik/runtime/` | Python runtime objects consumed by generated extensions | `handles.py` | runtime handle and wrapper runtime tests | -| `prik/types/` | Semantic-to-Python ecosystem type mappings | `numpy.py` | `tests/fortran/infrastructure/types/test_numpy.py` | | `prik/parsers/` | Public namespace for language and semantic `.pyi` frontends | child parser packages | `tests/fortran/source_parsing/parsing/`, `tests/c/parsing/`, `tests/fortran/semantic_pyi_format/parsing/` | | `prik/parsers/fortran/` | Fortran lexer, recursive parser, models, type resolver, and parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `type_resolver.py`, `cli.py` | `tests/fortran/source_parsing/parsing/`, `docs/developer/fortran-parser-reference.md` | -| `prik/semantics/` | Language-neutral semantic IR, source-to-IR conversion, `.pyi` conversion, and raw ownership or descriptor metadata | `models.py`, `fortran2ir.py`, `c2ir.py`, `pyi2ir.py`, `ownership_metadata.py`, `native_array_handles.py` | `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/` | +| `prik/semantics/` | Language-neutral semantic IR, scalar datatype vocabulary, source-to-IR conversion, `.pyi` conversion, and raw ownership or descriptor metadata | `models.py`, `scalar_types.py`, `fortran2ir.py`, `c2ir.py`, `pyi2ir.py`, `ownership_metadata.py`, `native_array_handles.py` | `tests/fortran/data_types/semantics/`, `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/` | | `prik/policy/` | Post-IR ownership, export, wrapper-policy construction, immutable policy models, descriptor-handle policy, and ordered completion | `ownership.py`, `exports.py`, `models.py`, `native_array_handles.py`, `construction.py`, `completion.py` | infrastructure semantics and feature-local policy tests | | `prik/planning/` | Editable backend-neutral wrapper-plan records and mechanical policy projection | `models.py`, `planner.py` | infrastructure and feature-local codegen tests | -| `prik/codegen/` | Plan-driven docstrings and direct lowering into C and Fortran syntax nodes | `docstrings.py`, `nodes.py`, `c/`, `fortran/` | infrastructure codegen, feature-local codegen, and end-to-end tests | +| `prik/codegen/` | Backend datatype projection, plan-driven docstrings, and direct lowering into C and Fortran syntax nodes | `primitive_scalar_types.py`, `docstrings.py`, `nodes.py`, `c/`, `fortran/` | data-type and infrastructure codegen, feature-local codegen, and end-to-end tests | | `prik/printers/` | Language-specific serialization of C nodes, Fortran nodes, and semantic IR | `c.py`, `fortran.py`, `pyi.py` | source-printer and semantic-contract printer tests | | `prik/compiling/` | Native compile objects, compiler command execution, shared-library linking, and native support installation; wrapper build orchestration lives in `prik/pipeline/build.py` | `objects.py`, `compilers.py`, `compiler_profiles.py`, `native_support.py` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py` | | `prik/binding_support/` | Bundled header-only native binding support copied into generated wrapper builds | support header | wrapper build tests | @@ -106,7 +108,9 @@ update this table, the package README files, and the mechanical checks in | `prik/cli.py` | CLI argument validation, stage selection, output routing, and wrapper-build entry. | | `prik/pipeline/build.py` | End-to-end source and `.pyi` wrapper build orchestration. | | `prik/pipeline/preprocessing.py` | Compiler-backed source preprocessing and dependency facts. | +| `prik/pipeline/type_mapping_report.py` | Target facts, semantic conversion, and backend NumPy projection rendered as a mapping report. | | `prik/probes/fortran_types.py` | Fortran kind and storage probing. | +| `prik/semantics/scalar_types.py` | Stable primitive scalar identities, families, and intrinsic storage widths. | | `prik/semantics/ownership_metadata.py` | Raw ownership and pointer-contract metadata keys and normalized semantic setters. | | `prik/semantics/native_array_handles.py` | Raw semantic descriptor-handle facts attached before policy completion. | | `prik/policy/ownership.py` | Central ownership, transfer, destruction, and generated-action policy. | @@ -127,6 +131,7 @@ update this table, the package README files, and the mechanical checks in | `prik/planning/planner.py` | Semantic policy to wrapper-plan conversion. | | `prik/naming/native_symbols.py` | Stable generated native-symbol construction shared by planning and code generation. | | `prik/codegen/docstrings.py` | Plan-driven Python-facing documentation generation. | +| `prik/codegen/primitive_scalar_types.py` | Primitive scalar backend and NumPy lowering catalogue. | | `prik/pipeline/wrapper.py` | Single plan-to-rendered-wrapper orchestration and generated-wrapper result records. | | `prik/codegen/fortran/bridge.py` | Direct Fortran bridge lowering from typed plans. | | `prik/codegen/c/binding.py` | Direct Python-extension binding lowering from typed plans. | diff --git a/docs/maintainer/internal-architecture/index.md b/docs/maintainer/internal-architecture/index.md index 5c9f5978a..81559de9e 100644 --- a/docs/maintainer/internal-architecture/index.md +++ b/docs/maintainer/internal-architecture/index.md @@ -17,7 +17,9 @@ details. They are separate from user guides and high-level design documents. - [Pipeline map](pipeline-map.md) - [AST design](ast-design.md) - [Symbol tables](symbol-tables.md) -- [Type system](type-system.md) +- [Datatype lifecycle](type-system.md): compiler probing, semantic scalar + identities, policy/planning boundaries, backend mappings, and runtime + validation. - [Semantic passes](semantic-passes.md) - [Dependency analysis](dependency-analysis.md) - [Wrapper generation pipeline](wrapper-generation-pipeline.md) diff --git a/docs/maintainer/internal-architecture/pipeline-map.md b/docs/maintainer/internal-architecture/pipeline-map.md index 7b8b37c44..17a940ce5 100644 --- a/docs/maintainer/internal-architecture/pipeline-map.md +++ b/docs/maintainer/internal-architecture/pipeline-map.md @@ -63,10 +63,11 @@ The Python package layout follows those ownership boundaries: | Package | Owns | Must not become | | --- | --- | --- | -| `prik/contracts/` | The public semantic `.pyi` vocabulary | A home for semantic conversion or runtime type mapping | -| `prik/types/` | Mappings from resolved semantic types to Python ecosystem types | A second semantic IR model | -| `prik/probes/` | Compiler-derived target facts and reports built from those facts | Semantic policy or build orchestration | -| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, and end-to-end wrapper build orchestration | Parser models, semantic decisions, or compiler implementation details | +| `prik/contracts/` | The public semantic `.pyi` vocabulary and its local runtime scalar factories | A home for semantic conversion or backend datatype lowering | +| `prik/semantics/scalar_types.py` | Stable scalar identities, families, and intrinsic storage facts | NumPy or generated-language spelling | +| `prik/codegen/primitive_scalar_types.py` | Semantic-to-backend and NumPy scalar projection for implemented lowering lanes | A reverse semantic inference service | +| `prik/probes/` | Compiler-derived target facts | Semantic policy, cross-stage reporting, or build orchestration | +| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, datatype mapping reports, and end-to-end wrapper build orchestration | Parser models, semantic decisions, or compiler implementation details | | `prik/runtime/` | Python objects used by generated extensions at execution time | Build-time semantic or codegen policy | | `prik/utilities/` | Small domain-neutral mechanisms such as class visitor dispatch | A miscellaneous home for semantic or pipeline concepts | diff --git a/docs/maintainer/internal-architecture/type-system.md b/docs/maintainer/internal-architecture/type-system.md index 6ff12b8d9..8a26545e9 100644 --- a/docs/maintainer/internal-architecture/type-system.md +++ b/docs/maintainer/internal-architecture/type-system.md @@ -1,19 +1,371 @@ --- -title: Type System +title: Datatype Lifecycle audience: maintainers -prerequisites: AST design, semantic IR -related: semantic-passes.md, ownership-tracking.md -status: planned-documentation +prerequisites: pipeline map, semantic IR +related: pipeline-map.md, wrapper-generation-pipeline.md, ownership-tracking.md, ../../user/reference/semantic-ir.md +status: maintained publication: draft --- -# Type System +# Datatype Lifecycle -Reserved maintainer page for native type facts, semantic datatypes, NumPy dtype -mapping, and target probing. +This page is the implementation contract for datatype handling inside PRIK. It +traces native declarations from compiler measurement and parsing through +semantic normalization, policy completion, wrapper planning, generated NumPy +boundaries, and runtime validation. It also identifies the separate registries +used at those stages and explains why they must not be collapsed into one +bidirectional type map. -## TODO +The central rule is: -- TODO: Document how compiler-probed native kinds become semantic and wrapper - types. -- TODO: Link datatype mappings to generated examples and tests. +> Compiler probes describe the selected native target, semantic IR gives those +> facts stable language-neutral identities, policy completes behavior for each +> use site, and code generation selects emitted representations from the +> completed plan. + +NumPy is a Python-boundary representation. It is not the authority for native +storage, semantic identity, ownership, mutability, or lifetime. + +## End-To-End Datatype Flow + +```text +native source declaration + -> compiler preprocessing + -> parser-native datatype, kind, shape, and attribute facts + -> compiler probes for target-dependent kind and storage values + -> source-to-IR conversion + -> stable semantic name and resolved dtype + -> origin and native spelling + -> rank, shape, storage category, and source provenance + -> post-IR policy completion + -> ownership, transfer, destruction, mutability, and projection + -> boundary storage mode and supported/blocked decision + -> wrapper planning + -> datatype family plus completed transfer/result/access plans + -> backend scalar registry and specialized datatype lowering + -> generated native bridge and Python/NumPy binding nodes + -> language printers + -> native compilation and linking + -> exact runtime boundary validation +``` + +Each arrow changes the representation for a reason. The parser preserves what +the source declared. Probing supplies facts the source spelling alone cannot +determine. Semantic conversion normalizes equivalent source forms. Policy adds +context-dependent behavior. Planning freezes that behavior into an +implementation contract. Codegen then chooses syntax and runtime operations; +it does not reinterpret the datatype. + +## The Four Datatype Authorities + +PRIK deliberately has four related but non-interchangeable authorities. + +| Authority | Owner | Key | Value | Purpose | +| --- | --- | --- | --- | --- | +| Native target facts | `prik/probes/` | compiler expression or native spelling | measured kind, storage, precision, signedness, or availability | Describe the selected compiler target. | +| Semantic scalar catalogue | `prik/semantics/scalar_types.py` | stable semantic name such as `Float64` | family and intrinsic storage facts | Define language-neutral scalar vocabulary without importing NumPy. | +| Runtime contract factories | `prik/contracts/__init__.py` | semantic contract name | real NumPy scalar factory or an explicit constructor error | Make public semantic `.pyi` symbols safely usable at runtime where supported. | +| Backend datatype catalogues | `prik/codegen/primitive_scalar_types.py` | resolved semantic name | emitted NumPy expressions plus native spellings, NumPy type macros, Python parse/result behavior, and descriptor spelling | Project semantic dtypes for reporting and lower implemented primitive scalar plans without coupling the two generators. | + +The same semantic name appears in more than one table because each table owns +different facts. Consistency tests compare their overlapping keys, but one +stage must not import a later stage merely to avoid repeating an invariant it +owns independently. + +## Native Target Probing + +### Why probing exists + +Source spelling is insufficient for target-dependent datatypes. A default +integer kind, a selected real kind, a legacy star-width declaration, and a +named interoperable kind can denote different storage on different compiler +targets or under different target-changing flags. + +The build pipeline therefore uses the same effective compiler configuration +for preprocessing, datatype measurement, native compilation, and generated +bridge compilation. Flags that change kinds, widths, ABI, or target architecture +must reach probing. A report measured under one target must not be reused as if +it described another. + +`prik/probes/fortran_types.py` compiles generated measurement programs and +returns `FortranTypeProbeReport`. Its cache identity includes the generated +source, compiler identity, flags, working directory, target environment, and +optional runner. The two main outputs are: + +- compile-time values used to resolve kind and specification expressions; +- storage facts for intrinsic datatype/kind pairs used by semantic conversion. + +`prik/pipeline/build.py` collects requirements from the parsed project, asks the +probe service only for required facts, and supplies the evaluated values and +type facts to `FortranToIRConverter`. Probes never decide ownership, Python +visibility, output projection, or wrapper support. + + + +### Probe reports are evidence, not semantic models + +A probe report records reproducible compiler observations. It may be serialized +or cached, but it does not become semantic IR and it does not contain wrapper +policy. Source-to-IR conversion owns the interpretation of those observations. + +The Markdown datatype report lives in `prik/pipeline/type_mapping_report.py` +because it intentionally combines several stages: + +```text +probe facts -> semantic conversion -> backend NumPy projection -> Markdown +``` + +That report is documentation and inspection output. It is not an alternative +conversion path and must reuse the normal converters and backend catalogue. + +## Parsing And Semantic Normalization + +Parsers preserve native declarations rather than prematurely replacing them +with Python or NumPy types. Relevant parser facts include: + +- native base type and kind spelling; +- declaration or measured storage width; +- scalar versus array shape and rank; +- pointer, allocatable, target, optional, and value attributes; +- character kind and length syntax; +- derived-type identity and scope; +- procedure/callback signature structure; +- source coordinates and the original native spelling. + +The source-to-IR converters combine those facts with target measurements and +produce `SemanticType` plus `SemanticOrigin` and `SemanticStorageContract`. +`SemanticType.name` is the public semantic identity. `SemanticType.dtype` is +the resolved storage dtype used by later stages. They can differ when a stable +public concept has target-specific storage. + +For example, an unresolved native default integer can begin as `Int`, then +resolve to `Int32` or `Int64` after compiler measurement. The converter records +the source spelling and target provenance; it does not replace those facts with +`numpy.int32` or `numpy.int64`. + +## Semantic Scalar Catalogue + +`prik/semantics/scalar_types.py` is the single semantic vocabulary for +primitive scalar names. Its immutable `SemanticScalarSpec` records only facts +that are intrinsic to the semantic identity: + +- datatype family; +- storage width when the semantic identity fixes one; +- whether the name represents a Boolean storage contract. + +The module exposes checked helpers for scalar membership and Boolean storage +width. It does not import NumPy and contains no emitted source spelling. +Extended `Float128` and `Complex256` catalogue entries intentionally leave +`storage_bits` unresolved because supported targets can store them in 80/96/128 +or 160/192/256 bits respectively; compiler facts select the actual storage. + +Boolean names demonstrate why the semantic and NumPy layers are distinct: + +| Semantic name | Native storage contract | NumPy boundary dtype | +| --- | --- | --- | +| `Bool` | default or interoperable Boolean, normalized to 8-bit boundary storage | `numpy.bool_` | +| `Bool8` | 8 bits | `numpy.bool_` | +| `Bool16` | 16 bits | `numpy.bool_` | +| `Bool32` | 32 bits | `numpy.bool_` | +| `Bool64` | 64 bits | `numpy.bool_` | + +The binding normalizes Boolean values at the boundary, while the generated +bridge uses the compiler-resolved native logical representation. A NumPy dtype +alone therefore cannot reconstruct the original semantic Boolean contract. + +## Runtime Contract Factories + +`prik/contracts/__init__.py` owns the public names used by generated and edited +semantic `.pyi` contracts. Its private contract-factory catalogue maps a +semantic name to a real NumPy scalar factory where a portable runtime value +exists. This lets expressions such as `Float64()` create the exact scalar type +required by a generated wrapper and lets typed descriptor contracts retain a +concrete `numpy.dtype`. + +Names without a portable runtime factory remain explicit contract symbols and +raise a focused constructor error. Examples include unresolved `Int`, `UInt`, +`CEnum`, `Char`, `String`, and `Void`. The contracts package does not perform +source-to-IR conversion and its factories do not define native ABI storage. + +## Backend Primitive Scalar Catalogue + +`prik/codegen/primitive_scalar_types.py` owns two readable mappings. +`NumpyDtypeRegistry.TYPES` maps every resolved semantic dtype with a maintained +NumPy projection to its emitted expression. `PrimitiveScalarTypeRegistry.TYPES` +contains the narrower set with implemented native wrapper lowering. Each +`BackendScalarType` entry uses keyword arguments so a maintainer can audit one +row without remembering positional field order. + +The fields cover: + +| Field | Meaning | +| --- | --- | +| `semantic_name` | Resolved semantic key consumed from the wrapper plan. | +| `c_spelling` | Native binding-side storage spelling. | +| `fortran_spelling` | Generated bridge declaration spelling. | +| `python_parse_unit` | Python argument parsing unit used by the binding. | +| `numpy_type_macro` | NumPy array dtype identity checked or allocated in generated code. | +| `python_result_kind` | Result-conversion path for an ordinary procedure result. | +| `python_type_name` | Python/NumPy scalar expression shown in validation diagnostics or constructors. | +| `python_module_result_kind` | Result-conversion path for module state. | +| `cfi_type_spelling` | Descriptor element type identity for descriptor-based boundaries. | + +The catalogue contains only implemented primitive scalar lowering lanes. +Adding a semantic name to the semantic catalogue does not automatically enable +wrapper generation. Unsupported entries must continue to fail during policy or +planning rather than acquiring guessed backend spellings. + + + +## Why There Is No Universal NumPy-To-Semantic Map + +The maintained lookup direction is: + +```text +resolved semantic dtype -> stage-owned NumPy or backend facts +``` + +The reverse direction is not generally valid: + +- every Boolean storage contract projects to `numpy.bool_`; +- `numpy.longdouble`, `numpy.clongdouble`, and `numpy.uintp` vary by platform; +- source concepts such as unresolved `Int`, `CEnum`, and fixed-length native + character storage require context that a NumPy dtype does not carry; +- ownership, mutability, rank, layout, pointer association, allocation state, + and callback identity are not dtype properties. + +Runtime validation may compare an actual NumPy dtype with the exact dtype in a +completed plan. It must not use the observed dtype to infer semantic meaning or +select a different lowering path. If a future frontend accepts NumPy types as +source annotations, that frontend must own an explicitly contextual and +possibly lossy input mapping. + +## Non-Primitive Datatype Families + +### Arrays + +An array is not a separate scalar dtype. Semantic IR stores its element dtype, +rank, shape expressions, bounds provenance, layout/order, contiguity, and +pointer or allocatable attributes in `SemanticArrayContract`. Policy completes +copy/alias behavior, writeback, nullability, descriptor ownership, and result +projection. The plan then records exact validation and transfer actions. + +Generated bindings validate dtype, rank, shape, layout, alignment, +writeability, and permitted stride forms from that plan. They do not silently +cast or transpose unless policy selected an explicit copy path. + +### Characters And Strings + +Character handling combines element kind, declared or resolved length, scalar +versus array rank, and ABI byte storage. `String` is the stable semantic family, +but `numpy.str_` is only a Python-facing representation; fixed native character +storage may instead use exact byte buffers. Length and encoding constraints +must therefore survive semantic IR and policy completion. + +### Derived Types + +Derived-type identity is scoped and semantic. Generated wrappers keep native +objects opaque and use holders, accessors, and completed lifecycle policy +instead of mirroring an arbitrary native layout in Python. Field datatypes pass +through the same semantic and policy stages as ordinary variables. + +Arrays of derived types remain unsupported unless the language-support matrix +states otherwise. A primitive scalar registry entry must never be fabricated +for a derived identity. + +### Pointers And Allocatables + +Pointer and allocatable arrays combine an element semantic dtype with descriptor +kind, association/allocation state, ownership, nullability, and release +responsibility. Policy completes those decisions before planning. Runtime +handles expose descriptor-backed operations, while generated code uses the +planned element dtype for validation and descriptor metadata. + +A live zero-copy NumPy view can become stale after native deallocation, +reallocation, or pointer reassociation. Datatype matching does not solve that +lifetime boundary; see [Ownership Tracking](ownership-tracking.md). + +### Callbacks + +A callback datatype is a full prototype: argument types, result type, calling +convention, value/reference storage, rank, and mutability. It is not reducible +to a scalar function-pointer token. Semantic conversion resolves the prototype, +policy completes callback handoff and result behavior, and planning freezes the +native slots used by codegen. + +## Policy And Planning Boundaries + +Datatype facts answer questions such as “this is a rank-two `Float64` array.” +They do not answer: + +- who owns it; +- whether it is borrowed, copied, moved, or aliased; +- whether native mutation is visible or discarded; +- whether an output is hidden and projected into the Python result; +- whether storage is stack, heap, or alias; +- whether destruction or descriptor release is required; +- whether a getter or setter is exposed. + +Those decisions belong to post-IR policy completion. `WrapperPlanner` projects +the completed facts into typed transfer, result, field, module-variable, and +lifecycle plans. Backend generators dispatch from those records into named +mechanisms and must fail if a required datatype lowering is absent. + +## Failure Rules + +| Failure | Stage that should reject it | +| --- | --- | +| Compiler cannot measure a required target fact | probe service | +| Native declaration is syntactically unsupported | parser | +| Native fact cannot map to a stable semantic datatype | source-to-IR conversion | +| Datatype is known but unsafe or unsupported in its use-site context | policy completion | +| Completed datatype/policy combination has no plan representation | wrapper planner | +| Planned datatype has no backend mechanism | codegen checked dispatch | +| Runtime value has the wrong exact dtype, rank, layout, or mutability | generated binding validation | + +No stage should silently replace a failed mapping with a nearby width, host +default, NumPy coercion, or different ownership path. + +## Change Workflow And Evidence + +When adding or changing a datatype: + +1. Add parser coverage for every accepted source spelling and source location. +2. Add probe coverage when storage or kind depends on the compiler target. +3. Add or update `SemanticScalarSpec` only for stable semantic vocabulary. +4. Verify source-to-IR conversion records the resolved dtype and native + provenance. +5. Complete use-site behavior in policy and add explicit blockers for + unsupported combinations. +6. Extend wrapper-plan records only when existing transfer/result records + cannot represent the completed behavior. +7. Add one backend catalogue entry or a specialized lowering mechanism. +8. Add generated-source assertions and an end-to-end runtime case when emitted + behavior changes. +9. Update the semantic datatype reference, feature matrix, and this page when + support boundaries change. + +Primary evidence owners are: + +| Concern | Tests | +| --- | --- | +| Target measurement | `tests/fortran/data_types/probes/` and `tests/c/probes/` | +| Semantic scalar catalogue and conversion | `tests/fortran/data_types/semantics/`, semantic conversion tests | +| Public contract factories | semantic `.pyi` contract tests | +| Backend scalar catalogue | `tests/fortran/data_types/codegen/` | +| Generated datatype report | `tests/fortran/data_types/pipeline/test_type_mapping_report.py` | +| Runtime scalar and array behavior | feature-local end-to-end datatype and array tests | + +The report and registry tests should assert readable representative mappings, +not preserve obsolete public helpers or duplicate every internal dictionary as +an external API. diff --git a/docs/maintainer/roadmap/documentation-content-checklist.md b/docs/maintainer/roadmap/documentation-content-checklist.md index 3e95592a0..2b180ea28 100644 --- a/docs/maintainer/roadmap/documentation-content-checklist.md +++ b/docs/maintainer/roadmap/documentation-content-checklist.md @@ -145,9 +145,10 @@ PRIK_C_DOCS_END --> explanation of the current wrapper stages, semantic-policy boundary, pass/planner/emitter distinctions, incremental decomposition criteria, and acceptance criteria for bridge and binding refactoring. -- [ ] `docs/maintainer/internal-architecture/type-system.md`: document scalar kinds, arrays, - characters, derived types, pointers, allocatables, callbacks, and unsupported - storage forms. +- [x] `docs/maintainer/internal-architecture/type-system.md`: maintained datatype + lifecycle from compiler probing through semantic normalization, policy, + planning, backend registries, generated NumPy boundaries, runtime validation, + and non-primitive storage families. - [ ] `docs/maintainer/internal-architecture/runtime-layer.md`: document native support installation, extension initialization, callbacks, cleanup, and shared native state. diff --git a/docs/user/reference/python-api.md b/docs/user/reference/python-api.md index f775e1a2a..da4af2c3c 100644 --- a/docs/user/reference/python-api.md +++ b/docs/user/reference/python-api.md @@ -212,7 +212,7 @@ module. The method requires that the shared-library file already exists, so a direct build can import at once and a Makefile result can import after `make` has produced the extension. -## Target type and NumPy helpers +## Target type probing | Symbol | Purpose | | --- | --- | @@ -222,14 +222,10 @@ has produced the extension. | `fortran_type_probe_expressions` | Produces expressions used by the Fortran type probe. | | `probe_fortran_type_expressions` | Runs Fortran type probes for selected expressions. | | `evaluate_fortran_type_requirements` | Evaluates semantic requirements against a Fortran type probe report. | -| `SEMANTIC_DTYPE_TO_NUMPY_DTYPE` | Default semantic dtype to NumPy dtype map. | -| `semantic_dtype_to_numpy_dtype` | Maps one semantic dtype to a NumPy dtype. | -| `semantic_dtype_to_numpy_dtype_map` | Returns a semantic dtype to NumPy dtype mapping. | -| `semantic_type_to_numpy_dtype` | Maps one semantic type to a NumPy dtype. | -| `numpy_dtype_expression` | Returns the generated expression for a NumPy dtype. | - -These helpers are public because wrapper contracts need deterministic target -type and NumPy dtype mapping. The CLI type-probe flags are documented in + +These helpers expose compiler-target measurement. Semantic-to-NumPy projection +is an internal code-generation concern consumed through completed wrapper +plans, not a public conversion API. The CLI type-probe flags are documented in [CLI Commands Reference](cli-commands.md). ## Current boundaries diff --git a/mkdocs.yml b/mkdocs.yml index a8ebbe302..9b8a83b68 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -161,7 +161,7 @@ nav: - Wrapper Generation Pipeline: maintainer/internal-architecture/wrapper-generation-pipeline.md - AST Design: maintainer/internal-architecture/ast-design.md - Semantic Passes: maintainer/internal-architecture/semantic-passes.md - - Type System: maintainer/internal-architecture/type-system.md + - Datatype Lifecycle: maintainer/internal-architecture/type-system.md - Runtime Layer: maintainer/internal-architecture/runtime-layer.md - Ownership Tracking: maintainer/internal-architecture/ownership-tracking.md - Dependency Analysis: maintainer/internal-architecture/dependency-analysis.md diff --git a/prik/README.md b/prik/README.md index 0a5e3162b..d5cbd178b 100644 --- a/prik/README.md +++ b/prik/README.md @@ -10,15 +10,14 @@ jumping directly into generated-code internals. | --- | --- | | `cli.py` | User CLI stages, output routing, diagnostics, and wrapper option validation. | | `contracts/` | Public names used by semantic `.pyi` contracts. | -| `pipeline/` | Preprocessing, semantic `.pyi` loading, wrapper generation orchestration, and end-to-end builds. | -| `probes/` | C ABI facts, Fortran kind/storage facts, and type mapping reports. | +| `pipeline/` | Preprocessing, semantic `.pyi` loading, datatype mapping reports, wrapper generation orchestration, and end-to-end builds. | +| `probes/` | Compiler-derived native target facts. | | `runtime/` | Python runtime objects used by generated extensions. | -| `types/` | Semantic-to-Python ecosystem type mappings. | | `parsers/` | Parser namespace containing the `c`, `fortran`, and semantic `.pyi` frontends. | -| `semantics/` | Language-neutral semantic IR, declaration-expression provenance, and `.pyi` conversion. | +| `semantics/` | Language-neutral semantic IR, scalar datatype vocabulary, declaration-expression provenance, and `.pyi` conversion. | | `policy/` | Completed ownership and interoperability policy. | | `planning/` | Editable backend-neutral wrapper implementation plans. | -| `codegen/` | Plan-driven documentation and direct C/Fortran syntax-node lowering. | +| `codegen/` | Backend datatype projection, plan-driven documentation, and direct C/Fortran syntax-node lowering. | | `printers/` | C, Fortran, and semantic `.pyi` serialization. | | `compiling/` | Native compiler objects, wrapper compilation, native support installation, and linking. | | `utilities/` | Shared parsing, normalization, rendering, evaluation, and visitor helpers. | diff --git a/prik/__init__.py b/prik/__init__.py index e6cec4a14..baa458f1f 100644 --- a/prik/__init__.py +++ b/prik/__init__.py @@ -59,13 +59,6 @@ "fortran_type_probe_expressions", "probe_fortran_type_expressions", } -_NUMPY_TYPE_EXPORTS = { - "SEMANTIC_DTYPE_TO_NUMPY_DTYPE", - "numpy_dtype_expression", - "semantic_dtype_to_numpy_dtype", - "semantic_dtype_to_numpy_dtype_map", - "semantic_type_to_numpy_dtype", -} _WRAPPING_EXPORTS = { "NativeBuildPlan", "NativeCompilationUnit", @@ -85,9 +78,6 @@ def __getattr__(name: str): if name in _FORTRAN_TYPE_PROBE_EXPORTS: module = import_module("prik.probes.fortran_types") return getattr(module, name) - if name in _NUMPY_TYPE_EXPORTS: - module = import_module("prik.types.numpy") - return getattr(module, name) if name in _WRAPPING_EXPORTS: module = import_module("prik.pipeline.build") return getattr(module, name) @@ -95,7 +85,6 @@ def __getattr__(name: str): __all__ = ( - "SEMANTIC_DTYPE_TO_NUMPY_DTYPE", "AllocatableArray", "CFile", "CParseError", @@ -143,7 +132,6 @@ def __getattr__(name: str): "fortran_project_to_semantic_modules", "fortran_type_probe_expressions", "main", - "numpy_dtype_expression", "opaque_dependency_modules", "parse_c_file", "parse_c_project", @@ -156,7 +144,4 @@ def __getattr__(name: str): "pyi_paths_to_semantic_modules", "pyi_text_to_semantic_module", "resolve_semantic_compile_time_values", - "semantic_dtype_to_numpy_dtype", - "semantic_dtype_to_numpy_dtype_map", - "semantic_type_to_numpy_dtype", ) diff --git a/prik/cli.py b/prik/cli.py index ee784d8c6..78da353cd 100644 --- a/prik/cli.py +++ b/prik/cli.py @@ -27,7 +27,7 @@ FortranTypeProbeReport, probe_fortran_type_expressions_cached, ) -from prik.probes.report import c_type_mapping_markdown, fortran_type_mapping_markdown +from prik.pipeline.type_mapping_report import c_type_mapping_markdown, fortran_type_mapping_markdown from prik.pipeline.preprocessing import ( PreprocessingConfig, PreprocessingError, diff --git a/prik/codegen/c/binding.py b/prik/codegen/c/binding.py index 7023304d0..0245a37bc 100644 --- a/prik/codegen/c/binding.py +++ b/prik/codegen/c/binding.py @@ -50,7 +50,7 @@ ) from prik.codegen.c.naming import CBindingNames from prik.codegen.c.python_surface import PythonSurfaceContext, PythonSurfaceEmitter -from prik.types.numpy import is_boolean_semantic_type_name +from prik.semantics.scalar_types import is_boolean_semantic_type_name from prik.codegen.nodes import ( CAllowThreadsBegin, CAllowThreadsEnd, diff --git a/prik/codegen/docstrings.py b/prik/codegen/docstrings.py index 37d78bd58..fd60d6427 100644 --- a/prik/codegen/docstrings.py +++ b/prik/codegen/docstrings.py @@ -34,7 +34,7 @@ OverloadPlan, ResultPlan, ) -from prik.types.numpy import BOOLEAN_SEMANTIC_TYPE_NAMES +from prik.semantics.scalar_types import BOOLEAN_SEMANTIC_TYPE_NAMES _SCALAR_TYPES = { diff --git a/prik/codegen/fortran/bridge.py b/prik/codegen/fortran/bridge.py index a80c94d4f..49d6ac4a5 100644 --- a/prik/codegen/fortran/bridge.py +++ b/prik/codegen/fortran/bridge.py @@ -53,7 +53,7 @@ OptionalMode, ScalarLogicalABI, ) -from prik.types.numpy import is_boolean_semantic_type_name +from prik.semantics.scalar_types import is_boolean_semantic_type_name from prik.codegen.nodes import ( CodeExpression, FortranAllocate, diff --git a/prik/codegen/primitive_scalar_types.py b/prik/codegen/primitive_scalar_types.py index b6919d3fb..5b1983ece 100644 --- a/prik/codegen/primitive_scalar_types.py +++ b/prik/codegen/primitive_scalar_types.py @@ -2,23 +2,72 @@ from __future__ import annotations +from collections.abc import Mapping from dataclasses import replace +from types import MappingProxyType from typing import ClassVar -from prik.types.numpy import BOOLEAN_SEMANTIC_TYPE_NAMES from prik.codegen.nodes import BackendScalarType +from prik.semantics.scalar_types import BOOLEAN_SEMANTIC_TYPE_NAMES + + +class NumpyDtypeRegistry: + """Project resolved semantic dtypes into emitted NumPy expressions.""" + + TYPES: ClassVar[Mapping[str, str]] = MappingProxyType( + { + "Bool": "numpy.bool_", + "Bool8": "numpy.bool_", + "Bool16": "numpy.bool_", + "Bool32": "numpy.bool_", + "Bool64": "numpy.bool_", + "Complex64": "numpy.complex64", + "Complex128": "numpy.complex128", + "Complex256": "numpy.clongdouble", + "Float16": "numpy.float16", + "Float32": "numpy.float32", + "Float64": "numpy.float64", + "Float128": "numpy.longdouble", + "Int8": "numpy.int8", + "Int16": "numpy.int16", + "Int32": "numpy.int32", + "Int64": "numpy.int64", + "SizeT": "numpy.uintp", + "String": "numpy.str_", + "UInt8": "numpy.uint8", + "UInt16": "numpy.uint16", + "UInt32": "numpy.uint32", + "UInt64": "numpy.uint64", + } + ) + + @classmethod + def expression_for(cls, semantic_dtype: str | None) -> str: + """Return the emitted NumPy expression for one resolved semantic dtype. + + The lookup is code-generation vocabulary, not semantic inference. + Unknown and unresolved names fail so callers cannot select a nearby + NumPy dtype. + """ + if semantic_dtype is None: + raise KeyError("Semantic dtype is not resolved") + dtype = str(semantic_dtype) + try: + return cls.TYPES[dtype] + except KeyError: + raise KeyError(f"No NumPy dtype mapping for semantic dtype {dtype!r}") from None _BOOL_BACKEND_TYPE = BackendScalarType( - "Bool", - "bool", - "logical(c_bool)", - "O", - "NPY_BOOL", - "python", - "bool", - "python", - "CFI_type_Bool", + semantic_name="Bool", + c_spelling="bool", + fortran_spelling="logical(c_bool)", + python_parse_unit="O", + numpy_type_macro="NPY_BOOL", + python_result_kind="python", + python_type_name="bool", + python_module_result_kind="python", + cfi_type_spelling="CFI_type_Bool", ) @@ -28,92 +77,92 @@ class PrimitiveScalarTypeRegistry: TYPES: ClassVar[dict[str, BackendScalarType]] = { **{name: replace(_BOOL_BACKEND_TYPE, semantic_name=name) for name in BOOLEAN_SEMANTIC_TYPE_NAMES}, "Int8": BackendScalarType( - "Int8", - "int8_t", - "integer(c_int8_t)", - "O", - "NPY_INT8", - "numpy", - "numpy.int8", - "numpy", - "CFI_type_int8_t", + semantic_name="Int8", + c_spelling="int8_t", + fortran_spelling="integer(c_int8_t)", + python_parse_unit="O", + numpy_type_macro="NPY_INT8", + python_result_kind="numpy", + python_type_name=NumpyDtypeRegistry.expression_for("Int8"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_int8_t", ), "Int16": BackendScalarType( - "Int16", - "int16_t", - "integer(c_int16_t)", - "O", - "NPY_INT16", - "numpy", - "numpy.int16", - "numpy", - "CFI_type_int16_t", + semantic_name="Int16", + c_spelling="int16_t", + fortran_spelling="integer(c_int16_t)", + python_parse_unit="O", + numpy_type_macro="NPY_INT16", + python_result_kind="numpy", + python_type_name=NumpyDtypeRegistry.expression_for("Int16"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_int16_t", ), "Int32": BackendScalarType( - "Int32", - "int32_t", - "integer(c_int32_t)", - "O", - "NPY_INT32", - "python", - "numpy.int32", - "numpy", - "CFI_type_int32_t", + semantic_name="Int32", + c_spelling="int32_t", + fortran_spelling="integer(c_int32_t)", + python_parse_unit="O", + numpy_type_macro="NPY_INT32", + python_result_kind="python", + python_type_name=NumpyDtypeRegistry.expression_for("Int32"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_int32_t", ), "Int64": BackendScalarType( - "Int64", - "int64_t", - "integer(c_int64_t)", - "O", - "NPY_INT64", - "python", - "numpy.int64", - "numpy", - "CFI_type_int64_t", + semantic_name="Int64", + c_spelling="int64_t", + fortran_spelling="integer(c_int64_t)", + python_parse_unit="O", + numpy_type_macro="NPY_INT64", + python_result_kind="python", + python_type_name=NumpyDtypeRegistry.expression_for("Int64"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_int64_t", ), "Float32": BackendScalarType( - "Float32", - "float", - "real(c_float)", - "O", - "NPY_FLOAT32", - "numpy", - "numpy.float32", - "numpy", - "CFI_type_float", + semantic_name="Float32", + c_spelling="float", + fortran_spelling="real(c_float)", + python_parse_unit="O", + numpy_type_macro="NPY_FLOAT32", + python_result_kind="numpy", + python_type_name=NumpyDtypeRegistry.expression_for("Float32"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_float", ), "Float64": BackendScalarType( - "Float64", - "double", - "real(c_double)", - "O", - "NPY_FLOAT64", - "python", - "numpy.float64", - "numpy", - "CFI_type_double", + semantic_name="Float64", + c_spelling="double", + fortran_spelling="real(c_double)", + python_parse_unit="O", + numpy_type_macro="NPY_FLOAT64", + python_result_kind="python", + python_type_name=NumpyDtypeRegistry.expression_for("Float64"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_double", ), "Complex64": BackendScalarType( - "Complex64", - "float complex", - "complex(c_float_complex)", - "O", - "NPY_COMPLEX64", - "numpy", - "numpy.complex64", - "numpy", - "CFI_type_float_Complex", + semantic_name="Complex64", + c_spelling="float complex", + fortran_spelling="complex(c_float_complex)", + python_parse_unit="O", + numpy_type_macro="NPY_COMPLEX64", + python_result_kind="numpy", + python_type_name=NumpyDtypeRegistry.expression_for("Complex64"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_float_Complex", ), "Complex128": BackendScalarType( - "Complex128", - "double complex", - "complex(c_double_complex)", - "O", - "NPY_COMPLEX128", - "python", - "numpy.complex128", - "numpy", - "CFI_type_double_Complex", + semantic_name="Complex128", + c_spelling="double complex", + fortran_spelling="complex(c_double_complex)", + python_parse_unit="O", + numpy_type_macro="NPY_COMPLEX128", + python_result_kind="python", + python_type_name=NumpyDtypeRegistry.expression_for("Complex128"), + python_module_result_kind="numpy", + cfi_type_spelling="CFI_type_double_Complex", ), } @@ -124,3 +173,9 @@ def type_for(cls, semantic_type_name: str) -> BackendScalarType: return replace(cls.TYPES[semantic_type_name]) except KeyError: raise ValueError(f"Unsupported first-lane scalar type {semantic_type_name!r}") from None + + +__all__ = ( + "NumpyDtypeRegistry", + "PrimitiveScalarTypeRegistry", +) diff --git a/prik/contracts/__init__.py b/prik/contracts/__init__.py index 80738f71f..e2e30ca0e 100644 --- a/prik/contracts/__init__.py +++ b/prik/contracts/__init__.py @@ -136,34 +136,59 @@ def apply(target): return apply -Bool = _contract_type("Bool", np.bool_) -Bool8 = _contract_type("Bool8", np.bool_) -Bool16 = _contract_type("Bool16", np.bool_) -Bool32 = _contract_type("Bool32", np.bool_) -Bool64 = _contract_type("Bool64", np.bool_) +_CONTRACT_NUMPY_FACTORIES: Final[dict[str, object]] = { + "Bool": np.bool_, + "Bool8": np.bool_, + "Bool16": np.bool_, + "Bool32": np.bool_, + "Bool64": np.bool_, + "Complex64": np.complex64, + "Complex128": np.complex128, + "Complex256": np.clongdouble, + "Float16": np.float16, + "Float32": np.float32, + "Float64": np.float64, + "Float128": np.longdouble, + "Int8": np.int8, + "Int16": np.int16, + "Int32": np.int32, + "Int64": np.int64, + "SizeT": np.uintp, + "UInt8": np.uint8, + "UInt16": np.uint16, + "UInt32": np.uint32, + "UInt64": np.uint64, +} + + +Bool = _contract_type("Bool", _CONTRACT_NUMPY_FACTORIES["Bool"]) +Bool8 = _contract_type("Bool8", _CONTRACT_NUMPY_FACTORIES["Bool8"]) +Bool16 = _contract_type("Bool16", _CONTRACT_NUMPY_FACTORIES["Bool16"]) +Bool32 = _contract_type("Bool32", _CONTRACT_NUMPY_FACTORIES["Bool32"]) +Bool64 = _contract_type("Bool64", _CONTRACT_NUMPY_FACTORIES["Bool64"]) Byte = _contract_type("Byte", constructor_error="Byte has no portable NumPy scalar default") CEnum = _contract_type("CEnum", constructor_error="CEnum requires a resolved native underlying type") Char = _contract_type("Char", constructor_error="Char has no portable NumPy scalar default") -Complex64 = _contract_type("Complex64", np.complex64) -Complex128 = _contract_type("Complex128", np.complex128) -Complex256 = _contract_type("Complex256", np.clongdouble) -Float16 = _contract_type("Float16", np.float16) -Float32 = _contract_type("Float32", np.float32) -Float64 = _contract_type("Float64", np.float64) -Float128 = _contract_type("Float128", np.longdouble) +Complex64 = _contract_type("Complex64", _CONTRACT_NUMPY_FACTORIES["Complex64"]) +Complex128 = _contract_type("Complex128", _CONTRACT_NUMPY_FACTORIES["Complex128"]) +Complex256 = _contract_type("Complex256", _CONTRACT_NUMPY_FACTORIES["Complex256"]) +Float16 = _contract_type("Float16", _CONTRACT_NUMPY_FACTORIES["Float16"]) +Float32 = _contract_type("Float32", _CONTRACT_NUMPY_FACTORIES["Float32"]) +Float64 = _contract_type("Float64", _CONTRACT_NUMPY_FACTORIES["Float64"]) +Float128 = _contract_type("Float128", _CONTRACT_NUMPY_FACTORIES["Float128"]) Int = _contract_type("Int", constructor_error="Int requires a resolved native width") -Int8 = _contract_type("Int8", np.int8) -Int16 = _contract_type("Int16", np.int16) -Int32 = _contract_type("Int32", np.int32) -Int64 = _contract_type("Int64", np.int64) +Int8 = _contract_type("Int8", _CONTRACT_NUMPY_FACTORIES["Int8"]) +Int16 = _contract_type("Int16", _CONTRACT_NUMPY_FACTORIES["Int16"]) +Int32 = _contract_type("Int32", _CONTRACT_NUMPY_FACTORIES["Int32"]) +Int64 = _contract_type("Int64", _CONTRACT_NUMPY_FACTORIES["Int64"]) Matrix = _contract_type("Matrix") -SizeT = _contract_type("SizeT", np.uintp) +SizeT = _contract_type("SizeT", _CONTRACT_NUMPY_FACTORIES["SizeT"]) String = _contract_type("String", constructor_error="String requires an explicit native length and encoding contract") UInt = _contract_type("UInt", constructor_error="UInt requires a resolved native width") -UInt8 = _contract_type("UInt8", np.uint8) -UInt16 = _contract_type("UInt16", np.uint16) -UInt32 = _contract_type("UInt32", np.uint32) -UInt64 = _contract_type("UInt64", np.uint64) +UInt8 = _contract_type("UInt8", _CONTRACT_NUMPY_FACTORIES["UInt8"]) +UInt16 = _contract_type("UInt16", _CONTRACT_NUMPY_FACTORIES["UInt16"]) +UInt32 = _contract_type("UInt32", _CONTRACT_NUMPY_FACTORIES["UInt32"]) +UInt64 = _contract_type("UInt64", _CONTRACT_NUMPY_FACTORIES["UInt64"]) Vector = _contract_type("Vector") Void = _contract_type("Void", constructor_error="Void is not a runtime value") diff --git a/prik/pipeline/README.md b/prik/pipeline/README.md index 6e5c106e8..de89681fa 100644 --- a/prik/pipeline/README.md +++ b/prik/pipeline/README.md @@ -8,6 +8,7 @@ native compiler mechanisms. | --- | --- | | `preprocessing.py` | Compiler preprocessing recipes and source mappings. | | `pyi.py` | Semantic `.pyi` loading, package assembly, and reference reconciliation. | +| `type_mapping_report.py` | Compiler-target facts converted through semantic IR and backend NumPy projection into inspection Markdown. | | `wrapper.py` | One completed-plan-to-rendered-wrapper generation workflow. | | `build.py` | Generated-source output, native compilation, linking, and extension results. | diff --git a/prik/pipeline/build.py b/prik/pipeline/build.py index 862ae7866..4810bd453 100644 --- a/prik/pipeline/build.py +++ b/prik/pipeline/build.py @@ -51,7 +51,7 @@ from prik.pipeline.pyi import _PyiSemanticModuleCache from prik.semantics.pyi_metadata import PYI_LOADED_METADATA from prik.planning import WrapperPlanner -from prik.types.numpy import boolean_storage_bits, is_boolean_semantic_type_name +from prik.semantics.scalar_types import boolean_storage_bits, is_boolean_semantic_type_name _DEFAULT_BUILD_DIR_NAME = "__prik__" diff --git a/prik/probes/report.py b/prik/pipeline/type_mapping_report.py similarity index 96% rename from prik/probes/report.py rename to prik/pipeline/type_mapping_report.py index a4d8fc9d9..3bb3e3dd7 100644 --- a/prik/probes/report.py +++ b/prik/pipeline/type_mapping_report.py @@ -1,9 +1,9 @@ -"""Generate target-specific native-to-semantic-to-NumPy mapping reports. +"""Orchestrate target-specific native-to-semantic-to-NumPy reports. -The public functions measure compiler-dependent target facts through the probe -stage, convert the supported native spellings through existing semantic -converters, and render Markdown for documentation or inspection. They report -the selected target; they do not define parser facts or semantic policy. +The public functions combine compiler probes, the normal semantic converters, +and codegen's NumPy projection catalogue before rendering Markdown. This is a +cross-stage inspection pipeline, not a probe implementation or an alternative +datatype conversion path. """ from __future__ import annotations @@ -12,6 +12,7 @@ from collections.abc import Sequence import platform +from prik.codegen.primitive_scalar_types import NumpyDtypeRegistry from prik.parsers.c.models import ( CBool, CChar, @@ -40,7 +41,6 @@ from prik.pipeline.preprocessing import PreprocessingConfig from prik.probes.c_types import probe_c_standard_types_cached from prik.probes.fortran_types import evaluate_fortran_type_facts, probe_fortran_type_expressions_cached -from prik.types.numpy import numpy_dtype_expression # C report inventory. @@ -340,7 +340,7 @@ def _numpy_dtype(semantic_dtype: str | None) -> str: note because the NumPy string type is not the native character layout. """ try: - expression = numpy_dtype_expression(semantic_dtype) + expression = NumpyDtypeRegistry.expression_for(semantic_dtype) except KeyError: return "unsupported" if semantic_dtype == "String": @@ -369,7 +369,7 @@ def _markdown_table(native_header: str, rows: list[tuple[str, str, str, str]]) - def main(argv: list[str] | None = None) -> int: """Print one compiler-generated C or Fortran datatype mapping table. - Use this entrypoint from python -m prik.probes.report with a required + Use this entrypoint from ``python -m prik.pipeline.type_mapping_report`` with a required language and optional compiler, target, runner, and cache settings. Argv is accepted for embedding and tests; on success the chosen report is written to standard output and zero is returned. Compiler probe and conversion diff --git a/prik/planning/planner.py b/prik/planning/planner.py index 9e35cd22b..b2acc46a2 100644 --- a/prik/planning/planner.py +++ b/prik/planning/planner.py @@ -121,7 +121,7 @@ TransformationPlan, ) from prik.naming.native_symbols import NativeSymbolNames -from prik.types.numpy import BOOLEAN_SEMANTIC_TYPE_NAMES +from prik.semantics.scalar_types import BOOLEAN_SEMANTIC_TYPE_NAMES from prik.utilities.visitor import ClassVisitor diff --git a/prik/policy/completion.py b/prik/policy/completion.py index 95a6a5d45..c72bc8bee 100644 --- a/prik/policy/completion.py +++ b/prik/policy/completion.py @@ -13,7 +13,7 @@ import re from collections.abc import Iterable -from prik.types.numpy import SEMANTIC_SCALAR_TYPE_NAMES +from prik.semantics.scalar_types import SEMANTIC_SCALAR_TYPE_NAMES from prik.policy.ownership import ( CodegenAction, OwnershipDecision, diff --git a/prik/policy/construction.py b/prik/policy/construction.py index f5c16ba08..82ccc3cf7 100644 --- a/prik/policy/construction.py +++ b/prik/policy/construction.py @@ -153,7 +153,7 @@ declaration_extent_references, resolve_declaration_extent, ) -from prik.types.numpy import BOOLEAN_SEMANTIC_TYPE_NAMES, is_boolean_semantic_type_name +from prik.semantics.scalar_types import BOOLEAN_SEMANTIC_TYPE_NAMES, is_boolean_semantic_type_name _PLAN_PRIMITIVE_SCALAR_TYPES = frozenset( diff --git a/prik/policy/ownership.py b/prik/policy/ownership.py index 429b791c9..d2a7433d7 100644 --- a/prik/policy/ownership.py +++ b/prik/policy/ownership.py @@ -52,7 +52,7 @@ ) from prik.semantics.models import PYTHON_VALUE_IMMUTABLE, PYTHON_VALUE_MUTABILITY_METADATA from prik.semantics.ownership_metadata import OWNERSHIP_POLICY_METADATA, POINTER_POLICY_METADATA -from prik.types.numpy import BOOLEAN_SEMANTIC_TYPE_NAMES +from prik.semantics.scalar_types import BOOLEAN_SEMANTIC_TYPE_NAMES # Completed policy vocabulary diff --git a/prik/printers/pyi.py b/prik/printers/pyi.py index b19450db9..5e8aa77f3 100644 --- a/prik/printers/pyi.py +++ b/prik/printers/pyi.py @@ -16,14 +16,15 @@ import keyword import re +from prik.codegen.primitive_scalar_types import NumpyDtypeRegistry from prik.contracts import CONTRACT_SYMBOLS, CONTRACT_TYPE_NAMES from prik.naming import NamingPolicy +from prik.semantics.scalar_types import SEMANTIC_SCALAR_TYPE_NAMES from prik.semantics.ownership_metadata import ( OWNERSHIP_POLICY_METADATA, POINTER_POLICY_FIELDS, POINTER_POLICY_METADATA, ) -from prik.types.numpy import SEMANTIC_DTYPE_TO_NUMPY_DTYPE, SEMANTIC_SCALAR_TYPE_NAMES from prik.semantics.metadata import ( ADDRESS_ROLE_METADATA, ADDRESS_ROLE_PROJECTION, @@ -1296,7 +1297,7 @@ def _constructor_accepts_field(field: SemanticVariable) -> bool: field.visibility == "public" and semantic_type.rank == 0 and semantic_type.name != "String" - and semantic_type.name in SEMANTIC_DTYPE_TO_NUMPY_DTYPE + and semantic_type.name in NumpyDtypeRegistry.TYPES ) @staticmethod diff --git a/prik/semantics/README.md b/prik/semantics/README.md index f1c7cf458..bb42979bb 100644 --- a/prik/semantics/README.md +++ b/prik/semantics/README.md @@ -9,6 +9,7 @@ implementation plans live in `../planning/`. | File | Owns | | --- | --- | | `models.py` | Semantic IR dataclasses and core model metadata. | +| `scalar_types.py` | Stable primitive scalar names, families, and intrinsic storage facts without NumPy or backend spellings. | | `metadata.py` | Cross-stage semantic metadata keys consumed after `.pyi`, C, or Fortran conversion. | | `fortran2ir.py` | Fortran parser facts to semantic modules. | | `c2ir.py` | C parser facts to semantic modules. | @@ -104,6 +105,7 @@ completion remains the next shared stage after those converters produce - `.pyi` wrapper checklist: `docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md` - Source navigation: `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` - Pipeline map: `docs/maintainer/internal-architecture/pipeline-map.md` +- Datatype lifecycle: `docs/maintainer/internal-architecture/type-system.md` - Semantic tests: `tests/fortran/semantic_ir/semantics/` - `.pyi` tests: `tests/fortran/semantic_pyi_format/` - Wrapper behavior that reaches the typed plan: `tests/fortran/` diff --git a/prik/semantics/c2ir.py b/prik/semantics/c2ir.py index 997d8049c..86a4fa9b9 100644 --- a/prik/semantics/c2ir.py +++ b/prik/semantics/c2ir.py @@ -13,7 +13,7 @@ from pathlib import Path from typing import Any -from prik.types.numpy import BOOLEAN_STORAGE_BITS +from prik.semantics.scalar_types import BOOLEAN_STORAGE_BITS from prik.parsers.c.models import ( CArray, diff --git a/prik/semantics/fortran2ir.py b/prik/semantics/fortran2ir.py index 107a24d2e..e109ace87 100644 --- a/prik/semantics/fortran2ir.py +++ b/prik/semantics/fortran2ir.py @@ -41,7 +41,11 @@ ) from prik.semantics.ownership_metadata import set_ownership_metadata from prik.semantics.metadata import BIND_TARGET_METADATA, PROJECTED_OUTPUT_METADATA, SCALAR_STORAGE_CATEGORY -from prik.types.numpy import BOOLEAN_STORAGE_BITS, SEMANTIC_SCALAR_TYPE_NAMES, is_boolean_semantic_type_name +from prik.semantics.scalar_types import ( + BOOLEAN_STORAGE_BITS, + SEMANTIC_SCALAR_TYPE_NAMES, + is_boolean_semantic_type_name, +) from prik.utilities.visitor import ClassVisitor from prik.semantics.models import ( diff --git a/prik/semantics/pyi2ir.py b/prik/semantics/pyi2ir.py index 95d98ca45..91533f9ad 100644 --- a/prik/semantics/pyi2ir.py +++ b/prik/semantics/pyi2ir.py @@ -20,7 +20,7 @@ is_declaration_expression_helper, is_public_declaration_expression, ) -from prik.types.numpy import SEMANTIC_SCALAR_TYPE_NAMES +from prik.semantics.scalar_types import SEMANTIC_SCALAR_TYPE_NAMES from prik.semantics.ownership_metadata import ( OWNERSHIP_POLICY_METADATA, set_ownership_metadata, diff --git a/prik/semantics/scalar_types.py b/prik/semantics/scalar_types.py new file mode 100644 index 000000000..acccb2d35 --- /dev/null +++ b/prik/semantics/scalar_types.py @@ -0,0 +1,110 @@ +"""Stable primitive scalar vocabulary shared by semantic consumers. + +This module owns language-neutral scalar identities and intrinsic storage +facts. It intentionally does not import NumPy or contain generated C or +Fortran spellings; those are boundary representations owned by codegen. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass +from enum import Enum +from types import MappingProxyType +from typing import Final + + +class SemanticScalarFamily(str, Enum): + """Classify one scalar identity without selecting a backend spelling.""" + + BOOLEAN = "boolean" + SIGNED_INTEGER = "signed_integer" + UNSIGNED_INTEGER = "unsigned_integer" + REAL = "real" + COMPLEX = "complex" + CHARACTER = "character" + BYTE = "byte" + ENUM = "enum" + VOID = "void" + + +@dataclass(frozen=True) +class SemanticScalarSpec: + """Describe intrinsic facts attached to one stable semantic scalar name. + + ``family`` groups identities used by semantic conversion and policy. + ``storage_bits`` is present only when the semantic name itself fixes the + storage width; target-dependent names such as ``Int`` and ``SizeT`` leave + it unresolved. + """ + + family: SemanticScalarFamily + storage_bits: int | None = None + + +SEMANTIC_SCALAR_TYPES: Final[Mapping[str, SemanticScalarSpec]] = MappingProxyType( + { + "Bool": SemanticScalarSpec(SemanticScalarFamily.BOOLEAN, 8), + "Bool8": SemanticScalarSpec(SemanticScalarFamily.BOOLEAN, 8), + "Bool16": SemanticScalarSpec(SemanticScalarFamily.BOOLEAN, 16), + "Bool32": SemanticScalarSpec(SemanticScalarFamily.BOOLEAN, 32), + "Bool64": SemanticScalarSpec(SemanticScalarFamily.BOOLEAN, 64), + "Byte": SemanticScalarSpec(SemanticScalarFamily.BYTE, 8), + "CEnum": SemanticScalarSpec(SemanticScalarFamily.ENUM), + "Char": SemanticScalarSpec(SemanticScalarFamily.CHARACTER), + "Complex64": SemanticScalarSpec(SemanticScalarFamily.COMPLEX, 64), + "Complex128": SemanticScalarSpec(SemanticScalarFamily.COMPLEX, 128), + "Complex256": SemanticScalarSpec(SemanticScalarFamily.COMPLEX), + "Float16": SemanticScalarSpec(SemanticScalarFamily.REAL, 16), + "Float32": SemanticScalarSpec(SemanticScalarFamily.REAL, 32), + "Float64": SemanticScalarSpec(SemanticScalarFamily.REAL, 64), + "Float128": SemanticScalarSpec(SemanticScalarFamily.REAL), + "Int": SemanticScalarSpec(SemanticScalarFamily.SIGNED_INTEGER), + "Int8": SemanticScalarSpec(SemanticScalarFamily.SIGNED_INTEGER, 8), + "Int16": SemanticScalarSpec(SemanticScalarFamily.SIGNED_INTEGER, 16), + "Int32": SemanticScalarSpec(SemanticScalarFamily.SIGNED_INTEGER, 32), + "Int64": SemanticScalarSpec(SemanticScalarFamily.SIGNED_INTEGER, 64), + "SizeT": SemanticScalarSpec(SemanticScalarFamily.UNSIGNED_INTEGER), + "String": SemanticScalarSpec(SemanticScalarFamily.CHARACTER), + "UInt": SemanticScalarSpec(SemanticScalarFamily.UNSIGNED_INTEGER), + "UInt8": SemanticScalarSpec(SemanticScalarFamily.UNSIGNED_INTEGER, 8), + "UInt16": SemanticScalarSpec(SemanticScalarFamily.UNSIGNED_INTEGER, 16), + "UInt32": SemanticScalarSpec(SemanticScalarFamily.UNSIGNED_INTEGER, 32), + "UInt64": SemanticScalarSpec(SemanticScalarFamily.UNSIGNED_INTEGER, 64), + "Void": SemanticScalarSpec(SemanticScalarFamily.VOID), + } +) + +SEMANTIC_SCALAR_TYPE_NAMES: Final[frozenset[str]] = frozenset(SEMANTIC_SCALAR_TYPES) +BOOLEAN_SEMANTIC_TYPE_NAMES: Final[frozenset[str]] = frozenset( + name for name, spec in SEMANTIC_SCALAR_TYPES.items() if spec.family is SemanticScalarFamily.BOOLEAN +) +BOOLEAN_STORAGE_BITS: Final[Mapping[str, int]] = MappingProxyType( + {name: spec.storage_bits for name, spec in SEMANTIC_SCALAR_TYPES.items() if name in BOOLEAN_SEMANTIC_TYPE_NAMES} +) + + +def is_boolean_semantic_type_name(name: str | None) -> bool: + """Return whether ``name`` identifies a supported Boolean storage contract.""" + return name in BOOLEAN_SEMANTIC_TYPE_NAMES + + +def boolean_storage_bits(name: str) -> int: + """Return the fixed native storage width of one Boolean semantic name. + + Unknown and non-Boolean names raise ``KeyError`` so callers cannot invent + a native representation when target measurement is required. + """ + return BOOLEAN_STORAGE_BITS[name] + + +__all__ = ( + "BOOLEAN_SEMANTIC_TYPE_NAMES", + "BOOLEAN_STORAGE_BITS", + "SEMANTIC_SCALAR_TYPES", + "SEMANTIC_SCALAR_TYPE_NAMES", + "SemanticScalarFamily", + "SemanticScalarSpec", + "boolean_storage_bits", + "is_boolean_semantic_type_name", +) diff --git a/prik/types/__init__.py b/prik/types/__init__.py deleted file mode 100644 index 30caaef32..000000000 --- a/prik/types/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Mappings from semantic types to Python ecosystem types.""" diff --git a/prik/types/numpy.py b/prik/types/numpy.py deleted file mode 100644 index 106737976..000000000 --- a/prik/types/numpy.py +++ /dev/null @@ -1,117 +0,0 @@ -"""NumPy dtype mappings for resolved semantic dtype names.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Final - -if TYPE_CHECKING: - from prik.semantics.models import SemanticType - - -BOOLEAN_STORAGE_BITS: Final[dict[str, int]] = { - "Bool": 8, - "Bool8": 8, - "Bool16": 16, - "Bool32": 32, - "Bool64": 64, -} -BOOLEAN_SEMANTIC_TYPE_NAMES: Final[frozenset[str]] = frozenset(BOOLEAN_STORAGE_BITS) - - -SEMANTIC_DTYPE_TO_NUMPY_DTYPE: Final[dict[str, str]] = { - **dict.fromkeys(BOOLEAN_SEMANTIC_TYPE_NAMES, "numpy.bool_"), - "Int8": "numpy.int8", - "Int16": "numpy.int16", - "Int32": "numpy.int32", - "Int64": "numpy.int64", - "UInt8": "numpy.uint8", - "UInt16": "numpy.uint16", - "UInt32": "numpy.uint32", - "UInt64": "numpy.uint64", - "Float16": "numpy.float16", - "Float32": "numpy.float32", - "Float64": "numpy.float64", - "Float128": "numpy.longdouble", - "Complex64": "numpy.complex64", - "Complex128": "numpy.complex128", - "Complex256": "numpy.clongdouble", - "String": "numpy.str_", - "SizeT": "numpy.uintp", -} - - -def is_boolean_semantic_type_name(name: str | None) -> bool: - """Return whether ``name`` identifies a supported Boolean storage contract. - - All supported names share NumPy's one-byte ``bool_`` boundary. Their - numeric suffix records native storage bits for language-specific lowering. - """ - return name in BOOLEAN_SEMANTIC_TYPE_NAMES - - -def boolean_storage_bits(name: str) -> int: - """Return the native storage bits represented by one Boolean contract. - - ``Bool`` and ``Bool8`` both return eight. Unknown names raise ``KeyError`` - so callers cannot silently invent a native Boolean representation. - """ - return BOOLEAN_STORAGE_BITS[name] - - -SEMANTIC_SCALAR_TYPE_NAMES: Final[frozenset[str]] = frozenset( - { - *SEMANTIC_DTYPE_TO_NUMPY_DTYPE, - "Byte", - "CEnum", - "Char", - "Int", - "UInt", - "Void", - } -) - - -def numpy_dtype_expression(semantic_dtype: str | None) -> str: - """Return the qualified NumPy dtype expression for a resolved semantic dtype.""" - if semantic_dtype is None: - raise KeyError("Semantic dtype is not resolved") - dtype = str(semantic_dtype) - try: - return SEMANTIC_DTYPE_TO_NUMPY_DTYPE[dtype] - except KeyError: - raise KeyError(f"No NumPy dtype mapping for semantic dtype {dtype!r}") from None - - -def semantic_dtype_to_numpy_dtype(semantic_dtype: str | None) -> Any: - """Return a live ``numpy.dtype`` for a resolved semantic dtype.""" - import numpy - - expression = numpy_dtype_expression(semantic_dtype) - return numpy.dtype(getattr(numpy, expression.removeprefix("numpy."))) - - -def semantic_dtype_to_numpy_dtype_map() -> dict[str, Any]: - """Return a dictionary mapping resolved semantic dtypes to live ``numpy.dtype`` objects.""" - return { - semantic_dtype: semantic_dtype_to_numpy_dtype(semantic_dtype) - for semantic_dtype in SEMANTIC_DTYPE_TO_NUMPY_DTYPE - } - - -def semantic_type_to_numpy_dtype(semantic_type: SemanticType) -> Any: - """Return a live ``numpy.dtype`` using ``SemanticType.dtype``, not ``SemanticType.name``.""" - return semantic_dtype_to_numpy_dtype(semantic_type.dtype) - - -__all__ = ( - "BOOLEAN_SEMANTIC_TYPE_NAMES", - "BOOLEAN_STORAGE_BITS", - "SEMANTIC_DTYPE_TO_NUMPY_DTYPE", - "SEMANTIC_SCALAR_TYPE_NAMES", - "boolean_storage_bits", - "is_boolean_semantic_type_name", - "numpy_dtype_expression", - "semantic_dtype_to_numpy_dtype", - "semantic_dtype_to_numpy_dtype_map", - "semantic_type_to_numpy_dtype", -) diff --git a/tests/docs/_structure_support.py b/tests/docs/_structure_support.py index 898540c26..0ae89c446 100644 --- a/tests/docs/_structure_support.py +++ b/tests/docs/_structure_support.py @@ -270,6 +270,7 @@ "prik/cli.py", "prik/pipeline/build.py", "prik/pipeline/preprocessing.py", + "prik/pipeline/type_mapping_report.py", "prik/probes/c_types.py", "prik/probes/fortran_types.py", "prik/semantics/ownership_metadata.py", @@ -281,6 +282,7 @@ "prik/parsers/fortran/cli.py", "prik/parsers/pyi/parser.py", "prik/semantics/models.py", + "prik/semantics/scalar_types.py", "prik/semantics/fortran2ir.py", "prik/semantics/c2ir.py", "prik/semantics/pyi2ir.py", @@ -294,6 +296,7 @@ "prik/planning/planner.py", "prik/naming/native_symbols.py", "prik/codegen/docstrings.py", + "prik/codegen/primitive_scalar_types.py", "prik/pipeline/wrapper.py", "prik/codegen/c/binding.py", "prik/codegen/fortran/bridge.py", @@ -319,6 +322,7 @@ "docs/user/reference/python-api.md", "docs/user/reference/semantic-ir.md", "docs/user/reference/semantic-pyi-format.md", + "docs/maintainer/internal-architecture/type-system.md", "docs/developer/build-system.md", "docs/developer/c-parser-reference.md", "docs/developer/fortran-parser-reference.md", diff --git a/tests/fortran/data_types/codegen/test_primitive_scalar_type_catalogue.py b/tests/fortran/data_types/codegen/test_primitive_scalar_type_catalogue.py new file mode 100644 index 000000000..b79e5ea57 --- /dev/null +++ b/tests/fortran/data_types/codegen/test_primitive_scalar_type_catalogue.py @@ -0,0 +1,43 @@ +"""Readable NumPy projection and primitive backend catalogue invariants.""" + +import pytest + +from prik.codegen.primitive_scalar_types import ( + NumpyDtypeRegistry, + PrimitiveScalarTypeRegistry, +) + + +def test_numpy_projection_catalogue_uses_resolved_semantic_names(): + assert NumpyDtypeRegistry.TYPES["Bool64"] == "numpy.bool_" + assert NumpyDtypeRegistry.TYPES["Int32"] == "numpy.int32" + assert NumpyDtypeRegistry.TYPES["Float128"] == "numpy.longdouble" + assert NumpyDtypeRegistry.TYPES["Complex256"] == "numpy.clongdouble" + assert NumpyDtypeRegistry.TYPES["SizeT"] == "numpy.uintp" + assert "Int" not in NumpyDtypeRegistry.TYPES + + +def test_numpy_projection_rejects_unresolved_and_unknown_semantic_dtypes(): + with pytest.raises(KeyError, match="Semantic dtype is not resolved"): + NumpyDtypeRegistry.expression_for(None) + + with pytest.raises(KeyError, match="No NumPy dtype mapping for semantic dtype 'Int'"): + NumpyDtypeRegistry.expression_for("Int") + + +def test_backend_catalogue_makes_each_emitted_representation_explicit(): + scalar = PrimitiveScalarTypeRegistry.type_for("Float64") + + assert scalar.semantic_name == "Float64" + assert scalar.c_spelling == "double" + assert scalar.fortran_spelling == "real(c_double)" + assert scalar.numpy_type_macro == "NPY_FLOAT64" + assert scalar.python_type_name == NumpyDtypeRegistry.TYPES["Float64"] + assert scalar.cfi_type_spelling == "CFI_type_double" + + +def test_backend_catalogue_returns_detached_records(): + scalar = PrimitiveScalarTypeRegistry.type_for("Int32") + scalar.c_spelling = "changed" + + assert PrimitiveScalarTypeRegistry.type_for("Int32").c_spelling == "int32_t" diff --git a/tests/fortran/infrastructure/types/test_mapping_report.py b/tests/fortran/data_types/pipeline/test_type_mapping_report.py similarity index 97% rename from tests/fortran/infrastructure/types/test_mapping_report.py rename to tests/fortran/data_types/pipeline/test_type_mapping_report.py index 4b169c828..e3f85ec26 100644 --- a/tests/fortran/infrastructure/types/test_mapping_report.py +++ b/tests/fortran/data_types/pipeline/test_type_mapping_report.py @@ -7,7 +7,7 @@ import pytest -import prik.probes.report as type_mapping_report +import prik.pipeline.type_mapping_report as type_mapping_report @pytest.mark.parametrize( @@ -109,7 +109,7 @@ def test_type_mapping_report_direct_script_runs_its_no_argument_example(): pytest.skip("cc is required for the direct type-mapping example") completed = subprocess.run( - [sys.executable, "prik/probes/report.py"], + [sys.executable, "prik/pipeline/type_mapping_report.py"], cwd=Path(__file__).resolve().parents[4], capture_output=True, text=True, diff --git a/tests/fortran/data_types/runtime/test_contract_scalar_constructors.py b/tests/fortran/data_types/runtime/test_contract_scalar_constructors.py index 97853af81..f3bacccdf 100644 --- a/tests/fortran/data_types/runtime/test_contract_scalar_constructors.py +++ b/tests/fortran/data_types/runtime/test_contract_scalar_constructors.py @@ -36,6 +36,8 @@ def test_concrete_primitive_default_constructors_return_zero_numpy_scalars(): assert isinstance(value, scalar_type), contract assert value == scalar_type(0), contract + assert set(contracts._CONTRACT_NUMPY_FACTORIES) == {contract.__name__ for contract, _ in cases} + def test_primitive_contract_constructors_reject_values_and_array_annotations(): with pytest.raises(TypeError, match="ordinary array contract annotations"): diff --git a/tests/fortran/data_types/semantics/test_scalar_type_catalogue.py b/tests/fortran/data_types/semantics/test_scalar_type_catalogue.py new file mode 100644 index 000000000..45b5df597 --- /dev/null +++ b/tests/fortran/data_types/semantics/test_scalar_type_catalogue.py @@ -0,0 +1,43 @@ +"""Semantic scalar catalogue invariants.""" + +import pytest + +from prik.semantics.scalar_types import ( + BOOLEAN_SEMANTIC_TYPE_NAMES, + SEMANTIC_SCALAR_TYPES, + SEMANTIC_SCALAR_TYPE_NAMES, + SemanticScalarFamily, + boolean_storage_bits, + is_boolean_semantic_type_name, +) + + +def test_scalar_catalogue_exposes_semantic_family_and_storage_without_numpy_facts(): + assert SEMANTIC_SCALAR_TYPES["Int32"].family is SemanticScalarFamily.SIGNED_INTEGER + assert SEMANTIC_SCALAR_TYPES["Int32"].storage_bits == 32 + assert SEMANTIC_SCALAR_TYPES["Float64"].family is SemanticScalarFamily.REAL + assert SEMANTIC_SCALAR_TYPES["Float64"].storage_bits == 64 + assert SEMANTIC_SCALAR_TYPES["String"].family is SemanticScalarFamily.CHARACTER + assert SEMANTIC_SCALAR_TYPES["String"].storage_bits is None + assert frozenset(SEMANTIC_SCALAR_TYPES) == SEMANTIC_SCALAR_TYPE_NAMES + + +def test_boolean_catalogue_preserves_native_widths_that_numpy_bool_cannot_distinguish(): + names = ("Bool", "Bool8", "Bool16", "Bool32", "Bool64") + + assert frozenset(names) == BOOLEAN_SEMANTIC_TYPE_NAMES + assert all(is_boolean_semantic_type_name(name) for name in names) + assert [boolean_storage_bits(name) for name in names] == [8, 8, 16, 32, 64] + assert not is_boolean_semantic_type_name(None) + assert not is_boolean_semantic_type_name("Int8") + + with pytest.raises(KeyError): + boolean_storage_bits("Int8") + + +def test_target_dependent_semantic_names_do_not_invent_storage_widths(): + assert SEMANTIC_SCALAR_TYPES["Int"].storage_bits is None + assert SEMANTIC_SCALAR_TYPES["UInt"].storage_bits is None + assert SEMANTIC_SCALAR_TYPES["SizeT"].storage_bits is None + assert SEMANTIC_SCALAR_TYPES["Float128"].storage_bits is None + assert SEMANTIC_SCALAR_TYPES["Complex256"].storage_bits is None diff --git a/tests/fortran/infrastructure/types/test_numpy.py b/tests/fortran/infrastructure/types/test_numpy.py deleted file mode 100644 index 3888add3a..000000000 --- a/tests/fortran/infrastructure/types/test_numpy.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Semantic-to-NumPy dtype mapping tests.""" - -import pytest - -from prik.semantics.models import SemanticType -from prik.types.numpy import ( - SEMANTIC_DTYPE_TO_NUMPY_DTYPE, - boolean_storage_bits, - is_boolean_semantic_type_name, - numpy_dtype_expression, - semantic_dtype_to_numpy_dtype, - semantic_dtype_to_numpy_dtype_map, - semantic_type_to_numpy_dtype, -) - - -def test_semantic_dtype_to_numpy_dtype_dictionary_uses_resolved_widths(): - assert SEMANTIC_DTYPE_TO_NUMPY_DTYPE == { - "Bool": "numpy.bool_", - "Bool8": "numpy.bool_", - "Bool16": "numpy.bool_", - "Bool32": "numpy.bool_", - "Bool64": "numpy.bool_", - "Int8": "numpy.int8", - "Int16": "numpy.int16", - "Int32": "numpy.int32", - "Int64": "numpy.int64", - "UInt8": "numpy.uint8", - "UInt16": "numpy.uint16", - "UInt32": "numpy.uint32", - "UInt64": "numpy.uint64", - "Float16": "numpy.float16", - "Float32": "numpy.float32", - "Float64": "numpy.float64", - "Float128": "numpy.longdouble", - "Complex64": "numpy.complex64", - "Complex128": "numpy.complex128", - "Complex256": "numpy.clongdouble", - "String": "numpy.str_", - "SizeT": "numpy.uintp", - } - assert "Int" not in SEMANTIC_DTYPE_TO_NUMPY_DTYPE - assert all(is_boolean_semantic_type_name(name) for name in ("Bool", "Bool8", "Bool16", "Bool32", "Bool64")) - assert [boolean_storage_bits(name) for name in ("Bool", "Bool8", "Bool16", "Bool32", "Bool64")] == [ - 8, - 8, - 16, - 32, - 64, - ] - - -def test_numpy_dtype_expression_rejects_unresolved_or_unknown_semantic_dtypes(): - with pytest.raises(KeyError, match="Semantic dtype is not resolved"): - numpy_dtype_expression(None) - - with pytest.raises(KeyError, match="No NumPy dtype mapping for semantic dtype 'Int'"): - numpy_dtype_expression("Int") - - -def test_semantic_type_to_numpy_dtype_uses_dtype_not_name(): - numpy = pytest.importorskip("numpy") - semantic_type = SemanticType("Int", dtype="Int64") - - assert semantic_type_to_numpy_dtype(semantic_type) == numpy.dtype(numpy.int64) - assert semantic_dtype_to_numpy_dtype("Float16") == numpy.dtype(numpy.float16) - assert semantic_dtype_to_numpy_dtype("Float64") == numpy.dtype(numpy.float64) - dtype_map = semantic_dtype_to_numpy_dtype_map() - assert dtype_map["Int32"] == numpy.dtype(numpy.int32) - assert set(dtype_map) == set(SEMANTIC_DTYPE_TO_NUMPY_DTYPE) From 7d9c29c689eb1d2e095d61676ad8aabe5bdb9d28 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 12:08:35 +0100 Subject: [PATCH 17/22] improve the structure of the project --- CHANGELOG.md | 4 + docs/developer/c-parser-reference.md | 4 +- docs/developer/compiler-preprocessing.md | 2 +- docs/developer/development-workflow.md | 20 +- docs/developer/feature-to-code-map.md | 6 +- docs/developer/repository-structure.md | 16 +- docs/developer/source-map.md | 53 +++-- docs/developer/testing-strategy.md | 2 +- .../internal-architecture/pipeline-map.md | 24 +- .../internal-architecture/type-system.md | 6 +- docs/user/reference/semantic-ir.md | 4 +- examples/lapack/ci/full_surface.py | 2 +- prik/README.md | 9 +- prik/__init__.py | 2 +- prik/cli.py | 10 +- prik/{compiling => compiler}/README.md | 4 +- prik/{compiling => compiler}/__init__.py | 0 .../compiler_profiles.py | 0 prik/{compiling => compiler}/compilers.py | 0 .../{compiling => compiler}/native_support.py | 0 prik/{compiling => compiler}/objects.py | 0 prik/parsers/c/README.md | 5 +- prik/parsers/c/parser.py | 2 +- prik/pipeline/README.md | 9 +- prik/pipeline/build.py | 10 +- prik/pipeline/type_mapping_report.py | 6 +- prik/planning/README.md | 3 + prik/policy/README.md | 3 + prik/preprocessing/README.md | 45 ++++ prik/preprocessing/__init__.py | 55 +++++ .../c/preprocessor.py => preprocessing/c.py} | 4 +- prik/preprocessing/fortran.py | 211 ++++++++++++++++++ prik/preprocessing/probes/__init__.py | 1 + prik/{ => preprocessing}/probes/c_types.py | 4 +- .../probes/fortran_types.py | 4 +- .../source.py} | 201 +---------------- prik/printers/README.md | 3 + prik/probes/__init__.py | 1 - tests/c/_support/fixture_outputs.py | 2 +- tests/c/_support/preprocessing.py | 2 +- .../parser/generate_c_parser_goldens.py | 2 +- tests/c/parsing/test_c_cli_skeleton.py | 2 +- tests/c/parsing/test_c_compiler_extensions.py | 2 +- tests/c/parsing/test_c_corpus.py | 2 +- tests/c/parsing/test_c_fixture_suite.py | 2 +- tests/c/parsing/test_c_lexer_preprocessor.py | 10 +- .../test_c_preprocessing_configuration.py | 4 +- .../test_c_preprocessing_dependencies.py | 16 +- .../test_c_preprocessing_execution.py | 6 +- tests/c/preprocessing/test_error_paths.py | 4 +- tests/c/preprocessing/test_source_mappings.py | 4 +- tests/c/probes/test_c_types.py | 10 +- tests/docs/_structure_support.py | 31 ++- tests/fortran/_support/wrapper_build.py | 4 +- .../policy/test_allocatable_result_policy.py | 2 +- .../compiling/test_compiler_verbose.py | 10 +- .../end_to_end/test_source_build_modes.py | 2 +- .../pipeline/test_generated_wrapper_build.py | 2 +- .../pipeline/test_parallel_compilation.py | 2 +- .../callbacks/policy/test_callback_policy.py | 2 +- .../pipeline/test_argument_contract.py | 2 +- .../pipeline/test_output_contract.py | 2 +- .../pipeline/test_stage_dispatch.py | 2 +- .../probes/test_fortran_type_probes.py | 10 +- .../compiling/test_verbose_commands.py | 2 +- .../policy/test_function_result_policy.py | 2 +- .../policy/test_generic_policy.py | 2 +- .../semantics/test_wrapper_policy.py | 2 +- .../policy/test_optional_policy.py | 2 +- .../policy/test_raw_address_policy.py | 2 +- .../test_authoritative_contract_runtime.py | 2 +- .../preprocessing/_support.py | 2 +- .../test_configuration_and_adapters.py | 4 +- .../test_dependencies_and_includes.py | 20 +- .../preprocessing/test_execution.py | 7 +- .../test_preprocessing_properties.py | 4 +- .../policy/test_string_wrapper_policy.py | 2 +- .../policy/test_subroutine_output_policy.py | 2 +- tools/wrapper_plan_staged_walkthrough.py | 2 +- 79 files changed, 562 insertions(+), 369 deletions(-) rename prik/{compiling => compiler}/README.md (97%) rename prik/{compiling => compiler}/__init__.py (100%) rename prik/{compiling => compiler}/compiler_profiles.py (100%) rename prik/{compiling => compiler}/compilers.py (100%) rename prik/{compiling => compiler}/native_support.py (100%) rename prik/{compiling => compiler}/objects.py (100%) create mode 100644 prik/preprocessing/README.md create mode 100644 prik/preprocessing/__init__.py rename prik/{parsers/c/preprocessor.py => preprocessing/c.py} (97%) create mode 100644 prik/preprocessing/fortran.py create mode 100644 prik/preprocessing/probes/__init__.py rename prik/{ => preprocessing}/probes/c_types.py (99%) rename prik/{ => preprocessing}/probes/fortran_types.py (99%) rename prik/{pipeline/preprocessing.py => preprocessing/source.py} (87%) delete mode 100644 prik/probes/__init__.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 86307b75f..da5b23817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ release tags add a leading `v` to the package version. ### Changed +- Reorganized compiler and pre-parse infrastructure into `prik.compiler` and + `prik.preprocessing`, including C/Fortran preprocessing and target probes; + the former `prik.compiling`, `prik.probes`, parser-local C preprocessor, and + pipeline-local preprocessing import paths were removed. - Replaced the public semantic-to-NumPy helper API with stage-owned semantic, contract-runtime, and code-generation datatype catalogues, and documented the complete internal datatype lifecycle from compiler probing to runtime diff --git a/docs/developer/c-parser-reference.md b/docs/developer/c-parser-reference.md index b0531e4de..d69efeb70 100644 --- a/docs/developer/c-parser-reference.md +++ b/docs/developer/c-parser-reference.md @@ -154,7 +154,7 @@ PRIK_C_DOCS_END --> generated by the shared prik CLI - compiler-derived target ABI probing for every modeled arithmetic primitive, `size_t`, `uint32_t`, `time_t`, and opaque `FILE` handles through - `prik.probes.c_types`, with reusable memory and persistent caches + `prik.preprocessing.probes.c_types`, with reusable memory and persistent caches - C directory/file-list discovery for `.c`, `.h`, and direct `.i` inputs in explicit C mode, while leaving Fortran directory scanning unchanged - include resolution for quoted includes relative to the current file and @@ -651,7 +651,7 @@ PRIK_C_DOCS_END --> Fortran target datatype mapping and compile-time path: @@ -856,7 +856,7 @@ The main ownership boundaries are: into validated typed plans; - `prik/pipeline/wrapper.py`: direct bridge, binding, and source artifact generation; -- `prik/compiling/`: compiler commands and shared-library linking; and +- `prik/compiler/`: compiler commands and shared-library linking; and - `prik/binding_support/`: native binding support copied into each build. ## Common Change Routes @@ -42,8 +44,8 @@ change crosses ownership boundaries. | Change area | Open first | Public docs to update | Focused evidence | | --- | --- | --- | --- | | CLI flags, stage selection, output formatting, diagnostics | `prik/cli.py` | `docs/user/reference/cli-commands.md`, `docs/user/getting-started/beginner-workflow.md` | `tests/fortran/command_line_interface/pipeline/`, `tests/docs/test_examples.py` | -| Compiler preprocessing, include paths, macros, and target flags | `prik/pipeline/preprocessing.py` | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/compiler-preprocessing.md`, `docs/developer/fortran-parser-reference.md` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` | -| Datatype probing, semantic normalization, NumPy projection, and mapping reports | `prik/probes/fortran_types.py`, `prik/semantics/scalar_types.py`, `prik/codegen/primitive_scalar_types.py`, `prik/pipeline/type_mapping_report.py` | `docs/maintainer/internal-architecture/type-system.md`, `docs/user/reference/semantic-ir.md` | `tests/fortran/data_types/` | +| Compiler preprocessing, include paths, macros, and target flags | `prik/preprocessing/source.py` | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/compiler-preprocessing.md`, `docs/developer/fortran-parser-reference.md` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` | +| Datatype probing, semantic normalization, NumPy projection, and mapping reports | `prik/preprocessing/probes/fortran_types.py`, `prik/semantics/scalar_types.py`, `prik/codegen/primitive_scalar_types.py`, `prik/pipeline/type_mapping_report.py` | `docs/maintainer/internal-architecture/type-system.md`, `docs/user/reference/semantic-ir.md` | `tests/fortran/data_types/` | | Fortran parser facts and diagnostics | `prik/parsers/fortran/parser.py` | `docs/developer/fortran-parser-reference.md`, `docs/user/examples/recipes/inspect-fortran-api.md` | `tests/fortran/source_parsing/parsing/` | | Semantic `.pyi` parsing, conversion, printing, package generation, and round-trip behavior | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/printers/pyi.py` | `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/pyi-contracts/index.md`, `docs/user/examples/recipes/semantic-pyi-contracts.md` | `tests/fortran/semantic_pyi_format/`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/semantic_pyi_format/pipeline/` | | Wrapper-planning errors and support claims | `prik/policy/completion.py`, `prik/planning/planner.py` | `docs/user/reference/diagnostic-codes.md`, `docs/user/language-support/feature-matrix.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | @@ -51,7 +53,7 @@ change crosses ownership boundaries. | Semantic `.pyi` wrapper orchestration from native artifacts | `prik/pipeline/build.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/pyi_contracts/exports_and_modules/`, `tests/fortran/pyi_contracts/functions_and_classes/` | | Ownership, lifetime, output projection, and unsupported wrapper policy | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py` | `docs/user/guide/memory-management.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | | Immediate callback policy, typed adapters, and trampolines | `prik/policy/models.py`, `prik/policy/construction.py`, `prik/policy/completion.py`, `prik/planning/models.py`, `prik/planning/planner.py`, `prik/codegen/c/binding.py`, `prik/codegen/fortran/bridge.py` | `docs/user/guide/callbacks.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/callbacks/` | -| Native compilation, binding support, and shared-library linking | `prik/pipeline/build.py`, `prik/compiling/compilers.py`, `prik/compiling/native_support.py` | `docs/user/reference/fortran-wrapper.md`, `docs/developer/build-system.md` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | +| Native compilation, binding support, and shared-library linking | `prik/pipeline/build.py`, `prik/compiler/compilers.py`, `prik/compiler/native_support.py` | `docs/user/reference/fortran-wrapper.md`, `docs/developer/build-system.md` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | | Public Python exports | `prik/__init__.py` | `README.md`, `docs/user/reference/python-api.md` | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` | | Reference BLAS source ownership, inventory, and numerical validation | `examples/blas/routine_inventory.py`, `examples/blas/tests/test_routine_coverage.py` | `examples/blas/README.md`, `docs/user/examples/blas-wrapper.md` | `examples/blas/tests/test_*.py`, `examples/blas/ci/full_surface.py`, dedicated real-libraries workflow | | Reference LAPACK source ownership, inventory, and numerical validation | `examples/lapack/routine_inventory.py`, `examples/lapack/tests/test_routine_coverage.py` | `examples/lapack/README.md`, `docs/user/examples/lapack-wrapper.md` | `examples/lapack/tests/test_*.py`, `examples/lapack/ci/full_surface.py`, dedicated real-libraries workflow | @@ -60,7 +62,7 @@ change crosses ownership boundaries. | Source navigation documentation | `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md`, package README files | `docs/developer/source-map.md` | `tests/docs/test_reference_and_source_map.py` | | Package | Purpose | Main files | Primary tests and docs | | --- | --- | --- | --- | | `prik/contracts/` | Public semantic `.pyi` contract vocabulary | `__init__.py` | `tests/fortran/semantic_pyi_format/`, semantic `.pyi` reference | -| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, cross-stage datatype reporting, plan-to-source wrapper generation, and native build orchestration | `preprocessing.py`, `pyi.py`, `type_mapping_report.py`, `wrapper.py`, `build.py` | preprocessing, `.pyi`, datatype report, wrapper generation, and build tests | -| `prik/probes/` | Compiler-derived target facts | `fortran_types.py` | target probe tests | +| `prik/compiler/` | Compiler execution, compile objects, vendor profiles, native support installation, and linking | `compilers.py`, `objects.py`, `compiler_profiles.py`, `native_support.py` | compiler and shared-library build tests | +| `prik/preprocessing/` | Compiler-backed source expansion, raw C metadata, native Fortran includes, provenance, and target probes | `source.py`, `c.py`, `fortran.py`, `probes/` | C and Fortran preprocessing and target-probe tests | +| `prik/pipeline/` | Semantic `.pyi` loading, cross-stage datatype reporting, plan-to-source wrapper generation, and native build orchestration | `pyi.py`, `type_mapping_report.py`, `wrapper.py`, `build.py` | `.pyi`, datatype report, wrapper generation, and build tests | | `prik/runtime/` | Python runtime objects consumed by generated extensions | `handles.py` | runtime handle and wrapper runtime tests | | `prik/parsers/` | Public namespace for language and semantic `.pyi` frontends | child parser packages | `tests/fortran/source_parsing/parsing/`, `tests/c/parsing/`, `tests/fortran/semantic_pyi_format/parsing/` | | `prik/parsers/fortran/` | Fortran lexer, recursive parser, models, type resolver, and parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `type_resolver.py`, `cli.py` | `tests/fortran/source_parsing/parsing/`, `docs/developer/fortran-parser-reference.md` | @@ -85,13 +88,12 @@ PRIK_C_DOCS_END --> | `prik/planning/` | Editable backend-neutral wrapper-plan records and mechanical policy projection | `models.py`, `planner.py` | infrastructure and feature-local codegen tests | | `prik/codegen/` | Backend datatype projection, plan-driven docstrings, and direct lowering into C and Fortran syntax nodes | `primitive_scalar_types.py`, `docstrings.py`, `nodes.py`, `c/`, `fortran/` | data-type and infrastructure codegen, feature-local codegen, and end-to-end tests | | `prik/printers/` | Language-specific serialization of C nodes, Fortran nodes, and semantic IR | `c.py`, `fortran.py`, `pyi.py` | source-printer and semantic-contract printer tests | -| `prik/compiling/` | Native compile objects, compiler command execution, shared-library linking, and native support installation; wrapper build orchestration lives in `prik/pipeline/build.py` | `objects.py`, `compilers.py`, `compiler_profiles.py`, `native_support.py` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py` | | `prik/binding_support/` | Bundled header-only native binding support copied into generated wrapper builds | support header | wrapper build tests | | `prik/utilities/` | Small shared Python utilities | `strings.py`, `visitor.py` | `tests/fortran/infrastructure/utilities/` and tests that exercise callers | @@ -107,9 +109,9 @@ update this table, the package README files, and the mechanical checks in | `prik/__init__.py` | Public Python API exports. | | `prik/cli.py` | CLI argument validation, stage selection, output routing, and wrapper-build entry. | | `prik/pipeline/build.py` | End-to-end source and `.pyi` wrapper build orchestration. | -| `prik/pipeline/preprocessing.py` | Compiler-backed source preprocessing and dependency facts. | +| `prik/preprocessing/source.py` | Compiler-backed source preprocessing and dependency facts. | | `prik/pipeline/type_mapping_report.py` | Target facts, semantic conversion, and backend NumPy projection rendered as a mapping report. | -| `prik/probes/fortran_types.py` | Fortran kind and storage probing. | +| `prik/preprocessing/probes/fortran_types.py` | Fortran kind and storage probing. | | `prik/semantics/scalar_types.py` | Stable primitive scalar identities, families, and intrinsic storage widths. | | `prik/semantics/ownership_metadata.py` | Raw ownership and pointer-contract metadata keys and normalized semantic setters. | | `prik/semantics/native_array_handles.py` | Raw semantic descriptor-handle facts attached before policy completion. | @@ -140,14 +142,14 @@ update this table, the package README files, and the mechanical checks in | `prik/printers/c.py` | C binding and header node serialization. | | `prik/printers/fortran.py` | Fortran bridge node serialization. | | `prik/printers/pyi.py` | Semantic IR serialization as editable `.pyi`. | -| `prik/compiling/objects.py` | Native compile object model. | -| `prik/compiling/compilers.py` | Compiler command execution and tool lookup. | -| `prik/compiling/native_support.py` | Native binding support installation for generated wrappers. | +| `prik/compiler/objects.py` | Native compile object model. | +| `prik/compiler/compilers.py` | Compiler command execution and tool lookup. | +| `prik/compiler/native_support.py` | Native binding support installation for generated wrappers. | | `prik/naming/policy.py` | Public wrapper names and generated target-language symbols. | | `prik/binding_support/` | Native binding support payload copied into generated builds. | @@ -195,7 +197,7 @@ PRIK_C_DOCS_END --> ```text prik/cli.py -> prik/parsers/c/parser.py - -> prik/probes/c_types.py + -> prik/preprocessing/probes/c_types.py -> prik/semantics/c2ir.py -> prik/printers/pyi.py -> prik/policy/completion.py @@ -215,7 +217,12 @@ The hardest source packages also have local README files: - `prik/parsers/fortran/README.md` - `prik/parsers/pyi/README.md` - `prik/semantics/README.md` -- `prik/compiling/README.md` +- `prik/policy/README.md` +- `prik/planning/README.md` +- `prik/printers/README.md` +- `prik/pipeline/README.md` +- `prik/preprocessing/README.md` +- `prik/compiler/README.md` | --- | --- | --- | --- | --- | | CLI request | `prik/cli.py` | source paths and stage flags | selected stage or wrapper build options | `tests/fortran/command_line_interface/pipeline/` | | Build orchestration | `prik/pipeline/build.py` | ordered Fortran sources or `.pyi` contracts plus explicit native artifacts | `WrapperBuildResult`, `NativeBuildPlan`, and `GeneratedWrapper` | wrapper build-mode tests | -| Preprocessing | `prik/pipeline/preprocessing.py` | source path, compiler config | preprocessed source and dependency facts | preprocessing tests | +| Preprocessing | `prik/preprocessing/source.py`, `prik/preprocessing/c.py`, `prik/preprocessing/fortran.py` | source path, compiler config | preprocessed source, raw directive facts, native include expansion, and dependency provenance | C and Fortran preprocessing tests | +| Target probes | `prik/preprocessing/probes/` | compiler expressions or native target spellings plus compiler flags | resolved kind, storage, precision, signedness, and availability facts | C and Fortran target-probe tests | | Parser project model | `prik/parsers/fortran/parser.py` (`_SourceUnitScanner` for structural boundaries/regions; `FortranParser` for scopes and model construction) | preprocessed Fortran source | parser project with modules, procedures, types, visibility | Fortran parser fixture tests | -| Target probes | `prik/probes/fortran_types.py` | semantic type requirements and compiler flags | resolved kind/storage facts | Fortran type probe tests | | Semantic IR | `prik/semantics/fortran2ir.py` | parser project and target facts | `SemanticModule` objects | semantic Fortran tests | | Semantic policy completion | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/construction.py` | full semantic modules with signatures and `.pyi` overrides | semantic modules annotated with every ownership, transfer, destruction, mutability, storage, accessor, and projection decision needed by wrapper generation | ownership-policy tests | | Wrapper planning | `prik/planning/planner.py`, `prik/planning/models.py` | policy-completed semantic modules | typed wrapper plans consuming completed decisions without rendering output text | `tests/fortran/infrastructure/codegen/`, wrapper tests | | Direct documentation, bridge, and binding generation | `prik/codegen/docstrings.py`, `prik/codegen/fortran/bridge.py`, `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/pipeline/wrapper.py` | validated typed wrapper plans | completed public docstrings, Fortran, C, header syntax nodes, and the embedded derived-class facade | `tests/fortran/infrastructure/codegen/`, wrapper tests | | Language printing | `prik/printers/c.py`, `prik/printers/fortran.py`, `prik/printers/pyi.py` | C or Fortran syntax nodes, or semantic IR | C, Fortran, header, or semantic `.pyi` text | printer and generated-contract tests | | Wrapper generation pipeline | `prik/pipeline/wrapper.py` | editable completed wrapper plan | one generated wrapper containing rendered sources and build metadata | wrapper-generation and golden tests | -| Compile and link | `prik/compiling/`, `prik/pipeline/build.py` | dependency-batched native objects, generated bridge and binding objects, compiler-process limit, and ordered link inputs | shared library | wrapper runtime and build-mode tests | +| Compile and link | `prik/compiler/`, `prik/pipeline/build.py` | dependency-batched native objects, generated bridge and binding objects, compiler-process limit, and ordered link inputs | shared library | wrapper runtime and build-mode tests | @@ -66,8 +66,9 @@ The Python package layout follows those ownership boundaries: | `prik/contracts/` | The public semantic `.pyi` vocabulary and its local runtime scalar factories | A home for semantic conversion or backend datatype lowering | | `prik/semantics/scalar_types.py` | Stable scalar identities, families, and intrinsic storage facts | NumPy or generated-language spelling | | `prik/codegen/primitive_scalar_types.py` | Semantic-to-backend and NumPy scalar projection for implemented lowering lanes | A reverse semantic inference service | -| `prik/probes/` | Compiler-derived target facts | Semantic policy, cross-stage reporting, or build orchestration | -| `prik/pipeline/` | Source preprocessing, semantic `.pyi` loading, datatype mapping reports, and end-to-end wrapper build orchestration | Parser models, semantic decisions, or compiler implementation details | +| `prik/compiler/` | Reusable compiler command execution, compile objects, profiles, native support installation, and linking | Source preprocessing, target probing, or pipeline orchestration | +| `prik/preprocessing/` | Source expansion, preprocessing provenance, native textual includes, and compiler-derived target facts | Parsing declarations, semantic conversion, or wrapper build orchestration | +| `prik/pipeline/` | Semantic `.pyi` loading, datatype mapping reports, wrapper rendering, and end-to-end wrapper build orchestration | Parser models, semantic decisions, preprocessing implementation, or compiler implementation details | | `prik/runtime/` | Python objects used by generated extensions at execution time | Build-time semantic or codegen policy | | `prik/utilities/` | Small domain-neutral mechanisms such as class visitor dispatch | A miscellaneous home for semantic or pipeline concepts | @@ -80,7 +81,7 @@ cross-cutting infrastructure. | Parser facts | parser packages | Source syntax, native declaration structure, source locations, and parser diagnostics | Wrapper policy, Python API projection, generated names, and compile/link decisions | | Semantic policy completion and ownership | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, and `prik/policy/construction.py` | Completed policy choices for ownership, lifetime, output projection, replacement, and ABI safety; immutable wrapper-policy vocabulary is separate from its construction rules | Raw parser syntax, backend-specific statement trees, and hidden lowering-time policy decisions | | Typed wrapper plan | `prik/planning/models.py` and `prik/planning/planner.py` | A validated, backend-neutral implementation plan projected from completed semantic decisions | Source-contract authority, policy inference, rendered documentation, and target-language statement details | -| Printers and compilation | `prik/printers/`, `prik/pipeline/wrapper.py`, and `prik/compiling/` | Text emission, generated wrapper layout, compiler commands, native objects, libraries, include directories, and link inputs | Semantic support decisions and plan rewriting policy | +| Printers and compilation | `prik/printers/`, `prik/pipeline/wrapper.py`, and `prik/compiler/` | Text emission, generated wrapper layout, compiler commands, native objects, libraries, include directories, and link inputs | Semantic support decisions and plan rewriting policy | | Stage family | First files to read | Source navigation owner | | --- | --- | --- | | CLI and output routing | `prik/cli.py`, parser CLI helpers | `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` | -| Source loading and preprocessing | `prik/pipeline/preprocessing.py` | `docs/developer/source-map.md`, parser references | +| Source loading and preprocessing | `prik/preprocessing/source.py`, `prik/preprocessing/c.py`, `prik/preprocessing/fortran.py` | `docs/developer/source-map.md`, parser references | | Editable semantic contracts | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/printers/pyi.py` | `docs/user/reference/semantic-pyi-format.md` | | Semantic and wrapper-planning errors | `prik/semantics/fortran2ir.py`, `prik/policy/completion.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py` | `docs/user/guide/error-handling.md` | | Wrapper policy and lowering | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py`, `prik/pipeline/wrapper.py` | `docs/user/reference/fortran-wrapper.md`, ownership docs | -| Native build | `prik/pipeline/build.py`, `prik/compiling/compilers.py`, `prik/compiling/native_support.py` | compiling package README and build-system docs | +| Native build | `prik/pipeline/build.py`, `prik/compiler/compilers.py`, `prik/compiler/native_support.py` | compiler package README and build-system docs | | Failure type | Preferred owner | | --- | --- | -| Source cannot be preprocessed | `prik/pipeline/preprocessing.py` | +| Compiler-backed source cannot be preprocessed | `prik/preprocessing/source.py` | +| Raw C preprocessing metadata cannot be represented | `prik/preprocessing/c.py` | +| Native Fortran include expansion fails | `prik/preprocessing/fortran.py` | +| Required target datatype facts cannot be measured | `prik/preprocessing/probes/` | | Source syntax cannot be represented by prik's parser model | parser package | | Source facts cannot form a semantic contract | semantic conversion | | Ownership, lifetime, ABI, projection, or wrapper support decision is unsafe | `prik/policy/ownership.py` or policy completion | | A completed policy is internally inconsistent while being projected | wrapper planner at the owner being projected | | Native-language validity does not affect prik's contract | Fortran or C compiler | | Generated code cannot represent a supported plan | bridge or binding generator with focused tests | -| Compiler/linker invocation is wrong | `prik/compiling/` or `prik/pipeline/build.py` | +| Compiler/linker invocation is wrong | `prik/compiler/` or `prik/pipeline/build.py` | | Python binding behavior is wrong | generated binding, native support, or ownership policy | diff --git a/docs/maintainer/internal-architecture/type-system.md b/docs/maintainer/internal-architecture/type-system.md index 8a26545e9..bb867523e 100644 --- a/docs/maintainer/internal-architecture/type-system.md +++ b/docs/maintainer/internal-architecture/type-system.md @@ -62,7 +62,7 @@ PRIK deliberately has four related but non-interchangeable authorities. | Authority | Owner | Key | Value | Purpose | | --- | --- | --- | --- | --- | -| Native target facts | `prik/probes/` | compiler expression or native spelling | measured kind, storage, precision, signedness, or availability | Describe the selected compiler target. | +| Native target facts | `prik/preprocessing/probes/` | compiler expression or native spelling | measured kind, storage, precision, signedness, or availability | Describe the selected compiler target. | | Semantic scalar catalogue | `prik/semantics/scalar_types.py` | stable semantic name such as `Float64` | family and intrinsic storage facts | Define language-neutral scalar vocabulary without importing NumPy. | | Runtime contract factories | `prik/contracts/__init__.py` | semantic contract name | real NumPy scalar factory or an explicit constructor error | Make public semantic `.pyi` symbols safely usable at runtime where supported. | | Backend datatype catalogues | `prik/codegen/primitive_scalar_types.py` | resolved semantic name | emitted NumPy expressions plus native spellings, NumPy type macros, Python parse/result behavior, and descriptor spelling | Project semantic dtypes for reporting and lower implemented primitive scalar plans without coupling the two generators. | @@ -87,7 +87,7 @@ bridge compilation. Flags that change kinds, widths, ABI, or target architecture must reach probing. A report measured under one target must not be reused as if it described another. -`prik/probes/fortran_types.py` compiles generated measurement programs and +`prik/preprocessing/probes/fortran_types.py` compiles generated measurement programs and returns `FortranTypeProbeReport`. Its cache identity includes the generated source, compiler identity, flags, working directory, target environment, and optional runner. The two main outputs are: @@ -101,7 +101,7 @@ type facts to `FortranToIRConverter`. Probes never decide ownership, Python visibility, output projection, or wrapper support. - `void` return -> `None`. - `_Bool` -> `Bool`. - All modeled primitive integer, real, and complex spellings consume supplied - `prik.probes.c_types` facts. Plain `char` signedness, integer widths, real + `prik.preprocessing.probes.c_types` facts. Plain `char` signedness, integer widths, real storage widths and precision metadata, and complex storage widths come from the selected compiler target. - `int` keeps semantic name `Int` while its concrete dtype follows the target. @@ -308,7 +308,7 @@ PRIK_C_DOCS_END --> - Local typedef chains are resolved when their parser model definitions are available. - `size_t` maps to `SizeT` without a target probe; supplied - `prik.probes.c_types` facts override standard typedefs with width-specific + `prik.preprocessing.probes.c_types` facts override standard typedefs with width-specific `Int*`, `UInt*`, or `Float*` semantic names. - Opaque standard-type probe facts such as `FILE` create named opaque semantic classes when referenced by converted declarations. diff --git a/examples/lapack/ci/full_surface.py b/examples/lapack/ci/full_surface.py index 0552e1d39..63b527a10 100644 --- a/examples/lapack/ci/full_surface.py +++ b/examples/lapack/ci/full_surface.py @@ -9,7 +9,7 @@ from ..routine_inventory import EXPECTED_LAPACK_PROCEDURES from examples.lapack.tests.helpers import assert_runtime_smoke from prik.parsers.fortran.parser import parse_fortran_file -from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source +from prik.preprocessing import PreprocessingConfig, preprocess_source pytestmark = [pytest.mark.fortran_end_to_end, pytest.mark.real_library] diff --git a/prik/README.md b/prik/README.md index d5cbd178b..e233cc9fd 100644 --- a/prik/README.md +++ b/prik/README.md @@ -9,9 +9,11 @@ jumping directly into generated-code internals. | File or package | Owns | | --- | --- | | `cli.py` | User CLI stages, output routing, diagnostics, and wrapper option validation. | +| `stage_values.py` | Shared stage-result records used by public inspection workflows. | | `contracts/` | Public names used by semantic `.pyi` contracts. | -| `pipeline/` | Preprocessing, semantic `.pyi` loading, datatype mapping reports, wrapper generation orchestration, and end-to-end builds. | -| `probes/` | Compiler-derived native target facts. | +| `compiler/` | Reusable compiler commands, compile objects, native support installation, and linking. | +| `preprocessing/` | C/Fortran source preparation, provenance, native includes, and compiler-derived target facts. | +| `pipeline/` | Semantic `.pyi` loading, datatype mapping reports, wrapper generation orchestration, and end-to-end builds. | | `runtime/` | Python runtime objects used by generated extensions. | | `parsers/` | Parser namespace containing the `c`, `fortran`, and semantic `.pyi` frontends. | | `semantics/` | Language-neutral semantic IR, scalar datatype vocabulary, declaration-expression provenance, and `.pyi` conversion. | @@ -19,7 +21,8 @@ jumping directly into generated-code internals. | `planning/` | Editable backend-neutral wrapper implementation plans. | | `codegen/` | Backend datatype projection, plan-driven documentation, and direct C/Fortran syntax-node lowering. | | `printers/` | C, Fortran, and semantic `.pyi` serialization. | -| `compiling/` | Native compiler objects, wrapper compilation, native support installation, and linking. | +| `binding_support/` | Header-only native support installed into generated wrapper builds. | +| `naming/` | Shared public-name and generated-symbol policy. | | `utilities/` | Shared parsing, normalization, rendering, evaluation, and visitor helpers. | The package root contains the public entrypoint modules plus the shared diff --git a/prik/__init__.py b/prik/__init__.py index baa458f1f..3c05814b9 100644 --- a/prik/__init__.py +++ b/prik/__init__.py @@ -76,7 +76,7 @@ def __getattr__(name: str): module = import_module("prik.cli") return getattr(module, name) if name in _FORTRAN_TYPE_PROBE_EXPORTS: - module = import_module("prik.probes.fortran_types") + module = import_module("prik.preprocessing.probes.fortran_types") return getattr(module, name) if name in _WRAPPING_EXPORTS: module = import_module("prik.pipeline.build") diff --git a/prik/cli.py b/prik/cli.py index 78da353cd..57b3473d9 100644 --- a/prik/cli.py +++ b/prik/cli.py @@ -19,16 +19,16 @@ from prik.parsers.fortran.parser import FortranParser from prik.semantics.c2ir import c_project_to_semantic_modules from prik.semantics.fortran2ir import fortran_file_to_semantic_modules -from prik.probes.c_types import ( +from prik.preprocessing.probes.c_types import ( CStandardTypeProbeError, probe_c_standard_types_cached, ) -from prik.probes.fortran_types import ( +from prik.preprocessing.probes.fortran_types import ( FortranTypeProbeReport, probe_fortran_type_expressions_cached, ) from prik.pipeline.type_mapping_report import c_type_mapping_markdown, fortran_type_mapping_markdown -from prik.pipeline.preprocessing import ( +from prik.preprocessing import ( PreprocessingConfig, PreprocessingError, run_compiler_preprocessor_with_recipe, @@ -759,7 +759,7 @@ def _fortran_compile_time_values( return None from prik.semantics.fortran2ir import collect_semantic_compile_time_requirements - from prik.probes.fortran_types import evaluate_fortran_type_requirements + from prik.preprocessing.probes.fortran_types import evaluate_fortran_type_requirements requirements = collect_semantic_compile_time_requirements(parsed) if not requirements: @@ -787,7 +787,7 @@ def _fortran_type_facts( return None from prik.semantics.fortran2ir import collect_fortran_type_storage_requirements - from prik.probes.fortran_types import evaluate_fortran_type_facts + from prik.preprocessing.probes.fortran_types import evaluate_fortran_type_facts requirements = collect_fortran_type_storage_requirements(parsed, compile_time_values=compile_time_values) if not requirements: diff --git a/prik/compiling/README.md b/prik/compiler/README.md similarity index 97% rename from prik/compiling/README.md rename to prik/compiler/README.md index 199d055a6..8f81ae5dd 100644 --- a/prik/compiling/README.md +++ b/prik/compiler/README.md @@ -1,4 +1,4 @@ -# Compiling Package +# Compiler Package This package owns native compiler command construction, compile objects, generated wrapper compilation, native support installation, and shared-library @@ -15,7 +15,7 @@ linking. Generated-wrapper object assembly and shared-library orchestration live in `prik/pipeline/build.py`, where the canonical rendered wrapper artifacts are -available. The compiling package does not import or regenerate wrapper plans, +available. The compiler package does not import or regenerate wrapper plans, infer semantic policy, or traverse an implicit dependency graph. ## Pipeline Position diff --git a/prik/compiling/__init__.py b/prik/compiler/__init__.py similarity index 100% rename from prik/compiling/__init__.py rename to prik/compiler/__init__.py diff --git a/prik/compiling/compiler_profiles.py b/prik/compiler/compiler_profiles.py similarity index 100% rename from prik/compiling/compiler_profiles.py rename to prik/compiler/compiler_profiles.py diff --git a/prik/compiling/compilers.py b/prik/compiler/compilers.py similarity index 100% rename from prik/compiling/compilers.py rename to prik/compiler/compilers.py diff --git a/prik/compiling/native_support.py b/prik/compiler/native_support.py similarity index 100% rename from prik/compiling/native_support.py rename to prik/compiler/native_support.py diff --git a/prik/compiling/objects.py b/prik/compiler/objects.py similarity index 100% rename from prik/compiling/objects.py rename to prik/compiler/objects.py diff --git a/prik/parsers/c/README.md b/prik/parsers/c/README.md index 22a50dfa4..9c0618e3e 100644 --- a/prik/parsers/c/README.md +++ b/prik/parsers/c/README.md @@ -15,10 +15,13 @@ the package root. | `parser.py` | Translation-unit parsing, project assembly, unsupported construct diagnostics. | | `lexer.py` | C tokenization and comment/source splitting helpers. | | `models.py` | Parser model dataclasses and C parse diagnostics. | -| `preprocessor.py` | Preprocessor metadata collection. | | `type_resolver.py` | C type resolution helpers used by parser and semantics. | | `cli.py` | C parser CLI report formatting and preprocessing recipe wiring. | +Raw directive and include metadata is collected before grammar parsing by +`prik/preprocessing/c.py`. The parser consumes those prepared facts; it does +not own preprocessing. + ## Tests And Docs - Public reference: `docs/developer/c-parser-reference.md` diff --git a/prik/parsers/c/parser.py b/prik/parsers/c/parser.py index 2ab480dba..c20e92840 100644 --- a/prik/parsers/c/parser.py +++ b/prik/parsers/c/parser.py @@ -129,7 +129,7 @@ CLongDoubleComplex, CLongLong, ) -from prik.parsers.c.preprocessor import collect_preprocessor_metadata +from prik.preprocessing.c import collect_preprocessor_metadata from prik.parsers.c.type_resolver import resolve_project_types _C_SOURCE_SUFFIXES = {".c", ".h", ".i"} diff --git a/prik/pipeline/README.md b/prik/pipeline/README.md index de89681fa..d5fa5fa63 100644 --- a/prik/pipeline/README.md +++ b/prik/pipeline/README.md @@ -6,7 +6,6 @@ native compiler mechanisms. | File | Owns | | --- | --- | -| `preprocessing.py` | Compiler preprocessing recipes and source mappings. | | `pyi.py` | Semantic `.pyi` loading, package assembly, and reference reconciliation. | | `type_mapping_report.py` | Compiler-target facts converted through semantic IR and backend NumPy projection into inspection Markdown. | | `wrapper.py` | One completed-plan-to-rendered-wrapper generation workflow. | @@ -16,3 +15,11 @@ native compiler mechanisms. validates the editable plan, invokes the C and Fortran node generators, prints their results through `../printers/`, assigns stable filenames, and returns one `GeneratedWrapper`. It does not write files or invoke a compiler. + +Source preparation and target measurement live in `../preprocessing/`. +Reusable compiler execution, compile objects, and linking live in +`../compiler/`. This package imports those services only while coordinating a +complete workflow. + +For cross-stage navigation, see `docs/developer/source-map.md` and +`docs/developer/feature-to-code-map.md`. diff --git a/prik/pipeline/build.py b/prik/pipeline/build.py index 4810bd453..db53648f1 100644 --- a/prik/pipeline/build.py +++ b/prik/pipeline/build.py @@ -14,16 +14,16 @@ import time from types import ModuleType -from prik.compiling.objects import ObjectFile -from prik.compiling.compilers import Compiler, get_condaless_search_path -from prik.compiling.native_support import install_native_support +from prik.compiler.objects import ObjectFile +from prik.compiler.compilers import Compiler, get_condaless_search_path +from prik.compiler.native_support import install_native_support from prik.parsers.fortran.parser import parse_fortran_project -from prik.probes.fortran_types import ( +from prik.preprocessing.probes.fortran_types import ( evaluate_fortran_type_facts, evaluate_fortran_type_requirements, resolve_fortran_logical_storage_types, ) -from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source +from prik.preprocessing import PreprocessingConfig, preprocess_source from prik.pipeline.wrapper import GeneratedSource, GeneratedWrapper, WrapperGenerator from prik.semantics.fortran2ir import ( collect_fortran_type_storage_requirements, diff --git a/prik/pipeline/type_mapping_report.py b/prik/pipeline/type_mapping_report.py index 3bb3e3dd7..661501166 100644 --- a/prik/pipeline/type_mapping_report.py +++ b/prik/pipeline/type_mapping_report.py @@ -38,9 +38,9 @@ from prik.semantics.c2ir import CToIRConverter from prik.semantics.fortran2ir import FortranToIRConverter, fortran_type_storage_expression -from prik.pipeline.preprocessing import PreprocessingConfig -from prik.probes.c_types import probe_c_standard_types_cached -from prik.probes.fortran_types import evaluate_fortran_type_facts, probe_fortran_type_expressions_cached +from prik.preprocessing import PreprocessingConfig +from prik.preprocessing.probes.c_types import probe_c_standard_types_cached +from prik.preprocessing.probes.fortran_types import evaluate_fortran_type_facts, probe_fortran_type_expressions_cached # C report inventory. diff --git a/prik/planning/README.md b/prik/planning/README.md index 588a084f2..a98b7caf8 100644 --- a/prik/planning/README.md +++ b/prik/planning/README.md @@ -11,3 +11,6 @@ backend-neutral wrapper plan. Planning must not infer semantic policy or render output text. Python-facing docstrings, C, Fortran, headers, and the generated Python class facade are rendered by `../codegen/` from the completed plan. + +For cross-stage navigation, see `docs/developer/source-map.md` and +`docs/developer/feature-to-code-map.md`. diff --git a/prik/policy/README.md b/prik/policy/README.md index cc38c59c5..c73484829 100644 --- a/prik/policy/README.md +++ b/prik/policy/README.md @@ -16,3 +16,6 @@ must not construct wrapper plans or render backend output. Raw ownership and pointer-contract metadata belongs to `../semantics/ownership_metadata.py`. Planning consumes completed records from this package through `../planning/planner.py`. + +For cross-stage navigation, see `docs/developer/source-map.md` and +`docs/developer/feature-to-code-map.md`. diff --git a/prik/preprocessing/README.md b/prik/preprocessing/README.md new file mode 100644 index 000000000..a2e5b2199 --- /dev/null +++ b/prik/preprocessing/README.md @@ -0,0 +1,45 @@ +# Preprocessing Package + +This package owns operations performed before C, Fortran, or semantic +conversion parses declarations. It prepares source text, preserves provenance, +expands native includes, and measures compiler-dependent target facts. + +## Entry Points + +| File or package | Owns | +| --- | --- | +| `source.py` | Compiler-backed C/Fortran expansion, invocation configuration, line-marker provenance, dependencies, macros, and preprocessing recipes. | +| `c.py` | Safe raw C directive and include metadata collected before C grammar parsing. | +| `fortran.py` | Native Fortran `INCLUDE` expansion after compiler preprocessing. | +| `probes/c_types.py` | Compiler-derived C target ABI facts and reusable cache reports. | +| `probes/fortran_types.py` | Compiler-derived Fortran kind/storage facts and reusable cache reports. | + +Import the shared public preprocessing API from `prik.preprocessing`. Import a +language-specific raw metadata or probe API from its canonical child module. +The previous pipeline-local, parser-local, and top-level probe paths are not +retained. + +## Boundary + +```text +compiler service + -> source preprocessing and target probes + -> parser-native facts + -> semantic IR +``` + +This package does not parse declarations, construct semantic IR, decide +ownership or wrapper support, render wrapper sources, or compile a completed +extension. `prik.compiler` supplies reusable compiler mechanisms; +`prik.pipeline` coordinates workflows that consume preprocessing results. + +## Tests And Docs + +- `tests/c/preprocessing/` +- `tests/c/probes/` +- `tests/fortran/source_preprocessing/preprocessing/` +- `tests/fortran/data_types/probes/` +- `docs/developer/compiler-preprocessing.md` +- `docs/maintainer/internal-architecture/type-system.md` +- `docs/developer/source-map.md` +- `docs/developer/feature-to-code-map.md` diff --git a/prik/preprocessing/__init__.py b/prik/preprocessing/__init__.py new file mode 100644 index 000000000..266d9008b --- /dev/null +++ b/prik/preprocessing/__init__.py @@ -0,0 +1,55 @@ +"""Pre-parse source preparation and compiler-derived target facts.""" + +from .source import ( + CommandTemplateAdapter, + CompilerAdapter, + GCCCompatibleCAdapter, + GNUFortranAdapter, + IncludedFile, + Invocation, + MacroDefinition, + PreprocessResult, + PreprocessingConfig, + PreprocessingDiagnostic, + PreprocessingError, + PreprocessingPlan, + PreprocessingRecipe, + SourceMapping, + build_compile_commands_invocation, + build_direct_preprocess_invocation, + build_preprocess_invocation, + build_template_preprocess_invocation, + parse_linemarker_mappings, + preprocess_source, + run_compiler_preprocessor, + run_compiler_preprocessor_with_recipe, + validate_macro_name, +) +from .fortran import expand_native_fortran_includes + +__all__ = ( + "CommandTemplateAdapter", + "CompilerAdapter", + "GCCCompatibleCAdapter", + "GNUFortranAdapter", + "IncludedFile", + "Invocation", + "MacroDefinition", + "PreprocessResult", + "PreprocessingConfig", + "PreprocessingDiagnostic", + "PreprocessingError", + "PreprocessingPlan", + "PreprocessingRecipe", + "SourceMapping", + "build_compile_commands_invocation", + "build_direct_preprocess_invocation", + "build_preprocess_invocation", + "build_template_preprocess_invocation", + "expand_native_fortran_includes", + "parse_linemarker_mappings", + "preprocess_source", + "run_compiler_preprocessor", + "run_compiler_preprocessor_with_recipe", + "validate_macro_name", +) diff --git a/prik/parsers/c/preprocessor.py b/prik/preprocessing/c.py similarity index 97% rename from prik/parsers/c/preprocessor.py rename to prik/preprocessing/c.py index f706ee77b..fcae91b57 100644 --- a/prik/parsers/c/preprocessor.py +++ b/prik/preprocessing/c.py @@ -1,8 +1,8 @@ """Collect safe raw-preprocessor metadata for the C parser. -This parser-local module deliberately does not expand macros or choose +This preprocessing-stage module deliberately does not expand macros or choose conditional-compilation branches; compiler-backed expansion belongs to -``prik.pipeline.preprocessing``. Before raw C grammar parsing, it normalizes +``prik.preprocessing.source``. Before raw C grammar parsing, it normalizes comments and continuations, records literal ``#include`` and ``#pragma`` facts, and reports unresolved quoted includes without reading included source. """ diff --git a/prik/preprocessing/fortran.py b/prik/preprocessing/fortran.py new file mode 100644 index 000000000..759a03c42 --- /dev/null +++ b/prik/preprocessing/fortran.py @@ -0,0 +1,211 @@ +"""Expand native Fortran textual includes before parsing. + +Compiler preprocessing and provenance collection live in +``prik.preprocessing.source``. This module owns the Fortran-specific pass that +expands native ``INCLUDE`` statements left in the compiler-expanded stream. +It preserves generated-to-original line mappings and reports missing files or +cycles without making parser or semantic decisions. +""" + +from __future__ import annotations + +import re +from collections.abc import Sequence +from pathlib import Path + +from prik.preprocessing.source import ( + DependencyKind, + IncludedFile, + PreprocessingConfig, + PreprocessingDiagnostic, + SourceMapping, + _exposure_for, + _parse_linemarker, + parse_linemarker_mappings, +) + + +_FORTRAN_INCLUDE_RE = re.compile( + r"^\s*include\s*(?P['\"])(?P[^'\"]+)(?P=quote)\s*$", + re.IGNORECASE, +) + + +def _mapping_for_generated_line( + mappings: Sequence[SourceMapping], generated_line: int, fallback: Path +) -> SourceMapping: + """Return a generated-line mapping or construct the established root fallback.""" + for mapping in mappings: + if mapping.generated_line == generated_line: + return mapping + return SourceMapping( + generated_line=generated_line, + original_path=str(fallback), + original_line=generated_line, + include_stack=[str(fallback)], + ) + + +def _resolve_fortran_include(target: str, including_file: str, include_dirs: Sequence[str]) -> Path | None: + """Find a native Fortran include beside its source before configured paths. + + Filesystem lookup errors on one candidate do not prevent checking later + include directories. The first existing regular file wins. + """ + candidates = [Path(including_file).parent / target] + candidates.extend(Path(include_dir) / target for include_dir in include_dirs) + for candidate in candidates: + try: + if candidate.is_file(): + return candidate + except OSError: + continue + return None + + +def _line_marker(line: int, path: str, flag: int | None = None) -> str: + """Render one escaped GCC-style line marker for expanded Fortran source.""" + escaped = path.replace("\\", "\\\\").replace('"', '\\"') + suffix = f" {flag}" if flag is not None else "" + return f'# {line} "{escaped}"{suffix}' + + +def expand_native_fortran_includes( + source: str, + *, + root_path: Path, + include_dirs: Sequence[str], + config: PreprocessingConfig | None = None, +) -> tuple[str, list[IncludedFile], list[SourceMapping], list[PreprocessingDiagnostic]]: + """Expand native Fortran ``INCLUDE`` statements after compiler preprocessing. + + Use this for a Fortran source stream that may still contain textual + ``include "file.inc"`` statements. The return value contains expanded + parser input, discovered include edges, generated-to-original mappings, and + recoverable diagnostics. Missing files and cycles are recorded while later + source lines continue to be emitted; :func:`preprocess_source` promotes + error diagnostics after it records the complete result. + + Relative includes resolve beside the including file before configured + include directories. Repeated non-cyclic includes are expanded repeatedly + and retain their separate dependency edges. + """ + + config = config or PreprocessingConfig() + diagnostics: list[PreprocessingDiagnostic] = [] + included_files: list[IncludedFile] = [] + generated_mappings: list[SourceMapping] = [] + line_counter = 0 + + def emit_line(line: str, mapping: SourceMapping, out: list[str]) -> None: + """Append one output line and its corresponding generated-line mapping. + + ``line_counter`` is shared across recursive expansions so mappings + retain output order even when included text contributes many lines. + """ + nonlocal line_counter + out.append(line) + line_counter += 1 + generated_mappings.append( + SourceMapping( + generated_line=line_counter, + original_path=mapping.original_path, + original_line=mapping.original_line, + include_stack=list(mapping.include_stack), + ) + ) + + def expand_text(text: str, current_file: Path, stack: list[Path]) -> list[str]: + """Recursively replace include lines in one source fragment. + + ``stack`` contains resolved paths currently being expanded and is used + only for cycle detection. The function appends diagnostics instead of + raising so siblings and following source survive independent failures. + """ + out: list[str] = [] + mappings = parse_linemarker_mappings(text, filename=str(current_file)) + mapping_by_line = {mapping.generated_line: mapping for mapping in mappings} + for generated_line, line in enumerate(text.splitlines(), start=1): + marker = _parse_linemarker(line) + if marker is not None: + mapping = _mapping_for_generated_line(mappings, generated_line, current_file) + emit_line(line, mapping, out) + continue + mapping = mapping_by_line.get(generated_line) or SourceMapping( + generated_line=generated_line, + original_path=str(current_file), + original_line=generated_line, + include_stack=[str(path) for path in stack], + ) + match = _FORTRAN_INCLUDE_RE.match(line) + if match is None: + emit_line(line, mapping, out) + continue + + target = match.group("path") + resolved = _resolve_fortran_include(target, mapping.original_path, include_dirs) + if resolved is None: + diagnostics.append( + PreprocessingDiagnostic( + category="INCLUDE_NOT_FOUND", + message=f'Fortran INCLUDE file "{target}" was not found', + path=mapping.original_path, + line=mapping.original_line, + ) + ) + continue + try: + resolved_abs = resolved.resolve() + except OSError: + resolved_abs = resolved.absolute() + if resolved_abs in stack: + cycle = " -> ".join(str(path) for path in [*stack, resolved_abs]) + diagnostics.append( + PreprocessingDiagnostic( + category="INCLUDE_CYCLE", + message=f"Fortran INCLUDE cycle detected: {cycle}", + path=mapping.original_path, + line=mapping.original_line, + ) + ) + continue + + kind: DependencyKind = "project" + included_files.append( + IncludedFile( + path=str(resolved_abs), + included_by=mapping.original_path, + include_line=mapping.original_line, + mechanism="fortran_include", + dependency_kind=kind, + exposure=_exposure_for(str(resolved_abs), kind, config), + ) + ) + emit_line(_line_marker(1, str(resolved_abs), 1), mapping, out) + try: + include_text = resolved.read_text(encoding="utf-8") + except OSError as exc: + diagnostics.append( + PreprocessingDiagnostic( + category="INCLUDE_NOT_FOUND", + message=f'Fortran INCLUDE file "{target}" could not be read: {exc}', + path=mapping.original_path, + line=mapping.original_line, + ) + ) + continue + out.extend(expand_text(include_text, resolved_abs, [*stack, resolved_abs])) + emit_line(_line_marker(mapping.original_line + 1, mapping.original_path, 2), mapping, out) + return out + + root_abs = root_path.resolve() if root_path.exists() else root_path.absolute() + expanded_lines = expand_text(source, root_abs, [root_abs]) + return ( + "\n".join(expanded_lines) + ("\n" if source.endswith("\n") else ""), + included_files, + generated_mappings, + diagnostics, + ) + + +__all__ = ("expand_native_fortran_includes",) diff --git a/prik/preprocessing/probes/__init__.py b/prik/preprocessing/probes/__init__.py new file mode 100644 index 000000000..55aa0c9a1 --- /dev/null +++ b/prik/preprocessing/probes/__init__.py @@ -0,0 +1 @@ +"""Compiler-derived target facts measured before semantic conversion.""" diff --git a/prik/probes/c_types.py b/prik/preprocessing/probes/c_types.py similarity index 99% rename from prik/probes/c_types.py rename to prik/preprocessing/probes/c_types.py index 8812714cd..32ddb3c33 100644 --- a/prik/probes/c_types.py +++ b/prik/preprocessing/probes/c_types.py @@ -21,7 +21,7 @@ from contextlib import suppress from typing import Any -from prik.pipeline.preprocessing import PreprocessingConfig, PreprocessingError, validate_macro_name +from prik.preprocessing import PreprocessingConfig, PreprocessingError, validate_macro_name # Probe schema, fact classification, and cache identity. @@ -632,7 +632,7 @@ def _write_cached_report(path: Path, report: CStandardTypeProbeReport) -> None: def main(argv: list[str] | None = None) -> int: """Run the C ABI probe CLI and print one report as indented JSON. - Use this entrypoint from python -m prik.probes.c_types with an explicit + Use this entrypoint from python -m prik.preprocessing.probes.c_types with an explicit compiler. Argv is accepted for embedding and tests; otherwise command-line arguments are parsed. Invalid macros and probe failures go through argparse, while success writes the report to standard output and returns diff --git a/prik/probes/fortran_types.py b/prik/preprocessing/probes/fortran_types.py similarity index 99% rename from prik/probes/fortran_types.py rename to prik/preprocessing/probes/fortran_types.py index d7d9d2fea..3b0d8de24 100644 --- a/prik/probes/fortran_types.py +++ b/prik/preprocessing/probes/fortran_types.py @@ -24,7 +24,7 @@ import tempfile from typing import Any -from prik.pipeline.preprocessing import PreprocessingConfig, PreprocessingError, validate_macro_name +from prik.preprocessing import PreprocessingConfig, PreprocessingError, validate_macro_name # Cache identity and generated-source configuration. @@ -882,7 +882,7 @@ def _value_for_expression(values: Mapping[str, int], expression: str) -> int | N def main(argv: list[str] | None = None) -> int: """Run the probe CLI and print one report as indented JSON. - Use this entrypoint from ``python -m prik.probes.fortran_types`` with an + Use this entrypoint from ``python -m prik.preprocessing.probes.fortran_types`` with an explicit ``--compiler`` and one or more ``--expr`` arguments. ``argv`` is accepted for embedding and tests; otherwise command-line arguments are parsed. Invalid macro definitions and probe failures are reported through diff --git a/prik/pipeline/preprocessing.py b/prik/preprocessing/source.py similarity index 87% rename from prik/pipeline/preprocessing.py rename to prik/preprocessing/source.py index 06daf3f20..f6b95af5e 100644 --- a/prik/pipeline/preprocessing.py +++ b/prik/preprocessing/source.py @@ -1,11 +1,12 @@ """Prepare C and Fortran source for the parser frontends. -The parsers intentionally consume one expanded source stream. This module -therefore owns compiler/preprocessor invocation, provenance and dependency -metadata, and the textual expansion of native Fortran ``INCLUDE`` statements -that compiler CPP leaves unresolved. It does not parse declarations or make -semantic policy decisions; callers pass :class:`PreprocessResult.source` to the -appropriate parser after this stage completes. +The parsers intentionally consume one expanded source stream. This module +therefore owns compiler/preprocessor invocation plus shared provenance and +dependency metadata. The Fortran-specific textual ``INCLUDE`` pass lives in +``prik.preprocessing.fortran`` and is coordinated here. Neither module parses +declarations or makes semantic policy decisions; callers pass +:class:`PreprocessResult.source` to the appropriate parser after this stage +completes. """ from __future__ import annotations @@ -21,7 +22,7 @@ from pathlib import Path from typing import ClassVar, Literal, Protocol -from prik.compiling.compiler_profiles import fortran_compiler_family +from prik.compiler.compiler_profiles import fortran_compiler_family PreprocessingCategory = Literal[ @@ -50,7 +51,6 @@ _LINE_DIRECTIVE_RE = re.compile( r'^\s*#\s*line\s+(?P\d+)(?:\s+(?:"(?P(?:[^"\\]|\\.)*)"|(?P\S+)))?\s*$' ) -_FORTRAN_INCLUDE_RE = re.compile(r"^\s*include\s*(?P['\"])(?P[^'\"]+)(?P=quote)\s*$", re.IGNORECASE) class PreprocessingError(Exception): @@ -1179,186 +1179,6 @@ def _parse_macro_definitions(source: str, mappings: Sequence[SourceMapping]) -> return macros -def _mapping_for_generated_line( - mappings: Sequence[SourceMapping], generated_line: int, fallback: Path -) -> SourceMapping: - """Return a generated-line mapping or construct the established root fallback.""" - for mapping in mappings: - if mapping.generated_line == generated_line: - return mapping - return SourceMapping( - generated_line=generated_line, - original_path=str(fallback), - original_line=generated_line, - include_stack=[str(fallback)], - ) - - -def _resolve_fortran_include(target: str, including_file: str, include_dirs: Sequence[str]) -> Path | None: - """Find a native Fortran include beside its source before configured paths. - - Filesystem lookup errors on one candidate do not prevent checking later - include directories. The first existing regular file wins. - """ - candidates = [Path(including_file).parent / target] - candidates.extend(Path(include_dir) / target for include_dir in include_dirs) - for candidate in candidates: - try: - if candidate.is_file(): - return candidate - except OSError: - continue - return None - - -def _line_marker(line: int, path: str, flag: int | None = None) -> str: - """Render one escaped GCC-style line marker for expanded Fortran source.""" - escaped = path.replace("\\", "\\\\").replace('"', '\\"') - suffix = f" {flag}" if flag is not None else "" - return f'# {line} "{escaped}"{suffix}' - - -# Native Fortran textual include expansion. - - -def expand_native_fortran_includes( - source: str, - *, - root_path: Path, - include_dirs: Sequence[str], - config: PreprocessingConfig | None = None, -) -> tuple[str, list[IncludedFile], list[SourceMapping], list[PreprocessingDiagnostic]]: - """Expand native Fortran ``INCLUDE`` statements after compiler CPP output. - - Use this for a Fortran source stream that may still contain textual - ``include "file.inc"`` statements. The return value contains expanded - parser input, discovered include edges, generated-to-original mappings, and - recoverable diagnostics. Missing files and cycles are recorded while later - source lines continue to be emitted; :func:`preprocess_source` promotes - error diagnostics after it records the complete result. - - Relative includes resolve beside the including file before configured - include directories. Repeated non-cyclic includes are expanded repeatedly - and retain their separate dependency edges. - """ - - config = config or PreprocessingConfig() - diagnostics: list[PreprocessingDiagnostic] = [] - included_files: list[IncludedFile] = [] - generated_mappings: list[SourceMapping] = [] - line_counter = 0 - - def emit_line(line: str, mapping: SourceMapping, out: list[str]) -> None: - """Append one output line and its corresponding generated-line mapping. - - ``line_counter`` is shared across recursive expansions so mappings - retain output order even when included text contributes many lines. - """ - nonlocal line_counter - out.append(line) - line_counter += 1 - generated_mappings.append( - SourceMapping( - generated_line=line_counter, - original_path=mapping.original_path, - original_line=mapping.original_line, - include_stack=list(mapping.include_stack), - ) - ) - - def expand_text(text: str, current_file: Path, stack: list[Path]) -> list[str]: - """Recursively replace include lines in one source fragment. - - ``stack`` contains resolved paths currently being expanded and is used - only for cycle detection. The function appends diagnostics instead of - raising so siblings and following source survive independent failures. - """ - out: list[str] = [] - mappings = parse_linemarker_mappings(text, filename=str(current_file)) - mapping_by_line = {mapping.generated_line: mapping for mapping in mappings} - for generated_line, line in enumerate(text.splitlines(), start=1): - marker = _parse_linemarker(line) - if marker is not None: - mapping = _mapping_for_generated_line(mappings, generated_line, current_file) - emit_line(line, mapping, out) - continue - mapping = mapping_by_line.get(generated_line) or SourceMapping( - generated_line=generated_line, - original_path=str(current_file), - original_line=generated_line, - include_stack=[str(path) for path in stack], - ) - match = _FORTRAN_INCLUDE_RE.match(line) - if match is None: - emit_line(line, mapping, out) - continue - - target = match.group("path") - resolved = _resolve_fortran_include(target, mapping.original_path, include_dirs) - if resolved is None: - diagnostics.append( - PreprocessingDiagnostic( - category="INCLUDE_NOT_FOUND", - message=f'Fortran INCLUDE file "{target}" was not found', - path=mapping.original_path, - line=mapping.original_line, - ) - ) - continue - try: - resolved_abs = resolved.resolve() - except OSError: - resolved_abs = resolved.absolute() - if resolved_abs in stack: - cycle = " -> ".join(str(path) for path in [*stack, resolved_abs]) - diagnostics.append( - PreprocessingDiagnostic( - category="INCLUDE_CYCLE", - message=f"Fortran INCLUDE cycle detected: {cycle}", - path=mapping.original_path, - line=mapping.original_line, - ) - ) - continue - - kind: DependencyKind = "project" - included_files.append( - IncludedFile( - path=str(resolved_abs), - included_by=mapping.original_path, - include_line=mapping.original_line, - mechanism="fortran_include", - dependency_kind=kind, - exposure=_exposure_for(str(resolved_abs), kind, config), - ) - ) - emit_line(_line_marker(1, str(resolved_abs), 1), mapping, out) - try: - include_text = resolved.read_text(encoding="utf-8") - except OSError as exc: - diagnostics.append( - PreprocessingDiagnostic( - category="INCLUDE_NOT_FOUND", - message=f'Fortran INCLUDE file "{target}" could not be read: {exc}', - path=mapping.original_path, - line=mapping.original_line, - ) - ) - continue - out.extend(expand_text(include_text, resolved_abs, [*stack, resolved_abs])) - emit_line(_line_marker(mapping.original_line + 1, mapping.original_path, 2), mapping, out) - return out - - root_abs = root_path.resolve() if root_path.exists() else root_path.absolute() - expanded_lines = expand_text(source, root_abs, [root_abs]) - return ( - "\n".join(expanded_lines) + ("\n" if source.endswith("\n") else ""), - included_files, - generated_mappings, - diagnostics, - ) - - # Result recipe construction and compiler execution. @@ -1613,6 +1433,8 @@ def preprocess_source( # Stage 4: resolve native Fortran includes that compiler CPP does not expand. if language == "fortran": + from prik.preprocessing.fortran import expand_native_fortran_includes + expanded_source, native_includes, native_mappings, native_diagnostics = expand_native_fortran_includes( expanded_source, root_path=source, @@ -1687,7 +1509,6 @@ def run_compiler_preprocessor( "build_direct_preprocess_invocation", "build_preprocess_invocation", "build_template_preprocess_invocation", - "expand_native_fortran_includes", "parse_linemarker_mappings", "preprocess_source", "run_compiler_preprocessor", @@ -1699,6 +1520,8 @@ def run_compiler_preprocessor( if __name__ == "__main__": from tempfile import TemporaryDirectory + from prik.preprocessing.fortran import expand_native_fortran_includes + with TemporaryDirectory() as directory: example_directory = Path(directory) root_path = example_directory / "greeting.F90" diff --git a/prik/printers/README.md b/prik/printers/README.md index 2af05c66f..69e368752 100644 --- a/prik/printers/README.md +++ b/prik/printers/README.md @@ -14,3 +14,6 @@ wrapper planning, cross-language orchestration, filenames, or build behavior. two native source printers, assigns stable wrapper filenames, and returns one generated-wrapper result. `../pipeline/build.py` writes or compiles that result. + +For cross-stage navigation, see `docs/developer/source-map.md` and +`docs/developer/feature-to-code-map.md`. diff --git a/prik/probes/__init__.py b/prik/probes/__init__.py deleted file mode 100644 index bbadab8cd..000000000 --- a/prik/probes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Compiler-derived target facts and reports.""" diff --git a/tests/c/_support/fixture_outputs.py b/tests/c/_support/fixture_outputs.py index a4e4823bf..652cebb05 100644 --- a/tests/c/_support/fixture_outputs.py +++ b/tests/c/_support/fixture_outputs.py @@ -8,7 +8,7 @@ from prik.parsers.c import CParser from prik.parsers.c.cli import attach_preprocessing_recipe -from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source +from prik.preprocessing import PreprocessingConfig, preprocess_source from prik.semantics.c2ir import c_project_to_semantic_module from prik.printers import emit_module diff --git a/tests/c/_support/preprocessing.py b/tests/c/_support/preprocessing.py index b7e6ba480..8589236f0 100644 --- a/tests/c/_support/preprocessing.py +++ b/tests/c/_support/preprocessing.py @@ -8,7 +8,7 @@ import pytest -from prik.pipeline.preprocessing import PreprocessingError +from prik.preprocessing import PreprocessingError def _fake_compiler(tmp_path: Path, output: str) -> tuple[Path, Path, dict[str, str]]: diff --git a/tests/c/fixtures/parser/generate_c_parser_goldens.py b/tests/c/fixtures/parser/generate_c_parser_goldens.py index 3e000aa0d..0bc5e5588 100644 --- a/tests/c/fixtures/parser/generate_c_parser_goldens.py +++ b/tests/c/fixtures/parser/generate_c_parser_goldens.py @@ -322,7 +322,7 @@ def _serialize_project(fixtures: list[Path]) -> dict: def _parse_fixture(parser, fixture: Path, *, filename: str, include_dirs: list[Path]): - from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source + from prik.preprocessing import PreprocessingConfig, preprocess_source compiler = shutil.which("cc") if compiler is None: diff --git a/tests/c/parsing/test_c_cli_skeleton.py b/tests/c/parsing/test_c_cli_skeleton.py index 2ad3415ef..814cf62a4 100644 --- a/tests/c/parsing/test_c_cli_skeleton.py +++ b/tests/c/parsing/test_c_cli_skeleton.py @@ -13,7 +13,7 @@ from prik.parsers.c import CParseError from prik.parsers.c import cli as c_parser_cli from prik import cli as prik_cli -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig CONTRACT_IMPORT = "from prik.contracts import Int32\n\n" diff --git a/tests/c/parsing/test_c_compiler_extensions.py b/tests/c/parsing/test_c_compiler_extensions.py index e26b48442..57b7d832f 100644 --- a/tests/c/parsing/test_c_compiler_extensions.py +++ b/tests/c/parsing/test_c_compiler_extensions.py @@ -268,7 +268,7 @@ def test_preprocessed_extension_diagnostics_and_declarations_use_linemarkers(): def test_gcc_preprocessed_standard_headers_remain_parseable(tmp_path: Path): from prik.parsers.c import parse_c_file - from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source + from prik.preprocessing import PreprocessingConfig, preprocess_source compiler = shutil.which("cc") if compiler is None: diff --git a/tests/c/parsing/test_c_corpus.py b/tests/c/parsing/test_c_corpus.py index 4b2cb9364..f6e77edc8 100644 --- a/tests/c/parsing/test_c_corpus.py +++ b/tests/c/parsing/test_c_corpus.py @@ -14,7 +14,7 @@ def _preprocessed_cjson_source(filename: str) -> str: - from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source + from prik.preprocessing import PreprocessingConfig, preprocess_source compiler = shutil.which("cc") if compiler is None: diff --git a/tests/c/parsing/test_c_fixture_suite.py b/tests/c/parsing/test_c_fixture_suite.py index 8fdd5b46f..0268d7da1 100644 --- a/tests/c/parsing/test_c_fixture_suite.py +++ b/tests/c/parsing/test_c_fixture_suite.py @@ -100,7 +100,7 @@ def test_c_fixture_headers_with_macros_require_preprocessing(fixture): ) def test_c_fixture_headers_parse_after_compiler_preprocessing(fixture, defines): from prik.parsers.c import parse_c_file - from prik.pipeline.preprocessing import PreprocessingConfig, preprocess_source + from prik.preprocessing import PreprocessingConfig, preprocess_source compiler = shutil.which("cc") if compiler is None: diff --git a/tests/c/parsing/test_c_lexer_preprocessor.py b/tests/c/parsing/test_c_lexer_preprocessor.py index bc42e2c61..eff413177 100644 --- a/tests/c/parsing/test_c_lexer_preprocessor.py +++ b/tests/c/parsing/test_c_lexer_preprocessor.py @@ -12,7 +12,7 @@ def test_c_preprocessor_module_direct_execution_example(): repository_root = Path(__file__).parents[3] result = subprocess.run( - [sys.executable, "prik/parsers/c/preprocessor.py"], + [sys.executable, "prik/preprocessing/c.py"], cwd=repository_root, capture_output=True, text=True, @@ -59,7 +59,7 @@ def test_lexer_removes_multiline_block_comments_but_preserves_following_line_num def test_line_continuations_preserve_original_line_numbers(): - from prik.parsers.c.preprocessor import normalize_c_source + from prik.preprocessing.c import normalize_c_source normalized = normalize_c_source( "#define SUM(a, b) \\\n ((a) + (b))\nint x;\n", @@ -100,7 +100,7 @@ def test_c_lexer_covers_linemarker_escapes_top_level_strings_and_eof_records(): normalize_c_source, split_top_level_c_source, ) - from prik.parsers.c.preprocessor import _record_location + from prik.preprocessing.c import _record_location assert _unescape_linemarker_filename(r"a\nb\rc\td\\e\"f\x") == 'a\nb\rc\td\\e"fx' assert _unescape_linemarker_filename("tail\\") == "tail\\" @@ -362,7 +362,7 @@ def test_c_preprocessor_helpers_cover_include_dirs_and_filesystem_errors(tmp_pat from pathlib import Path from prik.parsers.c.lexer import CLogicalRecord - from prik.parsers.c.preprocessor import _record_location, _resolve_local_include + from prik.preprocessing.c import _record_location, _resolve_local_include include_dir = tmp_path / "include" include_dir.mkdir() @@ -405,7 +405,7 @@ def raise_one_os_error(path): def test_collect_preprocessor_metadata_preserves_locations_and_diagnostics(tmp_path): - from prik.parsers.c.preprocessor import collect_preprocessor_metadata + from prik.preprocessing.c import collect_preprocessor_metadata include_dir = tmp_path / "include" include_dir.mkdir() diff --git a/tests/c/preprocessing/test_c_preprocessing_configuration.py b/tests/c/preprocessing/test_c_preprocessing_configuration.py index a7194c6d5..cbf9cfab1 100644 --- a/tests/c/preprocessing/test_c_preprocessing_configuration.py +++ b/tests/c/preprocessing/test_c_preprocessing_configuration.py @@ -5,8 +5,8 @@ import pytest -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import ( +import prik.preprocessing.source as preprocessing +from prik.preprocessing import ( PreprocessingConfig, PreprocessingError, build_compile_commands_invocation, diff --git a/tests/c/preprocessing/test_c_preprocessing_dependencies.py b/tests/c/preprocessing/test_c_preprocessing_dependencies.py index f6e08d0a1..d8c99de99 100644 --- a/tests/c/preprocessing/test_c_preprocessing_dependencies.py +++ b/tests/c/preprocessing/test_c_preprocessing_dependencies.py @@ -3,8 +3,8 @@ import json from pathlib import Path -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import PreprocessingConfig, build_compile_commands_invocation +import prik.preprocessing.source as preprocessing +from prik.preprocessing import PreprocessingConfig, build_compile_commands_invocation def test_compile_commands_filters_dependency_and_windows_compile_flags(tmp_path: Path): @@ -99,14 +99,6 @@ def test_linemarker_dependency_exposure_and_macro_edges(tmp_path: Path): "private" ) assert preprocessing._exposure_for("api.h", "root", PreprocessingConfig(include_exposure="roots-only")) == "public" - assert preprocessing._line_marker(3, 'dir\\api".h') == '# 3 "dir\\\\api\\".h"' - assert preprocessing._line_marker(3, "api.h", 1) == '# 3 "api.h" 1' - assert preprocessing._mapping_for_generated_line(mappings, mappings[0].generated_line, root) == mappings[0] - fallback = preprocessing._mapping_for_generated_line([], 99, root) - assert fallback.generated_line == 99 - assert fallback.original_path == str(root) - assert fallback.original_line == 99 - assert fallback.include_stack == [str(root)] no_filename_mappings = preprocessing.parse_linemarker_mappings("#line 42\nint next;\n", filename=str(root)) assert no_filename_mappings[0].original_path == str(root) assert no_filename_mappings[0].original_line == 42 @@ -219,10 +211,6 @@ def test_dependency_kind_requires_both_system_filename_brackets(): assert preprocessing._dependency_kind("api.h>") == "project" -def test_line_marker_escapes_paths_and_omits_absent_flag(): - assert preprocessing._line_marker(3, 'dir\\api".h') == '# 3 "dir\\\\api\\".h"' - - def test_linemarker_mapping_and_macro_helpers_cover_default_and_return_edges(): source = "\n".join( [ diff --git a/tests/c/preprocessing/test_c_preprocessing_execution.py b/tests/c/preprocessing/test_c_preprocessing_execution.py index f7e72b5d7..f1720ce9c 100644 --- a/tests/c/preprocessing/test_c_preprocessing_execution.py +++ b/tests/c/preprocessing/test_c_preprocessing_execution.py @@ -7,8 +7,8 @@ import pytest -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import ( +import prik.preprocessing.source as preprocessing +from prik.preprocessing import ( PreprocessingConfig, PreprocessingError, run_compiler_preprocessor, @@ -20,7 +20,7 @@ def test_preprocessing_module_direct_execution_example(): repository_root = Path(__file__).parents[3] result = subprocess.run( - [sys.executable, "prik/pipeline/preprocessing.py"], + [sys.executable, "prik/preprocessing/source.py"], cwd=repository_root, capture_output=True, text=True, diff --git a/tests/c/preprocessing/test_error_paths.py b/tests/c/preprocessing/test_error_paths.py index 51311f089..2d1909b17 100644 --- a/tests/c/preprocessing/test_error_paths.py +++ b/tests/c/preprocessing/test_error_paths.py @@ -6,8 +6,8 @@ import pytest -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import ( +import prik.preprocessing.source as preprocessing +from prik.preprocessing import ( PreprocessingConfig, PreprocessingError, ) diff --git a/tests/c/preprocessing/test_source_mappings.py b/tests/c/preprocessing/test_source_mappings.py index c82e04e07..ec433ddfe 100644 --- a/tests/c/preprocessing/test_source_mappings.py +++ b/tests/c/preprocessing/test_source_mappings.py @@ -2,8 +2,8 @@ from pathlib import Path -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import PreprocessingConfig +import prik.preprocessing.source as preprocessing +from prik.preprocessing import PreprocessingConfig def test_preprocess_source_preserves_plain_c_source_mapping(monkeypatch, tmp_path: Path): diff --git a/tests/c/probes/test_c_types.py b/tests/c/probes/test_c_types.py index abf2b7f31..7d789fe0d 100644 --- a/tests/c/probes/test_c_types.py +++ b/tests/c/probes/test_c_types.py @@ -10,8 +10,8 @@ import pytest -import prik.probes.c_types as c_type_probe -from prik.probes.c_types import ( +import prik.preprocessing.probes.c_types as c_type_probe +from prik.preprocessing.probes.c_types import ( CStandardTypeProbeRecipe, CStandardTypeProbeReport, CStandardTypeProbeError, @@ -22,7 +22,7 @@ probe_c_standard_types_cached, probe_c_standard_types, ) -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig _CC = shutil.which("cc") @@ -318,7 +318,7 @@ def test_c_standard_type_probe_cache_directory_precedence(monkeypatch, tmp_path) def test_c_standard_type_probe_module_cli_emits_json_for_semantic_input(): compiler = _required_c_compiler() completed = subprocess.run( - [sys.executable, "-m", "prik.probes.c_types", "--compiler", compiler], + [sys.executable, "-m", "prik.preprocessing.probes.c_types", "--compiler", compiler], capture_output=True, text=True, check=True, @@ -333,7 +333,7 @@ def test_c_standard_type_probe_module_cli_emits_json_for_semantic_input(): def test_c_standard_type_probe_direct_script_runs_its_no_argument_example(): completed = subprocess.run( - [sys.executable, "prik/probes/c_types.py"], + [sys.executable, "prik/preprocessing/probes/c_types.py"], cwd=Path(__file__).resolve().parents[3], capture_output=True, text=True, diff --git a/tests/docs/_structure_support.py b/tests/docs/_structure_support.py index 0ae89c446..ffda512b0 100644 --- a/tests/docs/_structure_support.py +++ b/tests/docs/_structure_support.py @@ -263,16 +263,19 @@ "prik/planning/README.md", "prik/printers/README.md", "prik/pipeline/README.md", - "prik/compiling/README.md", + "prik/preprocessing/README.md", + "prik/compiler/README.md", ] SOURCE_NAVIGATION_HOTSPOTS = [ "prik/__init__.py", "prik/cli.py", "prik/pipeline/build.py", - "prik/pipeline/preprocessing.py", + "prik/preprocessing/source.py", + "prik/preprocessing/c.py", + "prik/preprocessing/fortran.py", "prik/pipeline/type_mapping_report.py", - "prik/probes/c_types.py", - "prik/probes/fortran_types.py", + "prik/preprocessing/probes/c_types.py", + "prik/preprocessing/probes/fortran_types.py", "prik/semantics/ownership_metadata.py", "prik/semantics/native_array_handles.py", "prik/policy/ownership.py", @@ -303,9 +306,9 @@ "prik/printers/pyi.py", "prik/printers/c.py", "prik/printers/fortran.py", - "prik/compiling/objects.py", - "prik/compiling/compilers.py", - "prik/compiling/native_support.py", + "prik/compiler/objects.py", + "prik/compiler/compilers.py", + "prik/compiler/native_support.py", "prik/naming/policy.py", "prik/binding_support/", ] @@ -433,16 +436,26 @@ "user/examples/minpack-wrapper.md", ] MAJOR_SOURCE_PACKAGES = [ + "prik/compiler/", + "prik/preprocessing/", "prik/parsers/", "prik/semantics/", + "prik/policy/", + "prik/planning/", "prik/codegen/", - "prik/compiling/", + "prik/printers/", + "prik/pipeline/", ] PACKAGE_READMES = [ "prik/README.md", + "prik/compiler/README.md", + "prik/preprocessing/README.md", "prik/parsers/README.md", "prik/semantics/README.md", - "prik/compiling/README.md", + "prik/policy/README.md", + "prik/planning/README.md", + "prik/printers/README.md", + "prik/pipeline/README.md", ] ARCHIVED_OLD_DOCS = [ "old_docs/tutorial.md", diff --git a/tests/fortran/_support/wrapper_build.py b/tests/fortran/_support/wrapper_build.py index c2bc120bc..4511811a3 100644 --- a/tests/fortran/_support/wrapper_build.py +++ b/tests/fortran/_support/wrapper_build.py @@ -20,7 +20,7 @@ from tests.fortran._support.pyi_fixtures import assert_generated_pyi_package_matches_fixture from tests.fortran._support.fmath_cases import fmath_cases from prik import build_pyi_extension -from prik.compiling.objects import ObjectFile +from prik.compiler.objects import ObjectFile from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import ( NativeBuildPlan, @@ -30,7 +30,7 @@ _merge_wrapper_modules, _new_compiler, ) -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.pipeline.build import build_fortran_extension from prik.runtime.handles import AllocatableArray from prik.semantics.fortran2ir import fortran_project_to_semantic_modules diff --git a/tests/fortran/allocatables/policy/test_allocatable_result_policy.py b/tests/fortran/allocatables/policy/test_allocatable_result_policy.py index 72b381740..d40e11b4a 100644 --- a/tests/fortran/allocatables/policy/test_allocatable_result_policy.py +++ b/tests/fortran/allocatables/policy/test_allocatable_result_policy.py @@ -5,7 +5,7 @@ from tests.fortran._support.wrapper_build import wrapper_source from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, diff --git a/tests/fortran/building_shared_library/compiling/test_compiler_verbose.py b/tests/fortran/building_shared_library/compiling/test_compiler_verbose.py index a15752182..5b4d9bf91 100644 --- a/tests/fortran/building_shared_library/compiling/test_compiler_verbose.py +++ b/tests/fortran/building_shared_library/compiling/test_compiler_verbose.py @@ -3,11 +3,11 @@ import pytest -import prik.compiling.compiler_profiles as compiler_profiles -import prik.compiling.compilers as compiler_module -from prik.compiling.objects import ObjectFile -from prik.compiling.compilers import Compiler -from prik.compiling.compiler_profiles import available_compilers, fortran_compiler_family, vendors +import prik.compiler.compiler_profiles as compiler_profiles +import prik.compiler.compilers as compiler_module +from prik.compiler.objects import ObjectFile +from prik.compiler.compilers import Compiler +from prik.compiler.compiler_profiles import available_compilers, fortran_compiler_family, vendors def test_record_only_compiler_keeps_object_command_without_executing(monkeypatch, tmp_path: Path): diff --git a/tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py b/tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py index 06678d65b..4f1ca10b3 100644 --- a/tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py +++ b/tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py @@ -12,7 +12,7 @@ import pytest from tests.fortran._support.wrapper_build import _sole_native_module -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.pipeline.build import NativeBuildPlan, NativeLinkItem, build_fortran_extension NATIVE_FIXTURES = Path(__file__).parent / "fixtures" / "native" diff --git a/tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py b/tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py index 72b5e0510..12246b8ed 100644 --- a/tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py +++ b/tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py @@ -7,7 +7,7 @@ import pytest from tests.fortran._support.ownership_policy import parse_pyi_text -from prik.compiling.objects import ObjectFile +from prik.compiler.objects import ObjectFile from prik.pipeline.build import ( NativeBuildPlan, NativeLinkItem, diff --git a/tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py b/tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py index ef42e2aef..9312d74bd 100644 --- a/tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py +++ b/tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py @@ -8,7 +8,7 @@ import pytest -from prik.compiling.objects import ObjectFile +from prik.compiler.objects import ObjectFile from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import ( _compile_extension_objects, diff --git a/tests/fortran/callbacks/policy/test_callback_policy.py b/tests/fortran/callbacks/policy/test_callback_policy.py index 55f703ec7..91286ee4f 100644 --- a/tests/fortran/callbacks/policy/test_callback_policy.py +++ b/tests/fortran/callbacks/policy/test_callback_policy.py @@ -5,7 +5,7 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, diff --git a/tests/fortran/command_line_interface/pipeline/test_argument_contract.py b/tests/fortran/command_line_interface/pipeline/test_argument_contract.py index ddf7aca79..aea1bbace 100644 --- a/tests/fortran/command_line_interface/pipeline/test_argument_contract.py +++ b/tests/fortran/command_line_interface/pipeline/test_argument_contract.py @@ -9,7 +9,7 @@ import pytest from prik import cli as prik_cli -from prik.pipeline.preprocessing import PreprocessingError +from prik.preprocessing import PreprocessingError from tests.fortran.command_line_interface.pipeline._support import ( TEST_FILE, _MainParserError, diff --git a/tests/fortran/command_line_interface/pipeline/test_output_contract.py b/tests/fortran/command_line_interface/pipeline/test_output_contract.py index fd1877f7d..46125dd56 100644 --- a/tests/fortran/command_line_interface/pipeline/test_output_contract.py +++ b/tests/fortran/command_line_interface/pipeline/test_output_contract.py @@ -16,7 +16,7 @@ from prik import cli as prik_cli from prik.parsers.fortran import cli as fortran_parser_cli -from prik.pipeline.preprocessing import ( +from prik.preprocessing import ( PreprocessingConfig, PreprocessingDiagnostic, PreprocessingError, diff --git a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py b/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py index ddb859cbf..486227f40 100644 --- a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py +++ b/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py @@ -13,7 +13,7 @@ from prik import FortranParseError from prik import cli as prik_cli from prik.parsers.fortran import cli as fortran_parser_cli -from prik.pipeline.preprocessing import ( +from prik.preprocessing import ( PreprocessingConfig, PreprocessingDiagnostic, PreprocessingError, diff --git a/tests/fortran/data_types/probes/test_fortran_type_probes.py b/tests/fortran/data_types/probes/test_fortran_type_probes.py index a3eb1254d..d4e28f59f 100644 --- a/tests/fortran/data_types/probes/test_fortran_type_probes.py +++ b/tests/fortran/data_types/probes/test_fortran_type_probes.py @@ -9,14 +9,14 @@ import pytest -import prik.probes.fortran_types as fortran_type_probe +import prik.preprocessing.probes.fortran_types as fortran_type_probe from prik.semantics.fortran2ir import ( collect_semantic_compile_time_requirements, fortran_module_to_semantic_module, ) from prik import parse_fortran_file as parse_fortran_source from prik import parse_fortran_project -from prik.probes.fortran_types import ( +from prik.preprocessing.probes.fortran_types import ( FortranTypeProbeRecipe, FortranTypeProbeReport, FortranTypeProbeError, @@ -31,7 +31,7 @@ probe_fortran_type_expressions_cached, resolve_fortran_logical_storage_types, ) -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig _FC = shutil.which("gfortran") or shutil.which("f95") @@ -486,7 +486,7 @@ def test_fortran_type_probe_module_cli_emits_json_for_semantic_input(tmp_path): [ sys.executable, "-m", - "prik.probes.fortran_types", + "prik.preprocessing.probes.fortran_types", "--compiler", compiler, "--expr", @@ -511,7 +511,7 @@ def test_fortran_type_probe_module_cli_emits_json_for_semantic_input(tmp_path): def test_fortran_type_probe_direct_script_runs_its_no_argument_example(): completed = subprocess.run( - [sys.executable, "prik/probes/fortran_types.py"], + [sys.executable, "prik/preprocessing/probes/fortran_types.py"], cwd=Path(__file__).resolve().parents[4], capture_output=True, text=True, diff --git a/tests/fortran/error_handling/compiling/test_verbose_commands.py b/tests/fortran/error_handling/compiling/test_verbose_commands.py index 1818c2f7b..627bcc06e 100644 --- a/tests/fortran/error_handling/compiling/test_verbose_commands.py +++ b/tests/fortran/error_handling/compiling/test_verbose_commands.py @@ -1,7 +1,7 @@ import shlex import sys -from prik.compiling.compilers import Compiler +from prik.compiler.compilers import Compiler def test_run_command_verbose_prints_replayable_command(capsys): diff --git a/tests/fortran/functions/policy/test_function_result_policy.py b/tests/fortran/functions/policy/test_function_result_policy.py index 1b420f29b..4942b0cd1 100644 --- a/tests/fortran/functions/policy/test_function_result_policy.py +++ b/tests/fortran/functions/policy/test_function_result_policy.py @@ -5,7 +5,7 @@ from tests.fortran._support.wrapper_build import wrapper_source from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.policy.ownership import ( NativeBarrierAction, diff --git a/tests/fortran/generic_interfaces/policy/test_generic_policy.py b/tests/fortran/generic_interfaces/policy/test_generic_policy.py index 094424642..019678f57 100644 --- a/tests/fortran/generic_interfaces/policy/test_generic_policy.py +++ b/tests/fortran/generic_interfaces/policy/test_generic_policy.py @@ -5,7 +5,7 @@ from tests.fortran._support.wrapper_build import wrapper_source from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, diff --git a/tests/fortran/infrastructure/semantics/test_wrapper_policy.py b/tests/fortran/infrastructure/semantics/test_wrapper_policy.py index cc9c252ee..decc6e581 100644 --- a/tests/fortran/infrastructure/semantics/test_wrapper_policy.py +++ b/tests/fortran/infrastructure/semantics/test_wrapper_policy.py @@ -9,7 +9,7 @@ from prik.planning import WrapperPlanner from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.pipeline.pyi import pyi_file_to_semantic_module from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import ( diff --git a/tests/fortran/optional_arguments/policy/test_optional_policy.py b/tests/fortran/optional_arguments/policy/test_optional_policy.py index 1094c7b3a..3e8f7e1a0 100644 --- a/tests/fortran/optional_arguments/policy/test_optional_policy.py +++ b/tests/fortran/optional_arguments/policy/test_optional_policy.py @@ -5,7 +5,7 @@ from tests.fortran._support.wrapper_build import wrapper_source from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.pipeline.pyi import pyi_file_to_semantic_module from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import ( diff --git a/tests/fortran/raw_addresses/policy/test_raw_address_policy.py b/tests/fortran/raw_addresses/policy/test_raw_address_policy.py index de9e83736..a30d05e05 100644 --- a/tests/fortran/raw_addresses/policy/test_raw_address_policy.py +++ b/tests/fortran/raw_addresses/policy/test_raw_address_policy.py @@ -5,7 +5,7 @@ from tests.fortran._support.wrapper_build import wrapper_source from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, diff --git a/tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py b/tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py index b6ea799a9..e23db5741 100644 --- a/tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py +++ b/tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py @@ -11,7 +11,7 @@ from tests.fortran._support.pyi_fixtures import assert_generated_pyi_package_matches_fixture from tests.fortran._support.wrapper_build import _compiler from prik import build_pyi_extension -from prik.compiling.objects import ObjectFile +from prik.compiler.objects import ObjectFile from prik.pipeline.build import _new_compiler FEATURE_ROOT = Path(__file__).parents[1] diff --git a/tests/fortran/source_preprocessing/preprocessing/_support.py b/tests/fortran/source_preprocessing/preprocessing/_support.py index 60fb02dbb..cf8bc81fd 100644 --- a/tests/fortran/source_preprocessing/preprocessing/_support.py +++ b/tests/fortran/source_preprocessing/preprocessing/_support.py @@ -6,7 +6,7 @@ import pytest -from prik.pipeline.preprocessing import PreprocessingError +from prik.preprocessing import PreprocessingError def _fake_compiler(tmp_path: Path, output: str) -> tuple[Path, Path, dict[str, str]]: diff --git a/tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py b/tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py index 5d4fc2b01..7a26d31c8 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py +++ b/tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py @@ -5,8 +5,8 @@ import pytest -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import ( +import prik.preprocessing.source as preprocessing +from prik.preprocessing import ( PreprocessingConfig, PreprocessingError, build_direct_preprocess_invocation, diff --git a/tests/fortran/source_preprocessing/preprocessing/test_dependencies_and_includes.py b/tests/fortran/source_preprocessing/preprocessing/test_dependencies_and_includes.py index f83eaa34f..c94e21002 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_dependencies_and_includes.py +++ b/tests/fortran/source_preprocessing/preprocessing/test_dependencies_and_includes.py @@ -2,8 +2,9 @@ from pathlib import Path -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import PreprocessingConfig, expand_native_fortran_includes +import prik.preprocessing.source as preprocessing +import prik.preprocessing.fortran as fortran_preprocessing +from prik.preprocessing import PreprocessingConfig, expand_native_fortran_includes def test_linemarker_included_files_fortran_metadata_and_duplicate_entries(tmp_path: Path): @@ -105,6 +106,21 @@ def test_linemarker_nested_returns_restore_parent_stack(tmp_path: Path): assert direct_include.include_line == 1 +def test_native_include_line_marker_and_mapping_helpers_preserve_provenance(tmp_path: Path): + root = tmp_path / "root.F90" + mappings = preprocessing.parse_linemarker_mappings('# 7 "api.inc"\ninteger :: value\n', filename=str(root)) + + assert fortran_preprocessing._line_marker(3, 'dir\\api".inc') == '# 3 "dir\\\\api\\".inc"' + assert fortran_preprocessing._line_marker(3, "api.inc", 1) == '# 3 "api.inc" 1' + assert fortran_preprocessing._mapping_for_generated_line(mappings, mappings[0].generated_line, root) == mappings[0] + + fallback = fortran_preprocessing._mapping_for_generated_line([], 99, root) + assert fallback.generated_line == 99 + assert fallback.original_path == str(root) + assert fallback.original_line == 99 + assert fallback.include_stack == [str(root)] + + def test_native_fortran_include_expansion_is_recursive_and_preserves_duplicates(tmp_path: Path): root = tmp_path / "src" / "root.F90" include = root.parent / "decls.inc" diff --git a/tests/fortran/source_preprocessing/preprocessing/test_execution.py b/tests/fortran/source_preprocessing/preprocessing/test_execution.py index 68b870e2c..c0e6d1121 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_execution.py +++ b/tests/fortran/source_preprocessing/preprocessing/test_execution.py @@ -4,8 +4,9 @@ import pytest -import prik.pipeline.preprocessing as preprocessing -from prik.pipeline.preprocessing import ( +import prik.preprocessing.fortran as fortran_preprocessing +import prik.preprocessing.source as preprocessing +from prik.preprocessing import ( PreprocessingConfig, PreprocessingError, ) @@ -20,7 +21,7 @@ def test_preprocess_source_reparses_fortran_mapping_when_native_expansion_return lambda *_args, **_kwargs: type("Done", (), {"returncode": 0, "stdout": "ignored\n", "stderr": ""})(), ) monkeypatch.setattr( - preprocessing, + fortran_preprocessing, "expand_native_fortran_includes", lambda *_args, **_kwargs: ("integer :: value\n", [], [], []), ) diff --git a/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py b/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py index befbca04c..828649ffb 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py +++ b/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_properties.py`.""" -import prik.pipeline.preprocessing as preprocessing +import prik.preprocessing.source as preprocessing import pytest import sys from hypothesis import ( @@ -12,7 +12,7 @@ FortranParseError, parse_fortran_file, ) -from prik.pipeline.preprocessing import ( +from prik.preprocessing import ( PreprocessingConfig, preprocess_source, ) diff --git a/tests/fortran/strings/policy/test_string_wrapper_policy.py b/tests/fortran/strings/policy/test_string_wrapper_policy.py index 2cf1ffeeb..135d3b6cc 100644 --- a/tests/fortran/strings/policy/test_string_wrapper_policy.py +++ b/tests/fortran/strings/policy/test_string_wrapper_policy.py @@ -5,7 +5,7 @@ from tests.fortran._support.wrapper_build import wrapper_source from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.semantics.models import ( RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA, diff --git a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py index 3370a1786..dbda093eb 100644 --- a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py +++ b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py @@ -4,7 +4,7 @@ from tests.fortran._support.ownership_policy import parse_pyi_text from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.policy.ownership import ( NativeBarrierAction, diff --git a/tools/wrapper_plan_staged_walkthrough.py b/tools/wrapper_plan_staged_walkthrough.py index 0b8bf412b..806ecb0b9 100644 --- a/tools/wrapper_plan_staged_walkthrough.py +++ b/tools/wrapper_plan_staged_walkthrough.py @@ -13,7 +13,7 @@ from prik.parsers.fortran.parser import parse_fortran_project from prik.pipeline import build as pipeline -from prik.pipeline.preprocessing import PreprocessingConfig +from prik.preprocessing import PreprocessingConfig from prik.semantics.fortran2ir import fortran_project_to_semantic_modules from prik.policy.completion import complete_semantic_policies from prik.codegen import ( From 8f095e385f2b96d627428d1b9601835d798f3bcd Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 14:25:07 +0100 Subject: [PATCH 18/22] codex: reorganize wrapper architecture and contributor documentation Reorganize PRIK around explicit pipeline responsibilities and remove the old internal package paths without compatibility shims. - Separate post-IR policy, wrapper planning, code generation, language printers, and pipeline orchestration into dedicated packages. - Extract immutable policy models, improve ownership override construction, and document the complete ownership and datatype lifecycles. - Make WrapperGenerator the plan-to-generated-wrapper boundary and render docstrings from completed plans during code generation. - Extract PythonSurfaceEmitter and move overload selection into planned C dispatchers. - Consolidate compilers, preprocessing, and datatype probes under clear package owners. - Replace the old shared NumPy type layer with stage-owned semantic, runtime, and code-generation datatype catalogues. - Move the bundled binding runtime into prik.runtime.native_support. - Improve Fortran source-unit scanning, project assembly, and compile-time kind resolution. - Consolidate maintainer and developer documentation into one contributor documentation tree with a complete architecture guide. - Centralize runnable production-file examples under the execution_examples test owner. - Move generated documentation, distributions, and setuptools metadata under the hidden .artifacts directory. - Prevent compiler capability probes from leaving .mod files in the repository root. - Update package imports, tests, workflows, source maps, navigation, and CHANGELOG.md for the new structure. Verification includes focused compilation and execution-example tests, documentation tests, strict MkDocs and package builds, Twine validation, Ruff, Bandit, Vulture, codegen complexity, Radon policy, and whitespace checks. --- .artifacts/.gitignore | 2 + .github/workflows/docs.yml | 2 +- .github/workflows/publish-to-pypi.yml | 16 +- .gitignore | 2 - CHANGELOG.md | 17 + MANIFEST.in | 1 + docs/developer/architecture.md | 1840 +++++++++++++++++ docs/developer/c-parser-reference.md | 2 +- docs/{maintainer => developer}/ci-cd.md | 17 +- .../design/code-generation.md | 0 .../design/cpython-integration.md | 0 .../design/error-propagation-model.md | 0 .../{maintainer => developer}/design/index.md | 5 +- .../design/memory-ownership-model.md | 0 .../design/parser-architecture.md | 0 .../design/runtime-model.md | 0 .../design/semantic-analysis.md | 0 ...tilanguage-wrapper-runtime-architecture.md | 2 +- .../design/wrapper-design-notes.md | 2 +- docs/developer/development-workflow.md | 6 +- .../documentation-architecture.md | 67 +- docs/developer/feature-to-code-map.md | 2 +- docs/developer/index.md | 29 +- .../internal-architecture/ast-design.md | 0 .../dependency-analysis.md | 0 .../error-handling-pipeline.md | 0 .../internal-architecture/index.md | 4 +- .../ownership-tracking.md | 0 .../internal-architecture/pipeline-map.md | 4 +- .../internal-architecture/runtime-layer.md | 0 .../internal-architecture/semantic-passes.md | 0 .../internal-architecture/symbol-tables.md | 0 .../internal-architecture/type-system.md | 0 .../wrapper-generation-pipeline.md | 0 .../release-process.md | 14 +- docs/developer/repository-structure.md | 3 +- .../documentation-content-checklist.md | 68 +- .../fortran-test-suite-cleanup-checklist.md | 0 .../roadmap/index.md | 2 +- .../roadmap/native-array-handle-checklist.md | 0 .../roadmap/semantic-pyi-wrapper-checklist.md | 2 +- .../wrapper-plan-migration-checklist.md | 2 +- docs/developer/source-map.md | 7 +- docs/developer/testing-strategy.md | 7 +- docs/maintainer/README.md | 40 - .../maintainer/design/overall-architecture.md | 18 - docs/user/reference/configuration-files.md | 17 +- docs/user/reference/semantic-ir.md | 2 +- mkdocs.yml | 67 +- prik/README.md | 5 +- prik/__init__.py | 8 + prik/__main__.py | 4 +- prik/binding_support/__init__.py | 1 - prik/cli.py | 4 + prik/codegen/c/python_surface.py | 48 + prik/codegen/nodes.py | 28 + prik/codegen/primitive_scalar_types.py | 13 + prik/compiler/README.md | 2 +- prik/compiler/compiler_profiles.py | 10 + prik/compiler/compilers.py | 30 +- prik/compiler/native_support.py | 19 +- prik/compiler/objects.py | 15 + prik/contracts/__init__.py | 11 + prik/naming/native_symbols.py | 9 + prik/naming/policy.py | 18 + prik/parsers/c/cli.py | 21 +- prik/parsers/c/lexer.py | 14 + prik/parsers/fortran/__main__.py | 8 +- prik/parsers/fortran/cli.py | 37 +- prik/parsers/fortran/lexer.py | 15 +- prik/parsers/pyi/parser.py | 13 + prik/pipeline/pyi.py | 20 + prik/policy/exports.py | 19 + prik/policy/models.py | 30 + prik/policy/native_array_handles.py | 67 + prik/policy/ownership.py | 2 +- prik/preprocessing/README.md | 2 +- prik/preprocessing/fortran.py | 24 + prik/printers/c.py | 24 + prik/runtime/handles.py | 30 + .../native_support}/LICENSE | 0 prik/runtime/native_support/__init__.py | 1 + .../native_support}/prik_binding.h | 0 prik/semantics/README.md | 6 +- prik/semantics/models.py | 28 + prik/semantics/native_array_handles.py | 22 + prik/semantics/native_contract.py | 40 +- prik/semantics/ownership_metadata.py | 35 + prik/semantics/scalar_types.py | 8 + prik/stage_values.py | 20 + prik/utilities/strings.py | 10 + prik/utilities/visitor.py | 21 + pyproject.toml | 2 +- setup.cfg | 2 + tests/README.md | 9 +- tests/c/parsing/test_c_lexer_preprocessor.py | 22 - tests/c/parsing/test_c_project_resolution.py | 18 - tests/c/parsing/test_c_public_api_skeleton.py | 23 - .../test_c_preprocessing_execution.py | 42 - tests/c/probes/test_c_types.py | 17 - .../test_projects_and_diagnostics.py | 15 - tests/docs/_structure_support.py | 31 +- tests/docs/test_navigation.py | 27 +- tests/docs/test_publication.py | 1 - tests/docs/test_reference_and_source_map.py | 18 + tests/docs/test_user_content.py | 1 - tests/fortran/README.md | 3 +- tests/fortran/_support/wrapper_build.py | 1 + .../test_declaration_expression_utilities.py | 17 - .../compiling/test_support_probe_artifacts.py | 19 + .../end_to_end/test_source_build_modes.py | 12 - tests/fortran/conftest.py | 7 +- .../parsing/test_declarations_and_shapes.py | 22 - .../pipeline/test_type_mapping_report.py | 21 - .../probes/test_fortran_type_probes.py | 16 - .../infrastructure/codegen/test_binding.py | 16 - .../infrastructure/codegen/test_bridge.py | 17 - .../infrastructure/codegen/test_docstrings.py | 17 - .../infrastructure/codegen/test_plan.py | 17 - .../infrastructure/codegen/test_planner.py | 16 - .../codegen/test_pyi_printer.py | 17 - .../test_execution_examples.py | 558 +++++ .../pipeline/test_stage_values.py | 1 + .../pipeline/test_wrapper_generator.py | 15 - .../printers/test_source_printers.py | 16 - .../test_native_support.py | 4 +- .../semantics/test_ownership.py | 20 - .../semantics/test_policy_completion.py | 19 - .../semantics/test_wrapper_policy.py | 19 - .../semantics/test_compile_time_values.py | 16 - .../parsing/test_python_ast_contracts.py | 18 - .../parsing/test_public_entrypoints.py | 19 - tools/mkdocs_publication.py | 1 - 133 files changed, 3405 insertions(+), 758 deletions(-) create mode 100644 .artifacts/.gitignore create mode 100644 docs/developer/architecture.md rename docs/{maintainer => developer}/ci-cd.md (90%) rename docs/{maintainer => developer}/design/code-generation.md (100%) rename docs/{maintainer => developer}/design/cpython-integration.md (100%) rename docs/{maintainer => developer}/design/error-propagation-model.md (100%) rename docs/{maintainer => developer}/design/index.md (84%) rename docs/{maintainer => developer}/design/memory-ownership-model.md (100%) rename docs/{maintainer => developer}/design/parser-architecture.md (100%) rename docs/{maintainer => developer}/design/runtime-model.md (100%) rename docs/{maintainer => developer}/design/semantic-analysis.md (100%) rename docs/{maintainer => developer}/design/semantic-multilanguage-wrapper-runtime-architecture.md (99%) rename docs/{maintainer => developer}/design/wrapper-design-notes.md (99%) rename docs/{maintainer => developer}/documentation-architecture.md (79%) rename docs/{maintainer => developer}/internal-architecture/ast-design.md (100%) rename docs/{maintainer => developer}/internal-architecture/dependency-analysis.md (100%) rename docs/{maintainer => developer}/internal-architecture/error-handling-pipeline.md (100%) rename docs/{maintainer => developer}/internal-architecture/index.md (85%) rename docs/{maintainer => developer}/internal-architecture/ownership-tracking.md (100%) rename docs/{maintainer => developer}/internal-architecture/pipeline-map.md (99%) rename docs/{maintainer => developer}/internal-architecture/runtime-layer.md (100%) rename docs/{maintainer => developer}/internal-architecture/semantic-passes.md (100%) rename docs/{maintainer => developer}/internal-architecture/symbol-tables.md (100%) rename docs/{maintainer => developer}/internal-architecture/type-system.md (100%) rename docs/{maintainer => developer}/internal-architecture/wrapper-generation-pipeline.md (100%) rename docs/{maintainer => developer}/release-process.md (87%) rename docs/{maintainer => developer}/roadmap/documentation-content-checklist.md (87%) rename docs/{maintainer => developer}/roadmap/fortran-test-suite-cleanup-checklist.md (100%) rename docs/{maintainer => developer}/roadmap/index.md (95%) rename docs/{maintainer => developer}/roadmap/native-array-handle-checklist.md (100%) rename docs/{maintainer => developer}/roadmap/semantic-pyi-wrapper-checklist.md (99%) rename docs/{maintainer => developer}/roadmap/wrapper-plan-migration-checklist.md (99%) delete mode 100644 docs/maintainer/README.md delete mode 100644 docs/maintainer/design/overall-architecture.md delete mode 100644 prik/binding_support/__init__.py rename prik/{binding_support => runtime/native_support}/LICENSE (100%) create mode 100644 prik/runtime/native_support/__init__.py rename prik/{binding_support => runtime/native_support}/prik_binding.h (100%) create mode 100644 setup.cfg create mode 100644 tests/fortran/building_shared_library/compiling/test_support_probe_artifacts.py create mode 100644 tests/fortran/infrastructure/execution_examples/test_execution_examples.py create mode 100644 tests/fortran/infrastructure/pipeline/test_stage_values.py rename tests/fortran/infrastructure/{binding_support => runtime}/test_native_support.py (91%) diff --git a/.artifacts/.gitignore b/.artifacts/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/.artifacts/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2ad96136d..4a707998b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -147,7 +147,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' uses: actions/upload-pages-artifact@v4 with: - path: site + path: .artifacts/site deploy: name: Documentation deployment · GitHub Pages diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e2910e99a..bdd96ea4b 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -44,17 +44,17 @@ jobs: python -m pip install --upgrade pip python -m pip install build twine - name: Build source and wheel distributions - run: python -m build + run: python -m build --outdir .artifacts/dist - name: Check distribution metadata - run: python -m twine check dist/* + run: python -m twine check .artifacts/dist/* - name: Verify and install the wheel shell: bash run: | - mapfile -t wheels < <(compgen -G "dist/prik-*-py3-none-any.whl") - mapfile -t sdists < <(compgen -G "dist/prik-*.tar.gz") + mapfile -t wheels < <(compgen -G ".artifacts/dist/prik-*-py3-none-any.whl") + mapfile -t sdists < <(compgen -G ".artifacts/dist/prik-*.tar.gz") if (( ${#wheels[@]} != 1 || ${#sdists[@]} != 1 )); then echo "expected one universal wheel and one source distribution" >&2 - ls -la dist + ls -la .artifacts/dist exit 1 fi python -m venv "$RUNNER_TEMP/prik-release-check" @@ -68,7 +68,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: python-package-distributions - path: dist/ + path: .artifacts/dist/ if-no-files-found: error retention-days: 7 @@ -86,6 +86,8 @@ jobs: uses: actions/download-artifact@v4 with: name: python-package-distributions - path: dist/ + path: .artifacts/dist/ - name: Publish distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: .artifacts/dist/ diff --git a/.gitignore b/.gitignore index deab33b6a..70b6a2d00 100644 --- a/.gitignore +++ b/.gitignore @@ -10,13 +10,11 @@ mutants/ .ruff_cache/ .benchmarks/ htmlcov/ -site/ build/ *.pyc *.pyo *egg* -dist/* *.mod *.out diff --git a/CHANGELOG.md b/CHANGELOG.md index da5b23817..e979d8260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,28 @@ release tags add a leading `v` to the package version. ### Added +- Added a canonical contributor architecture guide with complete + folder-by-folder stage ownership, change routes, reproducible direct + examples, representative output, and focused evidence owners. - Added Zenodo version and concept DOI links to the citation metadata, README, and About page. ### Changed +- Moved generated documentation and distribution output under the hidden + `.artifacts/` directory in local commands and CI workflows. +- Centralized every production-file execution-example output contract in one + contributor-architecture test inventory with one named test per file. +- Renamed the central infrastructure owner to `execution_examples/` so its + responsibility is explicit in the test tree. +- Consolidated developer and maintainer material under one Contributor + Documentation tree and removed the separate maintainer documentation lane. +- Moved the bundled header-only binding runtime from the package root into + `prik.runtime.native_support`; generated builds continue to receive it under + their internal `binding_support/` include directory. +- Deferred the contributor architecture sections for the immature C input + parser and C-to-IR path while retaining the generated CPython C binding + backend documentation required by Fortran wrappers. - Reorganized compiler and pre-parse infrastructure into `prik.compiler` and `prik.preprocessing`, including C/Fortran preprocessing and target probes; the former `prik.compiling`, `prik.probes`, parser-local C preprocessor, and diff --git a/MANIFEST.in b/MANIFEST.in index fc4929396..e2755646a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ include CHANGELOG.md +include .artifacts/.gitignore diff --git a/docs/developer/architecture.md b/docs/developer/architecture.md new file mode 100644 index 000000000..3abe9769e --- /dev/null +++ b/docs/developer/architecture.md @@ -0,0 +1,1840 @@ +--- +title: Contributor Architecture Guide +audience: developers, maintainers, contributors +prerequisites: repository checkout +related: source-map.md, feature-to-code-map.md, repository-structure.md, testing-strategy.md +status: maintained +publication: draft +--- + +# Contributor Architecture Guide + +This is the first internal document to read before changing prik. It is the +single architectural map for developers, maintainers, and future +contributors: one description of the package tree, the data passed between +stages, the decisions owned by each stage, the files to open, the directly +runnable examples, and the tests that prove each boundary. + +The existing [Source Map](source-map.md) remains the detailed file index, and +the [Feature To Code Map](feature-to-code-map.md) remains the route from a +user-visible behavior to its implementation and evidence. Their architectural +explanations are summarized here without duplicating their detailed indexes. +This page is the canonical orientation document; the other maps are supporting +references rather than alternative starting points. + +## Documentation Decision + +Prik will keep one contributor documentation area: `docs/developer/`. A +maintainer is a developer with additional release, CI, roadmap, and stewardship +responsibilities, not a separate reader who should receive a different account +of the architecture. + +The former maintainer material now lives in the contributor tree by topic: + +```text +docs/developer/ + architecture.md + design/ + internal-architecture/ + roadmap/ + contributing/ + ci-cd.md + release-process.md + documentation-architecture.md +``` + +The separate `docs/maintainer/` lane has been removed together with its old +index and navigation entry. Design proposals and roadmaps remain clearly +labelled as proposals or plans; sharing one documentation area does not turn +planned architecture into implemented behavior. + +## System In One View + +The implemented source-driven Fortran path is: + +```text +CLI or Python build request + -> compiler-backed preprocessing and target probing + -> language parser facts + -> language-neutral semantic IR + -> complete post-IR interoperability policy + -> backend-neutral wrapper plan + -> C and Fortran syntax-node generation + -> language printers + -> one GeneratedWrapper + -> compiler and linker services + -> importable extension and runtime objects +``` + +Semantic `.pyi` input joins at semantic IR construction. + + + +The most important dependency rule is one-way authority. Parsers describe +source. Semantic IR preserves contract facts. Policy completion decides +ownership, transfer, destruction, storage, projection, setter exposure, and +support. Planning projects those completed decisions. Code generation and +printers implement the selected plan without inventing semantic behavior. + +## Direct-Execution Examples + +Every selected architectural entry file ends with a small, real example +guarded by: + +```python +if __name__ == "__main__": + ... +``` + +Run an example from the repository root with its filename, for example: + +```bash +python3 prik/parsers/fortran/parser.py +``` + +These examples are executable architecture notes. Each one must: + +- exercise the module's actual API or data model rather than a test fixture; +- show the module's input and output at its owning stage; +- remain deterministic and write only below a temporary directory when files + are needed; +- state or fail clearly when a compiler or optional target capability is + required; +- avoid importing policy into a parser, semantic inference into codegen, or any + other shortcut across the documented ownership boundaries; and +- be covered by a focused direct-execution test or by an existing CLI test. + +The result of every selected file example is asserted in one inventory: + +```text +tests/fortran/infrastructure/execution_examples/test_execution_examples.py +``` + +It contains one explicit test per production file, named in the form +`test_fortran___execution_example`. This central owner verifies +that the commands and outputs shown throughout this guide remain executable; +feature-local tests separately prove the underlying parser, policy, codegen, +runtime, or build behavior. The `pipeline/build.py` case retains its +`fortran_end_to_end` marker because that example really compiles, imports, and +calls an extension. + +Not every helper, model, or export file needs an artificial example. Each +folder section selects the files that best expose that folder's responsibility +and explains why those are its entry files. Secondary files are described when +they clarify the design and are exercised indirectly by the selected example. +Package-export `__init__.py` files normally declare namespaces and remain +side-effect free. Package `__main__.py` files are CLI launchers rather than +teaching examples, but they still use an explicit guard. The architecture +section for each package lists its useful filename-based examples and explains +what each demonstrates. + +## Folder-By-Folder Coverage + +The guide and examples follow dependency order. Each group was completed as a +reviewable checkpoint: first document the package contract, then add or repair +its direct examples, then run its focused tests before moving downstream. + +| Order | Folder or files | What the architecture section explains | Direct-execution coverage | Focused verification | +| --- | --- | --- | --- | --- | +| 1 | `prik/`, `prik/contracts/`, `prik/stage_values.py` | Public entrypoints, CLI dispatch, public semantic-contract vocabulary, and shared stage-record behavior | Guard the CLI launchers; demonstrate a public stage record and representative runtime contract scalars; keep export-only `__init__.py` files inert | public-entrypoint, CLI, contract-runtime, and stage-record tests | +| 2 | `prik/compiler/` | Reusable compiler profiles, object inputs, command execution, native support installation, and linking; no preprocessing or semantic policy | Demonstrate profile selection, an `ObjectFile`, a dry command boundary, and temporary native-support installation without creating a wrapper plan | compiler command, verbose output, profile, and shared-library build tests | +| 3 | `prik/preprocessing/` and `prik/preprocessing/probes/` | Compiler source expansion, provenance, native Fortran includes, and compiler-measured Fortran target facts | Preserve the source and Fortran-probe examples; add a native Fortran include example; make compiler requirements explicit | Fortran preprocessing and target-probe tests | +| 4 | `prik/parsers/fortran/` and `prik/parsers/pyi/` | The Fortran and semantic `.pyi` frontend boundaries, diagnostics, source locations, and project assembly | Keep parser/type-resolver examples; add lexer, report, and semantic `.pyi` parser examples; explain passive models and utilities without contrived demos; guard all CLI launchers | parser fixtures, diagnostics, public parser APIs, and parser CLI tests | +| 5 | `prik/semantics/` | Language-neutral IR models, scalar vocabulary, Fortran and semantic `.pyi` conversion, native-contract validation, and raw metadata that survives into policy | Keep the Fortran and semantic `.pyi` converter examples; add small model, scalar, metadata, native-handle, and native-contract flows | semantic conversion, semantic `.pyi`, datatype, and native-contract tests | + +| 6 | `prik/policy/` | Immutable completed-policy vocabulary, ownership resolution, export policy, feature-policy construction, descriptor-handle policy, and ordered completion | Keep ownership/construction/completion examples; add focused model, export, and native-array-policy examples using semantic input | infrastructure semantics, ownership, and feature-local policy tests | +| 7 | `prik/planning/` | Mechanical projection from completed semantic policy into the editable backend-neutral wrapper plan | Keep model and planner examples; ensure they show policy completion before planning and no rendering | planner and feature-local codegen-plan tests | +| 8 | `prik/codegen/` | Backend syntax nodes, datatype catalogues, docstrings, overload queries, Python facade generation, and C/Fortran lowering | Keep bridge/binding/docstring examples; add nodes, datatype registry, overload, naming, check, visitor, and Python-surface examples that consume completed plans | codegen infrastructure, golden output, complexity-policy, and feature-local codegen tests | +| 9 | `prik/printers/` | Pure serialization of already-formed C nodes, Fortran nodes, and semantic IR; no orchestration or semantic decisions | Keep Fortran and `.pyi` examples; add the matching C-node printing example | printer and generated-source golden tests | +| 10 | `prik/pipeline/` | Cross-stage `.pyi` loading, datatype reports, plan-to-rendered-wrapper orchestration, build orchestration, and returned artifacts | Keep wrapper/report/build examples; add a `.pyi` loading and reconciliation example; keep compiler-writing examples temporary | pipeline, build-mode, generated-wrapper, and semantic `.pyi` tests | +| 11 | `prik/runtime/` | Runtime handle responsibilities, generated-operation adapters, descriptor validation, and the bundled native support boundary | Add a Python runtime-handle example driven by explicit generated operations; document that `runtime/native_support` is a native payload with no substantive Python module to demonstrate | runtime handle, descriptor, ownership, and compiled runtime tests | +| 12 | `prik/naming/` and `prik/utilities/` | Cross-cutting public/native naming and genuinely domain-neutral parsing/string/visitor mechanisms | Keep the declaration-expression example; add public-name, native-symbol, string, and visitor examples | naming, utility, declaration-expression, and downstream consumer tests | +| 13 | Contributor documentation consolidation | Merge implemented architecture, design rationale, internal maps, governance workflows, and roadmaps into one developer tree; remove contradictory placeholders and duplicate audience lanes | Add a checked inventory tying every selected architectural entry file to a reproducible direct-execution route | complete documentation suite, link/navigation checks, direct-example suite, and whitespace checks | + +## Acceptance Criteria For Each Package Section + +A folder is complete in this guide only when its section contains all of the +following: + +1. Its purpose in one paragraph. +2. What it owns and what it must not own. +3. Its important files and the role of each file. +4. The input and output values crossing its boundary. +5. Its upstream and downstream dependencies. +6. At least one runnable `python3 .py` example, or an explicit reason + the folder contains only package manifests or non-Python payloads. +7. The focused tests that prove the contract. +8. A short change route telling a future contributor where to begin. + +The completed inventory compares every `prik/` folder and every entry file +selected by this guide against this checklist. It also checks that +each folder consciously identifies its main files, so omitting a helper does +not imply that every file is equally important. A folder is not complete +merely because it appears in a package table. + +## Package Root And Public Contracts + +The package root is the boundary between users and the internal pipeline. It +contains only public entrypoints and one shared stage-value mechanism; domain +implementations belong in named subpackages. + +| File or folder | Responsibility | +| --- | --- | +| `prik/__init__.py` | Flattens the supported Python API and lazily exposes heavyweight CLI, probe, and build functions. It must not become a second implementation home for those functions. | +| `prik/__main__.py` | Delegates `python3 -m prik` to `prik.cli.main`. Importing this launcher does not execute the CLI. | +| `prik/cli.py` | Parses user commands, validates cross-option combinations, selects inspection or build workflows, formats diagnostics, and delegates work to the owning parser or pipeline module. It coordinates stages but does not own their semantic rules. | +| `prik/stage_values.py` | Provides `StageRecord`, the mutable-producer/immutable-consumer handoff used for editable wrapper plans and generated artifacts. Recursive freezing converts mutable containers and rejects later mutation. | +| `prik/contracts/` | Defines the public names used in semantic `.pyi` files. Contract symbols are both parser-recognized syntax and, for supported primitive scalars or native descriptor handles, small runtime constructors. They are not semantic IR classes. | + +The root API depends on parsers, semantic conversion, contract loading, and +runtime handles. Those packages must not import the flattened root API back; +internal code imports canonical owners to avoid cycles and hidden dependency +direction. + +### `prik/__init__.py`: supported public API + +The package initializer is the public import surface. Its example deliberately +uses `prik.parse_fortran_file`, rather than reaching into an implementation +package, to show what a caller receives from the stable API: + +```bash +python3 prik/__init__.py +``` + +```text +PRIK 0.2.1 +Public parser result: subroutine ping from ping.f90 +``` + +The output demonstrates that the root exposes both package metadata and the +source-to-parser-model entrypoint. The result is still a parser fact; no +semantic conversion, policy completion, or wrapper generation has occurred. + +### `prik/cli.py`: command dispatch + +This file owns the top-level command vocabulary and routes a validated request +to its real stage owner. Running the file with an ordinary CLI option reaches +the same `main()` function as the installed `prik` command: + +```bash +python3 prik/cli.py --version +``` + +```text +prik 0.2.1 +``` + +This small output proves filename execution is a real CLI path, not a separate +tutorial implementation. Parse and build subcommands exercise the downstream +packages described later in this guide. + +### `prik/stage_values.py`: mutable-to-frozen handoff + +`StageRecord` lets a producing stage assemble a dataclass and then lets its +consumer freeze that value recursively: + +```bash +python3 prik/stage_values.py +``` + +```text +Editable parser output: geometry -> ['scale', 'norm'] +Frozen consumer input: geometry -> ('scale', 'norm') +Mutation rejected: ParserOutput is frozen by its consuming stage +``` + +The list becoming a tuple and the rejected assignment are the important +boundary: consumers can trust a completed plan or artifact not to change under +them. + +### `prik/contracts/__init__.py`: public contract vocabulary + +The contracts package contains names written by users in semantic `.pyi` +files. Some primitive names are also useful NumPy scalar constructors: + +```bash +python3 prik/contracts/__init__.py +``` + +```text +Float64() -> np.float64(0.0) (float64) +Float64[:, :] -> element=Float64, rank=2, shape=(slice(None, None, None), slice(None, None, None)) +``` + +The first line is a runtime NumPy scalar. The second is declarative contract +syntax describing element type, rank, and shape; it is interpreted later by +the semantic `.pyi` frontend rather than being a semantic IR object itself. + +Package `__init__.py` files normally remain export-only manifests. The root and +`contracts` initializers are exceptions because each contains substantive +public behavior worth demonstrating. `runtime/native_support/__init__.py` +remains empty: its folder owns a native header payload, not a Python API. + +Primary evidence: + +- `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` +- `tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py` +- `tests/fortran/infrastructure/pipeline/test_stage_values.py` +- `tests/fortran/data_types/runtime/test_contract_scalar_constructors.py` + +Start a root-level change in the narrow owner above. Public export changes also +update `docs/user/reference/python-api.md`; CLI changes update the CLI reference +and argument/output contract tests. A new cross-stage value should be placed at +the stage that produces it unless its freeze behavior is genuinely shared. + +## Compiler Services + +`prik/compiler/` is the reusable native-process layer. It receives explicit +source, object, include, library, flag, and link inputs from the build pipeline; +it constructs and optionally executes commands. It does not preprocess source, +measure datatype semantics, discover a wrapper API, complete ownership policy, +or decide build order. + +| Important file | Responsibility | +| --- | --- | +| `compiler_profiles.py` | Defines coherent GNU, Intel, LLVM, NVIDIA, and PGI language profiles, attaches the active Python/NumPy build settings, and maps a selected Fortran driver family to its matching C driver family. | +| `objects.py` | Defines the immutable `ObjectFile` input for one source-to-object invocation. The pipeline—not this value—owns dependency order and concurrency. | +| `compilers.py` | Selects configured executables, builds compile/link argv, records commands, runs subprocesses when enabled, and reports concise native failures. Its record-only mode exposes commands without compiling. | +| `native_support.py` | Installs the bundled header-only binding support and a NumPy API-version header into a generated-wrapper directory when the rendered wrapper requests it. | + +The upstream owner is `prik/pipeline/build.py`, which creates `ObjectFile` +records and decides dependency-ready batches. The downstream boundary is the +host compiler and linker process. `prik/preprocessing/` and its probes reuse +the same selected compiler identity and flags at earlier stages, but they own +their own source-expansion and measurement operations. + +### `compiler_profiles.py`: coherent compiler families + +The profile resolver normalizes a selected executable and supplies matching +language drivers and family-specific switches: + +```bash +python3 prik/compiler/compiler_profiles.py +``` + +```text +Selected family: gfortran +Compiler profile: GNU +Matching C executable: gcc +Fortran module-output flag: -J +``` + +This demonstrates why the pipeline selects a profile rather than independently +guessing C and Fortran flags: one family decision yields coherent drivers and +switches. + +### `objects.py`: one explicit compile input + +`ObjectFile` is the immutable request passed to a compiler invocation: + +```bash +python3 prik/compiler/objects.py +``` + +```text +Compile input: generated/bridge.f90 -> build/bridge.o +Language: fortran +Flags: ('-O2',) +Include directories: build/modules +``` + +The record contains everything needed for one source-to-object command. It +does not decide when that command is dependency-ready; ordering belongs to the +build pipeline. + +### `compilers.py`: command construction and execution + +The direct example uses record-only mode, so it exercises the real command +builder without compiling a file: + +```bash +python3 prik/compiler/compilers.py +``` + +```text +Compiler profile: GNU +Compile input: demo.c -> demo.o +Recorded without execution: True +Contains compile switch: True +Contains requested flag: True +Commands recorded: 1 +``` + +The output distinguishes compiler mechanics from orchestration: the caller +provided the source, object, and flag; this module converted them into one +recorded native command. + +### `native_support.py`: bundled support installation + +Generated C sources include bundled headers. This example installs the real +payload into a temporary wrapper directory: + +```bash +python3 prik/compiler/native_support.py +``` + +```text +Installed directory: binding_support +Binding header present: True +NumPy version header present: True +``` + +It shows the precise responsibility of this file: materialize requested native +support. Whether a wrapper requests that support was already decided by +generation. + +Primary evidence: + +- `tests/fortran/building_shared_library/compiling/test_compiler_verbose.py` +- `tests/fortran/error_handling/compiling/test_verbose_commands.py` +- `tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py` +- `tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py` +- `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` + +Start compiler-profile or argv changes in this package. Start ordering, +parallelism, artifact naming, or build-manifest changes in the pipeline. If a +requested change depends on ownership, dtype, projection, or Python API shape, +it belongs upstream of both packages rather than in a compiler flag branch. + +## Preprocessing And Target Probes + +`prik/preprocessing/` owns everything required to turn original Fortran source +into authoritative parser input and compiler-dependent target facts. It runs +before declaration parsing, but it is not one generic text cleanup pass: +compiler expansion, native Fortran includes, and executable datatype probes +deliberately remain separate mechanisms with separate results. + + + +| Important file | Responsibility | +| --- | --- | +| `source.py` | Configures and invokes compiler preprocessing, collects expanded source, line-marker provenance, dependency edges, macro metadata, diagnostics, and replayable recipes. It coordinates the Fortran include pass after compiler CPP. | + +| `fortran.py` | Recursively expands native Fortran `INCLUDE` statements left after compiler preprocessing while preserving dependency edges and generated-to-original source mappings. | + +| `probes/fortran_types.py` | Compiles and runs target programs for Fortran kind expressions, storage widths, logical representations, and compile-time values required by semantic conversion. | + +The source preprocessors output text and provenance consumed by the Fortran +parser. The probes output immutable reports consumed by Fortran semantic +conversion. Compiler identity, target flags, include paths, macros, working +directory, and optional cross-target runner are part of the probe recipe/cache +identity; measured facts must not silently cross targets. This package never +decides semantic scalar names, NumPy dtypes, ownership, or wrapper support. + + + +### `source.py`: compiler preprocessing with provenance + +This is the coordinating preprocessing entrypoint. Its example expands a +native Fortran include while retaining source provenance: + +```bash +python3 prik/preprocessing/source.py +``` + +```text +Before Fortran include expansion: +module greeting +include 'constants.inc' +... +After Fortran include expansion: +module greeting +integer, parameter :: answer = 42 +... +Native includes: 1; diagnostics: 0 +``` + + + +The changed source and the dependency/diagnostic counts show that the result is +parser input plus provenance, not just cleaned text. + + + +### `fortran.py`: native `INCLUDE` expansion + +Fortran `INCLUDE` remains distinct from compiler macro preprocessing: + +```bash +python3 prik/preprocessing/fortran.py +``` + +```text +Expanded parser input: +module geometry +integer, parameter :: dimensions = 3 +end module geometry +Native include dependencies: 1 +Generated source mappings: 5 +Diagnostics: 0 +``` + +The expanded declaration is accompanied by dependency and line-mapping facts, +which lets later parser diagnostics still identify original sources. + + + +### `probes/fortran_types.py`: measured Fortran target facts + +The Fortran probe resolves compiler-dependent kind expressions and storage +facts: + +```bash +python3 prik/preprocessing/probes/fortran_types.py +``` + +```text +selected_int_kind(9) = 4 +``` + +The result is a native kind value consumed by semantic datatype resolution. +It is not yet the stable semantic scalar name or NumPy dtype. The example +requires `gfortran` or `f95`. + +Primary evidence: + + +- `tests/fortran/source_preprocessing/preprocessing/` +- `tests/fortran/data_types/probes/test_fortran_type_probes.py` +- `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` + +Start source-expansion and provenance changes in `source.py`, native include +behavior in `fortran.py`, and target measurement or cache changes in the +Fortran probe. Parser grammar changes start downstream, while stable semantic +datatype vocabulary and NumPy lowering start in `semantics/scalar_types.py` +and `codegen/primitive_scalar_types.py`. + + + +## Parser Frontends + +`prik/parsers/` contains the active Fortran source frontend and the semantic +`.pyi` syntax frontend. Fortran owns its lexical rules, source models, +diagnostics, and project assembly; semantic `.pyi` deliberately reuses +Python's AST. A parser reports what source says. It must not choose ownership, +wrapper support, NumPy lowering, or generated API behavior. + + + +| Folder | Responsibility | +| --- | --- | + +| `parsers/fortran/` | Turns prepared fixed- or free-form Fortran into file/project models while preserving source locations, declarations, visibility, and unit structure. | +| `parsers/pyi/` | Reads semantic `.pyi` syntax into a standard Python `ast.Module`; interpretation belongs to `semantics/pyi2ir.py`. | + +In the Fortran package, `models.py` owns passive parser dataclasses and +diagnostic types. That file has no direct example because constructing an +isolated dataclass would hide the parser boundary; the parser examples below +produce the real models. `utils.py` contains subordinate source-form and +delimiter-aware helpers exercised by the lexer, type resolver, and parser. +Package initializers are export manifests, while `__main__.py` files are guarded +`python3 -m ...` launchers. + + + +### `parsers/fortran/lexer.py`: logical Fortran lines + +The Fortran lexer detects source form, strips comments, folds continuations, +and retains the original starting line for diagnostics: + +```bash +python3 prik/parsers/fortran/lexer.py +``` + +```text +Detected source form: free +line 1: subroutine shift(value,offset) +line 3: real, intent(inout) :: value +line 4: real, intent(in) :: offset +line 5: end subroutine shift +``` + +The first two physical lines become one logical parser record attributed to +line 1. That location contract is the lexer's output to the grammar parser. + +### `parsers/fortran/parser.py`: Fortran source models + +This is the main Fortran frontend and project assembler: + +```bash +python3 prik/parsers/fortran/parser.py +``` + +```text +Module: metrics +Parameter: n = 4 +Procedure: scale(values: real[1]) +``` + +The example shows three parser facts used downstream: source-unit ownership, a +compile-time parameter expression, and an argument's intrinsic spelling and +rank. It does not map `real` to a target kind or NumPy dtype by itself. + +### `parsers/fortran/type_resolver.py`: type-spec syntax + +The type resolver extracts kind and character metadata without evaluating +compiler-dependent expressions: + +```bash +python3 prik/parsers/fortran/type_resolver.py +``` + +```text +integer(4) -> 4 +real(kind=selected_real_kind(15, 307)) -> selected_real_kind(15, 307) +character(len=16, kind=c_char) -> len=16, kind=c_char +``` + +Preserving `selected_real_kind(...)` as syntax is intentional: target-probe +facts and semantic conversion decide its meaning later. + +### `parsers/fortran/cli.py`: Fortran parser reports + +The no-argument example renders a real in-memory parse through the stable +report formatter: + +```bash +python3 prik/parsers/fortran/cli.py +``` + +```text +File: geometry.f90 + Modules: 1 + - module geometry (vars=0, uses=0) + Procedures: 1 + - function norm(value:real[0]) -> real[0] +``` + +The report exposes parser structure and parser datatypes. Command-line paths +and options reuse the same formatter and may request JSON or downstream +semantic reports. + +### `parsers/pyi/parser.py`: syntax-only contract parsing + +This frontend intentionally stops at Python AST: + +```bash +python3 prik/parsers/pyi/parser.py +``` + +```text +Parsed AST: Module +Function node: scale +Argument annotation: Float64 +Semantic conversion performed: False +``` + +The last line makes the ownership boundary explicit. Recognizing `Float64` as +a PRIK semantic type belongs to `semantics/pyi2ir.py`, not this parser. + +Primary evidence: + + +- `tests/fortran/source_parsing/parsing/` +- `tests/fortran/command_line_interface/pipeline/` +- `tests/fortran/semantic_pyi_format/parsing/` + +Start lexical/source-coordinate changes in the Fortran `lexer.py`; grammar and +source-model construction in `parser.py`; cross-file identity in the Fortran +project parser; report output in `cli.py`; and semantic meaning downstream in +the corresponding converter. Parser support never by itself establishes +wrapper support. + + + +## Semantic IR + +`prik/semantics/` is the language-neutral contract layer. It receives Fortran +parser models or a parsed semantic `.pyi` AST and produces the same +`SemanticModule` graph. The graph preserves public names, native names, source +provenance, storage shape, projections, and raw contract metadata. It must not +complete ownership, select lowering actions, or render backend text; those +responsibilities belong to policy, planning, and codegen respectively. + + + +| Important file | Responsibility | +| --- | --- | +| `models.py` | Defines semantic modules, functions, classes, variables, types, storage/array contracts, projections, origins, and structural equality. | +| `scalar_types.py` | Defines stable scalar identities and intrinsic family/storage facts without NumPy or generated-language spellings. | + +| `fortran2ir.py` | Converts Fortran parser models and measured kind/compile-time facts into semantic modules. | +| `pyi2ir.py` | Interprets parsed Python AST as an editable semantic contract and reconciles imported semantic references. | +| `ownership_metadata.py` | Normalizes raw ownership and pointer requests recorded during IR construction; it does not resolve them. | +| `native_array_handles.py` | Marks allocatable/pointer descriptor handles and derives their ordinary array and element facets. | +| `native_contract.py` | Prepares and validates source-free `.pyi` native placement, projections, concrete types, and callback reconstruction. | + +`metadata.py` and `pyi_metadata.py` are intentionally passive registries for +shared keys. They have no direct example because their values become meaningful +only on the models demonstrated below. `semantics/__init__.py` is an export +manifest. Combined file loading and cross-file `.pyi` reconciliation are +pipeline orchestration and are documented with `pipeline/pyi.py` later. + +### `semantics/models.py`: the language-neutral graph + +The model example constructs the same values a frontend converter returns: + +```bash +python3 prik/semantics/models.py +``` + +```text +Semantic module: geometry +Function: scale -> native SCALE +Argument: values: Float64, rank=1, shape=('n',), order=F +Source provenance: fortran real +``` + +The public/native name distinction, shape/order contract, and source +provenance survive together. None of these values says how the generated +binding transfers or owns the argument. + +### `semantics/scalar_types.py`: stable scalar vocabulary + +This catalogue separates intrinsic semantic facts from target- or +backend-dependent representations: + +```bash +python3 prik/semantics/scalar_types.py +``` + +```text +Float64: family=real, storage=64 bits +Int: family=signed_integer, storage=target-dependent +Backend spelling stored here: False +``` + +`Float64` fixes a semantic width, while `Int` needs target-probe information. +Neither entry owns a NumPy dtype, C spelling, or Fortran bridge spelling; those +maps live at their respective runtime and code-generation boundaries. + + + +### `semantics/fortran2ir.py`: Fortran facts to semantic IR + +The Fortran converter normalizes measured kind and source storage information: + +```bash +python3 prik/semantics/fortran2ir.py +``` + +```text +math.scale(value): Float64 via reference storage +``` + +Here the source `real` declaration has become stable `Float64`, while reference +storage remains an explicit semantic fact. Ownership and Python/native barrier +actions are still undecided. + +### `semantics/pyi2ir.py`: editable contract to semantic IR + +This converter gives semantic meaning to the AST produced by +`parsers/pyi/parser.py`: + +```bash +python3 prik/semantics/pyi2ir.py +``` + +```text +math.scale(value): Float64 -> Float64 +``` + +Unlike the syntax-only parser example, this result contains a semantic module, +function, argument type, and result type. Contract validation happens here; +post-IR policy completion remains downstream. + +### `semantics/ownership_metadata.py`: unresolved ownership requests + +Frontends use these setters to normalize user/source claims before complete +signatures and relationships are available: + +```bash +python3 prik/semantics/ownership_metadata.py +``` + +```text +Raw ownership request: owner=caller, transfer=in_place, destruction=caller +Pointer contract: nullable=True, lifetime=owner, reassociation=forbidden +Completed lowering action present: False +``` + +The final line is the boundary: normalized metadata is input to policy +completion, not permission for a generator to infer a transfer or codegen +action. + +### `semantics/native_array_handles.py`: descriptor and data facets + +A native descriptor handle is semantically different from the array data it +currently addresses: + +```bash +python3 prik/semantics/native_array_handles.py +``` + +```text +Descriptor kind: allocatable +Data facet: Float64, rank=2, shape=('rows', 'columns') +Element facet: Float64, rank=0 +Handle marker retained by data facet: False +``` + +The derived data facet deliberately drops handle-only ownership and descriptor +metadata. Policy can therefore reason separately about the native container, +the exposed array view, and one element type. + +### `semantics/native_contract.py`: source-free native validation + +Semantic `.pyi` can describe a native artifact without available source, but +the contract must still reconstruct placement and ABI-relevant type facts: + +```bash +python3 prik/semantics/native_contract.py +``` + +```text +Prepared origin: fortran module math +Valid contract issues: 0 +Invalid contract issue: pyi_native_type_missing at math.broken.value +``` + +The validator prepares native origin information and reports a stable issue at +the exact semantic owner when a concrete dtype is missing. It validates the +contract; it does not compile or load the artifact. + +Primary evidence: + +- `tests/fortran/semantic_ir/semantics/` + +- `tests/fortran/semantic_pyi_format/` +- `tests/fortran/data_types/semantics/` +- `tests/fortran/native_array_handles/semantics/` +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` + +Start model-shape changes in `models.py`, stable primitive vocabulary in +`scalar_types.py`, Fortran or semantic `.pyi` conversion in its corresponding +`*2ir.py` file, and raw contract normalization in its focused helper. If the +question is which transfer, lifetime, setter, projection, or lowering action is +valid, the change starts in `prik/policy/`, not here. + + + +## Post-IR Policy + +`prik/policy/` is the last semantic authority before planning. It receives the +complete semantic module graph and resolves every choice needed by wrapper +generation: public exports, object kind, owner, transfer, destruction, +mutability/writeback, nullability, storage, projections, lifecycle actions, +descriptor operations, accessor behavior, and support blockers. A lower stage +may dispatch from these records but may not replace or infer them. + +| Important file | Responsibility | +| --- | --- | +| `models.py` | Defines immutable backend-neutral records for completed function, argument, result, call-slot, lifecycle, class, callback, array, and module-variable policy. | +| `ownership.py` | Resolves object kind and the ownership/transfer/destruction triple into storage and strict Python/native/codegen actions. | +| `exports.py` | Completes collision-checked Python namespace and local-name policy. | +| `construction.py` | Constructs coherent wrapper-facing policy records from already completed semantic and ownership facts. | +| `completion.py` | Runs policy completion in dependency order and attaches every completed record to semantic IR. | +| `native_array_handles.py` | Defines descriptor-handle/array ABI selectors, strict dispatch records, and build requirements selected by completed handle policy. | + +The package initializer exports only the public completion entrypoint. Policy +models are separate from construction rules so planning and codegen can depend +on completed vocabulary without depending on the rule implementation. + +### `policy/models.py`: immutable completed decisions + +The models example creates representative array and lifecycle records: + +```bash +python3 prik/policy/models.py +``` + +```text +Array policy: rank=2, shape=('rows', 'columns'), order=F +Lifecycle policy: copy_out writeback via copy_in_out +Completed record mutation rejected: True +``` + +Unlike raw semantic metadata, these records state the selected phase, +operation, and codegen action. Their immutability makes policy a reliable input +to planning and both backend generators. + +### `policy/ownership.py`: lifetime and barrier resolution + +The ownership resolver turns one semantic argument and its use context into a +complete decision: + +```bash +python3 prik/policy/ownership.py +``` + +```text +before: math.scale(value): Float64 semantic IR +after: scalar/caller/call_local; scalar_value -> pass_value +``` + +The result names the object kind, owner, transfer, Python extraction action, +and native handoff action. Binding and bridge code must consume those values; +they cannot rediscover them from `Float64` or argument intent. + +### `policy/exports.py`: completed Python placement + +Export policy resolves namespace placement and collision-safe local names: + +```bash +python3 prik/policy/exports.py +``` + +```text +Native semantic owner: math.SCALE_VALUE +Python export: linear_algebra.scale_value +Completed policy type: PythonExportPolicy +``` + +The native identity remains unchanged while the Python-facing path becomes an +explicit immutable policy value consumed downstream. + +### `policy/construction.py`: coherent wrapper policy + +Construction combines completed ownership with ABI, result, and native-call +slot rules: + +```bash +python3 prik/policy/construction.py +``` + +```text +before: math.scale(value): Float64 semantic IR +after: direct_transfer; result=native_scalar; native=pass_value +``` + +The output relates three sides of one call: bridge data movement, direct-result +ABI, and native-slot handoff. Construction does not generate a wrapper plan or +render code. + +### `policy/completion.py`: the mandatory ordered boundary + +Normal callers use this entrypoint rather than invoking individual rules: + +```bash +python3 prik/policy/completion.py +``` + +```text +before: math.scale(value): Float64 semantic IR +after: math.scale(value): scalar_value -> pass_value +``` + +Completion first resolves exports and dependent graph facts, then ownership, +accessor, feature, and wrapper policies. The attached `scalar_value -> +pass_value` actions make the semantic graph eligible for planning. Unsupported +contracts retain explicit blockers and fail before codegen. + +### `policy/native_array_handles.py`: descriptor ABI and build policy + +This example starts from an already completed pointer-handle policy: + +```bash +python3 prik/policy/native_array_handles.py +``` + +```text +Handle policy: pointer/pointer, storage=alias +Allowed operations: to_numpy, nullify +Array ABI: descriptor +Selected build header: ISO_Fortran_binding.h +``` + +The descriptor ABI, permitted operations, storage mode, and header requirement +are selected policy outputs. Planning and compilation consume them; neither +stage scans semantic datatypes to decide that the header is needed. + +Primary evidence: + +- `tests/fortran/infrastructure/semantics/` +- feature-local `tests/fortran/*/policy/` directories +- `tests/fortran/native_array_handles/policy/` +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` + +Start a new semantic decision in `completion.py` and its focused constructor or +resolver. Put reusable immutable output vocabulary in `models.py`. Extend +strict descriptor dispatch/build selection in `native_array_handles.py`. If a +change only projects an existing decision into implementation fields, it +belongs downstream in planning; if a generator currently guesses the decision, +remove the guess and complete the policy here first. + +## Wrapper Planning + +`prik/planning/` mechanically projects policy-completed semantic IR into one +backend-neutral `ModulePlan`. The plan joins shared transfer facts with +binding-specific and bridge-specific views, namespace placement, stable native +symbols, lifecycle ordering, and required headers. Planning may organize and +validate completed decisions; it may not reinterpret source declarations, +select ownership, or render output text. + +| Important file | Responsibility | +| --- | --- | +| `models.py` | Defines the editable typed plan tree rooted at `ModulePlan`, including namespace, function, argument/result, native-slot, lifecycle, descriptor, callback, derived-object, binding, and bridge views. | +| `planner.py` | Reads completed policy records, validates their presence/support, assigns shared roles and stable symbols, and constructs the plan tree in deterministic order. | + +The package initializer exports the plan types and `WrapperPlanner`; it contains +no separate behavior to demonstrate. Codegen receives only the completed plan, +and `WrapperGenerator` validates and freezes that tree before invoking either +backend. + +### `planning/models.py`: the typed plan representation + +The model example constructs the smallest coherent procedure plan directly: + +```bash +python3 prik/planning/models.py +``` + +```text +Plan owner: demo +Python export: ping +Native procedure: PING +Native slots: 0 +``` + +The same function has an explicit Python binding view and native bridge view. +The plan carries no native slots because the example subroutine has no +arguments or results. Constructing records here demonstrates representation, +not a shortcut around normal policy completion. + +### `planning/planner.py`: completed policy to plan + +The planner example follows the real boundary: construct semantic IR, complete +policy, then build the plan: + +```bash +python3 prik/planning/planner.py +``` + +```text +Plan owner: planner_demo +Python export: double_value +Native target: DOUBLE_VALUE +Conversion order: ('planner_demo.double_value.value',) +``` + +The final role is the stable shared identity used to order binding conversion +and connect the matching native-call slot. The planner copied selected actions +from completed policy; it did not decide them from `Float64`. + +Primary evidence: + +- `tests/fortran/infrastructure/codegen/test_plan.py` +- `tests/fortran/infrastructure/codegen/test_planner.py` +- feature-local `tests/fortran/*/codegen/` plan assertions +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` + +Start plan-shape changes in `models.py` and projection/indexing changes in +`planner.py`. A new field is justified when codegen needs an already completed +fact in typed form. If deciding the field requires reasoning about ownership, +intent, mutability, projections, or support, complete that decision in policy +first. Rendering, emitted temporaries, and source syntax belong downstream. + +## Backend Node Generation + +`prik/codegen/` consumes only a validated `ModulePlan` and produces typed C and +Fortran syntax nodes plus planned Python-facade source. It owns emitted-code +mechanisms—temporary declarations, conversion calls, native bridge bodies, +module initialization, and class-facade assembly—but it must not infer semantic +policy. Language printers serialize the resulting nodes in the next stage. + +| Folder or important file | Responsibility | +| --- | --- | +| `nodes.py` | Defines the typed C/Fortran syntax trees shared by emitters and printers. | +| `primitive_scalar_types.py` | Maps already resolved semantic scalar identities to C, Fortran, NumPy, CFI, and CPython conversion spellings. | +| `docstrings.py` | Renders Python-facing function/class documentation from completed plan facts. | +| `c/binding.py` | Lowers binding plan views into CPython/NumPy C nodes, headers, initialization, validation, and native-call wrappers. | +| `c/python_surface.py` | Emits the thin executable Python class/holder/module-proxy facade selected by completed class plans. | +| `fortran/bridge.py` | Lowers bridge plan views into `bind(C)` modules, procedures, holders, descriptors, accessors, and native calls. | + +Supporting files are deliberately smaller. `overloads.py` answers shared +structural questions over completed overload plans; `visitor.py` supplies +strict class-name dispatch; `c/naming.py` centralizes binding-local generated +names; and `checks.py` implements the static codegen ownership/complexity gate +invoked by `tools/check_codegen_complexity.py`. Package initializers are export +manifests. These helpers are demonstrated through their owners or maintainer +command rather than receiving artificial standalone examples. + +### `codegen/nodes.py`: typed syntax before printing + +The node example constructs one C tree and one Fortran tree without rendering +either language: + +```bash +python3 prik/codegen/nodes.py +``` + +```text +C node tree: CModule -> wrap_ping -> CReturn +Fortran node tree: FortranModule -> bind_c_ping -> FortranCall +Source text rendered: False +``` + +This is the boundary between generation and printing. Emitters choose typed +statements from the plan; printers later decide whitespace, punctuation, and +source layout. + +### `codegen/primitive_scalar_types.py`: boundary spellings + +Once semantic conversion has resolved `Float64`, codegen can look up every +required backend representation explicitly: + +```bash +python3 prik/codegen/primitive_scalar_types.py +``` + +```text +Float64: C=double; Fortran=real(c_double); NumPy=numpy.float64 +NumPy C macro: NPY_FLOAT64 +Fresh editable node per lookup: True +``` + +The readable mapping makes the datatype boundary auditable. A lookup returns a +fresh node so one generator cannot mutate global catalogue state. Unknown +semantic identities fail rather than coercing to a nearby dtype. + +### `codegen/docstrings.py`: plan-driven public documentation + +Docstrings are rendered after planning so their signatures, types, results, +and errors match the generated API: + +```bash +python3 prik/codegen/docstrings.py +``` + +```text +double_value(value) -> float64 + +Parameters +---------- +value : float64 + +Returns +------- +result : float64 + +Raises +------ +TypeError + If an argument has an incompatible Python type or dtype. +``` + +The planner no longer depends on `WrapperDocstringBuilder`; codegen reads the +completed plan and renders presentation text without changing the plan. + +### `codegen/c/python_surface.py`: generated Python facade + +Derived classes are planned surfaces rendered as Python source embedded in the +extension module: + +```bash +python3 prik/codegen/c/python_surface.py +``` + +```text +Rendered Python facade: +_prik_unset = object() + +_prik_ops_state = {} +class State: + 'Opaque native state.' + __slots__ = ('_prik_capsule', '_prik_owner', '_prik_ops', '_prik_origin') + def __new__(cls, *args, **kwargs): + 'Construction is disabled.' + raise TypeError('State objects come from native code.') +def _prik_wrap_State(capsule, owner=None, ops=None, origin='direct'): + ... +``` + +The absent constructor, slots, operation map, and wrapper helper all come from +the class plan. The emitter does not inspect a Fortran derived type to decide +whether construction is allowed. + +### `codegen/c/binding.py`: CPython/NumPy node lowering + +The C binding example completes and plans a scalar function, then shows the +generated node mechanism: + +```bash +python3 prik/codegen/c/binding.py +``` + +```text +Native procedure: DOUBLE_VALUE +Native call slots: implicit:value +C module: binding_demo_wrapper +Header guard: BINDING_DEMO_WRAPPER_H +Header prototypes: wrap_double_value +Binding wrapper: wrap_double_value +Return type: PyObject * +Parameters: + self: PyObject * + args: PyObject * + kwargs: PyObject * +Body nodes: + CDeclaration(...) + ... + CReturn(expression=CodeExpression(text='result_obj')) +``` + +This module produces structured C, not final source text. Its specialized +methods dispatch from planned barrier, ownership, result, descriptor, and +lifecycle actions into concrete node sequences. + +### `codegen/fortran/bridge.py`: `bind(C)` node lowering + +The matching bridge consumes the same shared call plan: + +```bash +python3 prik/codegen/fortran/bridge.py +``` + +```text +Native procedure: DOUBLE_VALUE +Native call slots: implicit:value +Bridge module: bind_c_bridge_demo_wrapper +Module uses: + use iso_c_binding, only: ... c_double ... + use bridge_demo, only: native_double_value => DOUBLE_VALUE +Bridge procedure: bind_c_double_value +Binding name: bind_c_double_value +Procedure kind: function +Result: result :: real(c_double) +Parameters: + value: real(c_double), value +Body nodes: + FortranAssignment(target='result', expression=CodeExpression(text='native_double_value(value)')) +``` + +The shared slot becomes a value dummy and native function call. The bridge +selected no ownership behavior locally; it implemented the native barrier and +result ABI already present in the plan. + +Primary evidence: + +- `tests/fortran/infrastructure/codegen/` +- feature-local `tests/fortran/*/codegen/` +- generated-node and golden fixtures below those owners +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` +- `python3 tools/check_codegen_complexity.py` + +Start a new emitted-code mechanism in the narrow binding, bridge, or facade +emitter that owns it, and add a typed node only when existing nodes cannot +represent it. Extend the primitive catalogue only for an already established +semantic scalar identity. If implementing the mechanism requires choosing +ownership, storage, projection, setter exposure, or support, stop and add the +missing policy/plan fact upstream first. + +## Language Printers + +`prik/printers/` is the final representation-to-text boundary. C and Fortran +printers serialize backend syntax nodes; the semantic `.pyi` printer serializes +semantic IR into an editable contract. Printers own formatting, escaping, +indentation, declaration order, and safe line wrapping. They do not invoke +generators, choose filenames, complete policy, or compile their output. + +| Important file | Responsibility | +| --- | --- | +| `c.py` | Serializes C translation units, headers, declarations, functions, CPython tables, and statements. | +| `fortran.py` | Serializes bridge modules, interfaces, procedures, declarations, and statements while safely wrapping free-form lines. | +| `pyi.py` | Serializes semantic modules and their contract/projection metadata as compact editable semantic `.pyi`. | + +The package initializer only exports the three printer classes and the +`emit_module` convenience function. C and Fortran source orchestration belongs +to `pipeline/wrapper.py`; `.pyi` loading belongs to `pipeline/pyi.py`. + +### `printers/c.py`: C nodes to source + +The C printer receives a formed module tree and freezes it before rendering: + +```bash +python3 prik/printers/c.py +``` + +```text +Rendered C binding source: +#include + +static PyObject * wrap_ping(PyObject * self) { + Py_INCREF(Py_None); + return Py_None; +} +``` + +Everything in the output was already represented by nodes: include, storage +class, signature, expression statement, and return. The printer supplied only +valid C layout and punctuation. + +### `printers/fortran.py`: Fortran nodes to source + +The Fortran printer renders the matching bridge representation: + +```bash +python3 prik/printers/fortran.py +``` + +```text +Rendered Fortran bridge source: +module bind_c_printer_demo_wrapper + use iso_c_binding, only: c_double + use printer_demo, only: native_double_value => DOUBLE_VALUE + implicit none +contains + function bind_c_double_value(value) result(result) bind(c, name="DOUBLE_VALUE") + real(c_double), value :: value + real(c_double) :: result + result = native_double_value(value) + end function bind_c_double_value +end module bind_c_printer_demo_wrapper +``` + +The printer supplies free-form indentation and line-length enforcement. The +module imports, native alias, binding name, dummy attributes, and assignment +were selected by bridge generation. + +### `printers/pyi.py`: semantic IR to editable contract + +The `.pyi` printer works from semantic IR rather than wrapper syntax nodes: + +```bash +python3 prik/printers/pyi.py +``` + +```text +Semantic module: printer_demo +from prik.contracts import Float64, bind + +@bind("DOUBLE_VALUE") +def double_value( + value: Float64 +) -> Float64: ... +``` + +It derives required contract imports and preserves the native binding name in +editable Python syntax. Printing does not complete or attach wrapper policy; +the emitted contract can be edited and loaded through the `.pyi` pipeline. + +Primary evidence: + +- `tests/fortran/infrastructure/printers/` +- semantic `.pyi` round-trip tests in `tests/fortran/semantic_pyi_format/` +- generated-source goldens in feature-local `printers/` and `codegen/` owners +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` + +Start formatting or node-serialization changes in the language printer that +owns that representation. If required information is absent from a node, add +it to generation or the plan rather than consulting semantic IR from a native +source printer. Filename, multi-source ordering, and returned artifact changes +belong to the wrapper pipeline. + +## Workflow Pipeline + +`prik/pipeline/` composes complete workflows across established stage +boundaries. It may decide which stage runs next, preserve progress/timing, +assign artifact filenames, write generated payloads, and coordinate compilation +and linking. It does not absorb parser grammars, semantic rules, policy +decisions, backend lowering, printer formatting, or compiler command mechanics. + +| Important file | Responsibility | +| --- | --- | +| `pyi.py` | Loads semantic `.pyi` text/files/path sets, caches conversion, reconciles external types, supplies opaque dependency modules, completes copies, and emits stub packages. | +| `type_mapping_report.py` | Runs target probes through semantic conversion and codegen dtype projection to produce an auditable target-specific Markdown report. | +| `wrapper.py` | Freezes and validates one plan, renders docstrings, invokes both node generators and printers, assigns stable names, and returns `GeneratedWrapper`. | +| `build.py` | Owns public source/`.pyi` build APIs, generated-file writing, native input plans, dependency-ready compilation, linking, manifests, and `WrapperBuildResult`. | + +The package initializer describes the high-level namespace but intentionally +does not flatten all of these substantial workflows. Source preprocessing and +target measurement remain in `preprocessing`; reusable command execution +remains in `compiler`. + +### `pipeline/pyi.py`: combined contract loading + +This example crosses the intentionally separate parser, converter, policy, and +printer stages through one loader workflow: + +```bash +python3 prik/pipeline/pyi.py +``` + +```text +Loaded semantic module: math +Loaded contract marker: True +Functions: scale +Re-emitted module: +from prik.contracts import Float64 + +def scale( + value: Float64 +) -> Float64: ... +``` + +The loaded module retains workflow metadata. Stub emission deep-copies it, +completes policy on the copy, and uses the semantic printer, so the caller's +original editable semantic graph is not repurposed as a wrapper plan. + +### `pipeline/type_mapping_report.py`: end-to-end datatype explanation + +The report pipeline connects a measured native type to semantic and NumPy +representations: + +```bash +python3 prik/pipeline/type_mapping_report.py +``` + +```text +| `int` | signed 32-bit | `Int (Int32 storage)` | `numpy.int32` | +``` + +The exact width is target-dependent. The four columns make the stage changes +explicit: native spelling, probed target fact, stable semantic identity with +resolved storage, and codegen NumPy expression. This example requires `cc`. + +### `pipeline/wrapper.py`: plan to rendered artifact + +`WrapperGenerator` is the single owner of the plan-to-text workflow: + +```bash +python3 prik/pipeline/wrapper.py +``` + +```text +Extension initializer: PyInit_generator_demo +Rendered sources: bind_c_generator_demo_wrapper.f90, generator_demo_wrapper.c, generator_demo_wrapper.h +Native support: binding_support +``` + +The result is a `GeneratedWrapper` containing source payloads, stable paths, +compile-source grouping, required headers/support, and initializer identity. +Nothing has been written or compiled yet. + +### `pipeline/build.py`: source to imported extension + +The build example uses the public API to create and call a small extension: + +```bash +python3 prik/pipeline/build.py +``` + +```text +scale(3.0, 2.5) = 7.5 +``` + +Behind this concise result, the workflow preprocesses and parses source, +measures required target facts, constructs semantic IR, completes policy, +plans and renders the wrapper, writes temporary generated/native sources, +compiles dependency-ready objects, links an extension, imports it through +`WrapperBuildResult`, and calls the generated Python API. It requires the +configured C and Fortran compilers. The central example test retains the +`fortran_end_to_end` marker for this reason. + +Primary evidence: + +- `tests/fortran/semantic_pyi_format/pipeline/` +- `tests/fortran/data_types/pipeline/` +- `tests/fortran/infrastructure/pipeline/` +- `tests/fortran/building_shared_library/pipeline/` +- `tests/fortran/building_shared_library/compiling/` +- `tests/fortran/building_shared_library/end_to_end/` +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` + +Start `.pyi` batch/cache/dependency behavior in `pyi.py`, cross-stage datatype +reporting in `type_mapping_report.py`, plan validation/artifact assembly in +`wrapper.py`, and disk/compiler/link/manifest behavior in `build.py`. A pipeline +helper should delegate a domain rule to its owning stage instead of becoming a +second implementation of that rule. + +## Runtime and Bundled Native Support + +`prik/runtime/` contains the Python objects that remain active after a generated +extension has been imported. Its important entry file, `handles.py`, turns the +small operation dictionaries exported by generated bindings into stable +`AllocatableArray` and `PointerArray` APIs. It validates descriptor metadata, +retains required owners, adapts operation signatures, and exposes live NumPy +views according to completed policy. It does not decide ownership or invent +operations absent from the plan. + +`prik/runtime/native_support/` owns the header-only native runtime payload used +by generated bindings. Its Python initializer marks the payload as a package so +`compiler/native_support.py` can locate and install it. The generated build +still receives a `binding_support/` directory because that is the logical +include name emitted by the C binding backend; it is not the source package's +architectural location. These headers are native implementation assets, not an +independent Python workflow, so the folder intentionally has no `python3` +example. + +| Important file | Responsibility | +| --- | --- | +| `runtime/handles.py` | Adapts generated descriptor operations into validated allocatable/pointer handle objects and NumPy views. | +| `runtime/native_support/prik_binding.h` | Defines the header-only capsule, array-validation, release, and Python/NumPy conversion runtime used by generated C bindings. | +| `compiler/native_support.py` | Locates the runtime payload and installs it as generated `binding_support/`; its direct example was shown in Compiler Services. | + +### `runtime/handles.py`: generated operations to a stable handle + +The example provides the same kind of raw callable dictionary that a generated +extension installs: + +```bash +python3 prik/runtime/handles.py +``` + +```text +Runtime handle: AllocatableArray +Descriptor kind: allocatable +Initial view: [1.0, 2.0, 3.0] +Resized shape: (4,) +Generated resize received NumPy extents: True +``` + +The adapter selected `AllocatableArray` from the completed descriptor kind, +validated the declared dtype and rank, and converted `resize(4)` into the +generated operation's scalar `numpy.int64` extent convention. `to_numpy()` +returned the operation-provided live storage rather than a detached snapshot. +Consequently, callers must discard or copy outstanding views before native +deallocation, reallocation, or pointer reassociation; PRIK cannot revoke an +already exposed NumPy view. + +Primary evidence: + +- `tests/fortran/allocatables/runtime/` +- `tests/fortran/pointers/runtime/` +- `tests/fortran/memory_management/runtime/` +- `tests/fortran/infrastructure/runtime/` +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` + +Start handle protocol, validation, owner-retention, and operation-adapter +changes in `runtime/handles.py`. Start header discovery or installation changes +in `runtime/native_support/` and `compiler/native_support.py`, respectively. A +new ownership or view policy belongs in post-IR policy first; runtime should +only enforce the completed choice. + +## Shared Naming and Utilities + +`prik/naming/` owns names whose stability and collision rules are shared across +planning and code generation. `prik/utilities/` contains small mechanisms that +are genuinely independent of a compiler stage. Neither folder owns semantic +policy, syntax grammar, or workflow orchestration. + +| Important file | Responsibility | +| --- | --- | +| `naming/policy.py` | Normalizes public Python names, reserves namespace entries, and allocates language-safe generated names. | +| `naming/native_symbols.py` | Compacts long owner identities into deterministic, compiler-safe native symbol fragments. | +| `utilities/declaration_expressions.py` | Translates, validates, resolves, evaluates, and renders declaration extents across stage boundaries. | +| `utilities/strings.py` | Supplies minimal collision-safe generated-string helpers. | +| `utilities/visitor.py` | Supplies class-MRO dispatch shared by parsers, semantic converters, generators, and printers. | + +### `naming/policy.py`: public and target-language names + +```bash +python3 prik/naming/policy.py +``` + +```text +Normalized public name: render_value +Collision-safe public name: render_value_2 +C destructor symbol: state_drop +``` + +The first two lines show that Python-visible normalization and collision +reservation are namespace-aware. The final line shows a separate lowering rule: +a Python destructor is translated and prefixed into a valid C symbol. These are +naming decisions, not emitted C syntax. + +### `naming/native_symbols.py`: stable compact identities + +```bash +python3 prik/naming/native_symbols.py +``` + +```text +Owner identity: geometry.point.coordinates +Stable native symbol: point_coordinate_d_c2fc5940 +Within 27-character limit: True +``` + +The readable prefix assists generated-source inspection; the checksum retains +the full owner identity's contribution when the preferred spelling must be +shortened. Repeated calls with the same inputs return the same symbol. + +### `utilities/declaration_expressions.py`: one extent across stages + +```bash +python3 prik/utilities/declaration_expressions.py +``` + +```text +Fortran extent: ubound(source, 1) - lbound(source, 1) + 1 +Public expression: source.shape[0] +Role-bound expression: __prik_extent_source_0 +Fortran rendering: native_source_extent_0 +Compile-time product: 6 +``` + +This is deliberately a staged utility: a source expression becomes public +semantic syntax, then a validated role token, then backend text using a +plan-supplied substitution. Rendering does not rediscover which argument owns +the extent. The independent final line demonstrates compile-time integer +evaluation used while resolving declarations. + +### `utilities/strings.py`: collision-safe local identifiers + +```bash +python3 prik/utilities/strings.py +``` + +```text +First available name: temporary_4 +Next counter: 5 +``` + +The helper skips occupied candidates and returns both the selected name and the +next counter, allowing an emitter to allocate further local names without +rescanning from the beginning. + +### `utilities/visitor.py`: explicit model dispatch + +```bash +python3 prik/utilities/visitor.py +``` + +```text +Exact handler: literal:42 +MRO fallback: expression:Expression +``` + +The dispatcher first selects an exact `_` handler and then +walks the model class MRO for an intentional base-model fallback. Each consumer +still defines its own handlers; this utility does not merge the C, Fortran, +semantic `.pyi`, codegen, or printer visitor responsibilities. + +Primary evidence: + +- `tests/fortran/infrastructure/naming/` +- `tests/fortran/infrastructure/utilities/` +- `tests/fortran/arrays/semantics/test_declaration_expression_utilities.py` +- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` + +Start public normalization and language-rule changes in `naming/policy.py`, +stable owner-derived ABI fragments in `naming/native_symbols.py`, and only +stage-neutral mechanisms in `utilities/`. If a helper begins consulting a +completed policy or choosing a backend behavior, move that responsibility to +the owning policy, planning, or generation stage. + +## Contributor Documentation + +All project-maintenance material lives under `docs/developer/`, presented in +the site navigation as **Contributor Documentation**. There is no separate +maintainer audience or `docs/maintainer/` tree: contributors need the same +architecture, testing, release, internal-design, and roadmap information. + +| Area | Responsibility | +| --- | --- | +| `docs/user/` | Installation, usage, language support, tutorials, reference material, troubleshooting, and documented limitations for wrapper users. | +| `docs/developer/architecture.md` | Canonical package map, stage ownership, direct execution examples, change routes, and evidence owners. | +| `docs/developer/contributing/` | Contribution workflow, code of conduct, and security guidance. | +| `docs/developer/design/` | Detailed design constraints and rationale that supplement this package map. | +| `docs/developer/internal-architecture/` | Maintained implementation details for cross-stage internals. | +| `docs/developer/roadmap/` | Explicit planned or incomplete work, kept separate from implemented behavior. | +| `docs/developer/testing-strategy.md` | Test ownership, markers, focused-suite selection, and verification rules. | +| `docs/developer/development-workflow.md` | Local environment, edit, validation, and review workflow. | +| `docs/developer/release-process.md` | Maintainer release procedure within the shared contributor corpus. | + +The executable snippets in this guide are production-owned examples. Their +output contracts are grouped in +`tests/fortran/infrastructure/execution_examples/test_execution_examples.py`, with one +explicitly named test per demonstrated file. This keeps documentation tests +focused on links, structure, and publication while making code-output drift +fail beside the stage and feature suites. + +When adding another important entry file, give it a small real public-API flow +under `if __name__ == "__main__"`, document its command, representative output, +and architectural meaning here, then add +`test_fortran___execution_example` to the central inventory. Do +not add an example merely to enumerate every helper file. diff --git a/docs/developer/c-parser-reference.md b/docs/developer/c-parser-reference.md index d69efeb70..965f583ca 100644 --- a/docs/developer/c-parser-reference.md +++ b/docs/developer/c-parser-reference.md @@ -1192,7 +1192,7 @@ PRIK_C_DOCS_END --> The C parser documentation now lives in this top-level file: `docs/developer/c-parser-reference.md`. Shared semantic behavior is documented in [`semantic-ir.md`](../user/reference/semantic-ir.md), and wrapper-generation policy notes live in -[`wrapper-design-notes.md`](../maintainer/design/wrapper-design-notes.md). +[`wrapper-design-notes.md`](design/wrapper-design-notes.md). PRIK_C_DOCS_END --> Pages > Build and deployment > Source > GitHub Actions**. Then open **Actions > Documentation > Run workflow**, select `main`, and run it. Later documentation changes deploy automatically after they are merged or pushed to `main`; maintainers do not build or upload -`site/` themselves. +`.artifacts/site/` themselves. Before changing a page to `publication: reviewed`, preview the production view with `python3 -m mkdocs serve`. Use diff --git a/docs/maintainer/design/code-generation.md b/docs/developer/design/code-generation.md similarity index 100% rename from docs/maintainer/design/code-generation.md rename to docs/developer/design/code-generation.md diff --git a/docs/maintainer/design/cpython-integration.md b/docs/developer/design/cpython-integration.md similarity index 100% rename from docs/maintainer/design/cpython-integration.md rename to docs/developer/design/cpython-integration.md diff --git a/docs/maintainer/design/error-propagation-model.md b/docs/developer/design/error-propagation-model.md similarity index 100% rename from docs/maintainer/design/error-propagation-model.md rename to docs/developer/design/error-propagation-model.md diff --git a/docs/maintainer/design/index.md b/docs/developer/design/index.md similarity index 84% rename from docs/maintainer/design/index.md rename to docs/developer/design/index.md index 92bb8afd1..4ab6aff23 100644 --- a/docs/maintainer/design/index.md +++ b/docs/developer/design/index.md @@ -9,14 +9,13 @@ publication: draft # Design Documents -Design documents record long-term technical decisions for maintainers. They do -not by themselves establish native binding support. +Design documents record long-term technical decisions and proposals for all +contributors. They do not by themselves establish native binding support. ## Pages - [Wrapper design notes](wrapper-design-notes.md) - [Semantic multilanguage wrapper runtime architecture](semantic-multilanguage-wrapper-runtime-architecture.md) -- [Overall architecture](overall-architecture.md) - [Parser architecture](parser-architecture.md) - [Semantic analysis](semantic-analysis.md) - [Code generation](code-generation.md) diff --git a/docs/maintainer/design/memory-ownership-model.md b/docs/developer/design/memory-ownership-model.md similarity index 100% rename from docs/maintainer/design/memory-ownership-model.md rename to docs/developer/design/memory-ownership-model.md diff --git a/docs/maintainer/design/parser-architecture.md b/docs/developer/design/parser-architecture.md similarity index 100% rename from docs/maintainer/design/parser-architecture.md rename to docs/developer/design/parser-architecture.md diff --git a/docs/maintainer/design/runtime-model.md b/docs/developer/design/runtime-model.md similarity index 100% rename from docs/maintainer/design/runtime-model.md rename to docs/developer/design/runtime-model.md diff --git a/docs/maintainer/design/semantic-analysis.md b/docs/developer/design/semantic-analysis.md similarity index 100% rename from docs/maintainer/design/semantic-analysis.md rename to docs/developer/design/semantic-analysis.md diff --git a/docs/maintainer/design/semantic-multilanguage-wrapper-runtime-architecture.md b/docs/developer/design/semantic-multilanguage-wrapper-runtime-architecture.md similarity index 99% rename from docs/maintainer/design/semantic-multilanguage-wrapper-runtime-architecture.md rename to docs/developer/design/semantic-multilanguage-wrapper-runtime-architecture.md index fec87b398..be5363940 100644 --- a/docs/maintainer/design/semantic-multilanguage-wrapper-runtime-architecture.md +++ b/docs/developer/design/semantic-multilanguage-wrapper-runtime-architecture.md @@ -2,7 +2,7 @@ title: Semantic Multilanguage Wrapper and Interoperability Runtime audience: maintainers prerequisites: semantic IR reference, wrapper design notes -related: ../design/overall-architecture.md, ../internal-architecture/wrapper-generation-pipeline.md +related: ../architecture.md, ../internal-architecture/wrapper-generation-pipeline.md status: design publication: draft --- diff --git a/docs/maintainer/design/wrapper-design-notes.md b/docs/developer/design/wrapper-design-notes.md similarity index 99% rename from docs/maintainer/design/wrapper-design-notes.md rename to docs/developer/design/wrapper-design-notes.md index 85d137474..45f04c8fd 100644 --- a/docs/maintainer/design/wrapper-design-notes.md +++ b/docs/developer/design/wrapper-design-notes.md @@ -2,7 +2,7 @@ title: Wrapper Design Notes audience: maintainers prerequisites: Fortran wrapper reference, semantic IR reference -related: overall-architecture.md, ../internal-architecture/wrapper-generation-pipeline.md +related: ../architecture.md, ../internal-architecture/wrapper-generation-pipeline.md status: design publication: draft --- diff --git a/docs/developer/development-workflow.md b/docs/developer/development-workflow.md index 80f11a13d..53a84311c 100644 --- a/docs/developer/development-workflow.md +++ b/docs/developer/development-workflow.md @@ -215,7 +215,7 @@ implementation files. | Semantic policy completion | `prik/policy/completion.py`, `prik/policy/ownership.py` | `tests/fortran/infrastructure/semantics/` and feature-local `policy/` directories | | Fortran wrapper orchestration | `prik/pipeline/build.py` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py` | | Wrapper planning, owner-local errors, and direct lowering | `prik/planning/models.py`, `prik/planning/planner.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/codegen/`, feature-local `codegen/` stages | -| Native compilation and binding support | `prik/compiler/`, `prik/binding_support/` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | +| Native compilation and binding support | `prik/compiler/`, `prik/runtime/native_support/` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | | Executable Markdown examples | `README.md`, `docs/*.md` | `tests/docs/test_examples.py` | - [Adding a Fortran construct](adding-a-fortran-construct.md) - [Adding a code-generation backend](adding-a-code-generation-backend.md) - [Contributing](contributing/index.md) + +## Design And Internal Architecture + +- [Design documents](design/index.md) +- [Internal architecture](internal-architecture/index.md) +- [Pipeline map](internal-architecture/pipeline-map.md) +- [Datatype lifecycle](internal-architecture/type-system.md) +- [Ownership tracking](internal-architecture/ownership-tracking.md) + +## Project Operations And Planning + +- [Documentation architecture](documentation-architecture.md) +- [CI/CD](ci-cd.md) +- [Release process](release-process.md) +- [Roadmaps](roadmap/index.md) diff --git a/docs/maintainer/internal-architecture/ast-design.md b/docs/developer/internal-architecture/ast-design.md similarity index 100% rename from docs/maintainer/internal-architecture/ast-design.md rename to docs/developer/internal-architecture/ast-design.md diff --git a/docs/maintainer/internal-architecture/dependency-analysis.md b/docs/developer/internal-architecture/dependency-analysis.md similarity index 100% rename from docs/maintainer/internal-architecture/dependency-analysis.md rename to docs/developer/internal-architecture/dependency-analysis.md diff --git a/docs/maintainer/internal-architecture/error-handling-pipeline.md b/docs/developer/internal-architecture/error-handling-pipeline.md similarity index 100% rename from docs/maintainer/internal-architecture/error-handling-pipeline.md rename to docs/developer/internal-architecture/error-handling-pipeline.md diff --git a/docs/maintainer/internal-architecture/index.md b/docs/developer/internal-architecture/index.md similarity index 85% rename from docs/maintainer/internal-architecture/index.md rename to docs/developer/internal-architecture/index.md index 81559de9e..650054145 100644 --- a/docs/maintainer/internal-architecture/index.md +++ b/docs/developer/internal-architecture/index.md @@ -9,7 +9,7 @@ publication: draft # Internal Architecture -Internal architecture pages are for maintainers who need implementation-level +Internal architecture pages are for contributors who need implementation-level details. They are separate from user guides and high-level design documents. ## Pages @@ -29,5 +29,5 @@ details. They are separate from user guides and high-level design documents. ## TODO -- TODO: Fill these pages from implementation evidence and maintainer workflows. +- TODO: Fill these pages from implementation evidence and contributor workflows. - TODO: Keep volatile internals out of user-facing workflow pages. diff --git a/docs/maintainer/internal-architecture/ownership-tracking.md b/docs/developer/internal-architecture/ownership-tracking.md similarity index 100% rename from docs/maintainer/internal-architecture/ownership-tracking.md rename to docs/developer/internal-architecture/ownership-tracking.md diff --git a/docs/maintainer/internal-architecture/pipeline-map.md b/docs/developer/internal-architecture/pipeline-map.md similarity index 99% rename from docs/maintainer/internal-architecture/pipeline-map.md rename to docs/developer/internal-architecture/pipeline-map.md index 5398a9d43..7d80c3743 100644 --- a/docs/maintainer/internal-architecture/pipeline-map.md +++ b/docs/developer/internal-architecture/pipeline-map.md @@ -1,8 +1,8 @@ --- title: Pipeline Map audience: maintainers -prerequisites: source map, overall architecture -related: ../../developer/source-map.md, wrapper-generation-pipeline.md, runtime-layer.md +prerequisites: contributor architecture guide, source map +related: ../architecture.md, ../source-map.md, wrapper-generation-pipeline.md, runtime-layer.md status: maintained publication: draft --- diff --git a/docs/maintainer/internal-architecture/runtime-layer.md b/docs/developer/internal-architecture/runtime-layer.md similarity index 100% rename from docs/maintainer/internal-architecture/runtime-layer.md rename to docs/developer/internal-architecture/runtime-layer.md diff --git a/docs/maintainer/internal-architecture/semantic-passes.md b/docs/developer/internal-architecture/semantic-passes.md similarity index 100% rename from docs/maintainer/internal-architecture/semantic-passes.md rename to docs/developer/internal-architecture/semantic-passes.md diff --git a/docs/maintainer/internal-architecture/symbol-tables.md b/docs/developer/internal-architecture/symbol-tables.md similarity index 100% rename from docs/maintainer/internal-architecture/symbol-tables.md rename to docs/developer/internal-architecture/symbol-tables.md diff --git a/docs/maintainer/internal-architecture/type-system.md b/docs/developer/internal-architecture/type-system.md similarity index 100% rename from docs/maintainer/internal-architecture/type-system.md rename to docs/developer/internal-architecture/type-system.md diff --git a/docs/maintainer/internal-architecture/wrapper-generation-pipeline.md b/docs/developer/internal-architecture/wrapper-generation-pipeline.md similarity index 100% rename from docs/maintainer/internal-architecture/wrapper-generation-pipeline.md rename to docs/developer/internal-architecture/wrapper-generation-pipeline.md diff --git a/docs/maintainer/release-process.md b/docs/developer/release-process.md similarity index 87% rename from docs/maintainer/release-process.md rename to docs/developer/release-process.md index 7ab302e08..eca113136 100644 --- a/docs/maintainer/release-process.md +++ b/docs/developer/release-process.md @@ -63,13 +63,17 @@ Build the same artifacts locally when reviewing the release candidate: ```bash python3 -m pip install --upgrade build twine -python3 -m build -python3 -m twine check dist/* +python3 -m build --outdir .artifacts/dist +python3 -m twine check .artifacts/dist/* ``` -`dist/` must contain one source distribution and one universal wheel. The -source distribution must include the repository-root `CHANGELOG.md`. Install -the wheel in a fresh virtual environment and verify `prik --version`, +`.artifacts/dist/` must contain one source distribution and one universal +wheel. The hidden `.artifacts/` tree contains reproducible local and CI output; +only its ignore placeholder is maintained source. The repository-root +`setup.cfg` also directs setuptools' temporary `.egg-info` metadata into that +hidden tree. The source distribution must include the repository-root +`CHANGELOG.md`. Install the wheel in a fresh virtual environment and verify +`prik --version`, `prik.__version__`, `prik --help`, and `python -m prik --help` before creating the release. diff --git a/docs/developer/repository-structure.md b/docs/developer/repository-structure.md index d723446db..afb8995a5 100644 --- a/docs/developer/repository-structure.md +++ b/docs/developer/repository-structure.md @@ -21,14 +21,13 @@ artifacts used by tests. Navigate by ownership boundary first, then by file. | `prik/compiler/` | Reusable compiler command execution, compile objects, vendor profiles, native support installation, and linking. | | `prik/preprocessing/` | C and Fortran source preprocessing, provenance, native includes, and compiler-derived target probes. | | `prik/pipeline/` | Semantic `.pyi` loading, cross-stage datatype reports, wrapper rendering, and high-level wrapper build orchestration. | -| `prik/runtime/` | Python runtime objects used by generated extension modules. | +| `prik/runtime/` | Python runtime objects plus bundled header-only native support used by generated extension modules. | | `prik/parsers/` | Public namespace for language and semantic-contract frontends and parser models. | | `prik/semantics/` | Semantic IR, scalar datatype vocabulary, source-to-IR conversion, and `.pyi` conversion. | | `prik/policy/` | Immutable post-IR policy vocabulary plus policy construction and completion. | | `prik/planning/` | Backend-neutral wrapper-plan records and mechanical projection from completed policy. | | `prik/codegen/` | Backend datatype projection and plan-driven native bridge/binding lowering. | | `prik/printers/` | Language-specific serialization of C nodes, Fortran nodes, and semantic IR. | -| `prik/binding_support/` | Bundled header-only native support copied into generated wrapper builds. | | `prik/naming/` | Unified public-name and generated-symbol policy. | | `prik/utilities/` | Small shared Python utilities. | | `examples/blas/` | Complete runnable Reference BLAS correctness project and the repository's single authoritative full BLAS source set under `native/`. | diff --git a/docs/maintainer/roadmap/documentation-content-checklist.md b/docs/developer/roadmap/documentation-content-checklist.md similarity index 87% rename from docs/maintainer/roadmap/documentation-content-checklist.md rename to docs/developer/roadmap/documentation-content-checklist.md index 2b180ea28..e3abd0cf4 100644 --- a/docs/maintainer/roadmap/documentation-content-checklist.md +++ b/docs/developer/roadmap/documentation-content-checklist.md @@ -104,10 +104,10 @@ PRIK_C_DOCS_END --> documentation front matter, no-compatibility-layer rule, parser/codegen organization, public contributor rules, TODO markers, support-claim discipline, and review expectations. -- [ ] `docs/maintainer/ci-cd.md`: document current GitHub Actions gates, +- [ ] `docs/developer/ci-cd.md`: document current GitHub Actions gates, coverage policy, static-analysis policy, docs checks, and local caveats for CI-only environment values. -- [ ] `docs/maintainer/release-process.md`: document versioning, changelog, +- [ ] `docs/developer/release-process.md`: document versioning, changelog, release verification, wheel/source distribution limits, and documentation publication steps. - [ ] `docs/developer/contributing/contribution-guide.md`: document setup, issue scope, @@ -119,54 +119,54 @@ PRIK_C_DOCS_END --> comments. ### Design And Internal Architecture -- [ ] `docs/maintainer/design/overall-architecture.md`: document system components, - pipeline stages, data contracts, supported language routes, and deferred - routes. -- [ ] `docs/maintainer/design/parser-architecture.md`: document parser ownership, +- [x] `docs/developer/architecture.md`: document system components, pipeline + stages, data contracts, supported language routes, and deferred routes in + the canonical contributor entry page. +- [ ] `docs/developer/design/parser-architecture.md`: document parser ownership, preprocessing boundaries, model facts, diagnostics, and fixture strategy. -- [ ] `docs/maintainer/design/semantic-analysis.md`: document source-to-IR lowering, +- [ ] `docs/developer/design/semantic-analysis.md`: document source-to-IR lowering, `.pyi`-to-IR loading, policy completion, wrapper-planning errors, and invariants. -- [ ] `docs/maintainer/design/runtime-model.md`: document native support files, generated +- [ ] `docs/developer/design/runtime-model.md`: document native support files, generated wrappers, native state, callbacks, threading, and finalization. -- [ ] `docs/maintainer/design/error-propagation-model.md`: document diagnostic categories, +- [ ] `docs/developer/design/error-propagation-model.md`: document diagnostic categories, Python exception projection, native failure handling, cleanup, and user-facing message shape. -- [ ] `docs/maintainer/design/memory-ownership-model.md`: finish the design page around +- [ ] `docs/developer/design/memory-ownership-model.md`: finish the design page around policy-completion ownership decisions, transfer actions, mutability, setter exposure, and release responsibility. -- [ ] `docs/maintainer/internal-architecture/ast-design.md`: document parser AST, semantic +- [ ] `docs/developer/internal-architecture/ast-design.md`: document parser AST, semantic IR, completed wrapper plans, generated source syntax, what each layer may store, and what must not leak across layers. -- [ ] `docs/maintainer/internal-architecture/semantic-passes.md`: document semantic pass +- [ ] `docs/developer/internal-architecture/semantic-passes.md`: document semantic pass ordering, completed policy decisions, planner validation, and handoff to `ir2ast`. -- [x] `docs/maintainer/internal-architecture/wrapper-generation-pipeline.md`: maintained +- [x] `docs/developer/internal-architecture/wrapper-generation-pipeline.md`: maintained explanation of the current wrapper stages, semantic-policy boundary, pass/planner/emitter distinctions, incremental decomposition criteria, and acceptance criteria for bridge and binding refactoring. -- [x] `docs/maintainer/internal-architecture/type-system.md`: maintained datatype +- [x] `docs/developer/internal-architecture/type-system.md`: maintained datatype lifecycle from compiler probing through semantic normalization, policy, planning, backend registries, generated NumPy boundaries, runtime validation, and non-primitive storage families. -- [ ] `docs/maintainer/internal-architecture/runtime-layer.md`: document native support +- [ ] `docs/developer/internal-architecture/runtime-layer.md`: document native support installation, extension initialization, callbacks, cleanup, and shared native state. -- [ ] `docs/maintainer/internal-architecture/dependency-analysis.md`: document current +- [ ] `docs/developer/internal-architecture/dependency-analysis.md`: document current source ordering, preprocessing dependency facts, generated build plans, and future automatic dependency discovery. -- [ ] `docs/maintainer/internal-architecture/error-handling-pipeline.md`: document +- [ ] `docs/developer/internal-architecture/error-handling-pipeline.md`: document diagnostic creation, path-aware `.pyi` loader errors, wrapper-planning failures, generated validation failures, and native runtime errors. -- [ ] `docs/maintainer/internal-architecture/symbol-tables.md`: document public naming, +- [ ] `docs/developer/internal-architecture/symbol-tables.md`: document public naming, generated-symbol reservation, collision policy, imports, scopes, and package names. @@ -211,9 +211,9 @@ PRIK_C_DOCS_END --> are planned, with expected prerequisites and runtime cost. - [ ] `docs/user/examples/index.md`: split verified cookbook recipes from planned larger examples and state the evidence required for each example. -- [ ] `docs/maintainer/design/index.md`: explain which design documents are accepted +- [ ] `docs/developer/design/index.md`: explain which design documents are accepted architecture and which are placeholders. -- [ ] `docs/maintainer/internal-architecture/index.md`: route maintainers to pipeline, +- [ ] `docs/developer/internal-architecture/index.md`: route contributors to pipeline, semantic pass, runtime, type-system, ownership, and symbol-table pages. - [ ] `docs/developer/contributing/index.md`: route contributors to contribution, pull-request, review, and coding-standard pages. @@ -235,8 +235,8 @@ PRIK_C_DOCS_END --> lowering, bridge, and binding boundaries. - [ ] Each page has been reviewed explicitly; change `publication: draft` to `publication: reviewed` only after that review. - - [ ] Each lane index is reviewed last, after the lane pages intended for its - initial publication are ready. A draft lane index keeps the complete lane + - [ ] Each area index is reviewed last, after the pages intended for its + initial publication are ready. A draft area index keeps the complete area out of production. - [ ] A local draft preview and the Pages workflow artifact have validated navigation, links, search, rendering, and the static site build before @@ -249,13 +249,13 @@ evidence. Keep them current as behavior changes, but do not treat them as the primary placeholder queue. - [x] `docs/index.md`: maintained website entry point for all reviewed - documentation lanes. -- [x] `docs/user/index.md`: maintained User documentation lane entry point. -- [x] `docs/developer/index.md`: maintained Developer documentation lane entry - point. -- [x] `docs/maintainer/README.md`: maintained Maintainer documentation entry - point, publication-gated like the User and Developer indexes. -- [x] `docs/maintainer/documentation-architecture.md`: maintained three-lane + documentation areas. +- [x] `docs/user/index.md`: maintained User documentation entry point. +- [x] `docs/developer/index.md`: maintained Contributor documentation entry + point for developers and maintainers. +- [x] `docs/developer/architecture.md`: canonical contributor architecture + orientation and folder-by-folder rollout plan. +- [x] `docs/developer/documentation-architecture.md`: maintained two-area documentation and publication contract. - [x] `docs/user/getting-started/index.md`: maintained beginner route from installation through the normal rebuild workflow. @@ -343,12 +343,12 @@ primary placeholder queue. parser reference. - [x] `docs/developer/quality-assurance.md`: maintained quality and QA policy reference. -- [x] `docs/maintainer/internal-architecture/pipeline-map.md`: maintained pipeline and +- [x] `docs/developer/internal-architecture/pipeline-map.md`: maintained pipeline and concept-ownership map. -- [x] `docs/maintainer/internal-architecture/ownership-tracking.md`: maintained +- [x] `docs/developer/internal-architecture/ownership-tracking.md`: maintained ownership philosophy, completed policy vocabulary, supported lifetime triples, pointer-policy boundary, validation order, source routes, and safety boundary. -- [x] `docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md`: active implementation +- [x] `docs/developer/roadmap/semantic-pyi-wrapper-checklist.md`: active implementation roadmap for semantic `.pyi` wrapper parity. before semantic policy completion runs. Evidence: `tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_pyi_parser_returns_python_ast_only`, `prik/semantics/README.md`, and - `docs/maintainer/internal-architecture/pipeline-map.md`. + `docs/developer/internal-architecture/pipeline-map.md`. - [x] Risky-but-explicit identity contracts document their exact behavior instead of being silently healed. Fixed-length `String[n]` `intent(inout)` identity calls may return `None` with no observable Python mutation when the diff --git a/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md b/docs/developer/roadmap/wrapper-plan-migration-checklist.md similarity index 99% rename from docs/maintainer/roadmap/wrapper-plan-migration-checklist.md rename to docs/developer/roadmap/wrapper-plan-migration-checklist.md index de7631354..042512b64 100644 --- a/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md +++ b/docs/developer/roadmap/wrapper-plan-migration-checklist.md @@ -3842,7 +3842,7 @@ they do not expose the public callback semantics deferred to Phase 10. native target ownership by default, explicit known-owner retention, direct association writeback, and holder-only destruction. Remove the old blanket target-ownership blocker rather than retaining it as a compatibility path. -- [x] Update public and maintainer documentation to teach the five actual +- [x] Update public and contributor documentation to teach the five actual declarations, six dummy forms, complete matrix, direct versus scoped address acquisition, holder and module transactions, `INTENT(IN)` pointer exception, target lifetime, native pointer-target ownership, multi-argument diff --git a/docs/developer/source-map.md b/docs/developer/source-map.md index a961bdbce..137ddea8e 100644 --- a/docs/developer/source-map.md +++ b/docs/developer/source-map.md @@ -45,7 +45,7 @@ change crosses ownership boundaries. | --- | --- | --- | --- | | CLI flags, stage selection, output formatting, diagnostics | `prik/cli.py` | `docs/user/reference/cli-commands.md`, `docs/user/getting-started/beginner-workflow.md` | `tests/fortran/command_line_interface/pipeline/`, `tests/docs/test_examples.py` | | Compiler preprocessing, include paths, macros, and target flags | `prik/preprocessing/source.py` | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/compiler-preprocessing.md`, `docs/developer/fortran-parser-reference.md` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` | -| Datatype probing, semantic normalization, NumPy projection, and mapping reports | `prik/preprocessing/probes/fortran_types.py`, `prik/semantics/scalar_types.py`, `prik/codegen/primitive_scalar_types.py`, `prik/pipeline/type_mapping_report.py` | `docs/maintainer/internal-architecture/type-system.md`, `docs/user/reference/semantic-ir.md` | `tests/fortran/data_types/` | +| Datatype probing, semantic normalization, NumPy projection, and mapping reports | `prik/preprocessing/probes/fortran_types.py`, `prik/semantics/scalar_types.py`, `prik/codegen/primitive_scalar_types.py`, `prik/pipeline/type_mapping_report.py` | `docs/developer/internal-architecture/type-system.md`, `docs/user/reference/semantic-ir.md` | `tests/fortran/data_types/` | | Fortran parser facts and diagnostics | `prik/parsers/fortran/parser.py` | `docs/developer/fortran-parser-reference.md`, `docs/user/examples/recipes/inspect-fortran-api.md` | `tests/fortran/source_parsing/parsing/` | | Semantic `.pyi` parsing, conversion, printing, package generation, and round-trip behavior | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/printers/pyi.py` | `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/pyi-contracts/index.md`, `docs/user/examples/recipes/semantic-pyi-contracts.md` | `tests/fortran/semantic_pyi_format/`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/semantic_pyi_format/pipeline/` | | Wrapper-planning errors and support claims | `prik/policy/completion.py`, `prik/planning/planner.py` | `docs/user/reference/diagnostic-codes.md`, `docs/user/language-support/feature-matrix.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | @@ -80,7 +80,7 @@ PRIK_C_DOCS_END --> | `prik/compiler/` | Compiler execution, compile objects, vendor profiles, native support installation, and linking | `compilers.py`, `objects.py`, `compiler_profiles.py`, `native_support.py` | compiler and shared-library build tests | | `prik/preprocessing/` | Compiler-backed source expansion, raw C metadata, native Fortran includes, provenance, and target probes | `source.py`, `c.py`, `fortran.py`, `probes/` | C and Fortran preprocessing and target-probe tests | | `prik/pipeline/` | Semantic `.pyi` loading, cross-stage datatype reporting, plan-to-source wrapper generation, and native build orchestration | `pyi.py`, `type_mapping_report.py`, `wrapper.py`, `build.py` | `.pyi`, datatype report, wrapper generation, and build tests | -| `prik/runtime/` | Python runtime objects consumed by generated extensions | `handles.py` | runtime handle and wrapper runtime tests | +| `prik/runtime/` | Python runtime objects and bundled native support consumed by generated extensions | `handles.py`, `native_support/` | runtime handle, native-support, and wrapper runtime tests | | `prik/parsers/` | Public namespace for language and semantic `.pyi` frontends | child parser packages | `tests/fortran/source_parsing/parsing/`, `tests/c/parsing/`, `tests/fortran/semantic_pyi_format/parsing/` | | `prik/parsers/fortran/` | Fortran lexer, recursive parser, models, type resolver, and parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `type_resolver.py`, `cli.py` | `tests/fortran/source_parsing/parsing/`, `docs/developer/fortran-parser-reference.md` | | `prik/semantics/` | Language-neutral semantic IR, scalar datatype vocabulary, source-to-IR conversion, `.pyi` conversion, and raw ownership or descriptor metadata | `models.py`, `scalar_types.py`, `fortran2ir.py`, `c2ir.py`, `pyi2ir.py`, `ownership_metadata.py`, `native_array_handles.py` | `tests/fortran/data_types/semantics/`, `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/` | @@ -88,7 +88,6 @@ PRIK_C_DOCS_END --> | `prik/planning/` | Editable backend-neutral wrapper-plan records and mechanical policy projection | `models.py`, `planner.py` | infrastructure and feature-local codegen tests | | `prik/codegen/` | Backend datatype projection, plan-driven docstrings, and direct lowering into C and Fortran syntax nodes | `primitive_scalar_types.py`, `docstrings.py`, `nodes.py`, `c/`, `fortran/` | data-type and infrastructure codegen, feature-local codegen, and end-to-end tests | | `prik/printers/` | Language-specific serialization of C nodes, Fortran nodes, and semantic IR | `c.py`, `fortran.py`, `pyi.py` | source-printer and semantic-contract printer tests | -| `prik/binding_support/` | Bundled header-only native binding support copied into generated wrapper builds | support header | wrapper build tests | | `prik/utilities/` | Small shared Python utilities | `strings.py`, `visitor.py` | `tests/fortran/infrastructure/utilities/` and tests that exercise callers | - -The most important dependency rule is one-way authority. Parsers describe -source. Semantic IR preserves contract facts. Policy completion decides -ownership, transfer, destruction, storage, projection, setter exposure, and -support. Planning projects those completed decisions. Code generation and -printers implement the selected plan without inventing semantic behavior. - -## Direct-Execution Examples - -Every selected architectural entry file ends with a small, real example -guarded by: - -```python -if __name__ == "__main__": - ... -``` - -Run an example from the repository root with its filename, for example: - -```bash -python3 prik/parsers/fortran/parser.py -``` - -These examples are executable architecture notes. Each one must: - -- exercise the module's actual API or data model rather than a test fixture; -- show the module's input and output at its owning stage; -- remain deterministic and write only below a temporary directory when files - are needed; -- state or fail clearly when a compiler or optional target capability is - required; -- avoid importing policy into a parser, semantic inference into codegen, or any - other shortcut across the documented ownership boundaries; and -- be covered by a focused direct-execution test or by an existing CLI test. - -The result of every selected file example is asserted in one inventory: - -```text -tests/fortran/infrastructure/execution_examples/test_execution_examples.py -``` - -It contains one explicit test per production file, named in the form -`test_fortran___execution_example`. This central owner verifies -that the commands and outputs shown throughout this guide remain executable; -feature-local tests separately prove the underlying parser, policy, codegen, -runtime, or build behavior. The `pipeline/build.py` case retains its -`fortran_end_to_end` marker because that example really compiles, imports, and -calls an extension. - -Not every helper, model, or export file needs an artificial example. Each -folder section selects the files that best expose that folder's responsibility -and explains why those are its entry files. Secondary files are described when -they clarify the design and are exercised indirectly by the selected example. -Package-export `__init__.py` files normally declare namespaces and remain -side-effect free. Package `__main__.py` files are CLI launchers rather than -teaching examples, but they still use an explicit guard. The architecture -section for each package lists its useful filename-based examples and explains -what each demonstrates. - -## Folder-By-Folder Coverage - -The guide and examples follow dependency order. Each group was completed as a -reviewable checkpoint: first document the package contract, then add or repair -its direct examples, then run its focused tests before moving downstream. - -| Order | Folder or files | What the architecture section explains | Direct-execution coverage | Focused verification | -| --- | --- | --- | --- | --- | -| 1 | `prik/`, `prik/contracts/`, `prik/stage_values.py` | Public entrypoints, CLI dispatch, public semantic-contract vocabulary, and shared stage-record behavior | Guard the CLI launchers; demonstrate a public stage record and representative runtime contract scalars; keep export-only `__init__.py` files inert | public-entrypoint, CLI, contract-runtime, and stage-record tests | -| 2 | `prik/compiler/` | Reusable compiler profiles, object inputs, command execution, native support installation, and linking; no preprocessing or semantic policy | Demonstrate profile selection, an `ObjectFile`, a dry command boundary, and temporary native-support installation without creating a wrapper plan | compiler command, verbose output, profile, and shared-library build tests | -| 3 | `prik/preprocessing/` and `prik/preprocessing/probes/` | Compiler source expansion, provenance, native Fortran includes, and compiler-measured Fortran target facts | Preserve the source and Fortran-probe examples; add a native Fortran include example; make compiler requirements explicit | Fortran preprocessing and target-probe tests | -| 4 | `prik/parsers/fortran/` and `prik/parsers/pyi/` | The Fortran and semantic `.pyi` frontend boundaries, diagnostics, source locations, and project assembly | Keep parser/type-resolver examples; add lexer, report, and semantic `.pyi` parser examples; explain passive models and utilities without contrived demos; guard all CLI launchers | parser fixtures, diagnostics, public parser APIs, and parser CLI tests | -| 5 | `prik/semantics/` | Language-neutral IR models, scalar vocabulary, Fortran and semantic `.pyi` conversion, native-contract validation, and raw metadata that survives into policy | Keep the Fortran and semantic `.pyi` converter examples; add small model, scalar, metadata, native-handle, and native-contract flows | semantic conversion, semantic `.pyi`, datatype, and native-contract tests | - -| 6 | `prik/policy/` | Immutable completed-policy vocabulary, ownership resolution, export policy, feature-policy construction, descriptor-handle policy, and ordered completion | Keep ownership/construction/completion examples; add focused model, export, and native-array-policy examples using semantic input | infrastructure semantics, ownership, and feature-local policy tests | -| 7 | `prik/planning/` | Mechanical projection from completed semantic policy into the editable backend-neutral wrapper plan | Keep model and planner examples; ensure they show policy completion before planning and no rendering | planner and feature-local codegen-plan tests | -| 8 | `prik/codegen/` | Backend syntax nodes, datatype catalogues, docstrings, overload queries, Python facade generation, and C/Fortran lowering | Keep bridge/binding/docstring examples; add nodes, datatype registry, overload, naming, check, visitor, and Python-surface examples that consume completed plans | codegen infrastructure, golden output, complexity-policy, and feature-local codegen tests | -| 9 | `prik/printers/` | Pure serialization of already-formed C nodes, Fortran nodes, and semantic IR; no orchestration or semantic decisions | Keep Fortran and `.pyi` examples; add the matching C-node printing example | printer and generated-source golden tests | -| 10 | `prik/pipeline/` | Cross-stage `.pyi` loading, datatype reports, plan-to-rendered-wrapper orchestration, build orchestration, and returned artifacts | Keep wrapper/report/build examples; add a `.pyi` loading and reconciliation example; keep compiler-writing examples temporary | pipeline, build-mode, generated-wrapper, and semantic `.pyi` tests | -| 11 | `prik/runtime/` | Runtime handle responsibilities, generated-operation adapters, descriptor validation, and the bundled native support boundary | Add a Python runtime-handle example driven by explicit generated operations; document that `runtime/native_support` is a native payload with no substantive Python module to demonstrate | runtime handle, descriptor, ownership, and compiled runtime tests | -| 12 | `prik/naming/` and `prik/utilities/` | Cross-cutting public/native naming and genuinely domain-neutral parsing/string/visitor mechanisms | Keep the declaration-expression example; add public-name, native-symbol, string, and visitor examples | naming, utility, declaration-expression, and downstream consumer tests | -| 13 | Contributor documentation consolidation | Merge implemented architecture, design rationale, internal maps, governance workflows, and roadmaps into one developer tree; remove contradictory placeholders and duplicate audience lanes | Add a checked inventory tying every selected architectural entry file to a reproducible direct-execution route | complete documentation suite, link/navigation checks, direct-example suite, and whitespace checks | - -## Acceptance Criteria For Each Package Section - -A folder is complete in this guide only when its section contains all of the -following: - -1. Its purpose in one paragraph. -2. What it owns and what it must not own. -3. Its important files and the role of each file. -4. The input and output values crossing its boundary. -5. Its upstream and downstream dependencies. -6. At least one runnable `python3 .py` example, or an explicit reason - the folder contains only package manifests or non-Python payloads. -7. The focused tests that prove the contract. -8. A short change route telling a future contributor where to begin. - -The completed inventory compares every `prik/` folder and every entry file -selected by this guide against this checklist. It also checks that -each folder consciously identifies its main files, so omitting a helper does -not imply that every file is equally important. A folder is not complete -merely because it appears in a package table. - -## Package Root And Public Contracts - -The package root is the boundary between users and the internal pipeline. It -contains only public entrypoints and one shared stage-value mechanism; domain -implementations belong in named subpackages. - -| File or folder | Responsibility | -| --- | --- | -| `prik/__init__.py` | Flattens the supported Python API and lazily exposes heavyweight CLI, probe, and build functions. It must not become a second implementation home for those functions. | -| `prik/__main__.py` | Delegates `python3 -m prik` to `prik.cli.main`. Importing this launcher does not execute the CLI. | -| `prik/cli.py` | Parses user commands, validates cross-option combinations, selects inspection or build workflows, formats diagnostics, and delegates work to the owning parser or pipeline module. It coordinates stages but does not own their semantic rules. | -| `prik/stage_values.py` | Provides `StageRecord`, the mutable-producer/immutable-consumer handoff used for editable wrapper plans and generated artifacts. Recursive freezing converts mutable containers and rejects later mutation. | -| `prik/contracts/` | Defines the public names used in semantic `.pyi` files. Contract symbols are both parser-recognized syntax and, for supported primitive scalars or native descriptor handles, small runtime constructors. They are not semantic IR classes. | - -The root API depends on parsers, semantic conversion, contract loading, and -runtime handles. Those packages must not import the flattened root API back; -internal code imports canonical owners to avoid cycles and hidden dependency -direction. - -### `prik/__init__.py`: supported public API - -The package initializer is the public import surface. Its example deliberately -uses `prik.parse_fortran_file`, rather than reaching into an implementation -package, to show what a caller receives from the stable API: - -```bash -python3 prik/__init__.py -``` - -```text -PRIK 0.2.1 -Public parser result: subroutine ping from ping.f90 -``` - -The output demonstrates that the root exposes both package metadata and the -source-to-parser-model entrypoint. The result is still a parser fact; no -semantic conversion, policy completion, or wrapper generation has occurred. - -### `prik/cli.py`: command dispatch - -This file owns the top-level command vocabulary and routes a validated request -to its real stage owner. Running the file with an ordinary CLI option reaches -the same `main()` function as the installed `prik` command: - -```bash -python3 prik/cli.py --version -``` - -```text -prik 0.2.1 -``` - -This small output proves filename execution is a real CLI path, not a separate -tutorial implementation. Parse and build subcommands exercise the downstream -packages described later in this guide. - -### `prik/stage_values.py`: mutable-to-frozen handoff - -`StageRecord` lets a producing stage assemble a dataclass and then lets its -consumer freeze that value recursively: - -```bash -python3 prik/stage_values.py -``` - -```text -Editable parser output: geometry -> ['scale', 'norm'] -Frozen consumer input: geometry -> ('scale', 'norm') -Mutation rejected: ParserOutput is frozen by its consuming stage -``` - -The list becoming a tuple and the rejected assignment are the important -boundary: consumers can trust a completed plan or artifact not to change under -them. - -### `prik/contracts/__init__.py`: public contract vocabulary - -The contracts package contains names written by users in semantic `.pyi` -files. Some primitive names are also useful NumPy scalar constructors: - -```bash -python3 prik/contracts/__init__.py -``` - -```text -Float64() -> np.float64(0.0) (float64) -Float64[:, :] -> element=Float64, rank=2, shape=(slice(None, None, None), slice(None, None, None)) -``` - -The first line is a runtime NumPy scalar. The second is declarative contract -syntax describing element type, rank, and shape; it is interpreted later by -the semantic `.pyi` frontend rather than being a semantic IR object itself. - -Package `__init__.py` files normally remain export-only manifests. The root and -`contracts` initializers are exceptions because each contains substantive -public behavior worth demonstrating. `runtime/native_support/__init__.py` -remains empty: its folder owns a native header payload, not a Python API. - -Primary evidence: - -- `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` -- `tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py` -- `tests/fortran/infrastructure/pipeline/test_stage_values.py` -- `tests/fortran/data_types/runtime/test_contract_scalar_constructors.py` - -Start a root-level change in the narrow owner above. Public export changes also -update `docs/user/reference/python-api.md`; CLI changes update the CLI reference -and argument/output contract tests. A new cross-stage value should be placed at -the stage that produces it unless its freeze behavior is genuinely shared. - -## Compiler Services - -`prik/compiler/` is the reusable native-process layer. It receives explicit -source, object, include, library, flag, and link inputs from the build pipeline; -it constructs and optionally executes commands. It does not preprocess source, -measure datatype semantics, discover a wrapper API, complete ownership policy, -or decide build order. - -| Important file | Responsibility | -| --- | --- | -| `compiler_profiles.py` | Defines coherent GNU, Intel, LLVM, NVIDIA, and PGI language profiles, attaches the active Python/NumPy build settings, and maps a selected Fortran driver family to its matching C driver family. | -| `objects.py` | Defines the immutable `ObjectFile` input for one source-to-object invocation. The pipeline—not this value—owns dependency order and concurrency. | -| `compilers.py` | Selects configured executables, builds compile/link argv, records commands, runs subprocesses when enabled, and reports concise native failures. Its record-only mode exposes commands without compiling. | -| `native_support.py` | Installs the bundled header-only binding support and a NumPy API-version header into a generated-wrapper directory when the rendered wrapper requests it. | - -The upstream owner is `prik/pipeline/build.py`, which creates `ObjectFile` -records and decides dependency-ready batches. The downstream boundary is the -host compiler and linker process. `prik/preprocessing/` and its probes reuse -the same selected compiler identity and flags at earlier stages, but they own -their own source-expansion and measurement operations. - -### `compiler_profiles.py`: coherent compiler families - -The profile resolver normalizes a selected executable and supplies matching -language drivers and family-specific switches: - -```bash -python3 prik/compiler/compiler_profiles.py -``` - -```text -Selected family: gfortran -Compiler profile: GNU -Matching C executable: gcc -Fortran module-output flag: -J -``` - -This demonstrates why the pipeline selects a profile rather than independently -guessing C and Fortran flags: one family decision yields coherent drivers and -switches. - -### `objects.py`: one explicit compile input - -`ObjectFile` is the immutable request passed to a compiler invocation: - -```bash -python3 prik/compiler/objects.py -``` - -```text -Compile input: generated/bridge.f90 -> build/bridge.o -Language: fortran -Flags: ('-O2',) -Include directories: build/modules -``` - -The record contains everything needed for one source-to-object command. It -does not decide when that command is dependency-ready; ordering belongs to the -build pipeline. - -### `compilers.py`: command construction and execution - -The direct example uses record-only mode, so it exercises the real command -builder without compiling a file: - -```bash -python3 prik/compiler/compilers.py -``` - -```text -Compiler profile: GNU -Compile input: demo.c -> demo.o -Recorded without execution: True -Contains compile switch: True -Contains requested flag: True -Commands recorded: 1 -``` - -The output distinguishes compiler mechanics from orchestration: the caller -provided the source, object, and flag; this module converted them into one -recorded native command. - -### `native_support.py`: bundled support installation - -Generated C sources include bundled headers. This example installs the real -payload into a temporary wrapper directory: - -```bash -python3 prik/compiler/native_support.py -``` - -```text -Installed directory: binding_support -Binding header present: True -NumPy version header present: True -``` - -It shows the precise responsibility of this file: materialize requested native -support. Whether a wrapper requests that support was already decided by -generation. - -Primary evidence: - -- `tests/fortran/building_shared_library/compiling/test_compiler_verbose.py` -- `tests/fortran/error_handling/compiling/test_verbose_commands.py` -- `tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py` -- `tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py` -- `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` - -Start compiler-profile or argv changes in this package. Start ordering, -parallelism, artifact naming, or build-manifest changes in the pipeline. If a -requested change depends on ownership, dtype, projection, or Python API shape, -it belongs upstream of both packages rather than in a compiler flag branch. - -## Preprocessing And Target Probes - -`prik/preprocessing/` owns everything required to turn original Fortran source -into authoritative parser input and compiler-dependent target facts. It runs -before declaration parsing, but it is not one generic text cleanup pass: -compiler expansion, native Fortran includes, and executable datatype probes -deliberately remain separate mechanisms with separate results. - - - -| Important file | Responsibility | -| --- | --- | -| `source.py` | Configures and invokes compiler preprocessing, collects expanded source, line-marker provenance, dependency edges, macro metadata, diagnostics, and replayable recipes. It coordinates the Fortran include pass after compiler CPP. | - -| `fortran.py` | Recursively expands native Fortran `INCLUDE` statements left after compiler preprocessing while preserving dependency edges and generated-to-original source mappings. | - -| `probes/fortran_types.py` | Compiles and runs target programs for Fortran kind expressions, storage widths, logical representations, and compile-time values required by semantic conversion. | - -The source preprocessors output text and provenance consumed by the Fortran -parser. The probes output immutable reports consumed by Fortran semantic -conversion. Compiler identity, target flags, include paths, macros, working -directory, and optional cross-target runner are part of the probe recipe/cache -identity; measured facts must not silently cross targets. This package never -decides semantic scalar names, NumPy dtypes, ownership, or wrapper support. - - - -### `source.py`: compiler preprocessing with provenance - -This is the coordinating preprocessing entrypoint. Its example expands a -native Fortran include while retaining source provenance: - -```bash -python3 prik/preprocessing/source.py -``` - -```text -Before Fortran include expansion: -module greeting -include 'constants.inc' -... -After Fortran include expansion: -module greeting -integer, parameter :: answer = 42 -... -Native includes: 1; diagnostics: 0 -``` - - - -The changed source and the dependency/diagnostic counts show that the result is -parser input plus provenance, not just cleaned text. - - - -### `fortran.py`: native `INCLUDE` expansion - -Fortran `INCLUDE` remains distinct from compiler macro preprocessing: - -```bash -python3 prik/preprocessing/fortran.py -``` - -```text -Expanded parser input: -module geometry -integer, parameter :: dimensions = 3 -end module geometry -Native include dependencies: 1 -Generated source mappings: 5 -Diagnostics: 0 -``` - -The expanded declaration is accompanied by dependency and line-mapping facts, -which lets later parser diagnostics still identify original sources. - - - -### `probes/fortran_types.py`: measured Fortran target facts - -The Fortran probe resolves compiler-dependent kind expressions and storage -facts: - -```bash -python3 prik/preprocessing/probes/fortran_types.py -``` - -```text -selected_int_kind(9) = 4 -``` - -The result is a native kind value consumed by semantic datatype resolution. -It is not yet the stable semantic scalar name or NumPy dtype. The example -requires `gfortran` or `f95`. - -Primary evidence: - - -- `tests/fortran/source_preprocessing/preprocessing/` -- `tests/fortran/data_types/probes/test_fortran_type_probes.py` -- `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` - -Start source-expansion and provenance changes in `source.py`, native include -behavior in `fortran.py`, and target measurement or cache changes in the -Fortran probe. Parser grammar changes start downstream, while stable semantic -datatype vocabulary and NumPy lowering start in `semantics/scalar_types.py` -and `codegen/primitive_scalar_types.py`. - - - -## Parser Frontends - -`prik/parsers/` contains the active Fortran source frontend and the semantic -`.pyi` syntax frontend. Fortran owns its lexical rules, source models, -diagnostics, and project assembly; semantic `.pyi` deliberately reuses -Python's AST. A parser reports what source says. It must not choose ownership, -wrapper support, NumPy lowering, or generated API behavior. - - - -| Folder | Responsibility | -| --- | --- | - -| `parsers/fortran/` | Turns prepared fixed- or free-form Fortran into file/project models while preserving source locations, declarations, visibility, and unit structure. | -| `parsers/pyi/` | Reads semantic `.pyi` syntax into a standard Python `ast.Module`; interpretation belongs to `semantics/pyi2ir.py`. | - -In the Fortran package, `models.py` owns passive parser dataclasses and -diagnostic types. That file has no direct example because constructing an -isolated dataclass would hide the parser boundary; the parser examples below -produce the real models. `utils.py` contains subordinate source-form and -delimiter-aware helpers exercised by the lexer, type resolver, and parser. -Package initializers are export manifests, while `__main__.py` files are guarded -`python3 -m ...` launchers. - - - -### `parsers/fortran/lexer.py`: logical Fortran lines +The C parser and C-to-IR frontend are intentionally deferred from the +published contributor workflow until the C input path is mature. Generated C +for the CPython/NumPy binding remains an essential, fully documented part of +the Fortran wrapper backend. -The Fortran lexer detects source form, strips comments, folds continuations, -and retains the original starting line for diagnostics: +## Authority And Dependency Rules -```bash -python3 prik/parsers/fortran/lexer.py -``` +Each stage may depend on completed output from the stage above it. Authority +does not flow backward. -```text -Detected source form: free -line 1: subroutine shift(value,offset) -line 3: real, intent(inout) :: value -line 4: real, intent(in) :: offset -line 5: end subroutine shift -``` +| Stage | May decide | Must not decide | +| --- | --- | --- | +| Preprocessing/probes | prepared source, provenance, dependencies, measured target facts | declaration meaning, semantic dtypes, wrapper support | +| Parsers | syntax facts, source structure, source-located diagnostics | ownership, Python API, lowering | +| Semantic IR | language-neutral identities, shapes, origins, raw contract metadata | completed ownership or emitted mechanisms | +| Policy | exports, object kind, owner, transfer, destruction, storage, writeback, nullability, projections, lifecycle, setters, support | source syntax or backend text | +| Planning | typed projection and organization of completed facts | new semantic decisions or presentation text | +| Codegen | backend-local mechanisms and syntax nodes selected by the plan | fallback policy inference | +| Printers | formatting and serialization | orchestration, filenames, semantic decisions | +| Pipeline | workflow order, artifacts, manifests, compilation scheduling | stage-local grammar or rules | +| Compiler/runtime | native command mechanics and enforcement of completed runtime contracts | wrapper API or lifetime policy selection | -The first two physical lines become one logical parser record attributed to -line 1. That location contract is the lexer's output to the grammar parser. +The most important rule is the policy boundary: before +`WrapperPlanner.build()` begins, every semantic choice needed by binding and +bridge generation must already be explicit. Lower stages dispatch from those +choices and fail closed when a required decision is missing. -### `parsers/fortran/parser.py`: Fortran source models +## Package Guide Map -This is the main Fortran frontend and project assembler: +Each package has one canonical detailed guide containing its local structure, +important files and objects, direct examples with output, focused test links, +change routes, and invariants. -```bash -python3 prik/parsers/fortran/parser.py -``` +| Package | Brief role | Detailed guide | +| --- | --- | --- | +| `contracts/` | Public semantic `.pyi` vocabulary | [Contracts](packages/contracts.md) | +| `compiler/` | Native command construction and execution | [Compiler](packages/compiler.md) | +| `preprocessing/` | Source preparation, provenance, includes, and target probes | [Preprocessing](packages/preprocessing.md) | +| `parsers/` | Fortran source and semantic `.pyi` syntax facts | [Parsers](packages/parsers.md) | +| `semantics/` | Language-neutral semantic IR construction | [Semantics](packages/semantics.md) | +| `policy/` | Complete post-IR interoperability decisions | [Policy](packages/policy.md) | +| `planning/` | Mechanical typed wrapper-plan projection | [Planning](packages/planning.md) | +| `codegen/` | Plan-driven backend nodes and Python facade | [Code generation](packages/codegen.md) | +| `printers/` | Serialization of formed representations | [Printers](packages/printers.md) | +| `pipeline/` | Cross-stage wrapper/build workflows and artifacts | [Pipeline](packages/pipeline.md) | +| `runtime/` | Imported-extension handles and bundled native support | [Runtime](packages/runtime.md) | +| `naming/` | Shared public and generated symbol rules | [Naming](packages/naming.md) | +| `utilities/` | Stage-neutral expressions, strings, and visitor dispatch | [Utilities](packages/utilities.md) | -```text -Module: metrics -Parameter: n = 4 -Procedure: scale(values: real[1]) -``` +The [datatype lifecycle](concepts/datatype-lifecycle.md) remains a separate +cross-cutting concept because one native datatype passes through probing, +semantic normalization, policy, codegen mapping, and runtime validation. -The example shows three parser facts used downstream: source-unit ownership, a -compile-time parameter expression, and an argument's intrinsic spelling and -rank. It does not map `real` to a target kind or NumPy dtype by itself. +## Tests And Evidence -### `parsers/fortran/type_resolver.py`: type-spec syntax +Choose tests by native language, public feature, and owning stage. The +[testing strategy](testing-strategy.md) is canonical for placement and command +selection. Package guides link directly to their focused suites. -The type resolver extracts kind and character metadata without evaluating -compiler-dependent expressions: +Direct source-file examples are production-owned `if __name__ == "__main__"` +flows, run from the repository root as: ```bash -python3 prik/parsers/fortran/type_resolver.py +python3 /.py ``` -```text -integer(4) -> 4 -real(kind=selected_real_kind(15, 307)) -> selected_real_kind(15, 307) -character(len=16, kind=c_char) -> len=16, kind=c_char -``` - -Preserving `selected_real_kind(...)` as syntax is intentional: target-probe -facts and semantic conversion decide its meaning later. - -### `parsers/fortran/cli.py`: Fortran parser reports +Their exact results are grouped in +[`tests/fortran/infrastructure/execution_examples/test_execution_examples.py`](../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py). +Documentation tests own links, navigation, metadata, publication, and package +guide structure; feature and stage tests own the demonstrated behavior. -The no-argument example renders a real in-memory parse through the stable -report formatter: +## Where A Change Begins -```bash -python3 prik/parsers/fortran/cli.py -``` - -```text -File: geometry.f90 - Modules: 1 - - module geometry (vars=0, uses=0) - Procedures: 1 - - function norm(value:real[0]) -> real[0] -``` - -The report exposes parser structure and parser datatypes. Command-line paths -and options reuse the same formatter and may request JSON or downstream -semantic reports. +| Change | Start here | Continue only when needed | +| --- | --- | --- | +| CLI option or dispatch | `prik/cli.py` | selected package and CLI/user documentation | +| Source expansion or provenance | `prik/preprocessing/source.py` | parser boundary tests | +| Fortran syntax fact | `prik/parsers/fortran/` | semantic converter if the IR changes | +| Semantic `.pyi` syntax | `prik/parsers/pyi/parser.py` or `prik/semantics/pyi2ir.py` | printer, policy, and user reference according to meaning | +| Stable semantic model/type | `prik/semantics/` | policy and downstream projections | +| Ownership, projection, setters, or support | `prik/policy/` | planning only to project the completed result | +| Plan representation | `prik/planning/` | binding/bridge generation consumers | +| Emitted native mechanism | narrow `prik/codegen/` owner | matching printer only if representation changes | +| Formatting | matching `prik/printers/` file | golden output tests | +| Build artifact or compilation workflow | `prik/pipeline/build.py` | compiler service when argv mechanics change | +| Runtime handle enforcement | `prik/runtime/handles.py` | policy first if permission/ownership is undecided | -### `parsers/pyi/parser.py`: syntax-only contract parsing +For exact file ownership use the [source map](source-map.md). When starting +from a documented capability use the [feature-to-code map](feature-to-code-map.md). -This frontend intentionally stops at Python AST: +## Contributor Documentation Structure -```bash -python3 prik/parsers/pyi/parser.py -``` +All developer, maintainer, design, testing, release, and roadmap material lives +in one contributor area: ```text -Parsed AST: Module -Function node: scale -Argument annotation: Float64 -Semantic conversion performed: False -``` - -The last line makes the ownership boundary explicit. Recognizing `Float64` as -a PRIK semantic type belongs to `semantics/pyi2ir.py`, not this parser. - -Primary evidence: - - -- `tests/fortran/source_parsing/parsing/` -- `tests/fortran/command_line_interface/pipeline/` -- `tests/fortran/semantic_pyi_format/parsing/` - -Start lexical/source-coordinate changes in the Fortran `lexer.py`; grammar and -source-model construction in `parser.py`; cross-file identity in the Fortran -project parser; report output in `cli.py`; and semantic meaning downstream in -the corresponding converter. Parser support never by itself establishes -wrapper support. - - - -## Semantic IR - -`prik/semantics/` is the language-neutral contract layer. It receives Fortran -parser models or a parsed semantic `.pyi` AST and produces the same -`SemanticModule` graph. The graph preserves public names, native names, source -provenance, storage shape, projections, and raw contract metadata. It must not -complete ownership, select lowering actions, or render backend text; those -responsibilities belong to policy, planning, and codegen respectively. - - - -| Important file | Responsibility | -| --- | --- | -| `models.py` | Defines semantic modules, functions, classes, variables, types, storage/array contracts, projections, origins, and structural equality. | -| `scalar_types.py` | Defines stable scalar identities and intrinsic family/storage facts without NumPy or generated-language spellings. | - -| `fortran2ir.py` | Converts Fortran parser models and measured kind/compile-time facts into semantic modules. | -| `pyi2ir.py` | Interprets parsed Python AST as an editable semantic contract and reconciles imported semantic references. | -| `ownership_metadata.py` | Normalizes raw ownership and pointer requests recorded during IR construction; it does not resolve them. | -| `native_array_handles.py` | Marks allocatable/pointer descriptor handles and derives their ordinary array and element facets. | -| `native_contract.py` | Prepares and validates source-free `.pyi` native placement, projections, concrete types, and callback reconstruction. | - -`metadata.py` and `pyi_metadata.py` are intentionally passive registries for -shared keys. They have no direct example because their values become meaningful -only on the models demonstrated below. `semantics/__init__.py` is an export -manifest. Combined file loading and cross-file `.pyi` reconciliation are -pipeline orchestration and are documented with `pipeline/pyi.py` later. - -### `semantics/models.py`: the language-neutral graph - -The model example constructs the same values a frontend converter returns: - -```bash -python3 prik/semantics/models.py -``` - -```text -Semantic module: geometry -Function: scale -> native SCALE -Argument: values: Float64, rank=1, shape=('n',), order=F -Source provenance: fortran real -``` - -The public/native name distinction, shape/order contract, and source -provenance survive together. None of these values says how the generated -binding transfers or owns the argument. - -### `semantics/scalar_types.py`: stable scalar vocabulary - -This catalogue separates intrinsic semantic facts from target- or -backend-dependent representations: - -```bash -python3 prik/semantics/scalar_types.py -``` - -```text -Float64: family=real, storage=64 bits -Int: family=signed_integer, storage=target-dependent -Backend spelling stored here: False -``` - -`Float64` fixes a semantic width, while `Int` needs target-probe information. -Neither entry owns a NumPy dtype, C spelling, or Fortran bridge spelling; those -maps live at their respective runtime and code-generation boundaries. - - - -### `semantics/fortran2ir.py`: Fortran facts to semantic IR - -The Fortran converter normalizes measured kind and source storage information: - -```bash -python3 prik/semantics/fortran2ir.py -``` - -```text -math.scale(value): Float64 via reference storage -``` - -Here the source `real` declaration has become stable `Float64`, while reference -storage remains an explicit semantic fact. Ownership and Python/native barrier -actions are still undecided. - -### `semantics/pyi2ir.py`: editable contract to semantic IR - -This converter gives semantic meaning to the AST produced by -`parsers/pyi/parser.py`: - -```bash -python3 prik/semantics/pyi2ir.py -``` - -```text -math.scale(value): Float64 -> Float64 -``` - -Unlike the syntax-only parser example, this result contains a semantic module, -function, argument type, and result type. Contract validation happens here; -post-IR policy completion remains downstream. - -### `semantics/ownership_metadata.py`: unresolved ownership requests - -Frontends use these setters to normalize user/source claims before complete -signatures and relationships are available: - -```bash -python3 prik/semantics/ownership_metadata.py -``` - -```text -Raw ownership request: owner=caller, transfer=in_place, destruction=caller -Pointer contract: nullable=True, lifetime=owner, reassociation=forbidden -Completed lowering action present: False -``` - -The final line is the boundary: normalized metadata is input to policy -completion, not permission for a generator to infer a transfer or codegen -action. - -### `semantics/native_array_handles.py`: descriptor and data facets - -A native descriptor handle is semantically different from the array data it -currently addresses: - -```bash -python3 prik/semantics/native_array_handles.py -``` - -```text -Descriptor kind: allocatable -Data facet: Float64, rank=2, shape=('rows', 'columns') -Element facet: Float64, rank=0 -Handle marker retained by data facet: False -``` - -The derived data facet deliberately drops handle-only ownership and descriptor -metadata. Policy can therefore reason separately about the native container, -the exposed array view, and one element type. - -### `semantics/native_contract.py`: source-free native validation - -Semantic `.pyi` can describe a native artifact without available source, but -the contract must still reconstruct placement and ABI-relevant type facts: - -```bash -python3 prik/semantics/native_contract.py -``` - -```text -Prepared origin: fortran module math -Valid contract issues: 0 -Invalid contract issue: pyi_native_type_missing at math.broken.value -``` - -The validator prepares native origin information and reports a stable issue at -the exact semantic owner when a concrete dtype is missing. It validates the -contract; it does not compile or load the artifact. - -Primary evidence: - -- `tests/fortran/semantic_ir/semantics/` - -- `tests/fortran/semantic_pyi_format/` -- `tests/fortran/data_types/semantics/` -- `tests/fortran/native_array_handles/semantics/` -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` - -Start model-shape changes in `models.py`, stable primitive vocabulary in -`scalar_types.py`, Fortran or semantic `.pyi` conversion in its corresponding -`*2ir.py` file, and raw contract normalization in its focused helper. If the -question is which transfer, lifetime, setter, projection, or lowering action is -valid, the change starts in `prik/policy/`, not here. - - - -## Post-IR Policy - -`prik/policy/` is the last semantic authority before planning. It receives the -complete semantic module graph and resolves every choice needed by wrapper -generation: public exports, object kind, owner, transfer, destruction, -mutability/writeback, nullability, storage, projections, lifecycle actions, -descriptor operations, accessor behavior, and support blockers. A lower stage -may dispatch from these records but may not replace or infer them. - -| Important file | Responsibility | -| --- | --- | -| `models.py` | Defines immutable backend-neutral records for completed function, argument, result, call-slot, lifecycle, class, callback, array, and module-variable policy. | -| `ownership.py` | Resolves object kind and the ownership/transfer/destruction triple into storage and strict Python/native/codegen actions. | -| `exports.py` | Completes collision-checked Python namespace and local-name policy. | -| `construction.py` | Constructs coherent wrapper-facing policy records from already completed semantic and ownership facts. | -| `completion.py` | Runs policy completion in dependency order and attaches every completed record to semantic IR. | -| `native_array_handles.py` | Defines descriptor-handle/array ABI selectors, strict dispatch records, and build requirements selected by completed handle policy. | - -The package initializer exports only the public completion entrypoint. Policy -models are separate from construction rules so planning and codegen can depend -on completed vocabulary without depending on the rule implementation. - -### `policy/models.py`: immutable completed decisions - -The models example creates representative array and lifecycle records: - -```bash -python3 prik/policy/models.py -``` - -```text -Array policy: rank=2, shape=('rows', 'columns'), order=F -Lifecycle policy: copy_out writeback via copy_in_out -Completed record mutation rejected: True -``` - -Unlike raw semantic metadata, these records state the selected phase, -operation, and codegen action. Their immutability makes policy a reliable input -to planning and both backend generators. - -### `policy/ownership.py`: lifetime and barrier resolution - -The ownership resolver turns one semantic argument and its use context into a -complete decision: - -```bash -python3 prik/policy/ownership.py -``` - -```text -before: math.scale(value): Float64 semantic IR -after: scalar/caller/call_local; scalar_value -> pass_value -``` - -The result names the object kind, owner, transfer, Python extraction action, -and native handoff action. Binding and bridge code must consume those values; -they cannot rediscover them from `Float64` or argument intent. - -### `policy/exports.py`: completed Python placement - -Export policy resolves namespace placement and collision-safe local names: - -```bash -python3 prik/policy/exports.py -``` - -```text -Native semantic owner: math.SCALE_VALUE -Python export: linear_algebra.scale_value -Completed policy type: PythonExportPolicy -``` - -The native identity remains unchanged while the Python-facing path becomes an -explicit immutable policy value consumed downstream. - -### `policy/construction.py`: coherent wrapper policy - -Construction combines completed ownership with ABI, result, and native-call -slot rules: - -```bash -python3 prik/policy/construction.py -``` - -```text -before: math.scale(value): Float64 semantic IR -after: direct_transfer; result=native_scalar; native=pass_value -``` - -The output relates three sides of one call: bridge data movement, direct-result -ABI, and native-slot handoff. Construction does not generate a wrapper plan or -render code. - -### `policy/completion.py`: the mandatory ordered boundary - -Normal callers use this entrypoint rather than invoking individual rules: - -```bash -python3 prik/policy/completion.py -``` - -```text -before: math.scale(value): Float64 semantic IR -after: math.scale(value): scalar_value -> pass_value -``` - -Completion first resolves exports and dependent graph facts, then ownership, -accessor, feature, and wrapper policies. The attached `scalar_value -> -pass_value` actions make the semantic graph eligible for planning. Unsupported -contracts retain explicit blockers and fail before codegen. - -### `policy/native_array_handles.py`: descriptor ABI and build policy - -This example starts from an already completed pointer-handle policy: - -```bash -python3 prik/policy/native_array_handles.py -``` - -```text -Handle policy: pointer/pointer, storage=alias -Allowed operations: to_numpy, nullify -Array ABI: descriptor -Selected build header: ISO_Fortran_binding.h -``` - -The descriptor ABI, permitted operations, storage mode, and header requirement -are selected policy outputs. Planning and compilation consume them; neither -stage scans semantic datatypes to decide that the header is needed. - -Primary evidence: - -- `tests/fortran/infrastructure/semantics/` -- feature-local `tests/fortran/*/policy/` directories -- `tests/fortran/native_array_handles/policy/` -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` - -Start a new semantic decision in `completion.py` and its focused constructor or -resolver. Put reusable immutable output vocabulary in `models.py`. Extend -strict descriptor dispatch/build selection in `native_array_handles.py`. If a -change only projects an existing decision into implementation fields, it -belongs downstream in planning; if a generator currently guesses the decision, -remove the guess and complete the policy here first. - -## Wrapper Planning - -`prik/planning/` mechanically projects policy-completed semantic IR into one -backend-neutral `ModulePlan`. The plan joins shared transfer facts with -binding-specific and bridge-specific views, namespace placement, stable native -symbols, lifecycle ordering, and required headers. Planning may organize and -validate completed decisions; it may not reinterpret source declarations, -select ownership, or render output text. - -| Important file | Responsibility | -| --- | --- | -| `models.py` | Defines the editable typed plan tree rooted at `ModulePlan`, including namespace, function, argument/result, native-slot, lifecycle, descriptor, callback, derived-object, binding, and bridge views. | -| `planner.py` | Reads completed policy records, validates their presence/support, assigns shared roles and stable symbols, and constructs the plan tree in deterministic order. | - -The package initializer exports the plan types and `WrapperPlanner`; it contains -no separate behavior to demonstrate. Codegen receives only the completed plan, -and `WrapperGenerator` validates and freezes that tree before invoking either -backend. - -### `planning/models.py`: the typed plan representation - -The model example constructs the smallest coherent procedure plan directly: - -```bash -python3 prik/planning/models.py -``` - -```text -Plan owner: demo -Python export: ping -Native procedure: PING -Native slots: 0 -``` - -The same function has an explicit Python binding view and native bridge view. -The plan carries no native slots because the example subroutine has no -arguments or results. Constructing records here demonstrates representation, -not a shortcut around normal policy completion. - -### `planning/planner.py`: completed policy to plan - -The planner example follows the real boundary: construct semantic IR, complete -policy, then build the plan: - -```bash -python3 prik/planning/planner.py -``` - -```text -Plan owner: planner_demo -Python export: double_value -Native target: DOUBLE_VALUE -Conversion order: ('planner_demo.double_value.value',) -``` - -The final role is the stable shared identity used to order binding conversion -and connect the matching native-call slot. The planner copied selected actions -from completed policy; it did not decide them from `Float64`. - -Primary evidence: - -- `tests/fortran/infrastructure/codegen/test_plan.py` -- `tests/fortran/infrastructure/codegen/test_planner.py` -- feature-local `tests/fortran/*/codegen/` plan assertions -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` - -Start plan-shape changes in `models.py` and projection/indexing changes in -`planner.py`. A new field is justified when codegen needs an already completed -fact in typed form. If deciding the field requires reasoning about ownership, -intent, mutability, projections, or support, complete that decision in policy -first. Rendering, emitted temporaries, and source syntax belong downstream. - -## Backend Node Generation - -`prik/codegen/` consumes only a validated `ModulePlan` and produces typed C and -Fortran syntax nodes plus planned Python-facade source. It owns emitted-code -mechanisms—temporary declarations, conversion calls, native bridge bodies, -module initialization, and class-facade assembly—but it must not infer semantic -policy. Language printers serialize the resulting nodes in the next stage. - -| Folder or important file | Responsibility | -| --- | --- | -| `nodes.py` | Defines the typed C/Fortran syntax trees shared by emitters and printers. | -| `primitive_scalar_types.py` | Maps already resolved semantic scalar identities to C, Fortran, NumPy, CFI, and CPython conversion spellings. | -| `docstrings.py` | Renders Python-facing function/class documentation from completed plan facts. | -| `c/binding.py` | Lowers binding plan views into CPython/NumPy C nodes, headers, initialization, validation, and native-call wrappers. | -| `c/python_surface.py` | Emits the thin executable Python class/holder/module-proxy facade selected by completed class plans. | -| `fortran/bridge.py` | Lowers bridge plan views into `bind(C)` modules, procedures, holders, descriptors, accessors, and native calls. | - -Supporting files are deliberately smaller. `overloads.py` answers shared -structural questions over completed overload plans; `visitor.py` supplies -strict class-name dispatch; `c/naming.py` centralizes binding-local generated -names; and `checks.py` implements the static codegen ownership/complexity gate -invoked by `tools/check_codegen_complexity.py`. Package initializers are export -manifests. These helpers are demonstrated through their owners or maintainer -command rather than receiving artificial standalone examples. - -### `codegen/nodes.py`: typed syntax before printing - -The node example constructs one C tree and one Fortran tree without rendering -either language: - -```bash -python3 prik/codegen/nodes.py -``` - -```text -C node tree: CModule -> wrap_ping -> CReturn -Fortran node tree: FortranModule -> bind_c_ping -> FortranCall -Source text rendered: False -``` - -This is the boundary between generation and printing. Emitters choose typed -statements from the plan; printers later decide whitespace, punctuation, and -source layout. - -### `codegen/primitive_scalar_types.py`: boundary spellings - -Once semantic conversion has resolved `Float64`, codegen can look up every -required backend representation explicitly: - -```bash -python3 prik/codegen/primitive_scalar_types.py -``` - -```text -Float64: C=double; Fortran=real(c_double); NumPy=numpy.float64 -NumPy C macro: NPY_FLOAT64 -Fresh editable node per lookup: True -``` - -The readable mapping makes the datatype boundary auditable. A lookup returns a -fresh node so one generator cannot mutate global catalogue state. Unknown -semantic identities fail rather than coercing to a nearby dtype. - -### `codegen/docstrings.py`: plan-driven public documentation - -Docstrings are rendered after planning so their signatures, types, results, -and errors match the generated API: - -```bash -python3 prik/codegen/docstrings.py -``` - -```text -double_value(value) -> float64 - -Parameters ----------- -value : float64 - -Returns -------- -result : float64 - -Raises ------- -TypeError - If an argument has an incompatible Python type or dtype. -``` - -The planner no longer depends on `WrapperDocstringBuilder`; codegen reads the -completed plan and renders presentation text without changing the plan. - -### `codegen/c/python_surface.py`: generated Python facade - -Derived classes are planned surfaces rendered as Python source embedded in the -extension module: - -```bash -python3 prik/codegen/c/python_surface.py -``` - -```text -Rendered Python facade: -_prik_unset = object() - -_prik_ops_state = {} -class State: - 'Opaque native state.' - __slots__ = ('_prik_capsule', '_prik_owner', '_prik_ops', '_prik_origin') - def __new__(cls, *args, **kwargs): - 'Construction is disabled.' - raise TypeError('State objects come from native code.') -def _prik_wrap_State(capsule, owner=None, ops=None, origin='direct'): - ... -``` - -The absent constructor, slots, operation map, and wrapper helper all come from -the class plan. The emitter does not inspect a Fortran derived type to decide -whether construction is allowed. - -### `codegen/c/binding.py`: CPython/NumPy node lowering - -The C binding example completes and plans a scalar function, then shows the -generated node mechanism: - -```bash -python3 prik/codegen/c/binding.py -``` - -```text -Native procedure: DOUBLE_VALUE -Native call slots: implicit:value -C module: binding_demo_wrapper -Header guard: BINDING_DEMO_WRAPPER_H -Header prototypes: wrap_double_value -Binding wrapper: wrap_double_value -Return type: PyObject * -Parameters: - self: PyObject * - args: PyObject * - kwargs: PyObject * -Body nodes: - CDeclaration(...) - ... - CReturn(expression=CodeExpression(text='result_obj')) -``` - -This module produces structured C, not final source text. Its specialized -methods dispatch from planned barrier, ownership, result, descriptor, and -lifecycle actions into concrete node sequences. - -### `codegen/fortran/bridge.py`: `bind(C)` node lowering - -The matching bridge consumes the same shared call plan: - -```bash -python3 prik/codegen/fortran/bridge.py -``` - -```text -Native procedure: DOUBLE_VALUE -Native call slots: implicit:value -Bridge module: bind_c_bridge_demo_wrapper -Module uses: - use iso_c_binding, only: ... c_double ... - use bridge_demo, only: native_double_value => DOUBLE_VALUE -Bridge procedure: bind_c_double_value -Binding name: bind_c_double_value -Procedure kind: function -Result: result :: real(c_double) -Parameters: - value: real(c_double), value -Body nodes: - FortranAssignment(target='result', expression=CodeExpression(text='native_double_value(value)')) -``` - -The shared slot becomes a value dummy and native function call. The bridge -selected no ownership behavior locally; it implemented the native barrier and -result ABI already present in the plan. - -Primary evidence: - -- `tests/fortran/infrastructure/codegen/` -- feature-local `tests/fortran/*/codegen/` -- generated-node and golden fixtures below those owners -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` -- `python3 tools/check_codegen_complexity.py` - -Start a new emitted-code mechanism in the narrow binding, bridge, or facade -emitter that owns it, and add a typed node only when existing nodes cannot -represent it. Extend the primitive catalogue only for an already established -semantic scalar identity. If implementing the mechanism requires choosing -ownership, storage, projection, setter exposure, or support, stop and add the -missing policy/plan fact upstream first. - -## Language Printers - -`prik/printers/` is the final representation-to-text boundary. C and Fortran -printers serialize backend syntax nodes; the semantic `.pyi` printer serializes -semantic IR into an editable contract. Printers own formatting, escaping, -indentation, declaration order, and safe line wrapping. They do not invoke -generators, choose filenames, complete policy, or compile their output. - -| Important file | Responsibility | -| --- | --- | -| `c.py` | Serializes C translation units, headers, declarations, functions, CPython tables, and statements. | -| `fortran.py` | Serializes bridge modules, interfaces, procedures, declarations, and statements while safely wrapping free-form lines. | -| `pyi.py` | Serializes semantic modules and their contract/projection metadata as compact editable semantic `.pyi`. | - -The package initializer only exports the three printer classes and the -`emit_module` convenience function. C and Fortran source orchestration belongs -to `pipeline/wrapper.py`; `.pyi` loading belongs to `pipeline/pyi.py`. - -### `printers/c.py`: C nodes to source - -The C printer receives a formed module tree and freezes it before rendering: - -```bash -python3 prik/printers/c.py -``` - -```text -Rendered C binding source: -#include - -static PyObject * wrap_ping(PyObject * self) { - Py_INCREF(Py_None); - return Py_None; -} -``` - -Everything in the output was already represented by nodes: include, storage -class, signature, expression statement, and return. The printer supplied only -valid C layout and punctuation. - -### `printers/fortran.py`: Fortran nodes to source - -The Fortran printer renders the matching bridge representation: - -```bash -python3 prik/printers/fortran.py -``` - -```text -Rendered Fortran bridge source: -module bind_c_printer_demo_wrapper - use iso_c_binding, only: c_double - use printer_demo, only: native_double_value => DOUBLE_VALUE - implicit none -contains - function bind_c_double_value(value) result(result) bind(c, name="DOUBLE_VALUE") - real(c_double), value :: value - real(c_double) :: result - result = native_double_value(value) - end function bind_c_double_value -end module bind_c_printer_demo_wrapper -``` - -The printer supplies free-form indentation and line-length enforcement. The -module imports, native alias, binding name, dummy attributes, and assignment -were selected by bridge generation. - -### `printers/pyi.py`: semantic IR to editable contract - -The `.pyi` printer works from semantic IR rather than wrapper syntax nodes: - -```bash -python3 prik/printers/pyi.py -``` - -```text -Semantic module: printer_demo -from prik.contracts import Float64, bind - -@bind("DOUBLE_VALUE") -def double_value( - value: Float64 -) -> Float64: ... -``` - -It derives required contract imports and preserves the native binding name in -editable Python syntax. Printing does not complete or attach wrapper policy; -the emitted contract can be edited and loaded through the `.pyi` pipeline. - -Primary evidence: - -- `tests/fortran/infrastructure/printers/` -- semantic `.pyi` round-trip tests in `tests/fortran/semantic_pyi_format/` -- generated-source goldens in feature-local `printers/` and `codegen/` owners -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` - -Start formatting or node-serialization changes in the language printer that -owns that representation. If required information is absent from a node, add -it to generation or the plan rather than consulting semantic IR from a native -source printer. Filename, multi-source ordering, and returned artifact changes -belong to the wrapper pipeline. - -## Workflow Pipeline - -`prik/pipeline/` composes complete workflows across established stage -boundaries. It may decide which stage runs next, preserve progress/timing, -assign artifact filenames, write generated payloads, and coordinate compilation -and linking. It does not absorb parser grammars, semantic rules, policy -decisions, backend lowering, printer formatting, or compiler command mechanics. - -| Important file | Responsibility | -| --- | --- | -| `pyi.py` | Loads semantic `.pyi` text/files/path sets, caches conversion, reconciles external types, supplies opaque dependency modules, completes copies, and emits stub packages. | -| `type_mapping_report.py` | Runs target probes through semantic conversion and codegen dtype projection to produce an auditable target-specific Markdown report. | -| `wrapper.py` | Freezes and validates one plan, renders docstrings, invokes both node generators and printers, assigns stable names, and returns `GeneratedWrapper`. | -| `build.py` | Owns public source/`.pyi` build APIs, generated-file writing, native input plans, dependency-ready compilation, linking, manifests, and `WrapperBuildResult`. | - -The package initializer describes the high-level namespace but intentionally -does not flatten all of these substantial workflows. Source preprocessing and -target measurement remain in `preprocessing`; reusable command execution -remains in `compiler`. - -### `pipeline/pyi.py`: combined contract loading - -This example crosses the intentionally separate parser, converter, policy, and -printer stages through one loader workflow: - -```bash -python3 prik/pipeline/pyi.py -``` - -```text -Loaded semantic module: math -Loaded contract marker: True -Functions: scale -Re-emitted module: -from prik.contracts import Float64 - -def scale( - value: Float64 -) -> Float64: ... -``` - -The loaded module retains workflow metadata. Stub emission deep-copies it, -completes policy on the copy, and uses the semantic printer, so the caller's -original editable semantic graph is not repurposed as a wrapper plan. - -### `pipeline/type_mapping_report.py`: end-to-end datatype explanation - -The report pipeline connects a measured native type to semantic and NumPy -representations: - -```bash -python3 prik/pipeline/type_mapping_report.py -``` - -```text -| `int` | signed 32-bit | `Int (Int32 storage)` | `numpy.int32` | -``` - -The exact width is target-dependent. The four columns make the stage changes -explicit: native spelling, probed target fact, stable semantic identity with -resolved storage, and codegen NumPy expression. This example requires `cc`. - -### `pipeline/wrapper.py`: plan to rendered artifact - -`WrapperGenerator` is the single owner of the plan-to-text workflow: - -```bash -python3 prik/pipeline/wrapper.py -``` - -```text -Extension initializer: PyInit_generator_demo -Rendered sources: bind_c_generator_demo_wrapper.f90, generator_demo_wrapper.c, generator_demo_wrapper.h -Native support: binding_support -``` - -The result is a `GeneratedWrapper` containing source payloads, stable paths, -compile-source grouping, required headers/support, and initializer identity. -Nothing has been written or compiled yet. - -### `pipeline/build.py`: source to imported extension - -The build example uses the public API to create and call a small extension: - -```bash -python3 prik/pipeline/build.py -``` - -```text -scale(3.0, 2.5) = 7.5 -``` - -Behind this concise result, the workflow preprocesses and parses source, -measures required target facts, constructs semantic IR, completes policy, -plans and renders the wrapper, writes temporary generated/native sources, -compiles dependency-ready objects, links an extension, imports it through -`WrapperBuildResult`, and calls the generated Python API. It requires the -configured C and Fortran compilers. The central example test retains the -`fortran_end_to_end` marker for this reason. - -Primary evidence: - -- `tests/fortran/semantic_pyi_format/pipeline/` -- `tests/fortran/data_types/pipeline/` -- `tests/fortran/infrastructure/pipeline/` -- `tests/fortran/building_shared_library/pipeline/` -- `tests/fortran/building_shared_library/compiling/` -- `tests/fortran/building_shared_library/end_to_end/` -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` - -Start `.pyi` batch/cache/dependency behavior in `pyi.py`, cross-stage datatype -reporting in `type_mapping_report.py`, plan validation/artifact assembly in -`wrapper.py`, and disk/compiler/link/manifest behavior in `build.py`. A pipeline -helper should delegate a domain rule to its owning stage instead of becoming a -second implementation of that rule. - -## Runtime and Bundled Native Support - -`prik/runtime/` contains the Python objects that remain active after a generated -extension has been imported. Its important entry file, `handles.py`, turns the -small operation dictionaries exported by generated bindings into stable -`AllocatableArray` and `PointerArray` APIs. It validates descriptor metadata, -retains required owners, adapts operation signatures, and exposes live NumPy -views according to completed policy. It does not decide ownership or invent -operations absent from the plan. - -`prik/runtime/native_support/` owns the header-only native runtime payload used -by generated bindings. Its Python initializer marks the payload as a package so -`compiler/native_support.py` can locate and install it. The generated build -still receives a `binding_support/` directory because that is the logical -include name emitted by the C binding backend; it is not the source package's -architectural location. These headers are native implementation assets, not an -independent Python workflow, so the folder intentionally has no `python3` -example. - -| Important file | Responsibility | -| --- | --- | -| `runtime/handles.py` | Adapts generated descriptor operations into validated allocatable/pointer handle objects and NumPy views. | -| `runtime/native_support/prik_binding.h` | Defines the header-only capsule, array-validation, release, and Python/NumPy conversion runtime used by generated C bindings. | -| `compiler/native_support.py` | Locates the runtime payload and installs it as generated `binding_support/`; its direct example was shown in Compiler Services. | - -### `runtime/handles.py`: generated operations to a stable handle - -The example provides the same kind of raw callable dictionary that a generated -extension installs: - -```bash -python3 prik/runtime/handles.py -``` - -```text -Runtime handle: AllocatableArray -Descriptor kind: allocatable -Initial view: [1.0, 2.0, 3.0] -Resized shape: (4,) -Generated resize received NumPy extents: True -``` - -The adapter selected `AllocatableArray` from the completed descriptor kind, -validated the declared dtype and rank, and converted `resize(4)` into the -generated operation's scalar `numpy.int64` extent convention. `to_numpy()` -returned the operation-provided live storage rather than a detached snapshot. -Consequently, callers must discard or copy outstanding views before native -deallocation, reallocation, or pointer reassociation; PRIK cannot revoke an -already exposed NumPy view. - -Primary evidence: - -- `tests/fortran/allocatables/runtime/` -- `tests/fortran/pointers/runtime/` -- `tests/fortran/memory_management/runtime/` -- `tests/fortran/infrastructure/runtime/` -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` - -Start handle protocol, validation, owner-retention, and operation-adapter -changes in `runtime/handles.py`. Start header discovery or installation changes -in `runtime/native_support/` and `compiler/native_support.py`, respectively. A -new ownership or view policy belongs in post-IR policy first; runtime should -only enforce the completed choice. - -## Shared Naming and Utilities - -`prik/naming/` owns names whose stability and collision rules are shared across -planning and code generation. `prik/utilities/` contains small mechanisms that -are genuinely independent of a compiler stage. Neither folder owns semantic -policy, syntax grammar, or workflow orchestration. - -| Important file | Responsibility | -| --- | --- | -| `naming/policy.py` | Normalizes public Python names, reserves namespace entries, and allocates language-safe generated names. | -| `naming/native_symbols.py` | Compacts long owner identities into deterministic, compiler-safe native symbol fragments. | -| `utilities/declaration_expressions.py` | Translates, validates, resolves, evaluates, and renders declaration extents across stage boundaries. | -| `utilities/strings.py` | Supplies minimal collision-safe generated-string helpers. | -| `utilities/visitor.py` | Supplies class-MRO dispatch shared by parsers, semantic converters, generators, and printers. | - -### `naming/policy.py`: public and target-language names - -```bash -python3 prik/naming/policy.py -``` - -```text -Normalized public name: render_value -Collision-safe public name: render_value_2 -C destructor symbol: state_drop -``` - -The first two lines show that Python-visible normalization and collision -reservation are namespace-aware. The final line shows a separate lowering rule: -a Python destructor is translated and prefixed into a valid C symbol. These are -naming decisions, not emitted C syntax. - -### `naming/native_symbols.py`: stable compact identities - -```bash -python3 prik/naming/native_symbols.py -``` - -```text -Owner identity: geometry.point.coordinates -Stable native symbol: point_coordinate_d_c2fc5940 -Within 27-character limit: True -``` - -The readable prefix assists generated-source inspection; the checksum retains -the full owner identity's contribution when the preferred spelling must be -shortened. Repeated calls with the same inputs return the same symbol. - -### `utilities/declaration_expressions.py`: one extent across stages - -```bash -python3 prik/utilities/declaration_expressions.py -``` - -```text -Fortran extent: ubound(source, 1) - lbound(source, 1) + 1 -Public expression: source.shape[0] -Role-bound expression: __prik_extent_source_0 -Fortran rendering: native_source_extent_0 -Compile-time product: 6 -``` - -This is deliberately a staged utility: a source expression becomes public -semantic syntax, then a validated role token, then backend text using a -plan-supplied substitution. Rendering does not rediscover which argument owns -the extent. The independent final line demonstrates compile-time integer -evaluation used while resolving declarations. - -### `utilities/strings.py`: collision-safe local identifiers - -```bash -python3 prik/utilities/strings.py -``` - -```text -First available name: temporary_4 -Next counter: 5 -``` - -The helper skips occupied candidates and returns both the selected name and the -next counter, allowing an emitter to allocate further local names without -rescanning from the beginning. - -### `utilities/visitor.py`: explicit model dispatch - -```bash -python3 prik/utilities/visitor.py -``` - -```text -Exact handler: literal:42 -MRO fallback: expression:Expression -``` - -The dispatcher first selects an exact `_` handler and then -walks the model class MRO for an intentional base-model fallback. Each consumer -still defines its own handlers; this utility does not merge the C, Fortran, -semantic `.pyi`, codegen, or printer visitor responsibilities. - -Primary evidence: - -- `tests/fortran/infrastructure/naming/` -- `tests/fortran/infrastructure/utilities/` -- `tests/fortran/arrays/semantics/test_declaration_expression_utilities.py` -- `tests/fortran/infrastructure/execution_examples/test_execution_examples.py` - -Start public normalization and language-rule changes in `naming/policy.py`, -stable owner-derived ABI fragments in `naming/native_symbols.py`, and only -stage-neutral mechanisms in `utilities/`. If a helper begins consulting a -completed policy or choosing a backend behavior, move that responsibility to -the owning policy, planning, or generation stage. - -## Contributor Documentation - -All project-maintenance material lives under `docs/developer/`, presented in -the site navigation as **Contributor Documentation**. There is no separate -maintainer audience or `docs/maintainer/` tree: contributors need the same -architecture, testing, release, internal-design, and roadmap information. - -| Area | Responsibility | -| --- | --- | -| `docs/user/` | Installation, usage, language support, tutorials, reference material, troubleshooting, and documented limitations for wrapper users. | -| `docs/developer/architecture.md` | Canonical package map, stage ownership, direct execution examples, change routes, and evidence owners. | -| `docs/developer/contributing/` | Contribution workflow, code of conduct, and security guidance. | -| `docs/developer/design/` | Detailed design constraints and rationale that supplement this package map. | -| `docs/developer/internal-architecture/` | Maintained implementation details for cross-stage internals. | -| `docs/developer/roadmap/` | Explicit planned or incomplete work, kept separate from implemented behavior. | -| `docs/developer/testing-strategy.md` | Test ownership, markers, focused-suite selection, and verification rules. | -| `docs/developer/development-workflow.md` | Local environment, edit, validation, and review workflow. | -| `docs/developer/release-process.md` | Maintainer release procedure within the shared contributor corpus. | - -The executable snippets in this guide are production-owned examples. Their -output contracts are grouped in -`tests/fortran/infrastructure/execution_examples/test_execution_examples.py`, with one -explicitly named test per demonstrated file. This keeps documentation tests -focused on links, structure, and publication while making code-output drift -fail beside the stage and feature suites. - -When adding another important entry file, give it a small real public-API flow -under `if __name__ == "__main__"`, document its command, representative output, -and architectural meaning here, then add -`test_fortran___execution_example` to the central inventory. Do -not add an example merely to enumerate every helper file. +docs/developer/ +├── index.md +├── architecture.md +├── source-map.md +├── feature-to-code-map.md +├── testing-strategy.md +├── packages/ # One detailed guide per production package +├── concepts/ # Cross-stage concepts +├── workflows/ # Contribution, QA, CI, docs, and releases +├── design/ # Accepted future architecture and open decisions +├── roadmap/ # Active incomplete work only +└── deferred/ # Intentionally unpublished input-language material +``` + +There is no separate maintainer tree. Completed migration logs and placeholder +pages are not maintained architecture; Git history retains them after their +still-valid decisions have moved to canonical guides. diff --git a/docs/developer/build-system.md b/docs/developer/build-system.md deleted file mode 100644 index b4dc6eb0e..000000000 --- a/docs/developer/build-system.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Build System -audience: developers, contributors -prerequisites: repository structure -related: testing-strategy.md, ../user/reference/configuration-files.md -status: planned-documentation -publication: draft ---- - -# Build System - -Reserved contributor page for Python packaging, native compilation, generated -makefiles, and documentation builds. - -## TODO - -- TODO: Document current build entrypoints and native toolchain assumptions. -- TODO: Add the documentation website build after the generator is selected. diff --git a/docs/developer/coding-standards.md b/docs/developer/coding-standards.md deleted file mode 100644 index e95d8aaaa..000000000 --- a/docs/developer/coding-standards.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Coding Standards -audience: contributors -prerequisites: repository structure -related: testing-strategy.md, quality-assurance.md -status: planned-documentation -publication: draft ---- - -# Coding Standards - -Reserved contributor page for Python style, linting, typing expectations, -documentation rules, and code organization. - -## TODO - -- TODO: Extract coding standards from existing contributor docs and static - analysis configuration. -- TODO: Include documentation front matter and placeholder rules. diff --git a/docs/developer/compiler-preprocessing.md b/docs/developer/compiler-preprocessing.md deleted file mode 100644 index 9f3daf9e2..000000000 --- a/docs/developer/compiler-preprocessing.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Compiler Preprocessing Reference -audience: developers, contributors -prerequisites: repository structure, native project compiler flags -related: source-map.md, c-parser-reference.md, fortran-parser-reference.md, ../user/examples/recipes/compiler-preprocessing.md -status: maintained -publication: draft ---- - -# Compiler-Backed Preprocessing Reference - -`prik/preprocessing/source.py` owns compiler-backed preprocessing for the wrapper -pipeline. The parsers consume one expanded source stream; they do not evaluate -CPP branches or emulate macro expansion. - -## Pipeline - -```text -source path - -> build preprocessing invocation - -> run compiler adapter - -> collect expanded stdout, linemarkers, macros, include files, diagnostics - -> expand remaining native Fortran INCLUDE statements - -> parse expanded source once -``` - -The compiler is authoritative for `#include`, `#define`, `#if`/`#ifdef`, -predefined macros, `-D`, `-U`, include paths, target flags, and sysroot -behavior. GNU Fortran does not preprocess files referenced by native Fortran -`include "file.inc"`, so prik expands those textually after compiler CPP -output. - -## Main Models - -- `PreprocessingConfig`: user/compiler configuration, adapter selection, - include exposure controls, and passthrough compiler arguments. -- `Invocation`: exact argv/cwd sent to the compiler adapter. -- `PreprocessResult`: expanded source plus recipe, included files, source - mappings, macro metadata, and preprocessing diagnostics. -- `IncludedFile`: include graph edge with mechanism, system/project - classification, and public/private exposure. -- `SourceMapping`: generated line to original file/line and include stack. -- `MacroDefinition`: active macro metadata when the adapter output exposes it. - -## Adapters - -Built-in adapters cover GCC-compatible C/Clang (`-E -x c`), GNU Fortran -(`-E -cpp`), compile database ingestion, and custom command templates for other -compiler families. A custom template must write expanded source to stdout: - -```bash -python -m prik parse include/api.h --language c --preprocess compiler \ - --preprocessor-adapter command-template \ - --preprocess-template 'vendor-cc --preprocess {include_dirs} {defines} {source}' -``` - -## Diagnostics - -Preprocessing errors use explicit categories and are printed by the CLI without -a traceback unless `--debug` is used: - -- `PREPROCESSOR_NOT_FOUND` -- `PREPROCESSOR_FAILED` -- `INVALID_COMPILER_ARGUMENTS` -- `UNSUPPORTED_COMPILER_CAPABILITY` -- `PROVENANCE_UNAVAILABLE` -- `INCLUDE_NOT_FOUND` -- `INCLUDE_CYCLE` - -## Include Exposure - -Root files and reachable project includes are public by default; system headers -are private. Use `--include-exposure roots-only`, `--public-include`, and -`--private-include` to control wrapper export. Private declarations remain -available to resolve public signatures, and private C handle types can be -emitted as opaque classes. diff --git a/docs/developer/internal-architecture/type-system.md b/docs/developer/concepts/datatype-lifecycle.md similarity index 97% rename from docs/developer/internal-architecture/type-system.md rename to docs/developer/concepts/datatype-lifecycle.md index bb867523e..274a385d5 100644 --- a/docs/developer/internal-architecture/type-system.md +++ b/docs/developer/concepts/datatype-lifecycle.md @@ -1,8 +1,8 @@ --- title: Datatype Lifecycle -audience: maintainers -prerequisites: pipeline map, semantic IR -related: pipeline-map.md, wrapper-generation-pipeline.md, ownership-tracking.md, ../../user/reference/semantic-ir.md +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, semantic IR +related: ../architecture.md, ../packages/preprocessing.md, ../packages/semantics.md, ../packages/codegen.md, ../../user/reference/semantic-ir.md status: maintained publication: draft --- @@ -293,7 +293,7 @@ planned element dtype for validation and descriptor metadata. A live zero-copy NumPy view can become stale after native deallocation, reallocation, or pointer reassociation. Datatype matching does not solve that -lifetime boundary; see [Ownership Tracking](ownership-tracking.md). +lifetime boundary; see the [Policy package](../packages/policy.md). ### Callbacks @@ -359,7 +359,10 @@ Primary evidence owners are: | Concern | Tests | | --- | --- | -| Target measurement | `tests/fortran/data_types/probes/` and `tests/c/probes/` | +| Target measurement | `tests/fortran/data_types/probes/` | + | Semantic scalar catalogue and conversion | `tests/fortran/data_types/semantics/`, semantic conversion tests | | Public contract factories | semantic `.pyi` contract tests | | Backend scalar catalogue | `tests/fortran/data_types/codegen/` | diff --git a/docs/developer/contributing/contribution-guide.md b/docs/developer/contributing/contribution-guide.md deleted file mode 100644 index e457feaea..000000000 --- a/docs/developer/contributing/contribution-guide.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Contribution Guide -audience: contributors -prerequisites: repository checkout -related: pull-request-workflow.md, ../index.md -status: maintained -publication: reviewed ---- - -# Contribution Guide - -The root [contribution guide](../../../CONTRIBUTING.md) defines the current -submission and verification requirements. - -## Contribution license - -prik is distributed under the MIT License. Contributions are accepted under -the same MIT terms. By submitting a contribution, a contributor agrees to -license it under those terms and represents that they have the right to do so. -Contributors whose work is owned by an employer or another organization must -obtain authorization before submitting it. - -## Change workflow - -Start by identifying the public behavior and its owning stage. Update the -relevant documentation before implementation, then change the implementation -and focused tests together. The [development workflow](../development-workflow.md) -maps common changes to their required evidence, and the -[pull request workflow](pull-request-workflow.md) covers submission and review. diff --git a/docs/developer/contributing/index.md b/docs/developer/contributing/index.md deleted file mode 100644 index aeb232853..000000000 --- a/docs/developer/contributing/index.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Contributing -audience: contributors -prerequisites: repository checkout -related: ../index.md, ../../../CONTRIBUTING.md -status: planned-documentation -publication: draft ---- - -# Contributing - -This section will collect contribution requirements and link to detailed -developer workflows. - -## Pages - -- [Contribution guide](contribution-guide.md) -- [Pull request workflow](pull-request-workflow.md) -- [Coding standards](../coding-standards.md) -- [Review process](review-process.md) - -## TODO - -- TODO: Keep contributor-facing rules separate from repository governance. -- TODO: Keep this section synchronized with `../../../CONTRIBUTING.md`. diff --git a/docs/developer/contributing/pull-request-workflow.md b/docs/developer/contributing/pull-request-workflow.md deleted file mode 100644 index d50c08007..000000000 --- a/docs/developer/contributing/pull-request-workflow.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Pull Request Workflow -audience: contributors -prerequisites: contribution guide -related: review-process.md, ../quality-assurance.md -status: planned-documentation -publication: draft ---- - -# Pull Request Workflow - -Reserved contributor page for branch preparation, tests, static analysis, -review, and merge expectations. - -## TODO - -- TODO: Document required local checks and CI gates. -- TODO: Add documentation update expectations for public behavior changes. diff --git a/docs/developer/contributing/review-process.md b/docs/developer/contributing/review-process.md deleted file mode 100644 index ebead7cb4..000000000 --- a/docs/developer/contributing/review-process.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Review Process -audience: developers, contributors -prerequisites: pull request workflow -related: pull-request-workflow.md, ../testing-strategy.md -status: planned-documentation -publication: draft ---- - -# Review Process - -Reserved contributor page for review expectations, requested changes, support -evidence, and documentation completeness. - -## TODO - -- TODO: Document review criteria for code, tests, docs, and architecture. -- TODO: Link feature review to language support and roadmap updates. diff --git a/docs/developer/c-parser-reference.md b/docs/developer/deferred/c-parser.md similarity index 99% rename from docs/developer/c-parser-reference.md rename to docs/developer/deferred/c-parser.md index 965f583ca..30cc199c2 100644 --- a/docs/developer/c-parser-reference.md +++ b/docs/developer/deferred/c-parser.md @@ -1,9 +1,9 @@ --- # PRIK_C_DOCS: title: C Parser Reference -title: Deferred Parser Reference -audience: developers -prerequisites: repository structure, parser architecture -related: adding-a-feature.md, repository-structure.md +title: Deferred C Parser Reference +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide +related: ../architecture.md, ../packages/parsers.md, ../packages/semantics.md status: maintained publication: draft --- @@ -800,7 +800,7 @@ declaration extensions are diagnosed rather than partially modeled; additional syntax diagnostics should be added only with focused tests. Generic grammar rejection uses `CPARSE_INVALID_SYNTAX`. Diagnostic codes are stable, explicit category identifiers for tests, tools, and documentation. The -shared registry is [`diagnostic-codes.md`](../user/reference/diagnostic-codes.md). +shared registry is [`diagnostic-codes.md`](../../user/reference/diagnostic-codes.md). PRIK_C_DOCS_END --> ## Testing Workflow @@ -1190,8 +1190,8 @@ PRIK_C_DOCS_END --> diff --git a/docs/developer/design/code-generation.md b/docs/developer/design/code-generation.md deleted file mode 100644 index 2fd6b74ee..000000000 --- a/docs/developer/design/code-generation.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Code Generation -audience: maintainers -prerequisites: semantic analysis -related: cpython-integration.md, runtime-model.md -status: planned-documentation -publication: draft ---- - -# Code Generation - -Reserved design page for lowering semantic IR into wrapper bridge and binding -artifacts. - -## TODO - -- TODO: Document supported code generation targets and deferred backend policy. -- TODO: Link dispatch tables, bridge generation, and binding generation to - internal architecture pages. diff --git a/docs/developer/design/cpython-integration.md b/docs/developer/design/cpython-integration.md deleted file mode 100644 index 3e0f792c8..000000000 --- a/docs/developer/design/cpython-integration.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -# PRIK_C_DOCS: title: CPython Integration -title: Deferred Python Extension Integration -audience: maintainers -prerequisites: code generation -related: runtime-model.md, error-propagation-model.md -status: planned-documentation -publication: draft ---- - - - - - - - - diff --git a/docs/developer/design/error-propagation-model.md b/docs/developer/design/error-propagation-model.md deleted file mode 100644 index f43f33450..000000000 --- a/docs/developer/design/error-propagation-model.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Error Propagation Model -audience: maintainers -prerequisites: runtime model -related: ../../user/guide/error-handling.md, cpython-integration.md -status: planned-documentation -publication: draft ---- - -# Error Propagation Model - -Reserved design page for diagnostic reporting, stage-owned errors, compiler -failures, runtime exceptions, and callback exceptions. - -## TODO - -- TODO: Link diagnostics and Python exceptions to troubleshooting pages. - - diff --git a/docs/developer/design/index.md b/docs/developer/design/index.md deleted file mode 100644 index 4ab6aff23..000000000 --- a/docs/developer/design/index.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Design Documents -audience: maintainers -prerequisites: developer documentation, user contracts -related: ../internal-architecture/index.md, semantic-multilanguage-wrapper-runtime-architecture.md -status: planned-documentation -publication: draft ---- - -# Design Documents - -Design documents record long-term technical decisions and proposals for all -contributors. They do not by themselves establish native binding support. - -## Pages - -- [Wrapper design notes](wrapper-design-notes.md) -- [Semantic multilanguage wrapper runtime architecture](semantic-multilanguage-wrapper-runtime-architecture.md) -- [Parser architecture](parser-architecture.md) -- [Semantic analysis](semantic-analysis.md) -- [Code generation](code-generation.md) -- [Runtime model](runtime-model.md) -- [Memory ownership model](memory-ownership-model.md) -- [Error propagation model](error-propagation-model.md) - - - -## TODO - -- TODO: Promote stable design explanations from existing notes into this tree. -- TODO: Keep design-only material clearly separated from supported user - behavior. diff --git a/docs/developer/design/memory-ownership-model.md b/docs/developer/design/memory-ownership-model.md deleted file mode 100644 index 0c451bb64..000000000 --- a/docs/developer/design/memory-ownership-model.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Memory Ownership Model -audience: maintainers -prerequisites: runtime model -related: ../../user/guide/memory-management.md, error-propagation-model.md -status: planned-documentation -publication: draft ---- - -# Memory Ownership Model - -Reserved design page for owner categories, transfer modes, lifetime invariants, -and blocked unsafe cases. - -## TODO - -- TODO: Promote the ownership model from the wrapper guide into a design - document. -- TODO: Link every owner category to runtime examples and tests. diff --git a/docs/developer/design/semantic-multilanguage-wrapper-runtime-architecture.md b/docs/developer/design/multilanguage-runtime.md similarity index 98% rename from docs/developer/design/semantic-multilanguage-wrapper-runtime-architecture.md rename to docs/developer/design/multilanguage-runtime.md index be5363940..af3498e85 100644 --- a/docs/developer/design/semantic-multilanguage-wrapper-runtime-architecture.md +++ b/docs/developer/design/multilanguage-runtime.md @@ -1,13 +1,18 @@ --- -title: Semantic Multilanguage Wrapper and Interoperability Runtime -audience: maintainers +title: Multilanguage Runtime Architecture +audience: developers, maintainers, contributors prerequisites: semantic IR reference, wrapper design notes -related: ../architecture.md, ../internal-architecture/wrapper-generation-pipeline.md +related: ../architecture.md, ../packages/semantics.md, ../packages/policy.md, wrapper-open-decisions.md status: design publication: draft --- -# Semantic Multilanguage Wrapper and Interoperability Runtime +# Multilanguage Runtime Architecture + +This is an accepted long-term design direction, not a statement that every +described backend or runtime component exists. The canonical description of +the implemented Fortran pipeline is the [architecture guide](../architecture.md) +and its [package guides](../packages/index.md). diff --git a/docs/developer/design/runtime-model.md b/docs/developer/design/runtime-model.md deleted file mode 100644 index 7e4f32007..000000000 --- a/docs/developer/design/runtime-model.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Runtime Model -audience: maintainers -prerequisites: CPython integration -related: memory-ownership-model.md, error-propagation-model.md -status: planned-documentation -publication: draft ---- - -# Runtime Model - -Reserved design page for runtime helper libraries, generated artifacts, native -calls, and wrapper execution. - -## TODO - -- TODO: Describe runtime helper responsibilities and generated artifact - boundaries. -- TODO: Document thread, callback, and OpenMP runtime considerations. diff --git a/docs/developer/design/semantic-analysis.md b/docs/developer/design/semantic-analysis.md deleted file mode 100644 index bfd3f7963..000000000 --- a/docs/developer/design/semantic-analysis.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Semantic Analysis -audience: maintainers -prerequisites: parser architecture -related: code-generation.md, ../../user/reference/semantic-ir.md -status: planned-documentation -publication: draft ---- - -# Semantic Analysis - -Reserved design page for conversion from parser facts into language-neutral -semantic IR and wrapper-planning errors. - -## TODO - -- TODO: Document semantic passes, normalization policy, and error boundaries. -- TODO: Link semantic behavior to `.pyi` and error-handling references. diff --git a/docs/developer/design/wrapper-design-notes.md b/docs/developer/design/wrapper-open-decisions.md similarity index 99% rename from docs/developer/design/wrapper-design-notes.md rename to docs/developer/design/wrapper-open-decisions.md index 45f04c8fd..14a476cbc 100644 --- a/docs/developer/design/wrapper-design-notes.md +++ b/docs/developer/design/wrapper-open-decisions.md @@ -1,13 +1,13 @@ --- -title: Wrapper Design Notes -audience: maintainers +title: Wrapper Open Decisions +audience: developers, maintainers, contributors prerequisites: Fortran wrapper reference, semantic IR reference -related: ../architecture.md, ../internal-architecture/wrapper-generation-pipeline.md +related: ../architecture.md, ../packages/policy.md, ../packages/planning.md, multilanguage-runtime.md status: design publication: draft --- -# Wrapper Design Notes +# Wrapper Open Decisions Reference details live in: -- `docs/developer/fortran-parser-reference.md` +- `docs/developer/packages/parsers.md` - `docs/user/reference/fortran-wrapper.md` - `docs/user/reference/semantic-ir.md` ## Known Semantic Gaps To Track diff --git a/docs/developer/development-workflow.md b/docs/developer/development-workflow.md deleted file mode 100644 index 53a84311c..000000000 --- a/docs/developer/development-workflow.md +++ /dev/null @@ -1,1588 +0,0 @@ ---- -title: Development Workflow -audience: developers, contributors -prerequisites: repository checkout, Python 3.10 or newer -related: index.md, quality-assurance.md -status: maintained -publication: draft ---- - -# Development Workflow - -This guide is for changing prik. It maps user-visible behavior to its owning -implementation and tests, then gives focused change and verification -workflows. - - - -## Start Here - -Install the project and QA dependencies: - -```bash -python3 -m pip install -e ".[qa]" -``` - -Run the smallest relevant test while iterating, then run the full suite: - -```bash -PYTHONPATH=. python3 -m pytest -q tests/fortran/command_line_interface/pipeline/ -PYTHONPATH=. python3 -m pytest -q -``` - -Before changing a public behavior, trace it through these layers: - - - -For example, a new CLI stage option normally requires: - -1. A focused contract test in `tests/fortran/command_line_interface/pipeline/`. -2. Dispatch or output routing in `prik/cli.py`. -3. Preprocessing tests if the option changes source loading. -4. A copy-paste command in the relevant user guide or checked example. -5. A tutorial update only when the main user workflow changes. - -## Support Evidence Rule - -Documentation must describe implemented behavior, not intended behavior. -Treat a support claim as established only when it is traceable to current -implementation plus one of these forms of evidence: - -- a focused test that proves the contract; -- a maintained fixture test that proves generated output; -- a repository command that has been run against a checked fixture; -- an explicit parser or semantic reference inventory backed by tests. - -Use these documentation roles consistently: - -| Document | Role | -| --- | --- | -| [Getting Started](../user/getting-started/index.md) | Main supported user workflow and boundaries | -| [Examples Gallery](../user/examples/index.md) | Checked commands and Python API recipes | -| [Fortran wrapper reference](../user/reference/fortran-wrapper.md) | Implemented Fortran runtime contract, mechanism, ownership, and build modes | -| [Fortran parser reference](fortran-parser-reference.md) | Developer inventory for the Fortran frontend | -| [Semantic IR reference](../user/reference/semantic-ir.md) | Accepted semantic IR and datatype contract | -| [Semantic .pyi format](../user/reference/semantic-pyi-format.md) | User-visible semantic `.pyi` syntax and roadmap | - - - -When adding a user example: - -1. Prefer a checked repository fixture or a short inline source string. -2. Run the command or snippet from the repository root. -3. Add or identify the focused test that owns the behavior. -4. State limitations next to the example when metadata is preserved but not - executed, such as `@native_call` projection metadata. - - - -### Automatically Verify Markdown Examples - -`tests/docs/test_examples.py` executes explicitly marked -`bash` CLI examples and `python` API snippets from `README.md` and Markdown -files under `docs/`. Bash examples must be `python3 -m prik` commands; the test replaces `python3` -with the active test interpreter and runs them without a shell. It rejects -shell operators, output-writing options, and options that select custom -executables or preprocessing command templates. Python snippets run with the -active test interpreter. - -Wrapper examples that need native compilation should use -`build_fortran_extension` with `TemporaryDirectory` so verification does not -leave build artifacts in the checkout. - -Mark a command that only needs to exit successfully: - -````markdown - -```bash -python3 -m prik semantics tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` -```` - -Mark a command whose stdout must match the documentation exactly: - -````markdown - -```bash -python3 -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - - -```text -File: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -... -``` -```` - -Use exact checks for stable human-readable output. Use run checks for large -JSON or semantic payloads whose detailed contract is already covered by -focused tests. The same markers can precede a `python` fenced block. Do not -mark placeholder commands, snippets that modify the checkout, -environment-dependent compiler recipes, or intentionally failing diagnostic -examples. - -When a command reads a checked fixture, include its source input in the user -documentation and verify the displayed source against the fixture: - -````markdown - -```fortran -module m1 -... -end module m1 -``` -```` - -Append a target profile to an exact marker only for compiler-generated output -that is intentionally architecture-specific: - -```markdown - -``` - -Off-target checks are skipped. The matching profile must still run the command -and compare its complete output. - -Run the documentation checks directly: - -```bash -PYTHONPATH=. python3 -m pytest -q tests/docs/test_examples.py -``` - -## References - -- [Getting Started](../user/getting-started/index.md): supported end-to-end user - workflow and current boundaries. -- [Examples Gallery](../user/examples/index.md): checked CLI and Python API - recipes. -- [Fortran parser reference](fortran-parser-reference.md): Fortran frontend scope, - recursive parser organization, API/CLI behavior, diagnostics, fixture - workflow, semantic handoff, and tests. -- [Semantic `.pyi` format](../user/reference/semantic-pyi-format.md): user-visible `.pyi` - loader/printer contract and roadmap. -- [Quality assurance](quality-assurance.md): active QA commands, tool benefits, known - defects found by each tool, and scheduled triage process. - - - -## User-Facing Contract Internals - -The tutorial, examples cookbook, `.pyi` format, and semantic reference describe -CLI stages, `.pyi` syntax, datatype names, and wrapper-plan diagnostics. The developer -task is to keep those user-visible contracts stable, tested, and traceable to -implementation files. - -### Source Ownership Map - -| User-visible area | Main implementation files | Main tests | -| --- | --- | --- | -| Fortran parse output | `prik/parsers/fortran/parser.py`, `prik/parsers/fortran/models.py`, `prik/parsers/fortran/lexer.py` | `tests/fortran/source_parsing/parsing/`, `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py`, `tests/fortran/source_parsing/parsing/test_error_handling.py` | -| CLI stage selection and output | `prik/cli.py`, `prik/parsers/fortran/cli.py` | `tests/fortran/command_line_interface/pipeline/` | -| Fortran target type probing and cache | `prik/preprocessing/probes/fortran_types.py` | `tests/fortran/data_types/probes/test_fortran_type_probes.py` | -| Generated target datatype mapping examples | `prik/pipeline/type_mapping_report.py` | `tests/fortran/data_types/pipeline/test_type_mapping_report.py`, `tests/docs/test_examples.py` | -| Fortran to semantic IR | `prik/semantics/fortran2ir.py`, `prik/semantics/models.py` | `tests/fortran/semantic_ir/semantics/` | -| `.pyi` printing | `prik/printers/pyi.py` | `tests/fortran/semantic_pyi_format/pipeline/`, `tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py` | -| `.pyi` parsing/loading/editing | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py` | `tests/fortran/semantic_pyi_format/` | -| Semantic policy completion | `prik/policy/completion.py`, `prik/policy/ownership.py` | `tests/fortran/infrastructure/semantics/` and feature-local `policy/` directories | -| Fortran wrapper orchestration | `prik/pipeline/build.py` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py` | -| Wrapper planning, owner-local errors, and direct lowering | `prik/planning/models.py`, `prik/planning/planner.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/codegen/`, feature-local `codegen/` stages | -| Native compilation and binding support | `prik/compiler/`, `prik/runtime/native_support/` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | -| Executable Markdown examples | `README.md`, `docs/*.md` | `tests/docs/test_examples.py` | - - - -### Wrapper Generator Class Organization - - - -Organize generators and printers using `FortranParser` in -`prik/parsers/fortran/parser.py` as the structural reference. A developer -should be able to read each class from top to bottom in the same order that -data moves through it: - -1. The class docstring states the class's responsibility and lists its method - sections. -2. Construction and public entrypoints come first. -3. Dispatched model handlers follow, grouped by feature and pipeline order. - Their names use the class's configured visitor prefix, for example - `_visit_`, `_print_`, or `_parse_`. -4. Helpers immediately follow the visitor group that owns them, or appear in - a final low-level helper section when several visitor groups share them. -5. Every method has a short contract docstring. The docstring explains the - method's purpose or invariant; it does not restate its name. - -Use the same visible section banners as `FortranParser`, for example -`Public entrypoints`, `Module visitors`, `Function visitors`, and `Shared -helpers`. Keep related visitors adjacent instead of sorting methods merely by -name. - -All model-type dispatch goes through `prik.utilities.visitor.ClassVisitor._visit` and a -matching `_` handler. Parser-model converters, semantic -lowering, `.pyi` AST visitors, bridges, bindings, and printers share that one -implementation; do not duplicate its MRO lookup in an individual class. - -An explicit table is allowed only for a genuine second dispatch dimension, -such as a completed policy action or primitive ABI datatype mapping. Such a -table must not replace model-class visitation. Do not add a second independent -visitor family, `visit_`, or scattered `isinstance` dispatch -schemes. -A method that performs ordinary work but is not a dispatch target must have a -descriptive helper name rather than a visitor-shaped name. - -Keep functionality on the class that owns its state and policy. A module-level -function is justified only when it is a deliberate public functional API or a -genuinely stateless utility shared by unrelated classes. Do not retain a -module-level function only to preserve an old internal call path. - -### `.pyi` Contract Internals - -User-visible `.pyi` syntax is first parsed to Python AST by -`prik/parsers/pyi/parser.py`, loaded from text/files by -`prik/pipeline/pyi.py`, converted to semantic IR by -`prik/semantics/pyi2ir.py`, and printed by -`prik/printers/pyi.py`. The converter and printer operate on -`prik/semantics/models.py`. - -Important implementation rules: - -- `Addr(T)` and `Addr(T)` are storage contracts, not just pretty syntax. -- Array subscriptions such as `Float64[n]` are semantic array contracts. -- `Annotated[..., ORDER_F]` and `ORDER_ANY` are non-default array storage - metadata. Plain multidimensional Fortran `.pyi` arrays use `ORDER_F`; do not - print or retain that default marker in a generated contract. - `Allocatable[T[...]]` and `Pointer[T[...]]` are descriptor-handle wrappers - around the array storage contract. Output and writeback behavior is - represented by writable storage plus `Returns["name", T]` when a Python - result is projected. - -- `Final[T]` is the public constant spelling. Do not reintroduce - `Constant` as user-facing `.pyi` syntax. -- `@native_call` is projection metadata. Use it only when the Python-visible - signature intentionally differs from the native signature. -- Generated stubs should preserve behavior-changing native contracts while - staying compact; exact source intent that does not change execution can stay - in semantic IR instead of the printed `.pyi`. -- Use `SourceName("...")` only when a source identifier cannot be used as the - Python target. Do not infer source identifiers from normalized Python names. -- Binding locals derived from a Python-visible argument must use the reserved - `bound_` namespace. Generated binding sources include Python, standard-library, - optional descriptor, NumPy, and runtime headers, so their imported identifier - sets are not a stable public-name vocabulary. -- Omit `Polymorphic` only for the passed-object dummy of a type-bound procedure, - where the binding itself restores that native fact. Ordinary `class(T)` - arguments must retain it. - -When changing `.pyi` syntax: - -1. Add or update parser tests in `tests/fortran/semantic_pyi_format/parsing/`. -2. Add or update printer tests in `tests/fortran/semantic_pyi_format/pipeline/`. -3. Update fixture tests only if the public generated contract changes. -4. Update the relevant [User Guide](../user/guide/index.md) or checked - [example](../user/examples/index.md) if users need to write or read the new - syntax. -5. Update [Semantic .pyi format](../user/reference/semantic-pyi-format.md) for the full user-facing reference. -6. Update [Semantic IR reference](../user/reference/semantic-ir.md) if the underlying semantic IR contract - changes. - -### Datatype Mapping Internals - -User-visible datatype names are semantic names, not raw parser spellings. -Mapping happens during parser-to-IR conversion: - -- Fortran intrinsic/kind mapping and compiler storage-fact application live in - `prik/semantics/fortran2ir.py`. -- The shared dtype names and storage contracts live in `prik/semantics/models.py`. -- Compiler-measured mapping snapshots are generated by - `prik/pipeline/type_mapping_report.py`. - -The complete ownership and lookup boundaries are documented for maintainers in -`docs/developer/internal-architecture/type-system.md`. - - - -When changing datatype mapping: - -1. Add focused Fortran conversion tests in - `tests/fortran/semantic_ir/semantics/`. -2. Add `.pyi` printer/loader coverage if the emitted syntax changes. -3. Update semantic fixtures only when serialized semantic IR intentionally - changes. -4. Update [Semantic IR reference](../user/reference/semantic-ir.md), plus the - relevant [User Guide](../user/guide/index.md) or checked - [example](../user/examples/index.md) when the visible user workflow or - examples change. -5. Regenerate and update the exact target mapping snapshots in - [Semantic IR reference](../user/reference/semantic-ir.md). The executable documentation test must match - the complete output of: - - - - ```bash - python3 -m prik probe --language fortran --compiler gfortran --format markdown - ``` - - - -For Fortran, keep both modern and legacy spellings in the generated report. -Legacy numeric `type*N` forms carry fixed total storage; compiler-dependent -default, kind, `DOUBLE PRECISION`, and `DOUBLE COMPLEX` forms use probe facts. - -### Error Ownership - -Diagnostics belong to the earliest stage that has enough facts to explain the -failure. Parsers report source syntax and preprocessing faults. Semantic -conversion reports facts that cannot form a valid contract. Policy completion -records every lowering decision; the wrapper planner reports an unsupported -completed policy with its owner path. Add focused tests to that owning stage, -and update the relevant user guide when a user can correct the input or -contract. - -### Parser To Wrapper Boundary - -Do not move wrapper policy into parsers. Parsers can preserve: - -- source locations; -- declaration and signature facts; -- type, pointer, array, callback, and aggregate facts; -- preprocessor provenance and diagnostics; -- unresolved references. - -Post-IR policy completion and wrapper planning decide: - -- ownership and lifetime; -- callback registration/unregistration policy; -- output-buffer projection; -- hidden pointer/size projection; -- ABI shim requirements; -- Python-visible signature adaptation. - -## Pipeline Internals - -The user-facing stages all start in `prik/cli.py`, but each stage owns a -different layer of the pipeline. - - - -### CLI And Language Resolution - -`prik/cli.py` is the shared command-line entrypoint. It is responsible for: - -- rejecting ambiguous directories and unknown suffixes without `--language`; -- building `PreprocessingConfig`; -- dispatching `parse`, `semantics`, `generate`, and `probe`; -- defaulting recognizable Fortran sources to a wrapper build when no - subcommand is selected; -- routing the default build and `generate --sources|--makefile` through - `prik/pipeline/build.py`; -- routing text, JSON, and `--out` output. - - - - - -The package-specific `prik/parsers/fortran/cli.py` remains for the Fortran parser -package entrypoint. New cross-language user behavior normally belongs in -`prik/cli.py`. - -### Preprocessing Internals - -`prik/preprocessing/source.py` owns compiler-backed preprocessing and provenance. The -main value object is `PreprocessingConfig`; the main execution path is -`run_compiler_preprocessor_with_recipe(...)`. - -Important contracts: - -- The preprocessing recipe is part of the parser payload when preprocessing - happened. It records compiler, adapter, argv, include directories, defines, - undefs, standard, extra compiler args, included files, source mappings, and - diagnostics. - - - - - -### Source Loading To Semantic IR Paths - -Keep source loading, parser models, and semantic conversion separate. Semantic -converters accept parsed models; they must not hide compiler preprocessing or -source loading inside conversion helpers. - -Fortran direct Python API, no CPP/FPP macros: - -```python -from prik import parse_fortran_file -from semantics.fortran2ir import fortran_module_to_semantic_module - -parsed = parse_fortran_file(source, filename="visibility_mod.f90") -semantic = fortran_module_to_semantic_module(parsed.modules[0]) -``` - -`parse_fortran_file(...)` runs the parser's internal line preparation: -source-form detection, comment stripping, and continuation folding. It does -not expand `#define`, `#ifdef`, or other CPP/FPP directives. Raw CPP/FPP -directives are rejected with `PARSE_PREPROCESSING_REQUIRED`. - -Fortran with macros or textual configuration must be compiler-preprocessed -before parsing: - -```python -from pathlib import Path - -from prik import parse_fortran_file -from semantics.fortran2ir import fortran_file_to_semantic_modules -from prik.preprocessing import PreprocessingConfig, preprocess_source - -path = Path("configured.F90") -preprocessed = preprocess_source( - path, - language="fortran", - config=PreprocessingConfig( - mode="compiler", - compiler="gfortran", - defines=["USE_MPI", "N=32"], - include_dirs=["include"], - ), -) - -parsed = parse_fortran_file(preprocessed.source, filename=str(path)) -modules = fortran_file_to_semantic_modules(parsed) -``` - -Choose the Fortran semantic helper from the parser model shape: - -- `fortran_module_to_semantic_module(parsed.modules[0])` for one selected - module. -- `[fortran_module_to_semantic_module(m) for m in parsed.modules]` when a file - contains multiple modules and no top-level standalone procedures matter. -- `fortran_file_to_semantic_modules(parsed, standalone_module_name=...)` when - top-level procedures should become a synthetic semantic module too. -- `fortran_project_to_semantic_modules(project)` when project-level module and - derived-type context matters. - -Fortran `parameter` values and kind expressions are not CPP macros. If the -parser leaves a Fortran compile-time expression symbolic, collect missing -values with `collect_semantic_compile_time_requirements(parsed)`, evaluate -them with the target compiler or a reusable type report, and pass -`compile_time_values=...` to the semantic converter. The shared CLI semantic -stage performs this target probing when a Fortran compiler or report is -configured; direct API callers must do it explicitly. - - - - - - - - - - - - - - - -### Semantic, `.pyi`, Wrapper-Planning, And Type-Probe Paths - - - -Input shapes are part of the contract: - -- `parse_fortran_file(source_or_path, filename=...)` accepts inline source - text. It reads from disk only when `source_or_path` names an existing file - and `filename` is omitted. Pass `filename` with inline text for diagnostic - provenance. -- `preprocess_source(path, language=..., config=...)` is path-based because it - shells out to a compiler. Feed `preprocessed.source` to the parser afterward. -- `parse_pyi_text(...)` accepts inline `.pyi` source text and returns Python - AST. `convert_pyi_to_ir(...)` converts that parsed AST to semantic IR. - `pyi_text_to_semantic_module(...)`, `pyi_file_to_semantic_module(...)`, and - `pyi_paths_to_semantic_modules(...)` combine parsing and conversion for - inline text, one file, or a file set. -- The CLI accepts source, `.pyi`, and directory paths. It does not accept - inline source text on the command line. - - - -CLI source stages: - - - -CLI `.pyi` wrapper build: - -```text -.pyi path(s) or directory - -> prik/parsers/pyi/parser.py - -> prik/pipeline/pyi.py pyi_paths_to_semantic_modules(...) - -> prik/semantics/pyi2ir.py - -> SemanticModule list - -> prik/policy/completion.py - -> complete_semantic_policies(...) - -> WrapperPlanner.build(...) -``` - -Generating `.pyi` from source is semantic conversion plus printing. In Python -API code, keep those calls visible: - -```python -from prik import emit_module_stubs, parse_fortran_file -from semantics.fortran2ir import fortran_file_to_semantic_modules - -parsed = parse_fortran_file(source, filename="api.f90") -modules = fortran_file_to_semantic_modules(parsed) -stubs = emit_module_stubs(modules) -``` - - - -Loading or editing `.pyi` is the opposite direction: - -```python -from prik import pyi_paths_to_semantic_modules - -modules = pyi_paths_to_semantic_modules("interfaces") -``` - -Use the `.pyi` helpers by input shape: - -- `parse_pyi_text(source, filename=...)` from `prik.parsers.pyi` for parser-only - AST parsing. -- `convert_pyi_to_ir(tree, module_name=..., source=...)` from `pyi2ir.py` for - AST-to-IR conversion. -- `pyi_text_to_semantic_module(source, module_name=..., filename=...)` from - `pyi_pipeline.py` for inline text. -- `pyi_file_to_semantic_module(path, module_name=...)` for one file. -- `pyi_paths_to_semantic_modules(paths_or_directory)` for a set of interfaces - that may reference each other. - -The `.pyi` pipeline uses a per-operation in-memory conversion cache. Wrapper -entry-contract discovery reuses the same converted modules when it later builds -the reconciled contract bundle, so an imported file is not parsed and converted -twice in one build. Do not make this cache process-global: semantic modules are -mutated by reconciliation, export selection, and policy completion. - - - -Compiler preprocessing flags all flow through `PreprocessingConfig`: - -| CLI flag | `PreprocessingConfig` field | Notes | -| --- | --- | --- | -| `--compiler` | `compiler` | Exact executable for direct preprocessing and automatic type probes. | -| `--preprocessor-adapter` | `adapter` | Adapter family, including `command-template`. | -| `--preprocess-template` | `command_template` | Custom command; requires `--preprocessor-adapter command-template`. | -| `-I` / `--include-dir` | `include_dirs` | Passed to compiler preprocessing and native Fortran include expansion. | -| `-D` / `--define` | `defines` | Macro definitions for compiler preprocessing. | -| `-U` / `--undef` | `undefs` | Macro undefinitions for compiler preprocessing. | -| `--std` | `std` | Passed as `-std=...`. | -| `--compiler-arg` | `compiler_args` | Raw target/sysroot/compiler options. | -| `--public-include`, `--private-include`, `--include-exposure` | include exposure fields | Controls provenance exposure, not parser grammar. | - - - - - - - - - - - -Fortran target datatype mapping and compile-time path: - -```text -Fortran source - -> parse_fortran_file(...) - -> collect_semantic_compile_time_requirements(...) - -> evaluate_fortran_type_requirements(...) - -> collect_fortran_type_storage_requirements(...) - -> evaluate_fortran_type_facts(...) - -> fortran_module_to_semantic_module(..., compile_time_values=..., type_facts=...) -``` - - - - - -### Fortran Runtime Wrapper Path - -`prik/pipeline/build.py::build_fortran_extension(...)` and -`prik/pipeline/build.py::build_pyi_extension(...)` are the public orchestration -boundaries for wrapper builds. Keep their stages explicit: - -```text -ordered source paths - -> preprocess_source(..., language="fortran") - -> parse_fortran_project(...) - -> compile-time expression and storage probes - -> fortran_project_to_semantic_modules(...) - -> merge public semantic modules - -> WrapperPlanner and WrapperGenerator - -> create_shared_library(...) - -> WrapperBuildResult -``` - -The main ownership boundaries are: - -- `prik/pipeline/build.py`: source order, preprocessing/probing, semantic merge, - `.pyi` entry-contract loading, native build plan assembly, output placement, - direct-versus-Makefile mode, and artifact reporting; -- `prik/planning/planner.py`: projection from completed semantic policy - into validated typed plans; -- `prik/pipeline/wrapper.py`: direct bridge, binding, and source - artifact generation; -- `prik/compiler/`: compiler commands and shared-library linking; and -- `prik/runtime/native_support/`: native runtime support copied into each build as `binding_support/`. - - - -Do not move semantic ownership or projection policy into printers. Do not infer -source dependencies: multi-source source builds compile in caller order, and -the first semantic module names the merged extension. `.pyi` builds use exactly -one semantic entry contract plus a separate extension-level -`NativeBuildPlan`; they must not recover Python API facts by reparsing native -implementation sources. `--makefile` records the compiler/linker plan -without executing it; for `.pyi` builds, `prik-build.json` is written first and -`Makefile.prik` is projected from that manifest. - - - - - -Runtime verification belongs under the relevant -`tests/fortran//end_to_end/` owner. The -[`tests/fortran` index](../../tests/fortran/README.md) and permanent -[contract ledger](../../tests/fortran/CONTRACT_COVERAGE.md) map generated -behavior to compiled/imported tests. Build-mode changes should at least cover -`tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, -`tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py`, -and the affected runtime subject test. - -### Parser Model Internals - -Parser models are source facts. They should answer "what did the source say?" -rather than "what Python wrapper should be generated?" - -Fortran: - -- `prik/parsers/fortran/parser.py` slices the file into grammar units, then parses - each unit's specification region. -- `prik/parsers/fortran/models.py` stores `FortranFile`, modules, procedures, - variables, derived types, interfaces, programs, submodules, and diagnostics. -- Execution bodies are intentionally skipped after the parser has enough - signature/source facts. - - - - - -Adding parser fields is a schema decision. Add fields only when downstream -semantic conversion, fixtures, diagnostics, or user-visible behavior need a -new fact. - -### Semantic IR Internals - -The semantic layer normalizes Fortran facts into language-neutral models from -`prik/semantics/models.py`. - - - -- `prik/semantics/fortran2ir.py` maps Fortran procedures, derived types, module - variables, kinds, shapes, storage contracts, visibility, imported references, - and compile-time values. -- `prik/printers/pyi.py` emits editable user contracts. -- `prik/parsers/pyi/parser.py` parses edited contracts to Python AST. -- `prik/pipeline/pyi.py` converts edited contract text, files, and path sets. -- `prik/semantics/pyi2ir.py` converts parsed `.pyi` AST back into semantic IR. -- `prik/semantics/native_contract.py` validates immutable native scope, ABI, - placement, type, callback, and projection facts before source-free codegen. -- Named data bindings keep role-specific semantic types: `SemanticVariable` - for module variables and constants, `SemanticArgument` for callable - parameters, and `SemanticField` for Fortran derived-type components. -- `prik/policy/completion.py` completes semantic policies after - Fortran or `.pyi` conversion and before wrapper planning or lowering. - - - - - -Keep semantic IR stable where possible. If a parser change does not affect the -semantic contract, avoid changing semantic fixtures. - -### `.pyi` Projection Internals - -`@native_call` is stored as projection metadata on `SemanticFunction`. The -loader and printer currently support `Arg`, `Return`, ABI-typed literal calls -such as `Int32(1)`, `Len`, `IsPresent`, `Work`, `Pass`, and `.shape[...]` -value references. Generated Fortran contracts use it when outputs make the -Python-visible argument order differ from native order. `Pass()` preserves the -hidden passed object when a type-bound method also needs such a projection. They do not currently -implement future wrapper projection helpers such as `Addr(Arg(...))`, `As[...]`, -status-return policy, ownership conversion, or coercion execution. - -The test ownership is: - -- loader syntax and error behavior: `tests/fortran/semantic_pyi_format/parsing/`; -- printer round-trip shape: `tests/fortran/semantic_pyi_format/pipeline/`; -- policy-completion decisions: `tests/fortran/infrastructure/semantics/` and feature-local `policy/` directories; -- wrapper-plan diagnostics: `tests/fortran/infrastructure/codegen/`. - - - -When adding projection syntax, first add loader tests that prove the accepted -syntax and rejected syntax. Then add policy or wrapper-plan tests only if the -new metadata affects those layers. - -## Testing Strategy - -Use the smallest test layer that proves the behavior, then add broader -coverage only when the public contract changes. - -### Test Layers - -| Layer | Purpose | Typical files | -| --- | --- | --- | -| Focused parser tests | One construct, diagnostic, or model field | `tests/fortran/source_parsing/parsing/test_*.py` | -| Parser fixture goldens | Serialized Fortran parser contracts | `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` | -| Semantic tests | Fortran parser facts converted to wrapper-neutral IR | `tests/fortran/semantic_ir/semantics/` | -| Policy tests | Completed policy decisions | `tests/fortran/infrastructure/semantics/` and feature-local `policy/` directories | -| Wrapper-plan tests | Unsupported plan diagnostics and generated plan shape | `tests/fortran/infrastructure/codegen/` | -| `.pyi` tests | Editable contract loader/printer behavior | `tests/fortran/semantic_pyi_format/`, `tests/fortran/semantic_pyi_format/pipeline/` | -| CLI tests | User commands, output routing, diagnostics | `tests/fortran/command_line_interface/pipeline/`, `tests/fortran/source_preprocessing/preprocessing/` | -| Wrapper build tests | Artifact placement, direct/Makefile modes, multi-source ordering | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py` | -| Wrapper runtime tests | Imported extension behavior, ownership, lifetime, and failures | Feature-local `tests/fortran/*/end_to_end/` suites indexed by `tests/fortran/README.md` | -| Property/fuzz tests | Broad parser robustness invariants | `tests/fortran/source_parsing/parsing/` and feature-local semantic property tests | - - - - - -### Choosing Tests For A Change - -- Parser-only source fact: focused parser test first; fixture golden only if - serialized output changes intentionally. -- CLI flag or output change: CLI test first; update README/user docs if the - visible command changes. -- New datatype mapping: semantic conversion test plus `.pyi` printer/loader - tests if emitted syntax changes. -- New `.pyi` syntax: loader and printer tests, plus policy or plan tests when - it changes a completed decision or lowering. -- New unsupported case: a semantic-conversion, policy, or wrapper-plan test at - the stage that detects it. -- Preprocessing behavior: preprocessing CLI tests and at least one parser path - that consumes the recipe. -- Wrapper orchestration or codegen behavior: the focused feature-local - `end_to_end/` or `codegen/` owner, including an imported runtime - assertion rather than build success alone. - -### Golden Fixture Rules - -Do not regenerate broad fixture sets to hide uncertainty. First write or run a -focused test that explains the intended behavior. Then regenerate only the -affected fixture group when the serialized contract really changed. - -Useful commands: - - - -### Coverage And CI Parity - -When investigating coverage failures, mirror the GitHub Actions coverage flow -instead of relying on a plain local run: - -```bash -COVERAGE_PROCESS_START=pyproject.toml PYTHONPATH=. coverage run -m pytest -python -m coverage combine -python -m coverage report -``` - -The `COVERAGE_PROCESS_START` environment variable matters because subprocess -CLI tests need the same coverage configuration as CI. - -## Feature Change Walkthroughs - -Use these walkthroughs when adding behavior. They are deliberately procedural: -change the smallest owned layer first, test that layer, then update downstream -contracts only when the public behavior actually changes. - - - - - - - - - - - - - - - -### Add A Fortran Parser Feature - -Example target: preserve a new declaration attribute, source fact, or argument -metadata item. - -1. Add a focused parser test in the file that owns the behavior: - `tests/fortran/source_parsing/parsing/`, - `tests/fortran/modules/parsing/test_scope_handling.py`, or - `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py`. -2. Implement parsing in `prik/parsers/fortran/parser.py`. Add model fields in - `prik/parsers/fortran/models.py` only if the parser output needs to expose the - new fact. -3. Add parser diagnostic coverage in `tests/fortran/source_parsing/parsing/test_error_handling.py` if - malformed source should now fail differently. -4. If project ordering, imports, or compile-time values change, update - `tests/fortran/modules/parsing/test_project_scope_models.py` or - `tests/fortran/data_types/probes/test_fortran_type_probes.py`. -5. If serialized parser JSON changes intentionally, regenerate the selected - fixture: - - ```bash - python tests/fortran/source_parsing/parsing/generate_parser_goldens.py tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 - ``` - -6. If the new fact affects semantic output, update `prik/semantics/fortran2ir.py` - and `tests/fortran/semantic_ir/semantics/`. -7. If generated `.pyi` changes, update `tests/fortran/semantic_pyi_format/pipeline/` - and the relevant fixture tests. -8. Update [Fortran parser reference](fortran-parser-reference.md), the relevant - [User Guide](../user/guide/index.md), checked - [example](../user/examples/index.md), or - [Semantic IR reference](../user/reference/semantic-ir.md) as needed. - -Focused verification: - -```bash -PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/ -PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py -PYTHONPATH=. pytest -q tests/fortran/semantic_ir/semantics/ -``` - -### Add Or Change Datatype Mapping - -Example target: map a new Fortran kind or compiler-probed storage fact. - - - -1. Add conversion coverage in `tests/fortran/semantic_ir/semantics/`. -2. Implement the mapping in `prik/semantics/fortran2ir.py`. -3. Keep the public semantic dtype names in `prik/semantics/models.py` stable unless - there is a deliberate schema decision. -4. If the emitted `.pyi` annotation changes, update - `tests/fortran/semantic_pyi_format/pipeline/` and - `tests/fortran/semantic_pyi_format/parsing/`. -5. Update the datatype tables in - [Semantic IR reference](../user/reference/semantic-ir.md), and update the - relevant [User Guide](../user/guide/index.md) or checked - [example](../user/examples/index.md) when a visible example changes. - - - - - -Focused verification: - -```bash -PYTHONPATH=. pytest -q tests/fortran/semantic_ir/semantics/ -PYTHONPATH=. pytest -q tests/fortran/semantic_pyi_format/pipeline/ tests/fortran/semantic_pyi_format/parsing/ -``` - - - -### Add `.pyi` Syntax Or Projection Behavior - -Example target: add a new `Annotated[...]` metadata item or projection helper. - -1. Add loader tests in `tests/fortran/semantic_pyi_format/parsing/`. -2. Update `prik/semantics/pyi2ir.py`. Update `prik/pipeline/pyi.py` - when loading or cross-file reconciliation changes. Update - `prik/parsers/pyi/parser.py` only when the raw Python AST parsing boundary - changes. -3. Add printer tests in `tests/fortran/semantic_pyi_format/pipeline/`. -4. Update `prik/printers/pyi.py`. -5. Update semantic models in `prik/semantics/models.py` only if the IR needs a new - field or constraint. -6. Update policy completion or wrapper planning if the syntax changes a - completed decision. -7. Update [Semantic IR reference](../user/reference/semantic-ir.md), plus the - relevant [User Guide](../user/guide/index.md) or checked - [example](../user/examples/index.md) when users need the new syntax in a - workflow. - -Focused verification: - -```bash -PYTHONPATH=. pytest -q tests/fortran/semantic_pyi_format/parsing/ -PYTHONPATH=. pytest -q tests/fortran/semantic_pyi_format/pipeline/ -PYTHONPATH=. pytest -q tests/fortran/semantic_ir/semantics/ tests/fortran/infrastructure/codegen/ -``` - -### Add A Stage-Owned Error - -Example target: report a new unsupported Fortran semantic contract clearly. - - - -1. Preserve the source fact in the parser if it is not already present. -2. Raise a semantic-conversion error when no valid contract can be formed; do - not attach a deferred diagnostic payload. -3. If the source facts are valid but a selected wrapper behavior is unsafe, - express that result in completed policy and let the planner name the owner - path and reason. -4. Add a focused conversion, policy, or wrapper-plan test at that owning - stage. -5. Update the relevant user guide and [Error Handling](../user/guide/error-handling.md) - when users can correct the source or edited `.pyi` contract. - -Focused verification: - -```bash -PYTHONPATH=. pytest -q tests/fortran/semantic_ir/semantics/ -PYTHONPATH=. pytest -q tests/fortran/semantic_ir/semantics/ -PYTHONPATH=. pytest -q tests/fortran/infrastructure/codegen/ -``` - - - -### Add Or Change CLI Behavior - -Example target: add a stage option, change output routing, or improve -diagnostic formatting. - -1. Add CLI tests in `tests/fortran/command_line_interface/pipeline/` first. -2. Implement shared dispatch and output behavior in `prik/cli.py`. -3. Keep Fortran package-specific CLI behavior in `prik/parsers/fortran/cli.py`. -4. If compiler preprocessing behavior changes, update `prik/preprocessing/source.py` - and preprocessing tests. -5. Update the relevant [User Guide](../user/guide/index.md) or checked - [example](../user/examples/index.md) for user-facing commands and this guide - for developer command maps. - -Focused verification: - -```bash -PYTHONPATH=. pytest -q tests/fortran/command_line_interface/pipeline/ -PYTHONPATH=. pytest -q tests/fortran/source_preprocessing/preprocessing/ -``` - -## Testing Map - -Use this map when changing one part of the project. Each section shows how to -call that part manually, which focused test file to run, and where to look for -more executable examples. Run the broader suite before merging. - -### Pre-Merge Checks - -Run the ordinary suite from the repository root before merging. Full -BLAS/LAPACK cases belong to their designated real-library lane: - -```bash -PYTHONPATH=. pytest -q -m "not real_library" \ - tests/c tests/docs tests/fortran tests/tools tests/workflows -``` - -Run the major suites individually while iterating: - -```bash -PYTHONPATH=. pytest -q tests/c -PYTHONPATH=. pytest -q -m "not real_library" tests/fortran -PYTHONPATH=. pytest -q tests/docs -PYTHONPATH=. pytest -q tests/tools -PYTHONPATH=. pytest -q tests/workflows -``` - -Maintainer-tool tests, workflow-safety tests, focused documentation smoke, one -compiled scalar-wrapper smoke test, and blocking static analysis run locally -before every push. Enable the tracked hook once in each clone: - -```bash -git config core.hooksPath .githooks -``` - -The hook runs static-analysis version validation, Ruff lint and formatting, -the codegen-complexity policy, Bandit, Vulture, the changed-code Radon policy, -the publication and user-content documentation smoke tests, one small public -CLI-to-native-call wrapper test, `tests/tools/`, and `tests/workflows/`. It -rejects the push on the first failure. GitHub Actions runs these checks again -as the shared enforcement boundary alongside the required product, complete -documentation, compiler, coverage, and real-library checks. The slower -documentation validators, verbose advisory Radon reports, and broader compiled -smoke matrix remain outside the quick local hook. - -As a project policy, do not merge pull requests unless all checks are green. - -### Fixture Maintenance - - - - - - - - - -Refresh all Fortran parser goldens: - -```bash -python tests/fortran/source_parsing/parsing/generate_parser_goldens.py -``` - -Refresh one Fortran fixture: - -```bash -python tests/fortran/source_parsing/parsing/generate_parser_goldens.py tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -In-test Fortran parser fixture update mode: - -```bash -FORTRAN_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q \ - tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py -``` - -Refresh semantic and `.pyi` fixtures: - -```bash -python tests/fortran/semantic_ir/semantics/generate_semantic_fixtures.py -WRAPPER_UPDATE_PYI_FIXTURES=1 python3 -m pytest -q tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py -``` - -When parser model output changes, include the regenerated parser goldens and a -short explanation in the PR. For `.pyi`, semantic IR, policy, or wrapper-planning behavior -changes, update the reviewed contracts under -`tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/` or -the semantic fixtures under `tests/fortran/semantic_ir/semantics/fixtures`. - - - - - - - - - - - - - - - - - - - - - -### Fortran Parser - -Manual call for one Fortran fixture: - -```bash -python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --language fortran --json -``` - -Manual Python API call: - -```python -from prik import parse_fortran_file - -parsed = parse_fortran_file( - "tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90", -) -print([module.name for module in parsed.modules]) -``` - -Focused tests by concern: - -- Parser walkthrough: - `PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_developer_tutorial.py` -- Procedures, declarations, derived types, and interfaces: - `PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/` -- Scope and project behavior: - `PYTHONPATH=. pytest -q tests/fortran/modules/parsing/test_scope_handling.py tests/fortran/modules/parsing/test_project_scope_models.py` -- Preprocessing and execution-boundary behavior: - `PYTHONPATH=. pytest -q tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` -- Parser diagnostics: - `PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_error_handling.py` -- Fixture goldens: - `PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` -- Parser error fixtures: - `PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_error_fixture_suite.py` - -Regenerate one Fortran fixture: - -```bash -python tests/fortran/source_parsing/parsing/generate_parser_goldens.py tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -Executable tutorial: `tests/fortran/source_parsing/parsing/test_developer_tutorial.py`. - -### Semantics And `.pyi` - -Manual calls: - - - -Focused tests by concern: - -- Fortran parser-to-IR conversion: - `PYTHONPATH=. pytest -q tests/fortran/semantic_ir/semantics/` -- Wrapper-plan support diagnostics: - `PYTHONPATH=. pytest -q tests/fortran/infrastructure/codegen/` -- `.pyi` printer: - `PYTHONPATH=. pytest -q tests/fortran/semantic_pyi_format/pipeline/` -- `.pyi` loader and edited stub behavior: - `PYTHONPATH=. pytest -q tests/fortran/semantic_pyi_format/parsing/` -- Semantic and `.pyi` fixtures: - `PYTHONPATH=. pytest -q tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py` - - - -Regenerate semantic and `.pyi` fixtures: - -```bash -python tests/fortran/semantic_ir/semantics/generate_semantic_fixtures.py -WRAPPER_UPDATE_PYI_FIXTURES=1 python3 -m pytest -q tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py -``` - -Executable examples: `tests/fortran/semantic_pyi_format/pipeline/` and -`tests/fortran/semantic_pyi_format/parsing/`. - -### CLI - -Manual calls: - - - -Focused tests: - -- Full CLI behavior: - `PYTHONPATH=. pytest -q tests/fortran/command_line_interface/pipeline/` -- Stage dispatch: - `PYTHONPATH=. pytest -q tests/fortran/command_line_interface/pipeline/ -k "parse or semantics or pyi or wrap"` -- Language and preprocessing selection: - `PYTHONPATH=. pytest -q tests/fortran/command_line_interface/pipeline/ -k "language or preprocessing"` - -Executable reference: `tests/fortran/command_line_interface/pipeline/`. diff --git a/docs/developer/feature-to-code-map.md b/docs/developer/feature-to-code-map.md index 084b59b61..dc77c092e 100644 --- a/docs/developer/feature-to-code-map.md +++ b/docs/developer/feature-to-code-map.md @@ -17,35 +17,34 @@ before documentation may call the behavior supported. | Feature or behavior | Public docs | Main implementation files | Focused tests | Support evidence | | --- | --- | --- | --- | --- | -| Fortran parse output | `docs/developer/fortran-parser-reference.md` | `prik/parsers/fortran/parser.py`, `models.py`, `lexer.py`, `type_resolver.py` | `tests/fortran/source_parsing/parsing/`, `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` | Parser facts and diagnostics match fixtures | +| Fortran parse output | `docs/developer/packages/parsers.md` | `prik/parsers/fortran/parser.py`, `models.py`, `lexer.py`, `type_resolver.py` | `tests/fortran/source_parsing/parsing/`, `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` | Parser facts and diagnostics match fixtures | +| CLI stage selection and output | `docs/user/getting-started/beginner-workflow.md`, `docs/user/reference/cli-commands.md` | `prik/cli.py`, `prik/parsers/fortran/cli.py` | `tests/fortran/command_line_interface/pipeline/`, Fortran parser CLI tests, documentation example tests | Command output and diagnostics match checked expectations | +| Compiler preprocessing | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/packages/preprocessing.md`, `docs/developer/packages/parsers.md` | `prik/preprocessing/source.py`, `prik/preprocessing/fortran.py` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` | Prepared Fortran input, dependencies, and source mappings are stable | | Semantic `.pyi` generation | `docs/user/reference/semantic-pyi-format.md` | `prik/printers/pyi.py` | `tests/fortran/semantic_pyi_format/pipeline/`, `tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py` | Printed `.pyi` round-trips or matches fixtures | | Semantic `.pyi` conversion and editing | `docs/user/reference/pyi-contracts/index.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/examples/recipes/semantic-pyi-contracts.md` | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `models.py` | `tests/fortran/semantic_pyi_format/` | Edited contracts parse to Python AST, then become semantic IR with preserved native facts | | Semantic and wrapper-planning errors | `docs/user/guide/error-handling.md`, `docs/user/reference/diagnostic-codes.md` | `prik/semantics/fortran2ir.py`, `prik/policy/completion.py`, `prik/planning/planner.py` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and feature-local `codegen/` tests | Each owning stage rejects unsupported or incomplete contracts | | Fortran wrapper orchestration | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md` | `prik/pipeline/build.py` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, multi-source wrapper tests | Builds report artifacts and compile/link as documented | | Completed semantic policy to generated wrapper | `docs/user/reference/fortran-wrapper.md` | `prik/policy/completion.py`, `prik/planning/models.py`, `prik/planning/planner.py`, `prik/codegen/docstrings.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/semantics/`, `tests/fortran/infrastructure/codegen/`, and feature-local policy/codegen tests | Runtime policy is explicit, the typed plan is complete, and the generated wrapper compiles and runs | -| Native compilation and binding support | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md`, `docs/developer/build-system.md`, `docs/developer/quality-assurance.md` | `prik/compiler/`, `prik/runtime/native_support/` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, build-mode tests | Generated sources compile, link, import, and clean up correctly | +| Native compilation and binding support | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md`, `docs/developer/packages/compiler.md`, `docs/developer/workflows/quality-assurance.md` | `prik/compiler/`, `prik/runtime/native_support/` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, build-mode tests | Generated sources compile, link, import, and clean up correctly | | Source documentation structure | `docs/developer/source-map.md` | `docs/`, package README files, `tests/docs/test_reference_and_source_map.py` | documentation metadata, navigation, source-map, and example tests | Pages have metadata, audience separation, and source coverage checks | +| Semantic IR | `docs/user/reference/semantic-ir.md` | `prik/semantics/models.py`, `fortran2ir.py`, `pyi2ir.py` | `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/` | Fortran or semantic `.pyi` facts lower without losing wrapper-relevant meaning | +| Generated Fortran bridge | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/fortran/bridge.py`, `prik/printers/fortran.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/codegen/`, feature-local codegen and end-to-end tests | Generated bridge compiles and preserves the native calling contract | +| Generated CPython binding | `docs/user/reference/fortran-wrapper.md` | `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/printers/c.py`, `prik/pipeline/wrapper.py` | `tests/fortran/infrastructure/codegen/`, feature-local codegen and end-to-end tests | Extension imports, validates Python inputs, dispatches overloads in C, and installs the derived-class Python facade | +| Public API exports | `README.md`, `docs/user/reference/python-api.md` | `prik/__init__.py` | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` | Import paths are intentional and documented | ## First-File Rule - When the user-visible behavior changes, update the public docs in the same row before or alongside the implementation. The documentation structure test keeps @@ -62,13 +61,10 @@ this routing page tied to the source hotspots and package README files. | Optional arguments | parser optional attributes, semantic arguments, binding argument parsing | Present/absent calls and unsupported combinations are tested | | Generic interfaces | parser interface facts, semantic overload sets, `FunctionOverloadSet`, binding dispatch | Overload selection and ambiguity failures are tested at runtime | | Enumerations | parser enum facts, semantic constants/classes, codegen projection | Python-visible values and unsupported enum forms are tested | -| Packaging and distribution | `prik/pipeline/build.py`, `prik/compiler/`, future packaging integration | Build artifacts, native dependencies, and platform constraints are documented and tested | - - +| Packaging and distribution | `prik/pipeline/build.py`, `prik/compiler/`, future packaging integration | Build artifacts, native dependencies, and platform constraints are documented and tested | ## Evidence Rule diff --git a/docs/developer/index.md b/docs/developer/index.md index eb17f9293..a5e94c27b 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -2,53 +2,55 @@ title: Contributor Documentation audience: developers, maintainers, contributors prerequisites: repository checkout -related: architecture.md, development-workflow.md, contributing/index.md +related: architecture.md, packages/index.md, workflows/contributing.md status: maintained publication: draft --- # Contributor Documentation -This is the single documentation area for changing and maintaining prik. Start -with the architecture guide, then follow the focused implementation, testing, -governance, and contribution references needed for the change. +This is the single documentation area for changing, testing, governing, and +releasing PRIK. Start with the architecture guide, then open the detailed +package, workflow, concept, design, or active-roadmap page needed for the task. ## Orientation -- [Contributor architecture guide](architecture.md) -- [Development workflow](development-workflow.md) -- [Repository structure](repository-structure.md) -- [Source map](source-map.md) -- [Feature-to-code map](feature-to-code-map.md) -- [Compiler preprocessing reference](compiler-preprocessing.md) -- [Fortran parser reference](fortran-parser-reference.md) -- [Quality assurance](quality-assurance.md) -- [Build system](build-system.md) -- [Testing strategy](testing-strategy.md) -- [Coding standards](coding-standards.md) - - - -## Change Workflows - -- [Adding a feature](adding-a-feature.md) -- [Adding a Fortran construct](adding-a-fortran-construct.md) -- [Adding a code-generation backend](adding-a-code-generation-backend.md) -- [Contributing](contributing/index.md) - -## Design And Internal Architecture - -- [Design documents](design/index.md) -- [Internal architecture](internal-architecture/index.md) -- [Pipeline map](internal-architecture/pipeline-map.md) -- [Datatype lifecycle](internal-architecture/type-system.md) -- [Ownership tracking](internal-architecture/ownership-tracking.md) - -## Project Operations And Planning - -- [Documentation architecture](documentation-architecture.md) -- [CI/CD](ci-cd.md) -- [Release process](release-process.md) -- [Roadmaps](roadmap/index.md) +- [Contributor architecture](architecture.md): shallow repository/package + structure, complete workflow, authority rules, CLI/root files, and package + routes. +- [Source package guides](packages/index.md): one detailed page per production + package, with local structure, important objects, runnable examples, tests, + change routes, and invariants. +- [Source map](source-map.md): exact file and hotspot lookup. +- [Feature-to-code map](feature-to-code-map.md): user-visible capability to + source, tests, and documentation. +- [Testing strategy](testing-strategy.md): language/feature/stage ownership and + verification selection. + +## Cross-Cutting Concepts + +- [Datatype lifecycle](concepts/datatype-lifecycle.md): compiler measurement, + semantic identity, policy, backend representation, and runtime validation. + +## Contributor Workflows + +- [Contributing](workflows/contributing.md) +- [Quality assurance](workflows/quality-assurance.md) +- [Continuous integration and delivery](workflows/ci.md) +- [Documentation architecture](workflows/documentation.md) +- [Release process](workflows/release.md) + +## Design And Planning + +- [Multilanguage runtime architecture](design/multilanguage-runtime.md) is an + explicit long-term design, not a support claim. +- [Wrapper open decisions](design/wrapper-open-decisions.md) records unresolved + or revisitable design questions. +- [Active roadmaps](roadmap/index.md) contain incomplete work only. + +## Deferred Input-Language Material + +The C parser/C-to-IR reference is retained under `deferred/` and excluded from +the published Fortran contributor workflow until that input path is mature. +This does not hide the generated CPython C binding backend used by Fortran +wrappers. diff --git a/docs/developer/internal-architecture/ast-design.md b/docs/developer/internal-architecture/ast-design.md deleted file mode 100644 index 72cc53bd4..000000000 --- a/docs/developer/internal-architecture/ast-design.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: AST Design -audience: maintainers -prerequisites: parser architecture -related: symbol-tables.md, type-system.md -status: planned-documentation -publication: draft ---- - -# AST Design - -Parser models and parsed semantic `.pyi` files may use syntax trees to preserve -source structure. Wrapper generation has no shared codegen AST: completed -semantic policy is projected into `WrapperPlan`, then the C binding and Fortran -bridge lower directly into their backend-specific source-syntax nodes. - -## TODO - -- TODO: Document AST ownership, source locations, and invariants. -- TODO: Link parser models, wrapper-plan records, and generated source-syntax - nodes to their tests. diff --git a/docs/developer/internal-architecture/dependency-analysis.md b/docs/developer/internal-architecture/dependency-analysis.md deleted file mode 100644 index 377094b5d..000000000 --- a/docs/developer/internal-architecture/dependency-analysis.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Dependency Analysis -audience: maintainers -prerequisites: semantic passes -related: wrapper-generation-pipeline.md, symbol-tables.md -status: planned-documentation -publication: draft ---- - -# Dependency Analysis - -Reserved maintainer page for source ordering, module imports, native object -linking, and generated artifact dependencies. - -## TODO - -- TODO: Document dependency analysis for source-driven and `.pyi`-driven builds. -- TODO: Link multi-source build tests and limitations. diff --git a/docs/developer/internal-architecture/error-handling-pipeline.md b/docs/developer/internal-architecture/error-handling-pipeline.md deleted file mode 100644 index b011f7335..000000000 --- a/docs/developer/internal-architecture/error-handling-pipeline.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Error Handling Pipeline -audience: maintainers -prerequisites: runtime layer, error propagation model -related: runtime-layer.md, ../../user/reference/diagnostic-codes.md -status: planned-documentation -publication: draft ---- - -# Error Handling Pipeline - -Reserved maintainer page for diagnostics, stage-owned errors, generated error -paths, Python exception state, and cleanup on failure. - -## TODO - -- TODO: Document error propagation from native callbacks through Python - exceptions. -- TODO: Link cleanup and ownership behavior for failure paths. diff --git a/docs/developer/internal-architecture/index.md b/docs/developer/internal-architecture/index.md deleted file mode 100644 index 650054145..000000000 --- a/docs/developer/internal-architecture/index.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Internal Architecture -audience: maintainers -prerequisites: design documents, developer guide -related: ../design/index.md, ../../developer/development-workflow.md -status: planned-documentation -publication: draft ---- - -# Internal Architecture - -Internal architecture pages are for contributors who need implementation-level -details. They are separate from user guides and high-level design documents. - -## Pages - -- [Pipeline map](pipeline-map.md) -- [AST design](ast-design.md) -- [Symbol tables](symbol-tables.md) -- [Datatype lifecycle](type-system.md): compiler probing, semantic scalar - identities, policy/planning boundaries, backend mappings, and runtime - validation. -- [Semantic passes](semantic-passes.md) -- [Dependency analysis](dependency-analysis.md) -- [Wrapper generation pipeline](wrapper-generation-pipeline.md) -- [Runtime layer](runtime-layer.md) -- [Ownership tracking](ownership-tracking.md) -- [Error handling pipeline](error-handling-pipeline.md) - -## TODO - -- TODO: Fill these pages from implementation evidence and contributor workflows. -- TODO: Keep volatile internals out of user-facing workflow pages. diff --git a/docs/developer/internal-architecture/pipeline-map.md b/docs/developer/internal-architecture/pipeline-map.md deleted file mode 100644 index 7d80c3743..000000000 --- a/docs/developer/internal-architecture/pipeline-map.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -title: Pipeline Map -audience: maintainers -prerequisites: contributor architecture guide, source map -related: ../architecture.md, ../source-map.md, wrapper-generation-pipeline.md, runtime-layer.md -status: maintained -publication: draft ---- - -# Pipeline Map - -This page is the source-code route through the current wrapper and inspection -pipelines. It complements the user-facing wrapper mechanism in -`docs/user/reference/fortran-wrapper.md` with the implementation files a maintainer should -open at each stage. - -## Source-Driven Fortran Wrapper Pipeline - - - -| Stage | Main source | Input | Output | Primary evidence | -| --- | --- | --- | --- | --- | -| CLI request | `prik/cli.py` | source paths and stage flags | selected stage or wrapper build options | `tests/fortran/command_line_interface/pipeline/` | -| Build orchestration | `prik/pipeline/build.py` | ordered Fortran sources or `.pyi` contracts plus explicit native artifacts | `WrapperBuildResult`, `NativeBuildPlan`, and `GeneratedWrapper` | wrapper build-mode tests | -| Preprocessing | `prik/preprocessing/source.py`, `prik/preprocessing/c.py`, `prik/preprocessing/fortran.py` | source path, compiler config | preprocessed source, raw directive facts, native include expansion, and dependency provenance | C and Fortran preprocessing tests | -| Target probes | `prik/preprocessing/probes/` | compiler expressions or native target spellings plus compiler flags | resolved kind, storage, precision, signedness, and availability facts | C and Fortran target-probe tests | -| Parser project model | `prik/parsers/fortran/parser.py` (`_SourceUnitScanner` for structural boundaries/regions; `FortranParser` for scopes and model construction) | preprocessed Fortran source | parser project with modules, procedures, types, visibility | Fortran parser fixture tests | -| Semantic IR | `prik/semantics/fortran2ir.py` | parser project and target facts | `SemanticModule` objects | semantic Fortran tests | -| Semantic policy completion | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/construction.py` | full semantic modules with signatures and `.pyi` overrides | semantic modules annotated with every ownership, transfer, destruction, mutability, storage, accessor, and projection decision needed by wrapper generation | ownership-policy tests | -| Wrapper planning | `prik/planning/planner.py`, `prik/planning/models.py` | policy-completed semantic modules | typed wrapper plans consuming completed decisions without rendering output text | `tests/fortran/infrastructure/codegen/`, wrapper tests | -| Direct documentation, bridge, and binding generation | `prik/codegen/docstrings.py`, `prik/codegen/fortran/bridge.py`, `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/pipeline/wrapper.py` | validated typed wrapper plans | completed public docstrings, Fortran, C, header syntax nodes, and the embedded derived-class facade | `tests/fortran/infrastructure/codegen/`, wrapper tests | -| Language printing | `prik/printers/c.py`, `prik/printers/fortran.py`, `prik/printers/pyi.py` | C or Fortran syntax nodes, or semantic IR | C, Fortran, header, or semantic `.pyi` text | printer and generated-contract tests | -| Wrapper generation pipeline | `prik/pipeline/wrapper.py` | editable completed wrapper plan | one generated wrapper containing rendered sources and build metadata | wrapper-generation and golden tests | -| Compile and link | `prik/compiler/`, `prik/pipeline/build.py` | dependency-batched native objects, generated bridge and binding objects, compiler-process limit, and ordered link inputs | shared library | wrapper runtime and build-mode tests | - - - -## Concept Ownership Rules - -The pipeline keeps separate concepts for contract facts, policy decisions, -generated implementation, and emitted source. Similar names across layers do -not mean those classes should be merged. - -The Python package layout follows those ownership boundaries: - -| Package | Owns | Must not become | -| --- | --- | --- | -| `prik/contracts/` | The public semantic `.pyi` vocabulary and its local runtime scalar factories | A home for semantic conversion or backend datatype lowering | -| `prik/semantics/scalar_types.py` | Stable scalar identities, families, and intrinsic storage facts | NumPy or generated-language spelling | -| `prik/codegen/primitive_scalar_types.py` | Semantic-to-backend and NumPy scalar projection for implemented lowering lanes | A reverse semantic inference service | -| `prik/compiler/` | Reusable compiler command execution, compile objects, profiles, native support installation, and linking | Source preprocessing, target probing, or pipeline orchestration | -| `prik/preprocessing/` | Source expansion, preprocessing provenance, native textual includes, and compiler-derived target facts | Parsing declarations, semantic conversion, or wrapper build orchestration | -| `prik/pipeline/` | Semantic `.pyi` loading, datatype mapping reports, wrapper rendering, and end-to-end wrapper build orchestration | Parser models, semantic decisions, preprocessing implementation, or compiler implementation details | -| `prik/runtime/` | Python objects used by generated extensions at execution time | Build-time semantic or codegen policy | -| `prik/utilities/` | Small domain-neutral mechanisms such as class visitor dispatch | A miscellaneous home for semantic or pipeline concepts | - -Semantic metadata and ownership policy remain in `prik/semantics/` even when -codegen consumes them. Downstream use does not turn semantic authority into -cross-cutting infrastructure. - -| Concept family | Owner | What belongs there | What must stay out | -| --- | --- | --- | --- | -| Parser facts | parser packages | Source syntax, native declaration structure, source locations, and parser diagnostics | Wrapper policy, Python API projection, generated names, and compile/link decisions | -| Semantic policy completion and ownership | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, and `prik/policy/construction.py` | Completed policy choices for ownership, lifetime, output projection, replacement, and ABI safety; immutable wrapper-policy vocabulary is separate from its construction rules | Raw parser syntax, backend-specific statement trees, and hidden lowering-time policy decisions | -| Typed wrapper plan | `prik/planning/models.py` and `prik/planning/planner.py` | A validated, backend-neutral implementation plan projected from completed semantic decisions | Source-contract authority, policy inference, rendered documentation, and target-language statement details | -| Printers and compilation | `prik/printers/`, `prik/pipeline/wrapper.py`, and `prik/compiler/` | Text emission, generated wrapper layout, compiler commands, native objects, libraries, include directories, and link inputs | Semantic support decisions and plan rewriting policy | - - - -Use these rules when adding a new notion: - -- Put it in semantic IR when the fact changes the user-visible or native - contract, must be preserved in `.pyi`, is needed for source-free wrapper - replay, or is required before policy completion can decide support. -- Put it in semantic policy completion or ownership policy when it is a safety decision rather - than a source fact: for example borrowed versus copied data, visible versus - hidden native outputs, replacement rules, destructor ownership, or unsupported - ABI combinations. If the decision depends on full signature context, complete - it in `prik/policy/completion.py` before wrapper planning. -- Put it in compiling or wrapping when it describes build inputs or build - execution: sources, objects, libraries, library directories, include - directories, compiler flags, link items, binding support files, and generated - artifact paths. - - - -Merge or move concepts only when their invariants match: - -- Merge a shared object only when it has the same meaning and lifetime in every - layer and carries no generated implementation state. Small immutable value - objects such as identity, origin, scalar-kind descriptors, or naming-policy - results are candidates. -- Move a codegen concept into semantics only when it can be represented without - a generated body, temporary, scope, include, or target-language expression and - the fact is needed for `.pyi`, policy completion, or source-free replay. -- Move a semantic concept into a wrapper plan only when it does not change the public - contract, native contract, completed policy, or `.pyi` representation and exists only - to print or compile wrapper code. - - - -Examples: - -- `@bind` and a native procedure name belong to semantic identity. The bridge - symbol used to call it belongs to codegen naming and lowering. -- Python keyword avoidance for a public name, such as a native `def` routine, - belongs to naming policy. The chosen public spelling is stored where the - contract needs it, while target-specific helper symbols stay generated. -- Wrapper syntax nodes, body statements, temporaries, includes, and backend - datatypes stay out of `prik/semantics/models.py`. - - - -## Stage Maintenance Map - -| Stage family | First files to read | Source navigation owner | -| --- | --- | --- | -| CLI and output routing | `prik/cli.py`, parser CLI helpers | `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` | -| Source loading and preprocessing | `prik/preprocessing/source.py`, `prik/preprocessing/c.py`, `prik/preprocessing/fortran.py` | `docs/developer/source-map.md`, parser references | -| Editable semantic contracts | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/printers/pyi.py` | `docs/user/reference/semantic-pyi-format.md` | -| Semantic and wrapper-planning errors | `prik/semantics/fortran2ir.py`, `prik/policy/completion.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py` | `docs/user/guide/error-handling.md` | -| Wrapper policy and lowering | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py`, `prik/pipeline/wrapper.py` | `docs/user/reference/fortran-wrapper.md`, ownership docs | -| Native build | `prik/pipeline/build.py`, `prik/compiler/compilers.py`, `prik/compiler/native_support.py` | compiler package README and build-system docs | - - - -## Semantic `.pyi` Wrapper Pipeline - -Semantic `.pyi` builds reuse the wrapper backend but start from edited -contracts and explicit native artifacts instead of reparsing native source for -the Python API. - -```text -.pyi contract - -> prik/parsers/pyi/parser.py - -> prik/pipeline/pyi.py - -> prik/semantics/pyi2ir.py - -> prik/semantics/native_contract.py - -> prik/policy/completion.py - -> prik/planning/planner.py - -> prik/pipeline/wrapper.py - -> compile and link pipeline -``` - -The `.pyi` path must preserve native ABI facts in the semantic contract. Missing -native build inputs or contradictory contract facts fail before bridge emission -or native compilation. Ownership, transfer, and destruction policy is completed -from the full `.pyi` signature before planning; the wrapper planner and backend -generators consume that completed policy and must not invent a different one. - -## Shared Semantic Policy Boundary - - - - - - - -The completed decision is also the only semantic input to bridge and binding -behavior selection. Each backend owns an explicit dispatch table keyed by the -completed object kind and codegen action. A selected leaf method may construct -backend-local helper variables, but it must not choose ownership, writeback, -nullability, release responsibility, or `stack`/`heap`/`alias` placement for the -contract value. Missing dispatch combinations are errors; there is no datatype- -based policy fallback in bridge or binding generation. - -CLI source inspection uses a compact language dispatch table for the source -portion of this route: - -```text -pipeline = SOURCE_SEMANTIC_PIPELINES[language] -parsed = pipeline.parser(...) -semantic_modules = pipeline.converter_to_ir(parsed, ...) -semantic_modules -> semantic policy completion -> wrapper planning or lowering -``` - -Per-language parser/converter entries may still perform target-specific -preprocessing or ABI/kind probes, but ownership, transfer, destruction, -mutability, nullability, projection, and lifetime decisions must stay out of -those entries and flow through semantic policy completion after IR exists. - -## Inspection-Only Pipeline - -Inspection stages stop before wrapper code generation: - -```text -native source - -> parser facts - -> semantic IR - -> semantic .pyi -``` - - - -## Where Failures Should Happen - -| Failure type | Preferred owner | -| --- | --- | -| Compiler-backed source cannot be preprocessed | `prik/preprocessing/source.py` | -| Raw C preprocessing metadata cannot be represented | `prik/preprocessing/c.py` | -| Native Fortran include expansion fails | `prik/preprocessing/fortran.py` | -| Required target datatype facts cannot be measured | `prik/preprocessing/probes/` | -| Source syntax cannot be represented by prik's parser model | parser package | -| Source facts cannot form a semantic contract | semantic conversion | -| Ownership, lifetime, ABI, projection, or wrapper support decision is unsafe | `prik/policy/ownership.py` or policy completion | -| A completed policy is internally inconsistent while being projected | wrapper planner at the owner being projected | -| Native-language validity does not affect prik's contract | Fortran or C compiler | -| Generated code cannot represent a supported plan | bridge or binding generator with focused tests | -| Compiler/linker invocation is wrong | `prik/compiler/` or `prik/pipeline/build.py` | -| Python binding behavior is wrong | generated binding, native support, or ownership policy | diff --git a/docs/developer/internal-architecture/runtime-layer.md b/docs/developer/internal-architecture/runtime-layer.md deleted file mode 100644 index 4c9077630..000000000 --- a/docs/developer/internal-architecture/runtime-layer.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Runtime Layer -audience: maintainers -prerequisites: wrapper generation pipeline -related: ownership-tracking.md, error-handling-pipeline.md -status: planned-documentation -publication: draft ---- - -# Runtime Layer - -Reserved maintainer page for shared runtime helpers used by generated wrappers. - -## TODO - -- TODO: Document runtime helper responsibilities and native/Python boundaries. -- TODO: Link array, callback, and allocation helpers to tests. diff --git a/docs/developer/internal-architecture/semantic-passes.md b/docs/developer/internal-architecture/semantic-passes.md deleted file mode 100644 index 844c6dde3..000000000 --- a/docs/developer/internal-architecture/semantic-passes.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Semantic Passes -audience: maintainers -prerequisites: type system, symbol tables -related: dependency-analysis.md, ../../user/reference/semantic-ir.md -status: planned-documentation -publication: draft ---- - -# Semantic Passes - -Reserved maintainer page for parser-to-IR conversion, validation, policy completion, -and `.pyi` round trips. - -## TODO - -- TODO: List semantic passes in execution order with owning modules. -- TODO: Document blocker policy and pass-specific tests. diff --git a/docs/developer/internal-architecture/symbol-tables.md b/docs/developer/internal-architecture/symbol-tables.md deleted file mode 100644 index 5dfe9222c..000000000 --- a/docs/developer/internal-architecture/symbol-tables.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Symbol Tables -audience: maintainers -prerequisites: AST design -related: type-system.md, dependency-analysis.md -status: planned-documentation -publication: draft ---- - -# Symbol Tables - -Reserved maintainer page for symbol collection, scope lookup, visibility, and -name resolution. - -## TODO - -- TODO: Document symbol table data structures and update rules. -- TODO: Link visibility and collision policy to wrapper tests. diff --git a/docs/developer/internal-architecture/wrapper-generation-pipeline.md b/docs/developer/internal-architecture/wrapper-generation-pipeline.md deleted file mode 100644 index 17a5e2171..000000000 --- a/docs/developer/internal-architecture/wrapper-generation-pipeline.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -title: Wrapper Generation Pipeline -audience: maintainers -prerequisites: semantic passes, code generation design -related: runtime-layer.md, ownership-tracking.md, ../roadmap/wrapper-plan-migration-checklist.md -status: maintained -publication: draft ---- - -# Wrapper Generation Pipeline - -This page describes the canonical wrapper-plan generation route. It covers the -completed scalar, string, array, native-handle, derived-type, class, callback, -module-state, generic, and build surfaces. - -## Architectural Boundary - -All semantic policy must be complete before wrapper planning begins. Post-IR -policy completion owns -object kind, ownership, transfer, destruction, mutability, writeback, -nullability, output projection, release responsibility, storage mode, getter -behavior, native setter assignment, and Python setter exposure. - -Planning projects those completed decisions into one editable `ModulePlan`. -Validation checks that the projections agree. Binding and bridge generation -then dispatch only from completed selectors into small named lowering methods; -they do not reconstruct policy from datatype, `intent`, shape, alias flags, or -local memory checks. - -The immutable backend-neutral vocabulary consumed across this boundary lives -in `prik/policy/models.py`. It owns completed action enums, policy records, and -stable cross-stage reason constants, but no construction rules. -`prik/policy/construction.py` owns the semantic rules that build and validate -those records, and `prik/policy/completion.py` owns their ordered attachment to -the semantic IR. Raw contract metadata remains in -`prik/semantics/ownership_metadata.py`; ownership resolution and completed -ownership vocabulary live in `prik/policy/ownership.py`. - -Planning is a separate package. `prik/planning/models.py` owns the editable, -backend-neutral wrapper-plan records and `prik/planning/planner.py` mechanically -projects completed policy into those records. Planning does not render C, -Fortran, Python, or documentation text. - -Native-source `intent` may be consumed while importing a source declaration to -propose default Python argument/result positions. It is not retained in the -semantic `.pyi` or post-IR ownership context. The editable Python signature, -`Returns[...]` projection, and ordered native-call mapping are authoritative. -Bridge entry dummies omit `intent`, leaving their storage permissive; that -contract controls wrapper copy-in, copy-back, and returned values, while the -compiled native procedure's own interface controls native access. - -Within that contract, an explicit native-call list is exhaustive for native -dummy positions. Matching named `Returns[...]` items attach result positions to -visible `Arg(i)` entries automatically; direct function results remain the first -ordinary Python return item, while hidden native output dummies require explicit -`Return(...)` entries. Descriptor reassociation follows the same rule: -`Pointer(Arg(i))` without a projected return uses a call-local adapter and -discards reassociation, while a matching projected return requires storage that -can preserve association writeback. - -Native transport overrides also live on that mapping. Primitive `Arg(i)` is a -value handoff and `Addr(Arg(i))` selects call-local address handoff. Wrapped -derived `Arg(i)` is a typed reference handoff and `Value(Arg(i))` selects exact -typed value handoff. `Returns[...]` never selects either ABI; it only assigns a -Python result position and writeback expectation. A derived `Value(...)` slot -does not expose aggregate layout at the C boundary: C still supplies an opaque -address, the bridge reconstructs the exact native type, and the Fortran compiler -applies the explicit interface's `VALUE` semantics at the typed call. - -Standalone legacy externals use a completed declaration mode. Procedures whose -ABI is valid with an implicit interface, including classic BLAS/LAPACK -subroutines and scalar functions, lower to `external` declarations; optional, -descriptor-rich, polymorphic, or array-result procedures retain explicit -interfaces. The bridge dispatches this completed mode and does not reclassify -the signature. - -The public direct-generation boundary is: - -```python -complete_semantic_policies(module) -plan = WrapperPlanner().build(module) -generated = WrapperGenerator().generate(plan) -``` - -`WrapperGenerator.generate()` in `prik/pipeline/wrapper.py` is the one wrapper -orchestrator. It completes plan-driven documentation, freezes and validates the -plan, runs both backend preflight checks, asks the C binding and Fortran bridge -generators for syntax nodes, renders those nodes through the language printers, -assigns their stable filenames, and returns one `GeneratedWrapper`. Build -integration writes or compiles that result; it does not own datatype transfer -policy. - -The language printers are the inverse-facing companions of the language -parsers. `prik/printers/c.py`, `prik/printers/fortran.py`, and -`prik/printers/pyi.py` serialize already-formed C nodes, Fortran nodes, or -semantic IR respectively. They do not plan wrappers or coordinate builds. - -Wrapper builds have no legacy route or fallback. An unsupported completed plan -fails with its exact owner path before either backend emits source. - -## Stable Tree and Datatype-Varying Records - -The shared plan has stable module, namespace, and function orchestration: - -```text -ModulePlan - binding: BindingModulePlan - bridge: BridgeModulePlan - namespaces: NamespacePlan ... - functions: FunctionPlan ... - binding: BindingFunctionPlan - bridge: BridgeFunctionPlan - arguments: ArgumentTransferPlan ... - binding: BindingArgumentPlan - bridge: BridgeArgumentPlan - native_call_slot: NativeCallSlotPlan - results: ResultPlan ... - binding: BindingResultPlan - bridge: BridgeResultPlan - native_call_slot: NativeCallSlotPlan | None - native_call_slots: NativeCallSlotPlan ... - lifecycle actions: LifecycleActionPlan ... - variables: ModuleVariablePlan ... -``` - -Most datatype-specific work belongs to `ArgumentTransferPlan` and -`ResultPlan`. Each is one transfer with explicit binding and bridge views. -`ModuleVariablePlan` is the other intentionally datatype-sensitive surface, -because getter, setter, and native assignment behavior depend on the stored -value. - -`FunctionPlan`, `NamespacePlan`, and `ModulePlan` remain orchestration records. -They own export names, call order, result order, runtime/GIL envelopes, and -aggregation, but not datatype policy. - -Python-facing documentation is generated from the completed wrapper plan. -`prik/codegen/docstrings.py` owns this presentation step and is invoked by the -wrapper pipeline after planning and before the plan is frozen. It fills -only unresolved documentation fields, preserving explicit editable-plan -overrides, and renders child documentation before class and namespace -summaries. C method-table emission and generated Python class assembly only -attach the completed text; neither backend infers signatures, ownership, -mutation, nullability, or exception behavior while rendering source. - -`OverloadPlan` stores candidates, exact argument-match records, receiver -conventions, and one unique integer candidate ID per overload set. The C -binding binds the supplied call shape, evaluates those completed predicates in -candidate order, and then switches on the selected ID to call the existing -candidate wrapper. This preserves first-match behavior for overlapping -optional domains without speculative native calls. Module generics are -installed directly in the C method table. `PythonSurfaceEmitter` owns only the -executable derived-class facade; overloaded methods and constructors emitted -there are thin receiver-forwarding descriptors over private C dispatchers. - -`NativeCallSlotPlan` and `LifecycleActionPlan` are subordinate transfer -details. Native slots stay indexed on `FunctionPlan` because native ABI order -can interleave argument slots, result slots, literals, and helpers. Lifecycle -actions stay indexed there because copy-out, cleanup, and release order may -span several arguments and results or differ on failure. Argument and hidden -result slots are the same mutable records referenced from both their transfer -owner and the function-wide index; they are not duplicated policy. - -## One Repeatable Transfer Algorithm - -Use this sequence for scalars, strings, arrays, and future datatype families: - -1. Post-IR policy completion classifies the value with `ObjectKind` and - completes ownership, transfer, storage, nullability, mutability, projection, - barrier actions, data action, and any justified copy reason. -2. Wrapper policy records the backend-neutral transfer and the ordered native - slot. It must report a blocker instead of leaving a semantic choice for a - backend. -3. `WrapperPlanner` mechanically projects one `ArgumentTransferPlan` or - `ResultPlan`, adds symbolic handoff roles, and shares the corresponding - `NativeCallSlotPlan` reference. -4. The shared validator checks graph consistency and common invariants, then - dispatches by the completed `object_kind` to scalar, string, or ordinary- - array validation. -5. Backend preflight dispatches by the same completed kind and action selectors - and rejects combinations it cannot lower. -6. The binding lowers Python extraction or result construction. The bridge - lowers ABI declarations, representation conversion, the ordered native - call, and native result production. Both communicate through planned - symbolic roles. -7. Function-level orchestration applies status handling and ordered lifecycle - actions, aggregates Python results, and returns. Printers and build - integration remain generic. - -When adding a datatype, first extend semantic policy and its transfer record, -then add one named validator and one named lowering method per affected -backend. Do not add a parallel plan hierarchy or datatype branches to module, -namespace, or function traversal. Add a new typed action only when the existing -selectors cannot express a genuine semantic choice. - -## Selector Vocabulary - -The action axes are deliberately orthogonal: - -| Selector | Question answered | Examples | -| --- | --- | --- | -| `ObjectKind` | What kind of object follows this route? | `SCALAR`, `STRING`, `NUMPY_ARRAY` | -| `source_kind` | Where is a result produced? | `direct_return`, `hidden_output` | -| `PythonBarrierAction` | How does the binding cross the Python boundary? | `SCALAR_VALUE`, `STRING_VALUE`, `ARRAY_STORAGE` | -| `NativeBarrierAction` | What native ABI transport is used? | `PASS_VALUE`, `PASS_CALL_LOCAL_ADDRESS`, `PASS_ARRAY_BUFFER` | -| `CodegenAction` | What ownership or transfer operation occurs? | `DIRECT_VALUE`, `CALL_LOCAL_INPUT`, `COPY_IN_OUT`, `COPY_OUT` | -| `BridgeDataAction` | What happens to the representation in the bridge? | `DIRECT_TRANSFER`, `ASSOCIATE_VIEW`, `COPY_REPRESENTATION` | -| `WritebackPhase` | When does a lifecycle operation run? | native mutation, copy-out, cleanup, release | - -Hiddenness is not a transfer operation. A hidden scalar result therefore uses -`source_kind="hidden_output"` with `CodegenAction.DIRECT_VALUE`; hidden strings -and ordinary arrays use the same source kind with `CodegenAction.COPY_OUT`. - -`NativeBarrierAction.PASS_ARRAY_BUFFER` identifies the Phase 6 ordinary-array -data-buffer ABI. Its handoff plan carries data, rank, extents, strides, and -itemsize. `PASS_NATIVE_DESCRIPTOR` is reserved for Phase 7 persistent native -descriptors and handles. Neither backend may substitute one for the other. -Array handoff shapes are completed bridge extents; native source bounds are -temporary import facts and must not appear in semantic `.pyi` or become extent -dependencies. A source dimension such as `0:LDB-1` therefore completes to -extent `LDB`, while the native procedure keeps control of its own indexing -bounds. When `PASS_NATIVE_DESCRIPTOR` also carries -optional absence, the completed optional mode lowers a valid call-local -placeholder descriptor plus a separate presence role. This keeps the bridge -entry ABI valid while presence dispatch omits the native dummy. - -`DatatypeFamily` remains useful after object-kind dispatch for primitive -element spelling and conversion, such as integer versus real scalar types or -the element type of an ordinary array. It must not be used to rediscover -whether the transfer itself is a scalar, string, or array. - -## Maintainer Inspection and Acceptance - -Inspect the real records directly with normal Python prints. The primary path -is `complete_semantic_policies()` -> `WrapperPlanner.build()` -> -`WrapperGenerator.generate()`. Generated wrappers from real passing -feature-local `tests/fortran/*/end_to_end/` cases are the behavioral -oracle; plan unit tests cover action and graph invariants. Production source -and semantic-`.pyi` builds both use this one path; unsupported completed policy -is an error before lowering, not a request to retry a legacy generator. - -A wrapper-generation change is acceptable when: - -- semantic decisions are complete before planning; -- datatype variation is confined to transfer, result, lifecycle, or - module-variable records and their named handlers; -- scalar, string, and array routes use the same planning and validation - sequence; -- binding and bridge consume the same shared roles and native-slot records; -- no backend infers policy or silently falls back to another action; -- focused plan tests, relevant wrapper runtime tests, documentation checks, and - static analysis pass. diff --git a/docs/developer/packages/codegen.md b/docs/developer/packages/codegen.md new file mode 100644 index 000000000..074db4db2 --- /dev/null +++ b/docs/developer/packages/codegen.md @@ -0,0 +1,219 @@ +--- +title: Code Generation Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, completed wrapper plan +related: ../architecture.md, index.md, planning.md, printers.md, pipeline.md +status: maintained +publication: draft +--- + +# Code Generation Package + +## Purpose And Boundaries + +`prik/codegen/` consumes a validated wrapper plan and produces typed C and +Fortran syntax nodes plus the planned Python facade source embedded in the +extension. It owns emitted mechanisms such as temporaries, conversions, +bridge bodies, module initialization, and class assembly. It must not complete +ownership, change wrapper support, print final native source, or compile it. + +## Local Structure + +```text +prik/codegen/ +├── nodes.py +├── primitive_scalar_types.py +├── docstrings.py +├── overloads.py +├── checks.py +├── visitor.py +├── c/ +│ ├── binding.py +│ ├── python_surface.py +│ └── naming.py +└── fortran/ + └── bridge.py +``` + +## Internal Workflow + +```text +validated ModulePlan + -> plan-driven public docstrings + -> CBindingGenerator + PythonSurfaceEmitter + -> FortranBridgeGenerator + -> typed C/Fortran nodes and Python facade text + -> language printers +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `nodes.py` | typed C and Fortran node families | Represents generated native syntax before serialization. | +| `primitive_scalar_types.py` | `PrimitiveScalarTypeRegistry`, `NumpyDtypeRegistry` | Maps resolved semantic scalar identities to explicit C, Fortran, NumPy, CFI, and CPython spellings. | +| `docstrings.py` | `WrapperDocstringBuilder` | Renders Python-facing documentation from the completed plan. | +| `c/binding.py` | `CBindingGenerator` | Lowers binding plan views into CPython/NumPy C nodes. | +| `c/python_surface.py` | `PythonSurfaceContext`, `PythonSurfaceEmitter` | Produces the planned derived-class, holder, and module-proxy Python facade. | +| `fortran/bridge.py` | `FortranBridgeGenerator` | Lowers bridge plan views into `bind(C)` modules, accessors, descriptors, and native calls. | + +`overloads.py` answers structural questions over completed overload plans; +`c/naming.py` owns binding-local generated names; `checks.py` powers the +codegen ownership and complexity gate. Specialized emitter methods are kept +local because they make the selected mechanism auditable. + +## Execution Examples + +Typed nodes before printing: + +```bash +python3 prik/codegen/nodes.py +``` + +```text +C node tree: CModule -> wrap_ping -> CReturn +Fortran node tree: FortranModule -> bind_c_ping -> FortranCall +Source text rendered: False +``` + +Primitive backend representations: + +```bash +python3 prik/codegen/primitive_scalar_types.py +``` + +```text +Float64: C=double; Fortran=real(c_double); NumPy=numpy.float64 +NumPy C macro: NPY_FLOAT64 +Fresh editable node per lookup: True +``` + +Plan-driven docstrings: + +```bash +python3 prik/codegen/docstrings.py +``` + +```text +double_value(value) -> float64 + +Parameters +---------- +value : float64 + +Returns +------- +result : float64 + +Raises +------ +TypeError + If an argument has an incompatible Python type or dtype. +``` + +The Python facade: + +```bash +python3 prik/codegen/c/python_surface.py +``` + +```text +Rendered Python facade: +_prik_unset = object() + +_prik_ops_state = {} +class State: + 'Opaque native state.' + __slots__ = ('_prik_capsule', '_prik_owner', '_prik_ops', '_prik_origin') + def __new__(cls, *args, **kwargs): + 'Construction is disabled.' + raise TypeError('State objects come from native code.') +def _prik_wrap_State(capsule, owner=None, ops=None, origin='direct'): + ... +``` + +The binding and bridge files also have direct examples: + +```bash +python3 prik/codegen/c/binding.py +``` + +The complete output is 22 lines. These exact selected lines identify the plan +and the native call inside the generated binding node tree: + +```text +Native procedure: DOUBLE_VALUE +Native call slots: implicit:value +C module: binding_demo_wrapper +Header guard: BINDING_DEMO_WRAPPER_H +Header prototypes: wrap_double_value +Binding wrapper: wrap_double_value +... + CExpressionStatement(expression=CodeExpression(text='result = bind_c_double_value(bound_value)')) +... + CReturn(expression=CodeExpression(text='result_obj')) +``` + +```bash +python3 prik/codegen/fortran/bridge.py +``` + +The complete output is 17 lines. Its exact selected lines show the matching +slot and bridge call: + +```text +Native procedure: DOUBLE_VALUE +Native call slots: implicit:value +Bridge module: bind_c_bridge_demo_wrapper +... +Bridge procedure: bind_c_double_value +Binding name: bind_c_double_value +Procedure kind: function +Result: result :: real(c_double) +... + FortranAssignment(target='result', expression=CodeExpression(text='native_double_value(value)')) +Internal procedures: (none) +``` + +Together the outputs demonstrate that both backends lower one shared plan +without asking the other backend to decide policy. + +## Tests + +- [Codegen infrastructure](../../../tests/fortran/infrastructure/codegen/) +- [Feature-local codegen suites](../../../tests/fortran/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- `python3 tools/check_codegen_complexity.py` + +## Change Routes + +- Add a mechanism to the narrow binding, bridge, or Python-surface emitter that + owns it. +- Add a node only when the existing syntax vocabulary cannot represent the + mechanism. +- Extend primitive lowering only for an established semantic scalar identity. +- If the change requires choosing ownership, storage, projection, setter + exposure, or support, stop and add the missing upstream policy/plan fact. + +## Invariants And Common Mistakes + +- Generators dispatch from completed plan actions; no datatype/intent fallback + may silently choose behavior. +- `WrapperDocstringBuilder` renders the plan and is not imported by planning. +- Large specialized emitters are acceptable when methods remain focused and + policy-free. + +Use one repeatable lowering sequence for every datatype family: + +1. Validate the completed object kind and action combination. +2. Binding generation lowers Python extraction or result construction. +3. Bridge generation lowers ABI declarations, representation conversion, + ordered native call slots, and native result production. +4. Function orchestration applies status handling and planned lifecycle + actions before aggregating Python results. +5. Printers serialize the formed nodes without revisiting the plan's policy. + +A new datatype should extend completed policy and one transfer/result shape, +then add one named validator and one named lowering method per affected +backend. It should not create a parallel module/function plan hierarchy or add +datatype branching to generic traversal. diff --git a/docs/developer/packages/compiler.md b/docs/developer/packages/compiler.md new file mode 100644 index 000000000..61f78f5fb --- /dev/null +++ b/docs/developer/packages/compiler.md @@ -0,0 +1,133 @@ +--- +title: Compiler Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, native compiler toolchain +related: ../architecture.md, index.md, pipeline.md, runtime.md, ../workflows/quality-assurance.md +status: maintained +publication: draft +--- + +# Compiler Package + +## Purpose And Boundaries + +`prik/compiler/` receives explicit source, object, include, library, flag, and +link inputs and turns them into native commands. It owns compiler-family +profiles, command construction and execution, and native-support installation. +It does not preprocess source, discover build order, probe datatype meaning, +or decide wrapper policy. + +## Local Structure + +```text +prik/compiler/ +├── compiler_profiles.py +├── objects.py +├── compilers.py +└── native_support.py +``` + +## Internal Workflow + +```text +explicit ObjectFile and link inputs from prik.pipeline + -> coherent compiler-family profile + -> compile/link argv + -> recorded or executed native process + -> object file or shared extension +``` + +The selected Fortran compiler family supplies its matching C driver and +family-specific switches. The pipeline owns dependency-ready batches; the +compiler executes one request at a time. + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `compiler_profiles.py` | compiler profile records, `fortran_compiler_family()` | Resolves GNU, Intel, LLVM, NVIDIA, or PGI language families and matching drivers. | +| `objects.py` | `ObjectFile` | Immutable input for one source-to-object command. | +| `compilers.py` | `Compiler` | Constructs, records, executes, and reports native compile/link commands. | +| `native_support.py` | `install_native_support()` | Installs the bundled header runtime and NumPy API-version header into a generated wrapper directory. | + +## Execution Examples + +Compiler-family selection: + +```bash +python3 prik/compiler/compiler_profiles.py +``` + +```text +Selected family: gfortran +Compiler profile: GNU +Matching C executable: gcc +Fortran module-output flag: -J +``` + +One immutable compilation request: + +```bash +python3 prik/compiler/objects.py +``` + +```text +Compile input: generated/bridge.f90 -> build/bridge.o +Language: fortran +Flags: ('-O2',) +Include directories: build/modules +``` + +Record-only command construction: + +```bash +python3 prik/compiler/compilers.py +``` + +```text +Compiler profile: GNU +Compile input: demo.c -> demo.o +Recorded without execution: True +Contains compile switch: True +Contains requested flag: True +Commands recorded: 1 +``` + +Bundled runtime installation: + +```bash +python3 prik/compiler/native_support.py +``` + +```text +Installed directory: binding_support +Binding header present: True +NumPy version header present: True +``` + +Together these outputs prove that profile selection, request construction, +native command mechanics, and support installation remain separate operations. + +## Tests + +- [Compiler construction tests](../../../tests/fortran/building_shared_library/compiling/) +- [Build pipeline tests](../../../tests/fortran/building_shared_library/pipeline/) +- [Source build modes](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py) +- [Runtime ABI compatibility](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Change driver families or flags in `compiler_profiles.py`. +- Change compile/link argv or subprocess reporting in `compilers.py`. +- Change build order, parallel scheduling, manifests, or artifact names in + `prik/pipeline/build.py`. +- Change native payload contents in `prik/runtime/native_support/`; change only + their installation here. + +## Invariants And Common Mistakes + +- Never infer ownership, dtype, Python API shape, or wrapper support here. +- Never silently mix a selected Fortran driver with an unrelated C profile. +- Each invocation receives explicit inputs; hidden project discovery belongs + upstream. diff --git a/docs/developer/packages/contracts.md b/docs/developer/packages/contracts.md new file mode 100644 index 000000000..deb4c2d67 --- /dev/null +++ b/docs/developer/packages/contracts.md @@ -0,0 +1,96 @@ +--- +title: Contracts Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, semantic .pyi format +related: index.md, parsers.md, semantics.md, ../architecture.md +status: maintained +publication: draft +--- + +# Contracts Package + +## Purpose And Boundaries + +`prik/contracts/` owns the public names written in semantic `.pyi` contracts. +Those names describe scalar types, arrays, storage, ownership requests, +projections, native calls, callbacks, and descriptor handles. The package is a +public syntax vocabulary; it does not define semantic IR, complete policy, or +generate wrappers. + +## Local Structure + +```text +prik/contracts/ +└── __init__.py +``` + +The single module is intentional. A semantic contract imports one stable +public namespace instead of depending on internal stage packages. + +## Internal Workflow + +```text +semantic .pyi text + -> names imported from prik.contracts + -> Python AST in prik.parsers.pyi + -> contract interpretation in prik.semantics.pyi2ir + -> completed policy in prik.policy +``` + +Some primitive symbols also construct exact NumPy scalar values at runtime. +Subscriptions such as `Float64[:, :]` construct declarative contract objects; +they do not create semantic IR objects. + +## Important File And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `prik/contracts/__init__.py` | `Float64`, `Int32`, `String`, `Addr`, `Allocatable`, `Pointer`, `Returns`, `Arg`, `bind`, `native_call` | Publishes the complete supported semantic `.pyi` vocabulary and the small runtime constructors required by that vocabulary. | + +The canonical public import path is part of the file format. Internal code may +interpret these names, but must not replace them with imports from semantics, +policy, or codegen. + +## Execution Example + +Run the real package entry file: + +```bash +python3 prik/contracts/__init__.py +``` + +```text +Float64() -> np.float64(0.0) (float64) +Float64[:, :] -> element=Float64, rank=2, shape=(slice(None, None, None), slice(None, None, None)) +``` + +The first line proves that a primitive contract scalar has exact NumPy runtime +behavior. The second proves that array subscription produces declarative rank +and shape syntax for later semantic interpretation. + +## Tests + +- [Contract runtime tests](../../../tests/fortran/data_types/runtime/) +- [Semantic `.pyi` parser tests](../../../tests/fortran/semantic_pyi_format/parsing/) +- [Semantic `.pyi` round-trip tests](../../../tests/fortran/semantic_pyi_format/pipeline/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Add or rename public syntax here first, then update `.pyi` parsing, + conversion, printing, user reference documentation, and focused round-trip + tests. +- Change semantic meaning in `prik/semantics/pyi2ir.py`, not in a runtime + constructor. +- Change ownership or lowering selection in policy after semantic conversion. + +## Invariants And Common Mistakes + +- Keep `prik.contracts` stable and public; do not expose internal policy models + through this namespace. +- A valid Python annotation is not automatically a supported wrapper contract. +- NumPy construction behavior must not become the semantic datatype authority. + +See the [semantic `.pyi` user reference](../../user/reference/semantic-pyi-format.md) +for the public language and the [semantics package](semantics.md) for its IR +interpretation. diff --git a/docs/developer/packages/index.md b/docs/developer/packages/index.md new file mode 100644 index 000000000..f2051dc4f --- /dev/null +++ b/docs/developer/packages/index.md @@ -0,0 +1,35 @@ +--- +title: Source Package Guides +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide +related: ../architecture.md, ../source-map.md, ../feature-to-code-map.md +status: maintained +publication: draft +--- + +# Source Package Guides + +These pages explain the production package one ownership boundary at a time. +Read the [architecture guide](../architecture.md) first for the complete flow, +then open the package that owns the change. + +| Package | Canonical guide | Boundary | +| --- | --- | --- | +| `prik.contracts` | [Contracts](contracts.md) | Public semantic `.pyi` vocabulary | +| `prik.compiler` | [Compiler](compiler.md) | Native command construction and execution | +| `prik.preprocessing` | [Preprocessing](preprocessing.md) | Source preparation, provenance, and target probes | +| `prik.parsers` | [Parsers](parsers.md) | Fortran and semantic `.pyi` syntax facts | +| `prik.semantics` | [Semantics](semantics.md) | Language-neutral semantic IR | +| `prik.policy` | [Policy](policy.md) | Completed post-IR interoperability decisions | +| `prik.planning` | [Planning](planning.md) | Mechanical projection into wrapper plans | +| `prik.codegen` | [Code generation](codegen.md) | Plan-driven backend nodes and Python facade source | +| `prik.printers` | [Printers](printers.md) | Serialization of formed representations | +| `prik.pipeline` | [Pipeline](pipeline.md) | Cross-stage workflow and artifact orchestration | +| `prik.runtime` | [Runtime](runtime.md) | Imported-extension handle behavior and native payload | +| `prik.naming` | [Naming](naming.md) | Shared public and generated symbol rules | +| `prik.utilities` | [Utilities](utilities.md) | Genuinely stage-neutral mechanisms | + +Each guide uses the same order: purpose and boundaries, local structure, +workflow, important files and objects, direct execution examples with expected +output, test owners, change routes, and invariants. Source-tree `README.md` +files remain short orientation notes and link back to these canonical guides. diff --git a/docs/developer/packages/naming.md b/docs/developer/packages/naming.md new file mode 100644 index 000000000..8d335da3a --- /dev/null +++ b/docs/developer/packages/naming.md @@ -0,0 +1,72 @@ +--- +title: Naming Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide +related: ../architecture.md, index.md, planning.md, codegen.md, ../source-map.md +status: maintained +publication: draft +--- + +# Naming Package + +## Purpose And Boundaries + +`prik/naming/` owns public and generated names whose stability and collision +rules are shared across planning and generation. It does not own semantic +policy or emitted source syntax. + +## Local Structure + +```text +prik/naming/ +├── policy.py +└── native_symbols.py +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `policy.py` | `NamingPolicy`, `NormalizedPublicName`, `PublicNameRecord`, `GeneratedSymbolRules` | Normalizes Python names, reserves namespaces, and applies target-language symbol rules. | +| `native_symbols.py` | `NativeSymbolNames` | Compacts long owner identities into deterministic compiler-safe fragments. | + +## Execution Examples + +```bash +python3 prik/naming/policy.py +``` + +```text +Normalized public name: render_value +Collision-safe public name: render_value_2 +C destructor symbol: state_drop +``` + +```bash +python3 prik/naming/native_symbols.py +``` + +```text +Owner identity: geometry.point.coordinates +Stable native symbol: point_coordinate_d_c2fc5940 +Within 27-character limit: True +``` + +The first example distinguishes public namespace allocation from generated +target naming. The second preserves a readable prefix while hashing the full +owner identity under a compiler symbol limit. + +## Tests + +- [Naming infrastructure](../../../tests/fortran/infrastructure/naming/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Change public normalization and collision policy in `policy.py`. +- Change stable ABI fragments in `native_symbols.py` with exact-name tests. + +## Invariants And Common Mistakes + +- Never consult completed ownership or emit language syntax here. +- The same inputs must always produce the same generated symbol. diff --git a/docs/developer/fortran-parser-reference.md b/docs/developer/packages/parsers.md similarity index 91% rename from docs/developer/fortran-parser-reference.md rename to docs/developer/packages/parsers.md index 434f931cc..25edc8acd 100644 --- a/docs/developer/fortran-parser-reference.md +++ b/docs/developer/packages/parsers.md @@ -1,13 +1,138 @@ --- -title: Fortran Parser Reference -audience: developers -prerequisites: repository structure, parser architecture -related: adding-a-fortran-construct.md, repository-structure.md +title: Parsers Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, prepared source +related: ../architecture.md, index.md, preprocessing.md, semantics.md, ../source-map.md status: maintained publication: draft --- -# Fortran parser reference (wrapper-focused subset) +# Parsers Package + +## Purpose And Boundaries + +`prik/parsers/` owns syntax-level frontends. The Fortran frontend preserves +source units, declarations, visibility, locations, and diagnostics. The +semantic `.pyi` frontend deliberately stops at Python AST. Parsers report what +source says; they do not choose ownership, wrapper support, NumPy lowering, or +generated API behavior. + +## Local Structure + +```text +prik/parsers/ +├── fortran/ +│ ├── lexer.py +│ ├── models.py +│ ├── parser.py +│ ├── type_resolver.py +│ ├── cli.py +│ ├── utils.py +│ └── __main__.py +└── pyi/ + └── parser.py +``` + +## Internal Workflow + +```text +prepared Fortran text -> logical lines -> parser models -> Fortran-to-IR +semantic .pyi text -> Python ast.Module -> .pyi-to-IR +``` + +The essential Fortran objects are `FortranParser`, `SourceUnit` and its unit +subclasses, `FortranFile`, `FortranProject`, `FortranParseError`, and the +public `parse_fortran_file()` and `parse_fortran_project()` functions. The +semantic `.pyi` frontend exposes `parse_pyi_text()` and `parse_pyi_file()` and +returns a standard `ast.Module`. + +## Important Files + +| File | Responsibility | +| --- | --- | +| `fortran/lexer.py` | Detects source form, strips comments, folds continuations, and preserves logical-line locations. | +| `fortran/models.py` | Defines passive parser models and diagnostics. | +| `fortran/parser.py` | Parses files/projects, resolves structural scope, and assembles source units. | +| `fortran/type_resolver.py` | Preserves parser-level type, kind, and character syntax without target evaluation. | +| `fortran/cli.py` | Formats stable human and JSON parser reports. | +| `pyi/parser.py` | Parses semantic `.pyi` syntax into Python AST without semantic interpretation. | + +## Execution Examples + +```bash +python3 prik/parsers/fortran/lexer.py +``` + +```text +Detected source form: free +line 1: subroutine shift(value,offset) +line 3: real, intent(inout) :: value +line 4: real, intent(in) :: offset +line 5: end subroutine shift +``` + +```bash +python3 prik/parsers/fortran/parser.py +``` + +```text +Module: metrics +Parameter: n = 4 +Procedure: scale(values: real[1]) +``` + +```bash +python3 prik/parsers/fortran/type_resolver.py +``` + +```text +integer(4) -> 4 +real(kind=selected_real_kind(15, 307)) -> selected_real_kind(15, 307) +character(len=16, kind=c_char) -> len=16, kind=c_char +``` + +```bash +python3 prik/parsers/fortran/cli.py +``` + +```text +File: geometry.f90 + Modules: 1 + - module geometry (vars=0, uses=0) + Procedures: 1 + - function norm(value:real[0]) -> real[0] +``` + +```bash +python3 prik/parsers/pyi/parser.py +``` + +```text +Parsed AST: Module +Function node: scale +Argument annotation: Float64 +Semantic conversion performed: False +``` + +The detailed Fortran reference below records the complete maintained subset, +API behavior, diagnostics, fixtures, and reimplementation constraints. + +## Tests + +- [Fortran parser tests](../../../tests/fortran/source_parsing/parsing/) +- [Fortran parser CLI tests](../../../tests/fortran/command_line_interface/pipeline/) +- [Semantic `.pyi` parsing tests](../../../tests/fortran/semantic_pyi_format/parsing/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +Start lexical/source-coordinate changes in `fortran/lexer.py`, grammar and +model construction in `fortran/parser.py`, report changes in `fortran/cli.py`, +and raw `.pyi` syntax changes in `pyi/parser.py`. Semantic meaning begins in +the matching converter. Parser support alone never establishes wrapper +support. + +## Detailed Fortran Reference This document defines the currently supported parser subset, expected behavior, and practical usage from terminal and Python. @@ -902,7 +1027,7 @@ exception keeps structured metadata for consumers: Diagnostic codes are for programmatic matching in tests, tools, and documentation. The category name states the failure class directly. The shared -registry is [`diagnostic-codes.md`](../user/reference/diagnostic-codes.md). +registry is [`diagnostic-codes.md`](../../user/reference/diagnostic-codes.md). `str(error)` and `error.format_diagnostic(color=False)` render a compiler-style diagnostic: @@ -1102,7 +1227,7 @@ subroutine accepted(x) end subroutine accepted ``` -See the [generated modern and legacy datatype mapping](../user/reference/semantic-ir.md#generated-linux-x86_64-mapping-example) +See the [generated modern and legacy datatype mapping](../../user/reference/semantic-ir.md#generated-linux-x86_64-mapping-example) for the exact GitHub Actions target results. ### 6.6 Source-form metadata @@ -1305,7 +1430,7 @@ compiler probe fact for character rows. For the maintained GitHub Actions Source-driven wrapper builds add the normalized native Fortran compiler flags to the internal probe configuration, so semantic type facts and native implementation compilation use the same default-kind profile. The -[generated target datatype mapping](../user/reference/semantic-ir.md#generated-linux-x86_64-mapping-example) +[generated target datatype mapping](../../user/reference/semantic-ir.md#generated-linux-x86_64-mapping-example) measures and verifies those storage facts. The Fortran probe cache key includes the generated expression source, resolved diff --git a/docs/developer/packages/pipeline.md b/docs/developer/packages/pipeline.md new file mode 100644 index 000000000..07613e22f --- /dev/null +++ b/docs/developer/packages/pipeline.md @@ -0,0 +1,157 @@ +--- +title: Pipeline Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, package guides for participating stages +related: ../architecture.md, index.md, compiler.md, planning.md, codegen.md, printers.md +status: maintained +publication: draft +--- + +# Pipeline Package + +## Purpose And Boundaries + +`prik/pipeline/` composes complete workflows across established stage +boundaries. It selects the next stage, preserves progress and timing, assigns +artifact names, writes generated payloads, coordinates compilation/linking, +and returns public results. It does not absorb parser grammar, semantic rules, +policy, backend lowering, printer formatting, or compiler command mechanics. + +## Local Structure + +```text +prik/pipeline/ +├── pyi.py +├── type_mapping_report.py +├── wrapper.py +└── build.py +``` + +## Internal Workflow + +```text +semantic modules or source-build request + -> completed policy and WrapperPlanner + -> WrapperGenerator + -> backend node generation + -> language printers + -> GeneratedWrapper + -> build.py writes sources and creates NativeBuildPlan + -> prik.compiler compiles and links + -> WrapperBuildResult +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `pyi.py` | `pyi_*_to_semantic_module()` workflows, `emit_module_stubs()` | Loads text/files/path sets, caches conversion per operation, reconciles external types, and emits stub packages. | +| `type_mapping_report.py` | report builders | Connects target probes, semantic conversion, and codegen dtype projection into an auditable report. | +| `wrapper.py` | `GeneratedSource`, `GeneratedWrapper`, `WrapperGenerator` | Validates/freezes a plan, invokes docstring and backend generation, prints sources, assigns names, and returns one in-memory wrapper artifact. | +| `build.py` | `NativeCompilationUnit`, `NativePrebuiltArtifact`, `NativeLinkItem`, `NativeBuildPlan`, `WrapperBuildResult` | Owns public source/`.pyi` build APIs, file output, native input plans, dependency-ready compilation, linking, manifests, and extension import. | + +## Execution Examples + +```bash +python3 prik/pipeline/pyi.py +``` + +```text +Loaded semantic module: math +Loaded contract marker: True +Functions: scale +Re-emitted module: +from prik.contracts import Float64 + +def scale( + value: Float64 +) -> Float64: ... +``` + +```bash +python3 prik/pipeline/type_mapping_report.py +``` + +```text +| `int` | signed 32-bit | `Int (Int32 storage)` | `numpy.int32` | +``` + +The exact width depends on the active target and requires a C compiler. The +columns expose native spelling, measured fact, semantic identity, and NumPy +projection rather than hiding them behind one universal datatype table. + +```bash +python3 prik/pipeline/wrapper.py +``` + +```text +Extension initializer: PyInit_generator_demo +Rendered sources: bind_c_generator_demo_wrapper.f90, generator_demo_wrapper.c, generator_demo_wrapper.h +Native support: binding_support +``` + +This result is still in memory: no file has been written or compiled. + +```bash +python3 prik/pipeline/build.py +``` + +```text +scale(3.0, 2.5) = 7.5 +``` + +The final example requires configured C and Fortran compilers. It follows the +entire public source-build path, imports the resulting extension, and calls its +generated Python API. + +## Tests + +- [Pipeline infrastructure](../../../tests/fortran/infrastructure/pipeline/) +- [Semantic `.pyi` pipeline](../../../tests/fortran/semantic_pyi_format/pipeline/) +- [Build pipeline](../../../tests/fortran/building_shared_library/pipeline/) +- [Compilation integration](../../../tests/fortran/building_shared_library/compiling/) +- [End-to-end builds](../../../tests/fortran/building_shared_library/end_to_end/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Change `.pyi` batch loading, reconciliation, or caching in `pyi.py`. +- Change cross-stage datatype reporting in `type_mapping_report.py`. +- Change plan-to-artifact orchestration in `wrapper.py`. +- Change disk output, manifests, native build requests, compilation scheduling, + linking, or imports in `build.py`. + +## Invariants And Common Mistakes + +- `WrapperGenerator` owns plan-to-rendered-wrapper orchestration, not semantic + decisions and not native compilation. +- Per-operation semantic caches must not become process-global because later + stages attach and freeze data. +- A pipeline helper delegates domain rules to their owning package. +- There is one direct generation route and no legacy retry: + + ```python + complete_semantic_policies(module) + plan = WrapperPlanner().build(module) + generated = WrapperGenerator().generate(plan) + ``` + + Unsupported completed policy fails with its exact owner before either + backend emits source. +- `.pyi` builds reuse the same backend but take API/ABI facts from one edited + entry contract plus explicit native inputs; they never reparse native source + to reconstruct the Python API. + +## Failure Ownership + +| Failure | Earliest owner | +| --- | --- | +| Compiler preprocessing or native include expansion | preprocessing | +| Required target facts cannot be measured | preprocessing probe | +| Source syntax cannot be represented | parser | +| Source facts cannot form a contract | semantic conversion | +| Lifetime, ABI, projection, or support is unsafe | policy completion | +| Completed policy is inconsistent while projected | planning | +| A supported plan lacks an emitted mechanism | binding or bridge generator | +| Native command or link plan is wrong | compiler or build pipeline | +| Imported runtime behavior is wrong | generated binding, runtime support, or upstream policy according to cause | diff --git a/docs/developer/packages/planning.md b/docs/developer/packages/planning.md new file mode 100644 index 000000000..43811a76d --- /dev/null +++ b/docs/developer/packages/planning.md @@ -0,0 +1,126 @@ +--- +title: Planning Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, completed policy +related: ../architecture.md, index.md, policy.md, codegen.md, ../source-map.md +status: maintained +publication: draft +--- + +# Planning Package + +## Purpose And Boundaries + +`prik/planning/` mechanically projects policy-completed semantic IR into one +backend-neutral `ModulePlan`. It joins common transfer facts with explicit +binding and bridge views, namespaces, stable native symbols, lifecycle order, +and build requirements. It may organize and validate completed decisions; it +may not reinterpret source declarations, choose policy, or render text. + +## Local Structure + +```text +prik/planning/ +├── models.py +└── planner.py +``` + +## Internal Workflow + +```text +policy-completed SemanticModule + -> WrapperPlanner validation and projection + -> editable ModulePlan + -> freeze at WrapperGenerator boundary + -> backend node generation +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `models.py` | `ModulePlan`, function, argument, result, slot, lifecycle, class, overload, binding, and bridge plan records | Defines the typed editable plan tree. | +| `planner.py` | `WrapperPlanner`, `_ClassPolicyCatalog` | Validates completed policy, creates indexes and symbols, and projects the plan deterministically. | + +The private class-policy catalogue is a validated lookup, not another semantic +authority. The planner does not generate docstrings or source. + +The stable plan tree keeps orchestration at module, namespace, and function +levels and confines datatype variation to transfers, results, lifecycle +actions, and module variables: + +```text +ModulePlan + -> binding and bridge module views + -> NamespacePlan + -> FunctionPlan + -> ArgumentTransferPlan + -> ResultPlan + -> NativeCallSlotPlan + -> LifecycleActionPlan + -> ModuleVariablePlan +``` + +Each argument or result owns explicit binding and bridge views. Its native-call +slot is the same record referenced from the transfer and the function-wide ABI +ordering index, not a duplicated policy fact. Function orchestration owns call, +result, lifecycle, GIL, and status order without becoming datatype policy. + +`OverloadPlan` stores ordered candidates, exact match records, receiver +conventions, and one candidate ID per overload set. Generated dispatch chooses +an ID before making a native call, preserving first-match behavior for +overlapping optional domains without speculative calls. + +## Execution Examples + +```bash +python3 prik/planning/models.py +``` + +```text +Plan owner: demo +Python export: ping +Native procedure: PING +Native slots: 0 +``` + +```bash +python3 prik/planning/planner.py +``` + +```text +Plan owner: planner_demo +Python export: double_value +Native target: DOUBLE_VALUE +Conversion order: ('planner_demo.double_value.value',) +``` + +The model example demonstrates representation. The planner example follows +the real sequence—semantic IR, policy completion, then planning—and shows the +stable role connecting binding conversion to the native call slot. + +## Tests + +- [Plan model tests](../../../tests/fortran/infrastructure/codegen/test_plan.py) +- [Planner tests](../../../tests/fortran/infrastructure/codegen/test_planner.py) +- [Feature-local codegen stages](../../../tests/fortran/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Add a plan field only for an already completed fact needed by lowering. +- Change projection or indexing in `planner.py`. +- Change ownership, mutability, projection, setter exposure, or support in + policy first. +- Change emitted temporaries or syntax downstream in codegen. + +## Invariants And Common Mistakes + +- Missing completed policy is an error, never a reason to infer a default. +- Binding and bridge views may share one ABI contract without hiding their + backend-specific lowering facts. +- Planning does not depend on presentation helpers such as docstring builders. +- Native slots may interleave argument, result, literal, and helper positions; + keep their function-wide order explicit. +- Lifecycle actions stay explicit because cleanup and writeback order may span + several transfers and differ on failure. diff --git a/docs/developer/internal-architecture/ownership-tracking.md b/docs/developer/packages/policy.md similarity index 69% rename from docs/developer/internal-architecture/ownership-tracking.md rename to docs/developer/packages/policy.md index ab8de29cc..5571f14d9 100644 --- a/docs/developer/internal-architecture/ownership-tracking.md +++ b/docs/developer/packages/policy.md @@ -1,13 +1,174 @@ --- -title: Ownership Tracking -audience: maintainers -prerequisites: runtime layer, memory ownership model -related: runtime-layer.md, wrapper-generation-pipeline.md, ../../user/guide/memory-management.md +title: Policy Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, semantic IR +related: ../architecture.md, index.md, semantics.md, planning.md, runtime.md, ../../user/guide/memory-management.md status: maintained publication: draft --- -# Ownership Tracking +# Policy Package + +## Purpose And Boundaries + +`prik/policy/` is the final semantic authority before planning. It resolves +public exports, object kind, owner, transfer, destruction, mutability, +writeback, nullability, storage, projections, lifecycle actions, descriptor +operations, setter behavior, and support blockers. Planning and generation may +dispatch from these immutable decisions but may not replace them. + +## Local Structure + +```text +prik/policy/ +├── models.py +├── ownership.py +├── exports.py +├── construction.py +├── completion.py +└── native_array_handles.py +``` + +## Internal Workflow + +```text +complete SemanticModule + normalized raw metadata + -> export and graph completion + -> ownership and feature-policy construction + -> complete_semantic_policies() + -> immutable policies attached to semantic IR + -> WrapperPlanner +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `models.py` | `FunctionWrapperPolicy`, argument/result/call-slot/lifecycle/class/callback/array records | Defines immutable backend-neutral completed policy. | +| `ownership.py` | `OwnershipDecision` and ownership vocabulary | Resolves object kind, lifetime triple, storage, and strict lowering actions. | +| `exports.py` | `PythonExportPolicy` | Completes collision-checked Python placement. | +| `construction.py` | `_FunctionPolicyContext` and feature constructors | Builds coherent function, result, native-slot, callback, and class policies. | +| `completion.py` | `complete_semantic_policies()` | Runs completion in explicit dependency order and attaches its results. | +| `native_array_handles.py` | `NativeArrayHandlePolicy` and ABI dispatch records | Completes descriptor operations, array ABI, and selected build requirements. | + +## Execution Examples + +```bash +python3 prik/policy/models.py +``` + +```text +Array policy: rank=2, shape=('rows', 'columns'), order=F +Lifecycle policy: copy_out writeback via copy_in_out +Completed record mutation rejected: True +``` + +```bash +python3 prik/policy/ownership.py +``` + +```text +before: math.scale(value): Float64 semantic IR +after: scalar/caller/call_local; scalar_value -> pass_value +``` + +```bash +python3 prik/policy/exports.py +``` + +```text +Native semantic owner: math.SCALE_VALUE +Python export: linear_algebra.scale_value +Completed policy type: PythonExportPolicy +``` + +```bash +python3 prik/policy/construction.py +``` + +```text +before: math.scale(value): Float64 semantic IR +after: direct_transfer; result=native_scalar; native=pass_value +``` + +```bash +python3 prik/policy/completion.py +``` + +```text +before: math.scale(value): Float64 semantic IR +after: math.scale(value): scalar_value -> pass_value +``` + +```bash +python3 prik/policy/native_array_handles.py +``` + +```text +Handle policy: pointer/pointer, storage=alias +Allowed operations: to_numpy, nullify +Array ABI: descriptor +Selected build header: ISO_Fortran_binding.h +``` + +These exact outputs show completed immutable decisions rather than generated source. +The completion entrypoint is mandatory for normal planning; individual example +builders exist only to expose their focused ownership boundaries. + +## Tests + +- [Policy infrastructure](../../../tests/fortran/infrastructure/semantics/) +- [Native handle policy](../../../tests/fortran/infrastructure/semantics/test_native_array_handles.py) +- [Feature-local policy suites](../../../tests/fortran/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Put reusable immutable output vocabulary in `models.py`. +- Start a new semantic decision in completion and its focused resolver or + constructor. +- Extend strict descriptor dispatch/build selection in + `native_array_handles.py`. +- If a generator guesses policy from datatype, intent, aliases, or local + memory checks, remove the guess and complete the decision here. + +## Invariants And Common Mistakes + +- Completion order remains visible and explicit; do not replace it with an + opaque pass registry. +- Blocked policies keep their owner path and reason. +- Shared policy models remain independent of construction implementation. + +## Orthogonal Selector Vocabulary + +Completed policy keeps separate questions separate: + +| Selector | Question | +| --- | --- | +| `ObjectKind` | What kind of value follows this route? | +| result source kind | Is a result direct or projected from a hidden output? | +| `PythonBarrierAction` | How does the binding validate or extract the Python object? | +| `NativeBarrierAction` | What transport crosses the native ABI boundary? | +| `CodegenAction` | What ownership or transfer operation occurs? | +| bridge data action | What representation operation occurs in the bridge? | +| writeback phase | When does mutation, copy-out, cleanup, or release happen? | + +Hiddenness is not an ownership action. Ordinary NumPy buffer handoff and +persistent native descriptor handoff are also distinct ABI choices; neither +backend may substitute one for the other. A datatype family may select an +element spelling only after object-kind/action dispatch. It must never be used +to rediscover whether the overall transfer is scalar, string, array, or native +handle. + +Native source `intent` may seed a generated default Python signature during +source conversion, but the editable signature, `Returns[...]` projection, and +ordered native-call mapping become authoritative. An explicit native-call list +is exhaustive for native dummy positions. Transport overrides such as +primitive `Addr(Arg(i))` or derived `Value(Arg(i))` select ABI transport; +`Returns[...]` selects Python projection and writeback expectation, not the +transport itself. + +## Ownership Resolution Reference PRIK represents ownership as a completed semantic contract, not as one label such as "owned" or "borrowed." A value is lowering-ready only after policy diff --git a/docs/developer/packages/preprocessing.md b/docs/developer/packages/preprocessing.md new file mode 100644 index 000000000..91009ff03 --- /dev/null +++ b/docs/developer/packages/preprocessing.md @@ -0,0 +1,135 @@ +--- +title: Preprocessing Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, native project compiler flags +related: ../architecture.md, index.md, parsers.md, semantics.md, ../concepts/datatype-lifecycle.md +status: maintained +publication: draft +--- + +# Preprocessing Package + +## Purpose And Boundaries + +`prik/preprocessing/` turns original Fortran source into authoritative parser +input and measures compiler-dependent target facts needed by semantic +conversion. Compiler expansion, native Fortran includes, and executable probes +are separate mechanisms with separate results. The package does not parse +declarations, assign semantic scalar identities, choose NumPy dtypes, or +complete wrapper policy. + +## Local Structure + +```text +prik/preprocessing/ +├── source.py +├── fortran.py +└── probes/ + └── fortran_types.py +``` + +The C preprocessing and target-probe modules remain deferred from the +published Fortran contributor workflow. + +## Internal Workflow + +```text +original Fortran path + PreprocessingConfig + -> compiler expansion and line-marker recovery + -> native Fortran INCLUDE expansion + -> PreprocessResult(source, provenance, dependencies, recipe, diagnostics) + -> Fortran parser + +compiler identity + target flags + kind/storage requirements + -> executable target probe + -> FortranTypeProbeReport + -> Fortran-to-IR conversion +``` + +Recipes retain compiler, adapter, argv, include directories, macro flags, +included files, source mappings, and diagnostics so a build can explain or +replay its parser input. Probe cache identity includes the compiler and target +configuration; measured facts must not cross targets silently. + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `source.py` | `PreprocessingConfig`, `PreprocessingRecipe`, `PreprocessResult`, `SourceMapping`, `IncludedFile` | Runs compiler preprocessing, collects provenance and dependencies, and coordinates native include expansion. | +| `fortran.py` | `expand_native_fortran_includes()` | Recursively expands native `INCLUDE` statements while preserving original locations and diagnostics. | +| `probes/fortran_types.py` | `FortranTypeProbeRecipe`, `FortranTypeProbeReport` | Compiles and runs target programs for kind expressions, storage widths, logical representations, and compile-time values. | + +## Execution Examples + +Coordinated preprocessing: + +```bash +python3 prik/preprocessing/source.py +``` + +```text +Before Fortran include expansion: +module greeting +include 'constants.inc' +... +After Fortran include expansion: +module greeting +integer, parameter :: answer = 42 +... +Native includes: 1; diagnostics: 0 +``` + +Native include expansion in isolation: + +```bash +python3 prik/preprocessing/fortran.py +``` + +```text +Expanded parser input: +module geometry +integer, parameter :: dimensions = 3 +end module geometry +Native include dependencies: 1 +Generated source mappings: 5 +Diagnostics: 0 +``` + +Compiler-measured Fortran type facts: + +```bash +python3 prik/preprocessing/probes/fortran_types.py +``` + +```text +selected_int_kind(9) = 4 +``` + +The first two outputs prove that prepared source retains dependency and source +mapping facts. The probe output is a native kind value, not yet a stable +semantic scalar or NumPy dtype. The probe example requires `gfortran` or +`f95`. + +## Tests + +- [Fortran preprocessing](../../../tests/fortran/source_preprocessing/preprocessing/) +- [Parser boundary tests](../../../tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py) +- [Fortran target probes](../../../tests/fortran/data_types/probes/test_fortran_type_probes.py) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Change compiler expansion, provenance, recipes, or diagnostics in + `source.py`. +- Change native `INCLUDE` behavior in `fortran.py`. +- Change target measurement or cache identity in `probes/fortran_types.py`. +- Change parser grammar downstream; change stable scalar identity or backend + mapping in the owning semantic/codegen package. + +## Invariants And Common Mistakes + +- Preserve original source coordinates through every source transformation. +- Run native probes in temporary working directories so `.mod` and other + compiler products cannot pollute the repository. +- Do not combine textual preprocessing and target measurement into one generic + operation simply because both run before parsing. diff --git a/docs/developer/packages/printers.md b/docs/developer/packages/printers.md new file mode 100644 index 000000000..b88ec815e --- /dev/null +++ b/docs/developer/packages/printers.md @@ -0,0 +1,118 @@ +--- +title: Printers Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, formed source representations +related: ../architecture.md, index.md, codegen.md, pipeline.md, parsers.md +status: maintained +publication: draft +--- + +# Printers Package + +## Purpose And Boundaries + +`prik/printers/` is the representation-to-text boundary. C and Fortran +printers serialize backend nodes; the semantic `.pyi` printer serializes +semantic IR. Printers own formatting, escaping, indentation, declaration +order, and safe line wrapping. They do not invoke generators, choose filenames, +complete policy, or compile output. + +## Local Structure + +```text +prik/printers/ +├── c.py +├── fortran.py +└── pyi.py +``` + +## Internal Workflow + +```text +formed C or Fortran node tree -> matching source printer -> native text +SemanticModule graph -> PyiPrinter -> editable .pyi +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `c.py` | `CSourcePrinter` | Serializes C translation units, headers, declarations, functions, tables, and statements. | +| `fortran.py` | `FortranSourcePrinter` | Serializes bridge modules, interfaces, declarations, procedures, and statements with free-form line wrapping. | +| `pyi.py` | `PyiPrinter`, `emit_module()`, `_PyiEmissionContext` | Serializes semantic modules and scopes imports, aliases, class names, namespaces, and default order for one emission. | + +The fact that code generation calls a printer at the end of wrapper rendering +does not make printing part of codegen ownership. `pipeline/wrapper.py` +coordinates both distinct stages. + +## Execution Examples + +```bash +python3 prik/printers/c.py +``` + +```text +Rendered C binding source: +#include + +static PyObject * wrap_ping(PyObject * self) { + Py_INCREF(Py_None); + return Py_None; +} +``` + +```bash +python3 prik/printers/fortran.py +``` + +```text +Rendered Fortran bridge source: +module bind_c_printer_demo_wrapper + use iso_c_binding, only: c_double + use printer_demo, only: native_double_value => DOUBLE_VALUE + implicit none +contains + function bind_c_double_value(value) result(result) bind(c, name="DOUBLE_VALUE") + real(c_double), value :: value + real(c_double) :: result + result = native_double_value(value) + end function bind_c_double_value +end module bind_c_printer_demo_wrapper +``` + +```bash +python3 prik/printers/pyi.py +``` + +```text +Semantic module: printer_demo +from prik.contracts import Float64, bind + +@bind("DOUBLE_VALUE") +def double_value( + value: Float64 +) -> Float64: ... +``` + +The native examples prove that punctuation and layout are added to already +formed nodes. The `.pyi` example proves that required contract imports and +native identity are derived without attaching wrapper policy. + +## Tests + +- [Printer infrastructure](../../../tests/fortran/infrastructure/printers/) +- [Semantic `.pyi` round trips](../../../tests/fortran/semantic_pyi_format/) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Change formatting or serialization in the matching printer. +- If information is missing from a native node, add it in generation or the + plan rather than consulting semantic IR from the printer. +- Change filenames or multi-source artifact order in the pipeline. + +## Invariants And Common Mistakes + +- Native source printers accept backend nodes, not semantic models. +- The `.pyi` printer accepts semantic IR, not wrapper plans. +- Emission contexts are per-operation and restored safely after failures. diff --git a/docs/developer/packages/runtime.md b/docs/developer/packages/runtime.md new file mode 100644 index 000000000..b03636c0e --- /dev/null +++ b/docs/developer/packages/runtime.md @@ -0,0 +1,96 @@ +--- +title: Runtime Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, completed native handle policy +related: ../architecture.md, index.md, policy.md, compiler.md, pipeline.md +status: maintained +publication: draft +--- + +# Runtime Package + +## Purpose And Boundaries + +`prik/runtime/` owns Python objects that remain active after importing a +generated extension and the bundled native header payload used by generated +bindings. Runtime objects validate descriptor metadata, retain owners, adapt +generated operations, and expose policy-selected NumPy views. They enforce +completed behavior; they do not decide ownership or invent missing operations. + +## Local Structure + +```text +prik/runtime/ +├── handles.py +└── native_support/ + ├── __init__.py + ├── prik_binding.h + └── LICENSE +``` + +## Internal Workflow + +```text +generated extension operation dictionary + -> descriptor metadata validation + -> AllocatableArray or PointerArray adapter + -> policy-permitted allocate/deallocate/resize/nullify/to_numpy operations +``` + +The native-support initializer only makes the payload locatable. The compiler +installs it into a generated `binding_support/` include directory. + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `handles.py` | `NativeArrayHandleBase`, `AllocatableArray`, `PointerArray` | Adapts generated descriptor operations into stable Python APIs and live NumPy views. | +| `native_support/prik_binding.h` | capsule, descriptor, validation, conversion, and release helpers | Supplies the header-only native runtime used by generated bindings. | +| `native_support/__init__.py` | package marker | Makes the payload discoverable without creating another runtime API. | + +## Execution Example + +```bash +python3 prik/runtime/handles.py +``` + +```text +Runtime handle: AllocatableArray +Descriptor kind: allocatable +Initial view: [1.0, 2.0, 3.0] +Resized shape: (4,) +Generated resize received NumPy extents: True +``` + +The example supplies the same operation dictionary shape exported by a +generated extension. It proves descriptor selection, validation, operation +adaptation, and the generated NumPy extent convention. The returned NumPy +storage is live, not a detached snapshot. + +The native payload intentionally has no standalone Python example: it is +compiled only as part of a generated binding. + +## Tests + +- [Allocatable runtime tests](../../../tests/fortran/allocatables/runtime/) +- [Pointer runtime tests](../../../tests/fortran/pointers/runtime/) +- [Memory-management runtime tests](../../../tests/fortran/memory_management/runtime/) +- [Runtime infrastructure](../../../tests/fortran/infrastructure/runtime/) +- [Compiled runtime compatibility](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Change handle protocol, validation, retention, or adapters in `handles.py`. +- Change header implementation in `native_support/` and installation in + `prik/compiler/native_support.py`. +- Complete any new ownership, operation permission, or view policy upstream + before runtime enforcement. + +## Invariants And Common Mistakes + +- Outstanding zero-copy NumPy views cannot be revoked after native + reallocation or pointer reassociation. Callers must discard or copy them. +- Runtime must reject operations absent from completed policy rather than + guessing permission from descriptor kind. +- The native support directory is a payload, not a second pipeline stage. diff --git a/docs/developer/packages/semantics.md b/docs/developer/packages/semantics.md new file mode 100644 index 000000000..525bf4dcc --- /dev/null +++ b/docs/developer/packages/semantics.md @@ -0,0 +1,158 @@ +--- +title: Semantics Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, parser package guide +related: ../architecture.md, index.md, parsers.md, policy.md, ../concepts/datatype-lifecycle.md +status: maintained +publication: draft +--- + +# Semantics Package + +## Purpose And Boundaries + +`prik/semantics/` converts Fortran parser facts or semantic `.pyi` AST into the +same language-neutral `SemanticModule` graph. It owns stable types, public and +native identities, shapes, projections, provenance, storage contracts, and raw +metadata. It does not complete ownership, select lowering actions, plan +wrappers, or emit source. + +## Local Structure + +```text +prik/semantics/ +├── models.py +├── scalar_types.py +├── fortran2ir.py +├── pyi2ir.py +├── metadata.py +├── pyi_metadata.py +├── ownership_metadata.py +├── native_array_handles.py +└── native_contract.py +``` + +The deferred C-to-IR path is intentionally excluded from the published +Fortran contributor workflow. + +## Internal Workflow + +```text +Fortran parser models + measured target facts ─┐ + ├─> SemanticModule graph +semantic .pyi AST ─────────────────────────────┘ + -> raw ownership/native contract metadata + -> prik.policy completion +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `models.py` | `SemanticModule`, `SemanticFunction`, `SemanticClass`, `SemanticArgument`, `SemanticType`, array/storage contracts, `SemanticOrigin` | Defines the language-neutral graph. | +| `scalar_types.py` | `SemanticScalarSpec` and scalar catalogue | Defines stable scalar identities and intrinsic family/storage facts without backend spellings. | +| `fortran2ir.py` | `FortranToIRConverter` | Resolves Fortran models and probed facts into semantic IR. | +| `pyi2ir.py` | `convert_pyi_to_ir()` | Interprets parsed Python AST as an editable semantic contract. | +| `ownership_metadata.py` | normalized ownership and pointer request setters | Stores unresolved frontend requests for later policy completion. | +| `native_array_handles.py` | `NativeArrayHandleFacts` | Separates descriptor, array-data, and element facets. | +| `native_contract.py` | `NativeContractIssue` and validation helpers | Prepares and validates source-free native placement and ABI facts. | + +`metadata.py` and `pyi_metadata.py` are passive shared-key registries. Combined +multi-file `.pyi` loading belongs to `prik/pipeline/pyi.py`. + +## Execution Examples + +```bash +python3 prik/semantics/models.py +``` + +```text +Semantic module: geometry +Function: scale -> native SCALE +Argument: values: Float64, rank=1, shape=('n',), order=F +Source provenance: fortran real +``` + +```bash +python3 prik/semantics/scalar_types.py +``` + +```text +Float64: family=real, storage=64 bits +Int: family=signed_integer, storage=target-dependent +Backend spelling stored here: False +``` + +```bash +python3 prik/semantics/fortran2ir.py +``` + +```text +math.scale(value): Float64 via reference storage +``` + +```bash +python3 prik/semantics/pyi2ir.py +``` + +```text +math.scale(value): Float64 -> Float64 +``` + +```bash +python3 prik/semantics/ownership_metadata.py +``` + +```text +Raw ownership request: owner=caller, transfer=in_place, destruction=caller +Pointer contract: nullable=True, lifetime=owner, reassociation=forbidden +Completed lowering action present: False +``` + +```bash +python3 prik/semantics/native_array_handles.py +``` + +```text +Descriptor kind: allocatable +Data facet: Float64, rank=2, shape=('rows', 'columns') +Element facet: Float64, rank=0 +Handle marker retained by data facet: False +``` + +```bash +python3 prik/semantics/native_contract.py +``` + +```text +Prepared origin: fortran module math +Valid contract issues: 0 +Invalid contract issue: pyi_native_type_missing at math.broken.value +``` + +These examples show stable semantic representation and raw contract facts. +None contains a completed binding or bridge action. + +## Tests + +- [Semantic IR conversion](../../../tests/fortran/semantic_ir/semantics/) +- [Semantic `.pyi` behavior](../../../tests/fortran/semantic_pyi_format/) +- [Datatype semantics](../../../tests/fortran/data_types/semantics/) +- [Native handle semantics](../../../tests/fortran/infrastructure/semantics/test_native_array_handles.py) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Change graph shape in `models.py` only when downstream contracts need a new + language-neutral fact. +- Change stable primitive vocabulary in `scalar_types.py`. +- Change frontend interpretation in the matching converter. +- Change lifetime, transfer, setter, projection, or support decisions in + policy, never in semantic conversion. + +## Invariants And Common Mistakes + +- Parser source spellings and backend dtype spellings are not semantic type + identities. +- Raw ownership metadata is not completed ownership policy. +- Preserve provenance when normalizing language-specific facts. diff --git a/docs/developer/packages/utilities.md b/docs/developer/packages/utilities.md new file mode 100644 index 000000000..61f95783d --- /dev/null +++ b/docs/developer/packages/utilities.md @@ -0,0 +1,86 @@ +--- +title: Utilities Package +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide +related: ../architecture.md, index.md, semantics.md, planning.md, codegen.md +status: maintained +publication: draft +--- + +# Utilities Package + +## Purpose And Boundaries + +`prik/utilities/` contains small mechanisms that are genuinely independent of +one compiler stage. A helper belongs here only while it avoids stage-owned +semantic policy, syntax grammar, and workflow orchestration. + +## Local Structure + +```text +prik/utilities/ +├── declaration_expressions.py +├── strings.py +└── visitor.py +``` + +## Important Files And Essential Objects + +| File | Important objects | Responsibility | +| --- | --- | --- | +| `declaration_expressions.py` | `ResolvedDeclarationExtent`, `DeclarationExpressionCall`, `ArrayExpressionSource` | Translates, validates, resolves, evaluates, and renders declaration extents across explicit stage boundaries. | +| `strings.py` | collision-safe local-name helpers | Allocates deterministic local names without owning a public naming policy. | +| `visitor.py` | `ClassVisitor` | Provides exact-class and intentional MRO fallback dispatch shared by independent visitors. | + +## Execution Examples + +```bash +python3 prik/utilities/declaration_expressions.py +``` + +```text +Fortran extent: ubound(source, 1) - lbound(source, 1) + 1 +Public expression: source.shape[0] +Role-bound expression: __prik_extent_source_0 +Fortran rendering: native_source_extent_0 +Compile-time product: 6 +``` + +The expression changes representation at explicit stages. Backend rendering +uses a plan-supplied substitution and does not rediscover argument ownership. + +```bash +python3 prik/utilities/strings.py +``` + +```text +First available name: temporary_4 +Next counter: 5 +``` + +```bash +python3 prik/utilities/visitor.py +``` + +```text +Exact handler: literal:42 +MRO fallback: expression:Expression +``` + +## Tests + +- [Utility infrastructure](../../../tests/fortran/infrastructure/utilities/) +- [Declaration-expression semantics](../../../tests/fortran/arrays/semantics/test_declaration_expression_utilities.py) +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) + +## Change Routes + +- Keep parsing, role resolution, evaluation, and backend rendering separate in + declaration-expression code. + +## Invariants And Common Mistakes + +- Consumers define their own visitor handlers; `ClassVisitor` does not merge + frontend or backend visitor responsibilities. +- Move a helper out of utilities as soon as it starts selecting semantic + policy or a pipeline action. diff --git a/docs/developer/quality-assurance.md b/docs/developer/quality-assurance.md deleted file mode 100644 index b476fea75..000000000 --- a/docs/developer/quality-assurance.md +++ /dev/null @@ -1,413 +0,0 @@ ---- -title: Quality Assurance -audience: developers, contributors -prerequisites: repository checkout, QA dependencies -related: testing-strategy.md, development-workflow.md -status: maintained -publication: draft ---- - -# Quality Assurance - -Last reviewed: 2026-07-31 - -This project uses a staged Python QA stack. Fast bug-focused checks, including -the bounded parser fuzz cases, run on pull requests. Maintainers can rerun the -fuzz-marked cases manually with the deeper Hypothesis profile. - -The selected active quality stack is adopted. Future Ruff/Radon threshold -ratchets are ongoing maintenance, not unfinished rollout work. Mutation -testing and pre-commit are not part of the active stack. - -## Active Cadence - -| Cadence | Tools | -| --- | --- | -| Local pre-push | Blocking static analysis, focused documentation smoke, one compiled scalar-wrapper smoke test, maintainer-tool tests in `tests/tools/`, and workflow-safety tests in `tests/workflows/` | -| Pull request and protected-branch push | pytest, bounded property/fuzz cases, stable-seed pytest-randomly, Ruff, Bandit, Vulture, staged Radon policy, and project coverage | -| Pull request, protected-branch push, weekly, and manual | Pinned Intel IFX/ICX and LLVM Flang/Clang profile checks plus strict Fortran toolchain smoke | -| Validated pull request and main-branch push | Pinned ARM64 prik/f2py correctness and rigorous performance benchmark | -| Manual discovery | Fuzz-marked parser tests with the deeper Hypothesis fuzz profile | -| Manual triage | Full Radon reports and low-severity Bandit review | -| Annual dependency review | Dependency vulnerability audit outside the routine per-change gate | - -Active GitHub Actions checks use stable, self-contained job names. Pull requests -are coordinated by `Pull Request` in five stages: - -1. Static analysis runs first. -2. Alternate-compiler smoke testing starts after static analysis succeeds. -3. The unit-test matrix starts after compiler smoke testing succeeds; its - Ubuntu Python 3.12 entry owns the project-coverage gate instead of repeating - that suite in a separate job. -4. BLAS/LAPACK validation starts only after the complete unit-test matrix, - including its coverage entry, succeeds. -5. The same pinned ARM64 documentation performance benchmark used on `main` - runs after native-library validation, and its generated snapshot is consumed - by the strict documentation build. - -An aggregate job runs with `always()` after every stage and fails unless all -required stage results succeeded. Configure the repository ruleset with this -single required status check: - -- `Pull Request / Validation · all required checks`. - -Treat that string as ruleset API. If its workflow or job display name changes, -replace the corresponding required-status-check entry; do not retain an alias -job for the previous name. The pull-request workflow declares its jobs directly -so check names contain only the `Pull Request` workflow name and the actual job -name; it does not add reusable-workflow caller stages between them. The -purpose-specific workflows retain the same complete job names for their -independent main, release, scheduled, and manual runs. - -## Install - -Install the package plus the QA toolchain: - -```bash -python -m pip install -e ".[qa]" -python tools/check_static_analysis_versions.py -``` - -If your shell only exposes `python3`, use: - -```bash -python3 -m pip install -e ".[qa]" -python3 tools/check_static_analysis_versions.py -``` - -## Local Commands - -Fast inner loop: - -```bash -pytest -q -python -m ruff check . -python -m ruff format . -``` - -CI-shaped local coverage run: - -```bash -HYPOTHESIS_PROFILE=ci \ -COVERAGE_PROCESS_START=pyproject.toml \ -PYTHONPATH=. \ -python -m coverage run -m pytest -q --randomly-seed=1 -python -m coverage combine -python -m coverage report -``` - -For subprocess coverage investigations, mirror that command shape before -deciding a fix. A plain local coverage run can miss subprocess data. -Every Python version excludes the full real-library wrapper examples while -retaining general native-bundle coverage. The `Real Libraries` component runs -the complete BLAS, LAPACK, FFTPACK, and MINPACK examples on Python 3.12. Each -job step sources the documented `build_all.sh` entrypoint before starting -pytest. BLAS and LAPACK additionally run their CI-only full-surface audits; -FFTPACK and MINPACK run their fail-closed public-inventory tests as part of the -maintained example suites. The job therefore verifies the copyable build and -test commands for all four libraries. A pull request may use the -`ignore-real-library-wrappers` label to skip that expensive component without -disabling the ordinary Python-version matrix. - -Every pull request and push to `main` runs the canonical Python 3.12 smoke and -ordinary-suite selections through `Quality Metrics`, then combines and -publishes their coverage data. The combined coverage.py report is the blocking -project gate and must remain at or above 90%. Codecov repeats that project -target for hosted reporting. Its -patch status is informational: changed-line coverage remains visible for -review, but a tiny defensive branch cannot independently fail an otherwise -passing project report. New reachable behavior should still receive focused -tests instead of relying on that reporting policy. - -Every matrix test run also writes a path-aware JUnit report. If pytest fails, the final -workflow step reads that report and prints a compact `Failed pytest nodes` -section containing every failed test node ID, including parametrization such -as `[source]` or `[generated-pyi]`. This summary is intentionally separate from -pytest's traceback output so failed names remain easy to find at the end of a -long GitHub Actions log. If pytest exits before producing a readable report, -the final step says that no report was available instead of hiding the failure. - -Reproduce an order-dependent failure from the stable CI seed: - -```bash -pytest -q --randomly-seed= -``` - -Run the same alternate-compiler lane used by GitHub Actions: - -```bash -python3 tools/run_fortran_toolchain_lane.py --compiler=/path/to/ifx -python3 tools/run_fortran_toolchain_lane.py --compiler=/path/to/flang -``` - -Use `--plan` to inspect the two pytest commands without executing them. Every -lane first runs the compiler-profile and focused preprocessing-CLI tests, then -runs the unchanged eight-node strict `toolchain_smoke` selection. GitHub -Actions pins IFX/ICX 2026.1.1 and Flang/Clang 22.1.8 on `ubuntu-24.04`; -compiler runtime directories are exported for extension loading. These are -tested CI pins, not inferred minimum supported versions. The Intel environment -installs both `ifx_linux-64` and `dpcpp_linux-64`: the former supplies IFX, -while the latter supplies the required ICX binding compiler. - -Run property and fuzz tests: - -```bash -pytest -q -m property --hypothesis-profile=ci -HYPOTHESIS_PROFILE=fuzz pytest -q -m fuzz --hypothesis-show-statistics -``` - -Run security checks: - -```bash -python -m bandit -c pyproject.toml -r prik --severity-level medium --confidence-level medium -``` - -Run dead-code and complexity checks: - - - - - -## Tool Decisions - -### pytest And coverage.py - -**Role:** behavioral regression backbone and branch-coverage floor. - -**Evidence:** recorded full-suite baseline is `3497 passed`; combined -subprocess branch coverage is `95.34%`, above the configured `95%` gate. - -**Decision:** keep as required baseline project gates. - -### pytest-randomly - -**Role:** catches hidden test-order coupling and makes failures reproducible -with seeds. - -**Evidence:** normal CI uses `--randomly-seed=1`, so order is shuffled but -reproducible. - -**Decision:** keep stable-seed PR CI. The changing-seed scheduled job was -removed as redundant maintenance overhead. - -### Hypothesis - -**Role:** generates edge cases for parsers, AST transforms, semantic IR, and -code generation. - - - -**Decision:** keep bounded property tests in normal test coverage and longer -fuzz profiles on schedule/manual dispatch. - -### Ruff - -**Role:** fast linting and formatting for undefined names, unused imports, -suspicious patterns, modernization, simplified control flow, and high McCabe -complexity. - -**Bugs or issues found:** raw regex issues, formatting drift, and static-risk -maintenance debt. These are static-risk findings, not runtime defects. - -**Decision:** keep as a blocking gate. Line-length diagnostics remain -intentionally unselected because wrapping parser diagnostics and embedded test -sources would add noise without improving correctness. - -### Bandit - -**Role:** security scanning for subprocess, filesystem, deserialization, and -credential-like patterns. - -**Evidence:** no medium- or high-severity findings. Reviewed low-severity -findings are parser sentinel/template tokens and intentional argv-based -compiler/preprocessor subprocess calls without shell execution. - -**Decision:** keep blocking at medium confidence/severity in CI. Re-review the -full low-severity report after subprocess-boundary changes. - -### Dependency Vulnerability Review - -**Role:** dependency vulnerability scanning. - -**Evidence:** routine per-change scans were noisy and slow relative to the -dependency churn in this project. - -**Decision:** do not run dependency vulnerability scanning as a pull-request or -local per-change gate. Revisit dependencies during an annual manual review or -when adding/upgrading runtime dependencies. - -### Vulture - -**Role:** dead-code detection. - -**Bugs or issues found:** removed dead Fortran parser parameters and unused test -lambda parameters reported by CI. - -**Decision:** keep blocking in CI with narrow exclusions. - -### Radon - -**Role:** complexity and maintainability tracking. - - - -**Bugs or issues found:** Radon found maintainability hotspots. CI also exposed -that the first staged policy was too strict for unchanged legacy hotspots; the -policy was corrected. - -**Decision:** keep `tools/check_radon_policy.py` blocking and keep full Radon -reports advisory/manual. - -### GitHub Actions - -**Role:** reproducible CI and scheduled discovery. - -**Bugs or issues found:** recent remote quality runs found Ruff raw-regex -issues, Ruff formatting drift, Vulture unused test parameters, and the -too-strict Radon policy. - -**Native artifact cache:** dedicated Python 3.12 BLAS and LAPACK jobs restore -the cache used by `examples.native_library`. On a miss, the example-owned -builder compiles each implementation corpus once; PRIK and f2py reuse the same -artifact. The ordinary pytest matrix excludes that full corpus while retaining -the lighter native-bundle tests. Requested coverage runs still -collect Python 3.12 coverage data; a final coverage job combines that artifact -and uploads the XML report. - -**Failure reporting:** each pytest matrix invocation writes -`pytest-results.xml`; the final failure-only step runs -`tools/print_pytest_failures.py` so all failed node IDs appear together at the -end of the job log. - -**Decision:** keep. Review scheduled results and record actionable failures -until fixed. - -## Historical Mutation Findings - -Mutation testing was useful during rollout, but it is no longer an adopted -tool. Do not keep `mutmut` as a regular dependency, workflow, or local wrapper. -A future annual mutation audit can be run outside the normal QA stack if -needed. - -Keep the ordinary regression tests and fixes that came from it: - -- Fortran directory-project parsing respecting the requested encoding; -- direct Fortran parser contracts for diagnostics, forwarding, registries, - ownership, provenance, source locations, boundaries, and loop progress. - - - -## Test Organization - -- Unit tests: keep narrow behavior tests under the owning language, feature, - and pipeline-stage directory. -- Regression tests: add focused tests next to the subsystem that failed. Mark - with `@pytest.mark.regression` when useful. -- Property tests: keep generated invariants beside the domain they exercise. -- Fuzz-like parser tests: keep bounded generators beside the owning parser - tests, mark with `@pytest.mark.fuzz`, and run with the `fuzz` Hypothesis - profile. - -Good invariants for this codebase: - -- parsing the same source twice produces the same JSON/dict representation; -- generated declarations preserve name order and source locations; -- semantic conversion is deterministic for equivalent parser models; -- Pyi emission can be parsed back into equivalent semantic IR for supported - subsets; -- malformed input raises parser-owned diagnostic exceptions, not arbitrary - exceptions. - -## Adoption Status - -Full adoption for the selected stack means: - -- fast PR gates are blocking and stable; -- fuzz-marked parser robustness tests run in the ordinary matrix and remain - available with a deeper manual profile; -- Ruff baseline ignores are removed or deliberately retained with a reason; -- Radon has a documented blocking policy for new or materially changed code. - -Current status by area: - -| Area | Status | Explanation | -| --- | --- | --- | -| Fast pull-request gates | Complete for adoption | Tests, coverage, Ruff, Bandit, Vulture, and staged Radon are wired as blocking gates. | -| Property and fuzz testing | Complete for adoption | Current parser, AST, semantic-IR, and code-generation invariants exist; future failures still need regression tests. | -| Dead-code detection | Complete for adoption | Vulture is clean and blocking; future public API additions should keep exclusions narrow. | -| Security and dependency scanning | Complete for adoption | Bandit is blocking; dependency vulnerability review is annual/manual or tied to dependency changes. | -| Complexity tracking | Complete for adoption | The staged Radon policy is blocking in CI; future hotspot decomposition can ratchet thresholds further. | - -Ongoing maintenance: - -1. Save minimized examples from actionable fuzz failures as focused regression - tests. -2. Lower Ruff/Radon complexity thresholds after hotspot refactors make that - safe. - -## Manual Fuzz Triage - -Run deeper discovery explicitly with the documented `HYPOTHESIS_PROFILE=fuzz` -command: - -1. Re-run a failing example to separate actionable failures from transient - local-environment failures. -2. Reproduce actionable failures with the logged Hypothesis profile and - save minimized examples as focused regression tests. -3. Record each actionable failure here or in the relevant issue until - the regression test and fix pass. - -## Progress Log - -| Date | Area | Result | Follow-up | -| --- | --- | --- | --- | -| 2026-05-31 | Initial stack integration | Added configuration, CI, documentation, and Hypothesis tests. | Continue staged strictness rollout. | -| 2026-05-31 | Bandit | Reviewed low-severity findings and confirmed no medium- or high-severity findings. | Re-review when command trust boundaries change. | -| 2026-05-31 | Hypothesis code generation | Added generated native-name escaping, stable synthetic-import ordering, and semantic-IR-to-Pyi parse-back invariants; fixed quoted `SourceName(...)` emission. | Keep storing minimized failures. | -| 2026-06-01 | Ruff formatting rollout | Formatted the historical Python tree and changed CI to `ruff format --check .`. | Continue complexity-policy ratchets. | -| 2026-06-01 | Radon and Ruff complexity policy | Added `tools/check_radon_policy.py`, made the staged Radon policy blocking in CI, and lowered Ruff McCabe from `50` to `45`. | Continue hotspot refactors and later threshold ratchets toward `20`. | -| 2026-06-02 | Historical mutation-derived tests | Added direct Fortran parser contracts and fixed the directory namespace encoding bug. | Keep the tests as normal regression coverage. | -| 2026-06-03 | Manual Quality workflow review | Reviewed workflow run `26832679820`: fuzz passed, changing random-order pytest passed, static analysis exposed Ruff fixes, and full-project mutation exceeded the `3h` Actions limit. | Mutation was removed from active adoption; scheduled fuzz moved to its own workflow. | -| 2026-06-03 | Quality workflow triage | Reviewed latest Quality runs; run `26856679038` for `remove mutmut` completed successfully. | No actionable scheduled or PR quality failure remains. | -| 2026-07-31 | Workflow naming and fuzz consolidation | Split the mixed workflow into purpose-named static-analysis, tests, BLAS/LAPACK, and coverage workflows; removed the stale scheduled fuzz workflow, whose pre-migration `tests/property` target no longer existed. | Keep the two fuzz-marked parser tests in the ordinary matrix and use the deeper profile manually when needed. | - - - -## References - -- Ruff configuration: https://docs.astral.sh/ruff/configuration/ -- Pytest configuration: https://docs.pytest.org/en/latest/reference/customize.html -- Coverage subprocess behavior: https://coverage.readthedocs.io/en/latest/config.html -- Codecov commit-status configuration: https://docs.codecov.com/docs/commit-status -- Hypothesis settings profiles: https://hypothesis.readthedocs.io/en/latest/tutorial/settings.html -- Vulture configuration: https://pypi.org/project/vulture/ -- Radon command line: https://radon.readthedocs.io/en/stable/commandline.html -- Bandit configuration: https://bandit.readthedocs.io/en/latest/config.html -- pytest-randomly: https://github.com/pytest-dev/pytest-randomly diff --git a/docs/developer/repository-structure.md b/docs/developer/repository-structure.md deleted file mode 100644 index afb8995a5..000000000 --- a/docs/developer/repository-structure.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -title: Repository Structure -audience: contributors -prerequisites: repository checkout -related: source-map.md, feature-to-code-map.md, build-system.md, testing-strategy.md -status: maintained -publication: draft ---- - -# Repository Structure - -The repository is a Python project with native fixtures and generated wrapper -artifacts used by tests. Navigate by ownership boundary first, then by file. - -## Source Tree - -| Path | Purpose | -| --- | --- | -| `prik/` | Python package implementation. Start with [source-map.md](source-map.md) for entrypoints and [feature-to-code-map.md](feature-to-code-map.md) when starting from behavior. | -| `prik/contracts/` | Public semantic `.pyi` contract vocabulary imported directly by generated and edited contracts. | -| `prik/compiler/` | Reusable compiler command execution, compile objects, vendor profiles, native support installation, and linking. | -| `prik/preprocessing/` | C and Fortran source preprocessing, provenance, native includes, and compiler-derived target probes. | -| `prik/pipeline/` | Semantic `.pyi` loading, cross-stage datatype reports, wrapper rendering, and high-level wrapper build orchestration. | -| `prik/runtime/` | Python runtime objects plus bundled header-only native support used by generated extension modules. | -| `prik/parsers/` | Public namespace for language and semantic-contract frontends and parser models. | -| `prik/semantics/` | Semantic IR, scalar datatype vocabulary, source-to-IR conversion, and `.pyi` conversion. | -| `prik/policy/` | Immutable post-IR policy vocabulary plus policy construction and completion. | -| `prik/planning/` | Backend-neutral wrapper-plan records and mechanical projection from completed policy. | -| `prik/codegen/` | Backend datatype projection and plan-driven native bridge/binding lowering. | -| `prik/printers/` | Language-specific serialization of C nodes, Fortran nodes, and semantic IR. | -| `prik/naming/` | Unified public-name and generated-symbol policy. | -| `prik/utilities/` | Small shared Python utilities. | -| `examples/blas/` | Complete runnable Reference BLAS correctness project and the repository's single authoritative full BLAS source set under `native/`. | -| `examples/lapack/` | Complete Reference LAPACK build and SciPy-backed float64 correctness project, with the repository's single authoritative LAPACK implementation source set under `native/`. | -| `benchmarks/` | Local prik/f2py correctness and performance comparison harness. Benchmark sources and scripts are maintained; native builds and result files are generated locally. | -| `tools/generate_performance_docs.py` | Validates paired runtime and clean-build `pyperf` results and generates the bounded public Performance snapshot and both charts. | - -The major source packages have local README files under `prik/` for -developers reading directly in the source tree. Those README files should link -back to the maintained source-navigation docs instead of old top-level docs. - -Only `prik/__init__.py`, `prik/__main__.py`, `prik/cli.py`, and the shared -`prik/stage_values.py` record module live directly at the package root. Public -library symbols are deliberately flattened through `prik/__init__.py`; -internal modules are imported through their owning package. -The deliberate public submodule namespaces are `prik.contracts`, whose import -path is part of semantic `.pyi` syntax, and `prik.parsers`, which groups the -language-specific frontends. Stable convenience functions remain flattened -through `prik/__init__.py`. - -## Tests - -| Path | Purpose | -| --- | --- | -| `tests/fortran//` | User-visible Fortran and semantic `.pyi` behavior, with documented features directly below the language root and stages below each feature. | -| `tests/fortran/{source_parsing,source_preprocessing,command_line_interface,semantic_ir}/` | Public cross-feature capabilities that begin from source or expose an inspection/reporting surface. | -| `tests/fortran/infrastructure/` | Internal cross-feature policy, wrapper-generation, compiler, and runtime frameworks with no honest public-capability owner. | -| `tests/fortran/building_shared_library/end_to_end/real_libraries/` | Opt-in numerical showcase tests that build actual FFTPACK and MINPACK checkouts, call their generated Python routines, and verify known results. | -| `tests/c/` | C input-language parsing, preprocessing, probe, semantic, CLI, and fixture evidence. | -| `tests/docs/` | Documentation metadata, navigation, executable examples, publication, and source-map synchronization. | -| `tests/tools/` | Maintainer commands and CI support scripts. | -| `tests/workflows/` | Exceptional checks for concrete repository-automation safety risks. | -| `examples/blas/tests/test_*.py` | User-facing real-library correctness documentation: explicit independent and PRIK/f2py differential validation for every Reference BLAS routine. | -| `examples/blas/ci/full_surface.py` | Maintainer-only complete BLAS export and smoke audit, selected explicitly by CI. | -| `examples/lapack/tests/test_*.py` | User-facing real-library correctness documentation: explicit independent and PRIK/SciPy/f2py validation for the reviewed double-precision routine inventory. | -| `examples/lapack/ci/full_surface.py` | Maintainer-only complete LAPACK export and smoke audit, selected explicitly by CI. | - - - -## Documentation - -| Path | Purpose | -| --- | --- | -| `docs/index.md` | Documentation landing page. | -| `docs/user/` | Product workflows, examples, reference, support status, and troubleshooting. | -| `docs/developer/` | Contributor-facing workflows and source navigation. | -| `docs/old_docs/` | Archived pre-reorganization material. Do not link active docs here unless explicitly discussing history. | - -## Source Navigation Contract - -Source navigation is considered maintained when these files agree: - -- [source-map.md](source-map.md): package ownership, hotspot index, and common - change routes. -- [feature-to-code-map.md](feature-to-code-map.md): user-visible features to - docs, implementation files, tests, and support evidence. -- `prik/README.md` and package README files: local entry points for developers - already browsing the source tree. -- `tests/docs/test_reference_and_source_map.py`: mechanical coverage for - the navigation pages and README links. -- `tests/docs/test_publication.py`: fail-closed website publication, lane - gating, navigation filtering, and repository-evidence link coverage. - -## Generated And Fixture Areas - -- `__prik__/` directories are wrapper build artifacts and should not be - hand-edited as source. -- `benchmarks/build/f2py/` and `benchmarks/results/` contain generated - comparison artifacts and are not repository sources. CI retains paired - result files as workflow artifacts and generates the website snapshot from - them without committing the raw files. -- Parser and `.pyi` fixture files should be regenerated with the documented - fixture commands instead of edited loosely. -- `examples/blas/native/` is maintained source, not generated test output. The - full-library and LAPACK integrations consume it directly rather than owning - another BLAS copy. -- `examples/lapack/native/` is maintained Reference LAPACK implementation - source, not generated test output. Upstream testing, timing, example, and - matrix-generator programs are outside this ownership boundary. -- `prik.egg-info/`, caches, and benchmark output are generated local artifacts, - not source ownership boundaries. - - diff --git a/docs/developer/roadmap/documentation-content-checklist.md b/docs/developer/roadmap/documentation-content-checklist.md index e3abd0cf4..00b3e79d8 100644 --- a/docs/developer/roadmap/documentation-content-checklist.md +++ b/docs/developer/roadmap/documentation-content-checklist.md @@ -2,7 +2,7 @@ title: Documentation Content Checklist audience: maintainers prerequisites: documentation architecture -related: ../documentation-architecture.md, index.md, semantic-pyi-wrapper-checklist.md +related: ../workflows/documentation.md, index.md, semantic-pyi-wrapper-checklist.md status: active-roadmap publication: draft --- @@ -40,7 +40,7 @@ these are true: - [ ] Documentation-only changes use focused docs checks and `git diff --check`; reserve the full static-analysis suite for code, tests, build/tooling changes, or explicit pre-merge verification. -- [ ] User, Developer, and Maintainer lane entry points, `mkdocs.yml`, related +- [ ] User and Contributor area entry points, `mkdocs.yml`, related front matter, and `tests/docs/test_navigation.py` stay synchronized. @@ -83,93 +83,38 @@ more specialized pages. unsupported features, and where to report bugs. PRIK_C_DOCS_END --> -### Developer And Contributor Guides +### Contributor Architecture And Package Guides -- [ ] `docs/developer/adding-a-feature.md`: document the feature workflow - from contract docs to implementation, tests, fixtures, support matrix, and - release notes. -- [ ] `docs/developer/adding-a-fortran-construct.md`: document parser, - semantic, policy, wrapper, docs, and fixture updates for a new Fortran - construct. -- [ ] `docs/developer/adding-a-code-generation-backend.md`: document - backend acceptance criteria, ownership boundaries, generated artifacts, tests, - and support claims. -- [ ] `docs/developer/testing-strategy.md`: document test layers, focused - verification paths, fixture regeneration, documentation examples, wrapper - runtime tests, and static-analysis gates. -- [ ] `docs/developer/build-system.md`: document native compile model, - generated Makefiles, build manifests, native support files, compiler probes, - and future packaging boundaries. -- [ ] `docs/developer/coding-standards.md`: document Python style, - documentation front matter, no-compatibility-layer rule, parser/codegen - organization, public contributor rules, TODO markers, support-claim - discipline, and review expectations. -- [ ] `docs/developer/ci-cd.md`: document current GitHub Actions gates, - coverage policy, static-analysis policy, docs checks, and local caveats for - CI-only environment values. -- [ ] `docs/developer/release-process.md`: document versioning, changelog, - release verification, wheel/source distribution limits, and documentation - publication steps. -- [ ] `docs/developer/contributing/contribution-guide.md`: document setup, issue scope, - expected docs updates, tests, static checks, and pull-request checklist. -- [ ] `docs/developer/contributing/pull-request-workflow.md`: document branch workflow, - commit message policy, required evidence, review response, and CI handling. -- [ ] `docs/developer/contributing/review-process.md`: document review focus, support - claims, docs completeness, fixture quality, and blocking versus advisory - comments. -### Design And Internal Architecture +- [x] `docs/developer/architecture.md`: shallow repository/package maps, + complete wrapper workflow, stage authority, root entrypoints, change routes, + and links to canonical package owners. +- [x] `docs/developer/packages/`: one maintained guide per top-level production + package with local structure, essential objects, executable examples, expected + output, focused tests, change routes, and invariants. +- [x] `docs/developer/concepts/datatype-lifecycle.md`: cross-stage datatype + authority from target probing through semantic identity, policy, backend + representation, and runtime validation. +- [x] `docs/developer/workflows/contributing.md`: documentation-first changes, + ownership lookup, support evidence, test selection, pull requests, review, + and contribution licensing. +- [x] `docs/developer/workflows/quality-assurance.md`: active blocking/advisory + tools, exact commands, coverage parity, compiler lanes, and local limits. +- [x] `docs/developer/workflows/ci.md`: staged GitHub validation, + documentation deployment, benchmark evidence, and stable ruleset context. +- [x] `docs/developer/workflows/release.md`: package identity, trusted + publishing, artifact review, publication, and clean-environment verification. +- [x] `docs/developer/workflows/documentation.md`: documentation placement, + metadata, publication, navigation, and continuous quality. +- [x] `docs/developer/design/multilanguage-runtime.md`: explicit long-term + architecture separated from current support claims. +- [x] `docs/developer/design/wrapper-open-decisions.md`: unresolved or + revisitable design questions separated from implemented package contracts. +- [x] `docs/developer/deferred/c-parser.md`: retained but unpublished C + parser/C-to-IR material, separate from the generated CPython C backend. -- [x] `docs/developer/architecture.md`: document system components, pipeline - stages, data contracts, supported language routes, and deferred routes in - the canonical contributor entry page. -- [ ] `docs/developer/design/parser-architecture.md`: document parser ownership, - preprocessing boundaries, model facts, diagnostics, and fixture strategy. -- [ ] `docs/developer/design/semantic-analysis.md`: document source-to-IR lowering, - `.pyi`-to-IR loading, policy completion, wrapper-planning errors, and invariants. -- [ ] `docs/developer/design/runtime-model.md`: document native support files, generated - wrappers, native state, callbacks, threading, and finalization. -- [ ] `docs/developer/design/error-propagation-model.md`: document diagnostic categories, - Python exception projection, native failure handling, cleanup, and user-facing - message shape. -- [ ] `docs/developer/design/memory-ownership-model.md`: finish the design page around - policy-completion ownership decisions, transfer actions, mutability, setter - exposure, and release responsibility. -- [ ] `docs/developer/internal-architecture/ast-design.md`: document parser AST, semantic - IR, completed wrapper plans, generated source syntax, what each layer may - store, and what must not leak across - layers. -- [ ] `docs/developer/internal-architecture/semantic-passes.md`: document semantic pass - ordering, completed policy decisions, planner validation, and handoff to - `ir2ast`. -- [x] `docs/developer/internal-architecture/wrapper-generation-pipeline.md`: maintained - explanation of the current wrapper stages, semantic-policy boundary, - pass/planner/emitter distinctions, incremental decomposition criteria, and - acceptance criteria for bridge and binding refactoring. -- [x] `docs/developer/internal-architecture/type-system.md`: maintained datatype - lifecycle from compiler probing through semantic normalization, policy, - planning, backend registries, generated NumPy boundaries, runtime validation, - and non-primitive storage families. -- [ ] `docs/developer/internal-architecture/runtime-layer.md`: document native support - installation, extension initialization, callbacks, cleanup, and shared native - state. -- [ ] `docs/developer/internal-architecture/dependency-analysis.md`: document current - source ordering, preprocessing dependency facts, generated build plans, and - future automatic dependency discovery. -- [ ] `docs/developer/internal-architecture/error-handling-pipeline.md`: document - diagnostic creation, path-aware `.pyi` loader errors, wrapper-planning failures, - generated validation failures, and native runtime errors. -- [ ] `docs/developer/internal-architecture/symbol-tables.md`: document public naming, - generated-symbol reservation, collision policy, imports, scopes, and package - names. - - +The old TODO-only contributor pages, duplicate pipeline/source maps, completed +wrapper-plan and native-array migration ledgers, and separate internal/design +indexes were removed after their stable facts moved to these owners. ### Tutorials And Examples @@ -211,12 +156,11 @@ PRIK_C_DOCS_END --> are planned, with expected prerequisites and runtime cost. - [ ] `docs/user/examples/index.md`: split verified cookbook recipes from planned larger examples and state the evidence required for each example. -- [ ] `docs/developer/design/index.md`: explain which design documents are accepted - architecture and which are placeholders. -- [ ] `docs/developer/internal-architecture/index.md`: route contributors to pipeline, - semantic pass, runtime, type-system, ownership, and symbol-table pages. -- [ ] `docs/developer/contributing/index.md`: route contributors to contribution, - pull-request, review, and coding-standard pages. +- [x] `docs/developer/packages/index.md`: route contributors from each production + package to its canonical guide. +- [x] `docs/developer/index.md`: distinguish implemented package references, + cross-cutting concepts, workflows, design proposals, active roadmaps, and + deferred input-language material. - [ ] Public documentation site publication gate: deploy the existing MkDocs documentation as the project website only after all of the following are true; do not create a separate marketing-content system for this milestone. @@ -255,7 +199,7 @@ primary placeholder queue. point for developers and maintainers. - [x] `docs/developer/architecture.md`: canonical contributor architecture orientation and folder-by-folder rollout plan. -- [x] `docs/developer/documentation-architecture.md`: maintained two-area +- [x] `docs/developer/workflows/documentation.md`: maintained two-area documentation and publication contract. - [x] `docs/user/getting-started/index.md`: maintained beginner route from installation through the normal rebuild workflow. @@ -333,19 +277,20 @@ primary placeholder queue. - [x] `docs/user/examples/recipes/`: maintained recipe lane for checked command and API examples. - [x] `docs/user/language-support/feature-matrix.md`: maintained support matrix. -- [x] `docs/developer/development-workflow.md`: maintained developer workflow. +- [x] `docs/developer/workflows/contributing.md`: maintained contributor + development and review workflow. - [x] `docs/developer/source-map.md`: maintained source route map. - [x] `docs/developer/feature-to-code-map.md`: maintained feature route map. -- [x] `docs/developer/repository-structure.md`: maintained repository tree - reference. -- [x] `docs/developer/fortran-parser-reference.md`: maintained Fortran +- [x] `docs/developer/architecture.md`: maintained shallow repository/package + structure and complete stage workflow. +- [x] `docs/developer/packages/parsers.md`: maintained Fortran parser reference. -- [x] `docs/developer/quality-assurance.md`: maintained quality and QA +- [x] `docs/developer/workflows/quality-assurance.md`: maintained quality and QA policy reference. -- [x] `docs/developer/internal-architecture/pipeline-map.md`: maintained pipeline and - concept-ownership map. -- [x] `docs/developer/internal-architecture/ownership-tracking.md`: maintained +- [x] `docs/developer/packages/index.md`: maintained package ownership map and + detailed package guide index. +- [x] `docs/developer/packages/policy.md`: maintained ownership philosophy, completed policy vocabulary, supported lifetime triples, pointer-policy boundary, validation order, source routes, and safety boundary. - [x] `docs/developer/roadmap/semantic-pyi-wrapper-checklist.md`: active implementation @@ -355,6 +300,6 @@ primary placeholder queue. When the C-input documentation phase resumes, extend the maintained user-guide index with a separate C-input route rather than mixing future behavior into the current Fortran workflow. -- [x] `docs/developer/c-parser-reference.md`: maintained C parser +- [x] `docs/developer/deferred/c-parser.md`: retained deferred C parser reference. PRIK_C_DOCS_END --> diff --git a/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md b/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md index ae0e74525..5a3c1afc1 100644 --- a/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md +++ b/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md @@ -1,8 +1,8 @@ --- title: Language-First Test Suite and Fortran Pipeline Cleanup Checklist audience: maintainers -prerequisites: testing strategy, pipeline map, current test-suite organization record -related: ../../developer/testing-strategy.md, ../../../tests/README.md, ../internal-architecture/pipeline-map.md +prerequisites: testing strategy, contributor architecture guide, current test-suite organization record +related: ../testing-strategy.md, ../../../tests/README.md, ../architecture.md status: active-roadmap publication: draft --- @@ -109,6 +109,7 @@ tests/ infrastructure/ semantics/ codegen/ + docs/ tools/ ``` @@ -144,13 +146,17 @@ behavior and public cross-feature capabilities do not. - [x] `tests/fortran/` owns tests whose native input contract is Fortran, including generated Fortran bridge and C/CPython binding behavior for that Fortran contract. + - [x] Documentation and maintainer-tool tests have named top-level owners; internal language-neutral mechanics mirror their `prik/` package under `tests/fortran/infrastructure/`. + - [x] Fortran receives the documentation-led behavioral cleanup. - [x] Old imports, forwarding fixtures, collection shims, path aliases, and compatibility fallbacks are not retained. @@ -246,9 +252,11 @@ Register a structural marker for cross-feature selection: ``` - [x] A focused `tests/fortran/` command collects no C-input test. + - [x] Feature-local fixtures live with their owner. Minimized parser regressions live under `source_parsing/parsing/`; BLAS and LAPACK live only under `examples/blas/` and `examples/lapack/`. @@ -556,6 +564,7 @@ state, datatype matrix, or public error remains covered. ### Mechanical C quarantine + ## 5. Migrate Fortran feature by feature @@ -1254,7 +1264,10 @@ before changing compiler product behavior. - [x] BLAS, LAPACK, parser-regression, and contract content exists only beneath its final owner; no SciFortran snapshot remains. - [x] Every permanent contract row resolves to final collected nodes. -- [x] Collect `tests/fortran/`, `tests/c/`, `tests/docs/`, and `tests/tools/` independently; +- [x] Collect `tests/fortran/`, `tests/docs/`, and `tests/tools/` independently; + run the local Fortran verification with `-m "not real_library"`. - [x] Run the new suites alone under the same CI-equivalent line-and-branch coverage procedure used for the baseline. @@ -1585,7 +1598,9 @@ Add time separately when: - [ ] The authoritative tree is language-first and feature-first within Fortran. + - [ ] Every Fortran test and fixture has a final Fortran owner. - [ ] Every maintained User Guide and `.pyi` feature page maps to one obvious feature directory and focused command. diff --git a/docs/developer/roadmap/index.md b/docs/developer/roadmap/index.md index 9c3cbf6d4..73a78750b 100644 --- a/docs/developer/roadmap/index.md +++ b/docs/developer/roadmap/index.md @@ -1,38 +1,25 @@ --- -title: Roadmap -audience: maintainers -prerequisites: user language support, developer documentation -related: ../../user/language-support/feature-matrix.md, fortran-test-suite-cleanup-checklist.md, wrapper-plan-migration-checklist.md, semantic-pyi-wrapper-checklist.md, native-array-handle-checklist.md, documentation-content-checklist.md +title: Active Roadmaps +audience: developers, maintainers, contributors +prerequisites: contributor architecture guide, current support matrix +related: ../../user/language-support/feature-matrix.md, semantic-pyi-wrapper-checklist.md, fortran-test-suite-cleanup-checklist.md, documentation-content-checklist.md status: active-roadmap publication: draft --- -# Roadmap +# Active Roadmaps -This repository-only roadmap tracks implementation and documentation work for -contributors. Public support status remains in User documentation. +Only incomplete work belongs here. Implemented behavior is documented in user +and package guides; completed migration ledgers are removed after their stable +decisions and evidence routes have moved to canonical documentation. -## Planned Features +## Active Work -- [Wrapper plan migration checklist](wrapper-plan-migration-checklist.md) -- [Semantic `.pyi` wrapper checklist](semantic-pyi-wrapper-checklist.md) -- [Native array handle checklist](native-array-handle-checklist.md) -- [Documentation content checklist](documentation-content-checklist.md) -- TODO: Populate from accepted roadmap issues and maintained checklists. +- [Semantic `.pyi` wrapper completion](semantic-pyi-wrapper-checklist.md) +- [Language-first test suite and remaining compiler/CI work](fortran-test-suite-cleanup-checklist.md) +- [Remaining documentation content](documentation-content-checklist.md) -## In-Progress Features - -- [Language-first test suite and Fortran pipeline cleanup](fortran-test-suite-cleanup-checklist.md) - -## Future Ideas - -- TODO: Separate exploratory ideas from committed plans. - -## Long-Term Vision - -- TODO: Describe the long-term documentation and wrapper ecosystem goals. - -## TODO - -- TODO: Add tracking issue links when public issue tracking is available. -- TODO: Keep language support status synchronized with the feature matrix. +Public support status remains authoritative in the +[feature matrix](../../user/language-support/feature-matrix.md). A checked +roadmap item is evidence of completed work, not a replacement for current +architecture, tests, or user documentation. diff --git a/docs/developer/roadmap/native-array-handle-checklist.md b/docs/developer/roadmap/native-array-handle-checklist.md deleted file mode 100644 index c8bc7dd76..000000000 --- a/docs/developer/roadmap/native-array-handle-checklist.md +++ /dev/null @@ -1,1074 +0,0 @@ ---- -title: Native Array Handle Checklist -audience: maintainers -prerequisites: semantic .pyi format, ownership policy, allocatables, pointers -related: index.md, ../../user/reference/semantic-pyi-format.md, ../../user/guide/allocatables.md, ../../user/guide/pointers.md -status: active-roadmap -publication: draft ---- - -# Native Array Handle Checklist - -This is the implementation and verification checklist for native Fortran array -descriptor handles: - -- `Allocatable[T[...]]` -- `Pointer[T[...]]` - -Use this page as the canonical checklist for this feature. The original prompts -are consolidated here; future implementation should use this page instead of -re-reading those prompts. - -The intended implementation is one shared native-array handle path with -descriptor-specific operations layered on top. Keep Allocatable and Pointer as -separate public contract types, but do not build two unrelated parser, policy, -runtime, bridge, or binding stacks. - -Scalar allocatable and pointer procedure projections are not part of this array -handle work. Scalars continue to use ordinary nullable Python values plus -`@native_call` descriptor projections such as `Allocatable(Arg(i))`, -`Pointer(Arg(i))`, `Allocatable(Return(...))`, and `Pointer(Return(...))`. -For scalar descriptor inputs, explicit `None` means a present but unallocated -allocatable descriptor or a present but unassociated pointer descriptor. Native -optional scalar descriptor dummies use the three-state scalar bridge path: -omission means `present(...)` false, explicit `None` means a present descriptor -with absent value state, and a value means a present descriptor with scalar -storage. Array handles keep a different rule: `Allocatable[T[...]] | None` and -`Pointer[T[...]] | None` mean optional absent handle only. - -## Core Contract Decisions - -- [x] `Allocatable[T[...]]` means a Python handle to a native allocatable array - descriptor. -- [x] `Pointer[T[...]]` means a Python handle to a native pointer array - descriptor. -- [x] `Allocatable[T[...]]` and `Pointer[T[...]]` are handles, not NumPy arrays. -- [x] `T[...]` remains the ordinary array data-buffer contract. -- [x] Passing a handle to an `Allocatable[T[...]]` or `Pointer[T[...]]` - parameter uses descriptor semantics. -- [x] Passing a handle to a `T[...]` parameter uses normal array-actual - semantics in the shared runtime handoff path. For native wrapper calls, pass - the handle's native array actual to the normal Fortran array dummy instead of - implicitly calling `.to_numpy()`; generated wrapper parameter integration is - tracked separately below. -- [x] Normal `T[...]` parameters require a valid array actual: allocatable - handles must be allocated, pointer handles must be associated, and - allocated/associated zero-length arrays remain valid. -- [x] Unallocated allocatable handles and unassociated pointer handles are - accepted only by descriptor-handle parameters such as `Allocatable[T[...]]` - and `Pointer[T[...]]`, where that state belongs inside the handle. -- [x] Plain NumPy arrays are rejected by the shared runtime descriptor-parameter - handoff for `Allocatable[T[...]]` and `Pointer[T[...]]` descriptor - parameters; generated wrapper parameter integration is tracked separately - below. -- [x] `| None` on a handle means the handle object itself may be absent for a - native optional dummy, not that a present handle is unallocated or - unassociated. -- [x] Unallocated allocatable state lives inside the allocatable handle: - `h.allocated is False` and `h.to_numpy() is None`. -- [x] Unassociated pointer state lives inside the pointer handle: - `p.associated is False` and `p.to_numpy() is None`. -- [x] `Annotated[T[...], Allocatable]` is not an active public allocatable-array - spelling after migration. -- [x] `Annotated[T[...], Pointer]` is not an active public pointer-array - spelling after migration. -- [x] `Snapshot[T]` is not an allocatable- or pointer-array extraction mode and - is no longer an active public contract. -- [x] Live native-array views, explicit user-requested NumPy copies, live - derived objects, and descriptor handles remain distinct concepts in docs, - diagnostics, runtime names, and tests. - -## Public `.pyi` Examples - -### Allocatable Handles - -```python -from prik.contracts import Allocatable, Float64, Int32 - -values: Allocatable[Float64[:]] - -class box: - values: Allocatable[Float64[:]] - -def resize(values: Allocatable[Float64[:]], n: Int32) -> None: ... - -def make_values(n: Int32) -> Allocatable[Float64[:]]: ... - -def maybe_optional( - values: Allocatable[Float64[:]] | None = ..., -) -> None: ... - -def scale(values: Float64[:]) -> None: ... -``` - -`scale()` is ordinary array-data semantics. It may accept an allocated -`Allocatable[Float64[:]]` at runtime by passing the handle's native array actual -to the normal Fortran array dummy. It does not receive an allocatable dummy -descriptor. - -### Pointer Handles - -```python -from prik.contracts import Float64, Int32, Pointer - -values: Pointer[Float64[:]] - -class box: - values: Pointer[Float64[:]] - -def reassociate( - values: Pointer[Float64[:]], - target: Pointer[Float64[:]], -) -> None: ... - -def maybe_optional( - values: Pointer[Float64[:]] | None = ..., -) -> None: ... - -def scale(values: Float64[:]) -> None: ... -``` - -`scale()` is ordinary array-data semantics. It may accept an associated -`Pointer[Float64[:]]` at runtime by passing the handle's native array actual to -the normal Fortran array dummy. It does not receive a pointer dummy descriptor. - -## Call Compatibility Model - -For a normal array data signature: - -```python -from prik.contracts import Allocatable, Float64, Pointer - -def f(x: Float64[:]) -> None: ... - -plain: Float64[:] -allocatable: Allocatable[Float64[:]] -pointer: Pointer[Float64[:]] - -f(plain) -f(allocatable) -f(pointer) -``` - -all three calls are valid when the runtime value can provide usable -`Float64[:]` data: - -- `f(plain)` passes the NumPy/data-buffer value directly; -- `f(allocatable)` verifies that the handle is allocated and compatible, then - passes the wrapped native allocatable array actual to the normal Fortran array - dummy; -- `f(pointer)` verifies that the handle is associated and compatible, then - passes the wrapped native pointer array actual to the normal Fortran array - dummy. - -This mirrors Fortran argument association: a non-allocatable array dummy can be -called with ordinary, allocatable, or pointer actual arrays when the actual -array is present/associated and otherwise valid. At the Python boundary this is -not allocatable-or-pointer dummy descriptor semantics; the callee sees a normal -array dummy. It also is not an implicit `.to_numpy()` conversion. - -If the user writes `f(allocatable.to_numpy())` or `f(pointer.to_numpy())`, that -is the explicit public extraction path. The result is treated like any other -ordinary array argument and must pass the normal validation rules, including -non-`None` state and any mutability requirements. - -For descriptor signatures: - -```python -def g(x: Allocatable[Float64[:]]) -> None: ... - -def h(x: Pointer[Float64[:]]) -> None: ... -``` - -`g()` requires an allocatable handle and `h()` requires a pointer handle. A -plain NumPy array is rejected because it has no native allocatable or pointer -descriptor to pass. - -Internally, model this as a handle with an array-data facet plus descriptor -facts, not as a global rewrite of the base array type. For example, an -`Allocatable[Float64[:]]` value should expose or carry: - -- array data type: `Float64[:]`; -- descriptor kind: `allocatable`; -- descriptor operations: allocation state, descriptor passing, deallocate, and - resize. - -A `Pointer[Float64[:]]` value should expose or carry: - -- array data type: `Float64[:]`; -- descriptor kind: `pointer`; -- descriptor operations: association state, descriptor passing, nullify, and - any policy-gated allocation/deallocation operations. -- default handle mode: `Pointer[T[...]]` must be usable without - `PointerPolicy(...)` for conservative handle creation, association - inspection, descriptor handoff, `nullify()` where legal, and supported - extraction operations. -- explicit policy mode: `PointerPolicy(...)` enables or requests behavior that - needs otherwise unprovable facts, such as allocation, deallocation, - reassociation, target lifetime, or unsafe ownership transfer. - -The implementation may use predicates or metadata equivalent to -`is_allocatable` and `is_pointer` on the specific handle semantic type, but -plain `Float64[:]` itself remains the data-buffer contract. Do not make a -normal array parameter infer descriptor semantics merely because a handle is -accepted as an array-like runtime value. - -## Recommended Implementation Order - -### 1. Documentation And Contract Sync - -Update the public docs first so the intended behavior is explicit before code -changes. - -- [x] Update `docs/user/guide/allocatables.md`. -- [x] Update `docs/user/guide/pointers.md`. -- [x] Update `docs/user/reference/semantic-pyi-format.md`. -- [x] Update memory-management or language-support pages if they describe - allocatable or pointer arrays as NumPy arrays, `ndarray | None`, metadata - annotations, or `Snapshot[T]`. -- [x] Document that `Allocatable[T[...]]` is a handle, not an ndarray. -- [x] Document that `Pointer[T[...]]` is a handle to pointer association state, - not an ndarray. -- [x] Document that `h.to_numpy()` returns a live view of the current allocation - or `None`, never an automatic detached copy. -- [x] Document that `p.to_numpy()` returns the current target view or `None`, - and can expose strided pointer targets when descriptor support is available. -- [x] Document that passing a handle to a handle parameter is descriptor - passing, while passing a handle to `T[...]` uses normal array-actual - semantics through the handle's native array-data facet. -- [x] Document call compatibility for `def f(x: T[...])`: ordinary arrays, - allocated allocatable handles, and associated pointer handles are accepted - as array actuals without implicit `.to_numpy()` conversion. -- [x] Document that normal `T[...]` parameters reject unallocated allocatable - handles and unassociated pointer handles because there is no valid array - actual to pass. Allocated or associated zero-length arrays are still valid. -- [x] Document that explicit `f(h.to_numpy())` is a separate user-requested - ndarray path and follows ordinary ndarray validation. -- [x] Document that parameters annotated as `Allocatable[T[...]]` or - `Pointer[T[...]]` pass native descriptors, so they require the corresponding - handle object. Ordinary arrays are accepted only by normal `T[...]` - parameters. -- [x] Document that plain ndarray inputs are rejected for descriptor-handle - parameters. -- [x] Document that module allocatables and pointer arrays expose handles, not - `ndarray | None` module attributes. -- [x] Document that derived allocatable and pointer fields expose handles. -- [x] Document that allocatable function results can return owned handles only - when prik creates stable owner storage. -- [x] Document that pointer handles do not imply target ownership. -- [x] Document that pointer `nullify()` is default, while pointer - `allocate()`, `deallocate()`, and `resize()` require explicit policy. -- [x] Document stale-view hazards after descriptor-changing operations, - reassociation, nullification, deallocation, or reallocation. -- [x] Remove active public examples of `Annotated[T[...], Allocatable]` and - `Annotated[T[...], Pointer]` for this feature. - -### 2. Public Contract Symbols, Parser, And Printer - -Implement the public contract wrappers once and parameterize by descriptor kind. - -- [x] Add `Allocatable[...]` as a real array-handle contract wrapper. -- [x] Add `Pointer[...]` as a real array-handle contract wrapper. -- [x] Parse `Allocatable[T[...]]` into a semantic representation for a native - allocatable array handle. -- [x] Parse `Pointer[T[...]]` into a semantic representation for a native - pointer array handle. -- [x] Parse optional absent callable-argument handles as - `Allocatable[T[...]] | None = ...` and `Pointer[T[...]] | None = ...`. -- [x] Reject `Allocatable[T[...]] | None` and `Pointer[T[...]] | None` outside - optional callable arguments. -- [x] Reject optional/defaulted callable-argument handles that omit the - explicit `| None` spelling. -- [x] Preserve normal `T[...]` type identity as array data semantics, not - descriptor semantics. -- [x] Do not interpret `Snapshot[T]` as a native-array descriptor wrapper. -- [x] Remove the obsolete public `Snapshot` contract, its semantic `.pyi` - parsing/printing, generated contracts, and recursive derived-object lowering. -- [x] Reject or fully migrate `Annotated[T[...], Allocatable]` from active - public contracts. -- [x] Reject or fully migrate `Annotated[T[...], Pointer]` from active public - contracts. -- [x] Keep metadata-only allocatable or pointer facts only as temporary internal - migration facts, not as accepted public syntax. -- [x] Print generated module allocatables as `Allocatable[T[...]]`. -- [x] Print generated derived allocatable fields as `Allocatable[T[...]]`. -- [x] Print allocatable descriptor arguments and supported handle results as - `Allocatable[T[...]]`. -- [x] Print generated module pointer arrays as `Pointer[T[...]]`. -- [x] Print generated derived pointer fields as `Pointer[T[...]]`. -- [x] Print pointer descriptor arguments and supported handle results as - `Pointer[T[...]]`. - -### 3. Semantic IR Representation - -Create one semantic representation family for native array handles with a -descriptor-kind field rather than separate unrelated models. - -- [x] Represent common native-array handle facts: - - descriptor kind: allocatable or pointer; - - element semantic type; - - dtype; - - rank; - - shape metadata when statically known; - - array data type/facet used when a handle is passed to a normal `T[...]` - parameter; - - string element length metadata when applicable; - - optional-absent handle state; - - source origin: module variable, derived field, argument, or result; - - native access path. -- [x] Keep handle semantic types distinct from normal array semantic types. -- [x] Store allocatable/pointer descriptor facts on the handle semantic type, - not as a global mutation of the plain array type. -- [x] Permit shared predicates or metadata equivalent to `is_allocatable` and - `is_pointer` on handle semantic types when that helps policy dispatch. -- [x] Keep each handle's base array data type available for ordinary `T[...]` - call compatibility. -- [x] Keep scalar descriptor projection metadata on the existing scalar - nullable-value path, not on array handle types. -- [x] Preserve `T[...]` arguments/results as normal array data in semantic IR - even when runtime may later accept a handle through data coercion. -- [x] Verify `Snapshot[T]` is absent from active semantic IR and remains - unrelated to native-array-handle extraction. - -### 4. Post-IR Policy Completion - -Complete every semantic decision before wrapper planning. Bridge and binding -generators must dispatch from these decisions rather than inferring policy from -datatype, intent, origin, dotted access shape, alias metadata, or local memory -checks. - -The completed decision is recorded as `NativeArrayHandlePolicy` metadata on -each handle declaration or result type. That policy records the descriptor kind, -handle origin/kind, owner retention, borrowed-vs-owned descriptor status, -getter/setter behavior, native assignment behavior, release responsibility, -target lifetime, generated destroy behavior, storage mode, optional -absent-handle state, `.to_numpy()` extraction policy, and descriptor operation -permissions. It also records whether the selected path requires pointer C -descriptor interop or owned-allocatable CFI storage, so build integration can -gate `ISO_Fortran_binding.h` from completed policy instead of raw datatype -checks. Wrapper planning must fail before lowering a native array handle that -is missing this completed policy. - -For pointer handles, plain `Pointer[T[...]]` gets a default conservative -operation table for descriptor association, `nullify()`, and unavailable -extraction reporting. `PointerPolicy(...)` adds facts for behavior that needs -an explicit contract. `allocate(shape)` requires an explicit reassociation -value that allows allocation, `deallocate()` requires an explicit deallocation -value, and `resize(shape)` requires both sides to opt into resize. When an -explicit pointer policy selects descriptor-view extraction, the completed policy -records the `pointer_c_descriptor` interop requirement even if another planning -blocker still prevents wrapper lowering. - -- [x] Add one completed native-array-handle policy decision shared by - Allocatable and Pointer. -- [x] Complete descriptor kind before lowering: allocatable or pointer. -- [x] Complete handle kind before lowering: - - `borrowed_module_descriptor`; - - `borrowed_field_descriptor`; - - `argument_descriptor`; - - `owned_result_descriptor`; - - `optional_absent_handle`; - - `unsupported`. -- [x] Complete ownership and lifetime retention before lowering. -- [x] Complete whether the Python handle is borrowed or owned before lowering. -- [x] Complete getter behavior before lowering. -- [x] Complete Python setter exposure, if any, before lowering. -- [x] Complete native setter assignment behavior, if any, before lowering. -- [x] Complete output projection/readback behavior before lowering. -- [x] Complete release responsibility and generated destroy behavior before - lowering. -- [x] Complete `.to_numpy()` policy before lowering: - - `borrowed_view`; - - `descriptor_view`; - - `contiguous_view`; - - `unsupported`. -- [x] Complete standard C-descriptor interop requirement before lowering: - `none`, `module_allocatable_c_descriptor`, or `pointer_c_descriptor`. -- [x] Complete nullability and optional-absent-handle behavior before lowering. -- [x] Complete contract-value storage mode before lowering: `stack`, `heap`, or - `alias`. -- [x] Keep descriptor-argument and optional-absent-handle policies semantically - complete before lowering; generated bridge descriptor pass-through dispatches - from this completed policy. -- [x] Fail wrapper planning with a clear diagnostic when descriptor ownership, target - lifetime, shape, addressability, release responsibility, or extraction policy - is incomplete. - -#### Allocatable Policy Items - -- [x] Complete allocated-state support. -- [x] Complete live-view mechanism independently from `Aliased`: direct - borrowed access where legal, otherwise standard descriptor access. -- [x] Give plain and `Aliased` module allocatable handles the same native-owned - borrowed lifetime, mutability, and live-view-or-`None` public behavior. -- [x] Block unsupported descriptor extraction explicitly instead of copying. -- [x] Complete `deallocate()` permission. -- [x] Complete `resize(shape)` permission. -- [x] Complete function-result ownership as wrapper-owned stable descriptor - storage. -- [x] Mark unsupported allocatable array forms as wrapper-planning errors rather than - silently falling back to NumPy-array copy behavior. - -#### Pointer Policy Items - -- [x] Complete association-state support. -- [x] Complete target lifetime policy. -- [x] Complete `to_numpy()` extraction policy: - - descriptor view; - - contiguous view; - - unsupported. -- [x] Select pointer `to_numpy()` policy from completed `PointerPolicy(...)` - facts before lowering: contiguous targets use `contiguous_view`, and - strided/general targets use `descriptor_view`. A copy-oriented pointer policy - may retain unrelated meaning, but must not make extraction copy. -- [x] Complete `nullify()` permission as the default pointer descriptor - operation. -- [x] Complete a default conservative handle profile for plain - `Pointer[T[...]]` without requiring `PointerPolicy(...)`. -- [x] Complete `allocate(shape)` permission only when explicit pointer policy - allows allocation through this pointer. -- [x] Complete `deallocate()` permission only when explicit pointer policy - allows deallocation through this pointer. -- [x] Add an explicit unsafe/user-responsibility deallocation policy value, - `unsafe_deallocate`, for callers who knowingly request deallocation without - prik-proven target ownership. -- [x] Complete `resize(shape)` permission only when explicit pointer policy - allows resize through this pointer. -- [x] Do not expose pointer `allocate()`, `deallocate()`, or `resize()` when - policy disallows those operations. -- [x] Treat pointer handle ownership as descriptor/association access by - default, not target ownership. -- [x] For pointer results, support `owned_result_descriptor` only when stable - owner storage and target lifetime are explicit; otherwise stop wrapper planning. - -### 5. Shared Runtime Handle Foundation - -Add or reuse one internal runtime base for both public handle classes. - -- [x] Implement or reuse `NativeArrayHandleBase`. -- [x] Put shared dtype metadata on the base. -- [x] Put shared rank metadata on the base. -- [x] Put shared shape-query dispatch on the base. -- [x] Put shared `to_numpy()` dispatch on the base. -- [x] Put shared owner/lifetime retention on the base. -- [x] Put shared generated ops table/accessor storage on the base. -- [x] Validate generated operation table names and callables when a runtime - handle is constructed. -- [x] Require generated runtime handles to provide the shared `shape` operation - at construction time. -- [x] Require generated runtime handles to provide an internal `array_actual` - operation for normal `T[...]` native array-actual handoff, distinct from - explicit public `to_numpy()` extraction. -- [x] Require generated runtime handles to provide an internal `descriptor` - operation for `Allocatable[T[...]]` and `Pointer[T[...]]` descriptor-parameter - handoff. -- [x] Reject generated `array_actual` or `descriptor` handoff operations that - return `None`, so present handles cannot collapse into optional absent-handle - state. -- [x] Require generated `array_actual` operations to return the internal typed - native-array handoff object carrying a non-null native data address. -- [x] Require generated `descriptor` operations to return either decoded - standard descriptor fields or a contiguous native data address that the - shared runtime normalizes into those fields. An unallocated or unassociated - descriptor may carry a null `base_addr`; that state remains distinct from an - absent optional handle. -- [x] Put shared borrowed-vs-owned descriptor kind on the base. -- [x] Validate shared runtime descriptor kind at handle construction, so - generated handles can only use `allocatable` or `pointer` descriptor tags. -- [x] Put shared owned-handle release state and finalizer dispatch on the base, - so generated owner-storage handles can call a generated `destroy` operation - exactly once. -- [x] Require generated owned-handle operation tables to provide a callable - `destroy` operation at construction time. -- [x] Run owned-handle destroy operations before marking the handle closed, so - generated destroy accessors can still read descriptor or owner state. -- [x] Mark owned handles closed after a generated destroy attempt even when - destroy reports an error, so finalizers cannot retry the same native release. -- [x] Leave room for optional generation or stale-view tracking later. -- [x] Add an internal runtime array-actual validation and handoff hook for - future normal `T[...]` handle inputs, without calling `to_numpy()`. -- [x] Add an internal runtime normal-array argument dispatcher that keeps - ordinary ndarray validation and native-handle array-actual handoff as - separate paths while sharing dtype, rank, shape, layout, and writeability - checks. -- [x] Add an internal runtime normal-array argument ABI packer that returns the - generated Bind-C array tuple fields from either an ndarray data pointer or a - native handle `array_actual` handoff: pointer address, optional runtime rank, - optional item size, extents, and optional upper bounds plus unit strides. -- [x] Normalize runtime layout validation for handle array-actual handoff with - the same supported `C` and `F` layout expectations used by the ndarray path. -- [x] Normalize runtime handle shapes as non-negative extents, rejecting - negative dimensions while preserving zero-length arrays as valid array - actuals. -- [x] Let the internal runtime normal-array argument dispatcher enforce native - byte order and alignment when generated binding policy requests those checks. -- [x] Add an internal runtime descriptor-parameter validation and handoff hook - for future `Allocatable[T[...]]` and `Pointer[T[...]]` binding inputs, - including optional absent-handle `None` mapping. -- [x] Validate descriptor-parameter handoff kind before mapping optional - absent-handle `None`, so unsupported descriptor kinds cannot silently pass. -- [x] Add an internal runtime descriptor-argument field packer that returns - validated `base_addr`, `elem_len`, `rank`, and per-dimension lower-bound, - extent, and stride-multiplier facts. Generated C uses those facts to establish - call-local `CFI_CDESC_T(rank)` storage for non-projected descriptor calls; - Python never supplies compiler-private descriptor storage. -- [x] Add a distinct direct standard-C-descriptor handoff for projected writable - handles. Owned allocatable handles pass their persistent `CFI_cdesc_t*` so - native allocation, deallocation, and shape changes update the same caller - handle instead of a discarded call-local descriptor copy. -- [x] Use a dedicated non-null runtime presence token for present optional - handle arguments, rather than reusing the descriptor handoff object as the - presence field. -- [x] Pack optional descriptor arguments with the same validated descriptor - facts plus a distinct presence token. Optional absent handles produce null - fact fields and a null presence token; present unallocated or unassociated - handles produce present descriptor facts whose `base_addr` may be null. -- [x] Carry the completed `.to_numpy()` extraction policy on the runtime - handle. -- [x] Remove detached-copy dispatch from the runtime handle; extraction-enabled - operations must supply live storage or standard descriptor facts. -- [x] Validate generated `.to_numpy()` operations return either a NumPy array or - `None` before applying borrowed-view, descriptor-view, or contiguous-view - policy. -- [x] Validate non-`None` `.to_numpy()` results against the handle's declared - dtype and rank before returning them to Python. -- [x] Short-circuit absent descriptor state before generated extraction, so an - unallocated allocatable handle or unassociated pointer handle returns `None` - from `to_numpy()` without relying on backend extraction code. -- [x] Reject generated `.to_numpy()` results that report `None` after the - handle has reported present descriptor state, so backend extraction cannot - collapse allocated or associated handles into absent state. -- [x] Require generated runtime handles with an extraction-enabled - `to_numpy_policy` to provide the generated `to_numpy` operation at - construction time; handles without extraction support must use - `to_numpy_policy="unsupported"`. -- [x] Enforce live contiguous-view and descriptor-view `.to_numpy()` policies - in the shared runtime handle without a copy fallback. -- [x] Implement `AllocatableArray` as a descriptor-specific subclass. -- [x] Require allocatable runtime handles to provide the generated `allocated` - operation at construction time. -- [x] Implement `PointerArray` as a descriptor-specific subclass. -- [x] Require pointer runtime handles to provide generated `associated` and - default `nullify` operations at construction time. - -Runtime handle support means the shared Python class enforces the completed -policy once generated operations provide access to current native storage. -Bridge generation for module variables, fields, arguments, results, and C -descriptor extraction remains tracked in the codegen and integration sections -below. - -#### Allocatable Runtime API - -- [x] `h.allocated -> bool` -- [x] `h.shape -> tuple[int, ...] | None` -- [x] `h.to_numpy() -> ndarray | None` -- [x] `h.deallocate()` -- [x] `h.resize(shape)` -- [x] `h.to_numpy()` returns `None` when unallocated. -- [x] `h.to_numpy()` returns a live mutable view for every supported allocated - handle, using direct or descriptor access as selected by completed policy. -- [x] Users can call `.copy()` on the returned NumPy array when they need - independent lifetime. -- [x] Existing views may become stale after descriptor-changing operations; - accessing stale views is unsupported and may crash. - -#### Pointer Runtime API - -- [x] `p.associated -> bool` -- [x] `p.shape -> tuple[int, ...] | None` -- [x] `p.to_numpy() -> ndarray | None` -- [x] `p.nullify()` -- [x] Optional, policy-gated `p.allocate(shape)` -- [x] Optional, policy-gated `p.deallocate()` -- [x] Optional, policy-gated `p.resize(shape)` -- [x] `p.to_numpy()` returns `None` when unassociated. -- [x] `p.to_numpy()` returns a live borrowed NumPy view when associated and - descriptor extraction is supported. -- [x] `p.to_numpy()` supports strided views when descriptor support is - available. -- [x] `p.to_numpy()` raises a clear error when descriptor extraction is - unavailable and no fallback is supported. -- [x] Old borrowed views are documented as stale after native code nullifies, - reassociates, deallocates, or otherwise changes the pointer target. - -### 6. IR-to-AST And Codegen Model - -Lower only completed policy decisions into named implementation methods. - -- [x] Use one completed array interop policy object for array-like bridge and - binding decisions. The policy selects a named ABI lane: - `data_buffer` for ordinary `T[...]` NumPy/data-pointer semantics, or - `descriptor` for `Allocatable[T[...]]` and `Pointer[T[...]]` descriptor - semantics. -- [x] Keep the generated implementation methods separate under that dispatcher: - the data-buffer lane emits the existing pointer/shape/stride ABI for normal - arrays, while the descriptor lane emits descriptor-handle ABI operations and - any gated TS 29113 reader code. -- [x] Add codegen model nodes or metadata for native-array handle creation. -- [x] Add codegen model nodes or metadata for generated native-array handle ops. -- [x] Route Allocatable and Pointer handles through the same lowering path with - descriptor-kind-specific operations. -- [x] Keep `@native_call Addr(Arg(i))` as data-address projection only. -- [x] Add native-array-handle bridge/binding dispatchers keyed by completed - descriptor kind and handle kind. -- [x] Route native-array module-variable bridge generation through completed - handle policy before ordinary module-variable array dispatch. -- [x] Route native-array derived-field bridge and binding generation through - completed handle policy before ordinary field array dispatch. -- [x] Route native-array function-result bridge and binding generation through - completed handle policy before ordinary array result dispatch. -- [x] Select descriptor passing from `Allocatable[T[...]]` or - `Pointer[T[...]]` plus completed policy, not from `Addr`. -- [x] Lower unsupported policy decisions to planning/codegen errors, not - fallback behavior. - -### 7. Bridge Generation - -Generate descriptor-access routines through the shared handle-ops shape, then -specialize operation bodies by descriptor kind. - -- [x] Block generated descriptor-handle accessors with explicit native-array - codegen blockers until the descriptor-access routines below exist. -- [x] Add the shared Bind-C/binding/runtime construction substrate for - generated handle objects: explicit operation-name maps, module-owner - retention, runtime factory creation, and pointer-address handoff wrapping. -- [x] Generate module allocatable handles as borrowed descriptor handles. -- [x] Create module allocatable handle objects at module initialization. -- [x] Store complete generated operation pointers/accessors for module - allocatable variables, including portable descriptor handoff and generated - `resize(shape)` operations. The operation table covers state, shape, - array-actual and descriptor-fact handoff, `.to_numpy()`, `deallocate()`, and - `resize(shape)` without exposing a compiler-private descriptor layout. -- [x] Do not move ownership out of the Fortran module for ordinary module - allocatable attribute reads. -- [x] Generate derived-field allocatable handles as borrowed descriptor handles. -- [x] Keep the parent wrapper object alive for derived-field allocatable - handles. -- [x] Generate field operations that access `parent%field`. -- [x] Generate owned allocatable function-result handles when policy supports - stable owner storage. -- [x] Use wrapper-owned standard C descriptor storage for allocatable results: - allocate persistent rank-specific `CFI_CDESC_T(rank)` storage and establish - it with allocatable attribute. Numeric function results populate a local - allocatable once, then transfer that allocation with `move_alloc`; generated - shape-changing operations use `CFI_allocate`. -- [x] Assign a supported numeric direct allocatable function result once into a - bridge-local allocatable, then `move_alloc` that allocation into the - allocatable `intent(out)` dummy backed by persistent CFI storage. Do not - generate a collector or a second intrinsic assignment. Rank-one, matrix, and - higher-rank results preserve allocated, zero-sized, and unallocated state. -- [x] Return a native pointer to owner storage for owned allocatable handles. -- [x] Generate destroy routines called by the Python handle finalizer for owned - allocatable handles. -- [x] Generate module pointer handles as borrowed descriptor handles. -- [x] Create module pointer handle objects at module initialization. -- [x] Store complete generated operation pointers/accessors for module pointer - variables, including portable descriptor handoff and policy-gated - `allocate(shape)`, `deallocate()`, and `resize(shape)` operations. The current - generated module operation table covers association state, shape, - pointer-address handoff wrappers, `nullify()`, and the policy-gated - shape-changing operations when completed policy enables them. Descriptor - handoff uses standard C descriptor facts rather than guessing a compiler - descriptor layout. -- [x] Do not transfer ownership of pointer targets for module pointer handles. -- [x] Generate derived-field pointer handles as borrowed descriptor handles. -- [x] Keep the parent wrapper object alive for derived-field pointer handles. -- [x] Generate field operations that access `parent%field`. -- [x] Route pointer descriptor-argument bridge and binding generation through - completed handle policy before ordinary array argument dispatch. -- [x] Model native descriptor-handle argument handoff as a dedicated Bind-C - descriptor tuple selected by bridge and binding descriptor-argument handlers - through completed output-projection policy. Non-projected calls establish - standard call-local descriptor storage from validated runtime facts. - Projected writable calls pass persistent standard C descriptor storage - directly so descriptor mutation remains attached to the caller handle. Both - paths add an explicit presence token only for optional absent handles. -- [x] Generate pointer descriptor-argument handoff for `Pointer[T[...]]` - parameters. -- [x] Route allocatable descriptor-argument bridge and binding generation - through completed handle policy before ordinary array argument dispatch. -- [x] Generate allocatable descriptor-argument handoff for - `Allocatable[T[...]]` parameters. -- [x] Do not guess compiler-specific descriptor layouts. Descriptor-based - interop must use the TS 29113 / Fortran 2018 C descriptor path or fail - wrapper planning with an explicit diagnostic. - -#### Pointer Descriptor Extraction - -This path is feature-gated. It may use TS 29113 / Fortran 2018 C descriptors -only when descriptor-view interop is selected, and it must not add a global -`ISO_Fortran_binding.h` requirement to wrappers that do not need descriptor -decoding. - -- [x] Use TS 29113 / Fortran 2018 C descriptors for general pointer-array - `to_numpy()` when this path is enabled. -- [x] Use `ISO_Fortran_binding.h` only for descriptor-based pointer interop - paths. -- [x] Do not require `ISO_Fortran_binding.h` globally. -- [x] In the shared runtime helper, build NumPy shape from decoded descriptor - `dim[i].extent` fields supplied by generated descriptor-interoperability - code. -- [x] In the shared runtime helper, build NumPy strides from decoded descriptor - `dim[i].sm` fields supplied by generated descriptor-interoperability code. -- [x] Let pointer `descriptor_view` extraction operations return decoded - descriptor fields as mappings or field-record objects, with the shared - runtime converting those fields into the NumPy view instead of requiring - every generated operation to call the helper. -- [x] Validate decoded pointer descriptor rank against the handle's declared - rank before constructing the NumPy view. -- [x] Reject decoded pointer descriptors with null `base_addr` after the handle - has reported associated state. -- [x] Support positive and negative descriptor stride multipliers in the shared - runtime descriptor-view helper by computing the full buffer window before - constructing the NumPy view. -- [x] In the shared runtime helper, read and validate decoded descriptor - `base_addr`, `elem_len`, `rank`, `dim[i].lower_bound`, `dim[i].extent`, and - `dim[i].sm` fields for pointer views. -- [x] Add a shared generated C/CPython descriptor-reader primitive that decodes - a `CFI_cdesc_t*` into the runtime descriptor-view mapping shape, without - exposing TS 29113 layout details in public Python APIs. -- [x] Generate code that reads TS 29113 descriptor `base_addr`, `elem_len`, - `rank`, `dim[i].lower_bound`, `dim[i].extent`, and `dim[i].sm` for pointer - descriptor-view operations in private generated CPython operation wrappers. -- [x] Support strided pointer targets, including negative strides, in the shared - runtime once generated descriptor-interoperability code supplies decoded - descriptor fields. -- [x] If descriptor support is unavailable, choose one explicit policy: - contiguous-only pointer views when shape/address are safely available, - explicitly implemented copy fallback, or a wrapper-planning failure with a - clear diagnostic. There is no deferred blocker payload. - -### 8. Python Binding Generation - -Keep descriptor-handle argument conversion separate from normal array data -conversion. For normal `T[...]` parameters, support both ordinary ndarray inputs -and native handle inputs, but do not implement handle inputs by implicitly -calling `.to_numpy()`. The handle path should route to a native array-actual -handoff when the wrapped call is native. - -- [x] Accept `AllocatableArray` objects for `Allocatable[T[...]]` parameters. -- [x] Reject plain NumPy arrays for `Allocatable[T[...]]` parameters. -- [x] Accept `PointerArray` objects for `Pointer[T[...]]` parameters. -- [x] Reject plain NumPy arrays for `Pointer[T[...]]` parameters. -- [x] Accept `None` for optional-absent handle parameters only when the `.pyi` - annotation includes `| None`. -- [x] Convert `None` optional handles into native absent optional dummies, not - into unallocated or unassociated handle objects. - The CPython binding layer now packs required and optional descriptor-handle - arguments through the runtime descriptor-argument helper, and bridge - generation passes descriptor dummies through the Bind-C descriptor tuple. -- [x] For normal `T[...]` parameters, accept ndarray inputs through the existing - array data path. -- [x] For concrete-rank numeric normal `T[...]` parameters in generated Bind-C - wrapper calls, accept allocated allocatable handles only by validating the - handle state and passing the wrapped native allocatable array actual to the - normal native array dummy. -- [x] For concrete-rank numeric normal `T[...]` parameters in generated Bind-C - wrapper calls, accept associated pointer handles only by validating the - handle state and passing the wrapped native pointer array actual to the normal - native array dummy. -- [x] Share dtype, rank, shape, layout, and mutability validation policy between - ndarray inputs and handle inputs for concrete-rank numeric `T[...]`, while - keeping the existing ndarray pointer/shape handoff and native-handle - array-actual handoff as separate generated implementation branches. -- [x] Treat explicit `h.to_numpy()` or `p.to_numpy()` results that are NumPy - arrays as ordinary ndarray input through the existing array-storage path. -- [x] Reject read-only arrays returned by explicit `h.to_numpy()` or - `p.to_numpy()` when the native dummy requires writable storage, reusing the - existing writable ndarray validation. -- [x] Add direct wrapper coverage showing explicit `h.to_numpy()` or - `p.to_numpy()` returning `None` is rejected as an ordinary ndarray argument - for non-nullable `T[...]` dummies. -- [x] Reject unallocated allocatable handles for concrete-rank numeric `T[...]` - unless nullable data-buffer behavior is explicitly implemented. -- [x] Reject unassociated pointer handles for concrete-rank numeric `T[...]` - unless nullable data-buffer behavior is explicitly implemented. -- [x] Reject unallocated allocatable handles for optional, assumed-rank, and - character `T[...]` unless nullable - data-buffer behavior is explicitly implemented. -- [x] Reject unassociated pointer handles for optional, assumed-rank, and - character `T[...]` unless nullable - data-buffer behavior is explicitly implemented. - -### 9. Compilation And Build Gating - -- [x] Require descriptor interop support only when a generated wrapper uses the - pointer C-descriptor path or persistent CFI owner storage for an allocatable - result. -- [x] Do not require `ISO_Fortran_binding.h` for allocatable-only builds that - contain no owned allocatable result handles. -- [x] Require `ISO_Fortran_binding.h` locally when owned allocatable result - handles use persistent `CFI_CDESC_T` storage. -- [x] Do not require `ISO_Fortran_binding.h` for pointer builds that do not use - descriptor-based pointer interop. -- [x] Collect native-array build requirements from completed handle policy - metadata, not from raw `Allocatable[...]` or `Pointer[...]` syntax. -- [x] Record native-array build requirements in replayable `.pyi` wrapper build - manifests. -- [x] Emit a clear planning or build diagnostic when pointer descriptor interop - is required but unavailable. - -## Test Checklist - -### Parser And Printer Tests - -- [x] Parse and print `Allocatable[Float64[:]]`. -- [x] Parse and print `Allocatable[String[:][:]]`. -- [x] Parse and print `Allocatable[Float64[:, :]]`. -- [x] Parse and print `Allocatable[Float64[:]] | None = ...`. -- [x] Reject `Allocatable[Float64[:]] | None` on non-argument declarations. -- [x] Parse and print `Pointer[Float64[:]]`. -- [x] Parse and print `Pointer[Float64[:, :]]`. -- [x] Parse and print `Pointer[String[8][:]]` if string arrays are supported. -- [x] Parse and print `Pointer[Float64[:]] | None = ...`. -- [x] Reject `Pointer[Float64[:]] | None` on non-argument declarations. -- [x] Verify normal `T[...]` type identity remains array data semantics even - when runtime can accept handles by data coercion. -- [x] Verify `Allocatable[T[...]]` and `Pointer[T[...]]` retain a base array - data type that matches the wrapped `T[...]` annotation. -- [x] Reject `Snapshot[T]` in active contracts. -- [x] Reject or migrate `Annotated[T[...], Allocatable]`. -- [x] Reject or migrate `Annotated[T[...], Pointer]`. -- [x] Verify no generated `.pyi` uses `Snapshot[T]`. -- [x] Verify no generated active `.pyi` uses public - `Annotated[T[...], Allocatable]` or `Annotated[T[...], Pointer]` for array - descriptor handles. - -### Semantic IR And Policy Tests - -- [x] Verify Allocatable and Pointer handles use the same semantic handle - representation family with distinct descriptor kinds. -- [x] Verify handle types are distinct from normal array data types. -- [x] Verify handle semantic types carry descriptor facts without mutating the - plain array semantic type. -- [x] Verify handle semantic types expose the base array data type used for - `T[...]` call compatibility. -- [x] Verify module-variable, derived-field, argument, result, and optional - absent handle origins complete policy before lowering. -- [x] Verify native array handle policy carries owner retention, target - lifetime, and generated destroy behavior before lowering. -- [x] Verify bridge/binding layers dispatch from completed policy decisions. -- [x] Verify incomplete ownership, lifetime, release, addressability, or - descriptor-extraction facts produce wrapper-planning errors. -- [x] Verify descriptor-handle arguments stop wrapper planning until generated handle - handoff exists instead of falling back to NumPy-array conversion. -- [x] Verify plain `Pointer[T[...]]` generates the default conservative handle - profile without requiring `PointerPolicy(...)`. -- [x] Verify missing owner/release facts block only ownership-changing pointer - operations, not association inspection, handle passing, or other safe default - handle operations. -- [x] Verify `Addr(Arg(i))` rejects `Allocatable[T[...]]` and - `Pointer[T[...]]` descriptor handles instead of acting as descriptor passing. -- [x] Verify pointer allocation/deallocation/resize permissions are absent or - blocked unless explicit pointer policy allows them. -- [x] Verify unsafe/user-responsibility deallocation is available only through - the explicit policy value and never by default. -- [x] Verify completed `PointerPolicy(...)` facts select `contiguous_view` or - `descriptor_view` before lowering, never an extraction-only copy action, and - only descriptor-view paths request pointer C-descriptor interop. -- [x] Verify pointer C-descriptor interop requirements produce an explicit - wrapper-planning error while that interop path is unavailable. - -### Shared Runtime Handle Tests - -- [x] Verify `AllocatableArray` and `PointerArray` use the same common - `to_numpy()`, `shape`, dtype, rank, owner-retention, and ops-table path. -- [x] Verify common shape metadata is reported consistently. -- [x] Verify common dtype metadata is reported consistently. -- [x] Verify handles keep required module, parent object, or owner storage alive. -- [x] Verify invalid generated operation tables fail at handle construction - before descriptor state or native handoff is queried. -- [x] Verify handles without the shared generated `shape`, `array_actual`, or - `descriptor` operations fail at construction. -- [x] Verify generated `array_actual` and `descriptor` handoff operations cannot - return `None`; optional absent handles are the only runtime path that maps to - absent descriptor fact fields. -- [x] Verify generated array-actual operations must return the internal typed - native-array handoff object with a non-null pointer address, rejecting - booleans, non-integers, zero addresses, negative addresses, and arbitrary - Python objects before generated bridge handoff. -- [x] Verify descriptor operations accept validated decoded standard descriptor - fields or a contiguous data address, preserve null `base_addr` as present - unallocated/unassociated state, and reject malformed field records. -- [x] Verify shared runtime handles reject unsupported descriptor-kind tags - before any generated operations are used. -- [x] Verify owned handles call generated destroy ops exactly once when closed - or finalized, and borrowed handles do not destroy native owner storage. -- [x] Verify owned handles without generated `destroy` operations fail at - construction instead of leaking through a suppressed finalizer error. -- [x] Verify owned-handle destroy operations can inspect live handle state - before the handle is marked closed. -- [x] Verify owned handles are marked closed after a failing destroy attempt, - preventing finalizer retries of the same generated release operation. -- [x] Verify generated `.to_numpy()` operations cannot return non-NumPy objects - from borrowed-view or contiguous-view policies, and cannot return non-NumPy - objects from descriptor-view policy unless the value is a decoded pointer - descriptor field mapping or field-record object. -- [x] Verify generated `.to_numpy()` arrays and decoded pointer descriptor - views must match the handle's declared dtype and rank. -- [x] Verify `to_numpy()` returns `None` for unallocated allocatable handles and - unassociated pointer handles before generated extraction or unsupported-policy - errors are reached. -- [x] Verify generated extraction cannot return `None`, or a decoded pointer - descriptor with null `base_addr`, after the handle has reported present - descriptor state. -- [x] Verify extraction-enabled handles without generated `to_numpy` fail at - construction, while unsupported extraction raises the completed-policy error. -- [x] Verify contiguous-view policy rejects non-contiguous arrays and never - copies; descriptor-view policy preserves validated shape and strides. -- [x] Verify the internal runtime array-actual hook rejects absent descriptor - state and uses generated handoff ops instead of `to_numpy()`. -- [x] Verify the internal runtime array-actual hook validates expected dtype, - rank, shape, layout, and writeability before generated handoff. -- [x] Verify handle array-actual layout validation normalizes `C` and `F` - expectations consistently with ndarray validation and rejects unsupported - layout names before generated handoff. -- [x] Verify the internal runtime normal-array argument dispatcher accepts - ordinary ndarrays through an ndarray path, accepts allocated/associated - handles through native array-actual handoff, and rejects unallocated or - unassociated handles without calling `to_numpy()`. -- [x] Verify the internal runtime normal-array argument ABI packer emits the - generated Bind-C array tuple shape for ndarray inputs and for - allocated/associated handle inputs without calling `to_numpy()`. -- [x] Verify the internal runtime normal-array argument dispatcher preserves - zero-length array actuals and rejects handle shapes with negative extents. -- [x] Verify the internal runtime normal-array argument dispatcher rejects - byte-swapped or unaligned ndarray inputs when generated binding policy - requests native byte order or alignment. -- [x] Verify the internal runtime descriptor-parameter hook accepts only the - matching handle class/kind, rejects ordinary arrays, validates expected dtype, - rank, and shape, and maps optional `None` to an absent native handle. -- [x] Verify optional absent-handle `None` still rejects unsupported descriptor - kinds before returning the native absent-handle sentinel. -- [x] Verify the internal runtime descriptor-argument field packer returns - `base_addr`, `elem_len`, `rank`, and each dimension's lower bound, extent, and - stride multiplier; maps optional absent-handle `None` to null fact fields; - and uses a distinct non-null presence token for present optional handles. -- [x] Verify projected writable handle arguments require a typed direct - standard-descriptor handoff and reject fact-only descriptors before native - mutation can detach the caller's handle state. -- [x] Verify CPython binding generation packs required and optional - descriptor-handle arguments through the runtime helper, dispatches - non-projected calls to standard call-local CFI storage, dispatches projected - writable calls to persistent descriptor storage, and passes the selected - descriptor pointer through the completed Bind-C tuple shape. -- [x] Verify allocatable handles without generated `allocated` fail at - construction. -- [x] Verify pointer handles without generated `associated` or `nullify` fail - at construction. - -### Allocatable Runtime Tests - -- [x] Module allocatable attribute is a handle object. -- [x] `h.allocated` updates after allocate, deallocate, and resize. -- [x] `h.shape` updates after allocate, deallocate, and resize. -- [x] `h.to_numpy()` returns `None` when unallocated. -- [x] Plain and `Aliased` allocated module handles both return mutable live - views, and mutating either view updates native module storage. -- [x] A fresh extraction follows allocation, deallocation, resize, and - reallocation state; an explicit `.copy()` remains independent. -- [x] Tests state the stale-view contract without dereferencing deliberately - stale storage. -- [x] Derived allocatable field is a handle object. -- [x] Derived-field handle keeps the parent wrapper alive. -- [x] Derived-field `deallocate()` operates on `parent%field`. -- [x] Derived-field `resize(shape)` operates on `parent%field`. -- [x] Allocatable function result returns an owned handle. -- [x] Owned result handle finalizer deallocates native owner storage. -- [x] Owned result handle `to_numpy()` works after the bridge returns. -- [x] `Allocatable[T[...]]` parameter accepts allocatable handles. -- [x] `Allocatable[T[...]]` parameter rejects plain ndarray. -- [x] `T[...]` parameter accepts ndarray. -- [x] Concrete-rank numeric `T[...]` parameter accepts allocated allocatable handle through native - array-actual handoff, without implicitly calling `h.to_numpy()`. -- [x] Concrete-rank numeric `T[...]` parameter applies the same dtype, rank, shape, layout, and - mutability validation policy to ndarray inputs and allocated allocatable - handles. -- [x] Explicit `T[...]` calls with `h.to_numpy()` follow the ordinary ndarray - path and reject `None` or read-only arrays when writable storage is required. -- [x] Concrete-rank numeric `T[...]` parameter rejects unallocated allocatable handle unless nullable - data-buffer behavior is explicitly supported. - -### Pointer Runtime Tests - -- [x] Module pointer attribute is a handle object. -- [x] `p.associated` reflects association state. -- [x] `p.shape` reflects association state and target shape. -- [x] `p.to_numpy()` returns `None` when unassociated. -- [x] `p.to_numpy()` returns a borrowed view when associated and supported. -- [x] `p.nullify()` disassociates the native pointer descriptor. -- [x] Derived pointer field is a handle object. -- [x] Derived-field pointer handle keeps the parent wrapper alive. -- [x] Derived-field pointer operations access `parent%field`. -- [x] Pointer associated with a slice returns a NumPy view with expected shape - and strides when C descriptors are available. -- [x] Runtime pointer handles raise a clear unavailable-operation error when - no descriptor-extraction `to_numpy()` operation is generated. -- [x] Runtime pointer descriptor-view extraction validates required decoded - TS29113 fields before constructing a NumPy view. -- [x] If C descriptors are unavailable, test the selected explicit behavior: - contiguous live view or clear wrapper-planning diagnostic, never a copy fallback. -- [x] Pointer `deallocate()` and `resize()` are absent or raise when policy - disallows them. -- [x] Pointer `allocate()`, `deallocate()`, and `resize()` work only when - explicit pointer policy allows them. -- [x] `Pointer[T[...]]` parameter accepts pointer handles. -- [x] `Pointer[T[...]]` parameter rejects plain ndarray. -- [x] Concrete-rank numeric `T[...]` parameter accepts an associated pointer - handle through native array-actual handoff when the target is contiguous, - without implicitly calling `p.to_numpy()`. -- [x] Concrete-rank numeric `T[...]` parameter applies the same dtype, rank, shape, layout, and - mutability validation policy to ndarray inputs and associated pointer - handles. -- [x] Reject noncontiguous pointer targets from the pointer/shape array-actual - handoff instead of silently treating their elements as contiguous. -- [x] Explicit `T[...]` calls with `p.to_numpy()` follow the ordinary ndarray - path and reject `None` or read-only arrays when writable storage is required. -- [x] Concrete-rank numeric `T[...]` parameter rejects unassociated pointer handle unless nullable - data-buffer behavior is explicitly supported. - -### Build Gating Tests - -- [x] Pointer descriptor interop includes or requires `ISO_Fortran_binding.h` - only when descriptor-based pointer interop is used. -- [x] Borrowed/argument-only allocatable builds do not require - `ISO_Fortran_binding.h`; owned allocatable result builds include it for their - persistent CFI storage path. -- [x] Builds that need unavailable pointer descriptor interop fail with a clear - diagnostic rather than guessing descriptor layout. - -### Documentation Regression Tests - -- [x] Public docs show `Allocatable[T[...]]` for allocatable array handles. -- [x] Public docs show `Pointer[T[...]]` for pointer array handles. -- [x] Public docs do not show `Annotated[T[...], Allocatable]` as the active - public spelling. -- [x] Public docs do not show `Annotated[T[...], Pointer]` as the active public - spelling. -- [x] Public docs do not show `Snapshot[T]` as an active array descriptor - contract. -- [x] Public docs explain `to_numpy()` as the explicit user-facing extraction - operation for both handle types, not the required internal implementation of - handle-to-native calls. - -## Completion Criteria - -The feature is complete only when all of these are true: - -- [x] Allocatable and Pointer array handles share one internal handle - foundation. -- [x] Public contract syntax is `Allocatable[T[...]]` and `Pointer[T[...]]`. -- [x] Active parser/printer paths reject or remove the old annotation and - snapshot forms. -- [x] Post-IR policy completes every handle decision before wrapper planning. -- [x] Bridge and binding generation dispatch from completed policy only. -- [x] Runtime handles expose the documented APIs and state transitions. -- [x] Pointer descriptor extraction never guesses compiler-specific descriptor - layout. -- [x] Build gating keeps descriptor interop requirements local to the paths that - need them. -- [x] Runtime tests cover module variables, derived fields, arguments, data - coercion, owned allocatable results, pointer association, pointer nullify, and - pointer policy gating. -- [x] Documentation and generated `.pyi` fixtures no longer present old public - forms as active contracts. diff --git a/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md b/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md index a94bf05ae..0f5550c18 100644 --- a/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md +++ b/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md @@ -429,7 +429,7 @@ PRIK_C_DOCS_END --> before semantic policy completion runs. Evidence: `tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_pyi_parser_returns_python_ast_only`, `prik/semantics/README.md`, and - `docs/developer/internal-architecture/pipeline-map.md`. + `docs/developer/architecture.md` and the detailed package guides. - [x] Risky-but-explicit identity contracts document their exact behavior instead of being silently healed. Fixed-length `String[n]` `intent(inout)` identity calls may return `None` with no observable Python mutation when the diff --git a/docs/developer/roadmap/wrapper-plan-migration-checklist.md b/docs/developer/roadmap/wrapper-plan-migration-checklist.md deleted file mode 100644 index 042512b64..000000000 --- a/docs/developer/roadmap/wrapper-plan-migration-checklist.md +++ /dev/null @@ -1,4869 +0,0 @@ ---- -title: Wrapper Plan Migration Checklist -audience: maintainers -prerequisites: pipeline map, semantic IR, ownership policy -related: ../internal-architecture/pipeline-map.md, ../../user/reference/semantic-ir.md, semantic-pyi-wrapper-checklist.md, index.md -status: active-roadmap -publication: draft ---- - -# Wrapper Plan Migration Checklist - -This file is the canonical implementation contract for wrapper-plan migration. -It replaces the generic semantic-IR wrapper lowering route one eligible module -at a time. The migration changes representation and generation organization; it -does not intentionally change the established Python, native ABI, ownership, or -build behavior of a migrated lane. - -## Canonical Pipeline - -```text -Semantic IR - -> post-IR policy completion - -> WrapperPlanner.build(module) - -> editable ModulePlan - -> WrapperGenerator.generate(plan) - -> freeze and validate the received plan - -> recursively synthesize C binding nodes - -> recursively synthesize Fortran bridge nodes - -> print backend nodes - -> GeneratedWrapper - -> existing build/link orchestration -``` - -There is no public or wrapper-domain representation between `ModulePlan` and -backend syntax nodes. `CModule`, `CHeader`, `CFunction`, `FortranModule`, and -`FortranFunction` are direct printer inputs, not another wrapper planning -stage. - -The public generation boundary is deliberately small: - -```python -complete_semantic_policies(module) -plan = WrapperPlanner().build(module) -generated_wrapper = WrapperGenerator().generate(plan) -``` - -`WrapperGenerator.generate` accepts `ModulePlan` only. It does not accept -semantic modules, build a plan itself, select an alternate lowering route, or -retry a prior route after direct generation begins. - -Semantic `.pyi` generation remains outside this route. A semantic `.pyi` -contract can supply the semantic module consumed by planning, but planning does -not change `.pyi` emission. - -## One Shared Plan, Explicit Backend Views - -`ModulePlan` is one shared semantic-and-ABI contract. It is not a C plan joined -to a Fortran plan and it does not contain backend nodes or source text. - -Every owner that crosses or coordinates the boundary has binding and bridge -child plans in the same editable tree: - -```text -ModulePlan - binding: BindingModulePlan - bridge: BridgeModulePlan - functions: FunctionPlan ... - binding: BindingFunctionPlan - bridge: BridgeFunctionPlan - arguments: ArgumentTransferPlan ... - binding: BindingArgumentPlan - bridge: BridgeArgumentPlan - native_call_slot: NativeCallSlotPlan - transformations: TransformationPlan ... - results: ResultPlan ... - binding: BindingResultPlan - bridge: BridgeResultPlan - native_call_slot: NativeCallSlotPlan | None - transformations: TransformationPlan ... - lifecycle: LifecycleActionPlan ... - binding: BindingLifecyclePlan | None - bridge: BridgeLifecyclePlan | None - native_call_slots: ordered references to argument/result slots plus - function-owned literal or helper slots -``` - -`ArgumentTransferPlan` remains the only argument-owner record; do not add a -generic duplicate `ArgumentPlan`. Its backend-facing child plans are -deliberately distinct and directly editable: - -- `binding: BindingArgumentPlan` describes the Python input, its C conversion - action, and - the C handoff value/role it produces; -- `bridge: BridgeArgumentPlan` describes the C ABI slot, value-versus-address - convention, - native action, and Fortran value that the bridge consumes; -- `native_call_slot` records the exact native-call position and source; -- an argument or hidden result's `native_call_slot` is the same mutable record - referenced from `FunctionPlan.native_call_slots`, not a copied record that a - maintainer must edit twice; -- result and lifecycle records identify later producers, consumers, ordering, - and responsibility through their own binding and bridge views; -- native slots and lifecycle actions are subordinate transfer details, not - parallel datatype-policy systems. They remain indexed on `FunctionPlan` - because native ABI order and success/failure lifecycle order may span more - than one argument or result. A function-owned literal, status helper, or - other ABI slot may also have no single argument/result owner. - -The action vocabulary keeps source placement, data transfer, and native ABI -transport orthogonal. `ResultPlan.source_kind` says whether a result comes from -a `direct_return` or `hidden_output`; `CodegenAction` says how the value moves -or is owned (`DIRECT_VALUE`, `COPY_OUT`, `WRAPPER_INSTANCE`, and so on). A -hidden scalar therefore remains `DIRECT_VALUE`, while hidden strings and -ordinary arrays are `COPY_OUT`; hidden descriptor-owned objects use their -completed ownership action. `HIDDEN_OUTPUT` is not a codegen action because -hiddenness is a source location, not a transfer operation. - -Likewise, `NativeBarrierAction.PASS_ARRAY_BUFFER` means the Phase 6 data-buffer -ABI whose handoff plan carries data, rank, extents, strides, and itemsize. -`NativeBarrierAction.PASS_NATIVE_DESCRIPTOR` is reserved for the persistent -native descriptors and handles introduced in Phase 7. Neither backend may use -one action as a fallback for the other. - -`NativeBarrierAction.PASS_RAW_ADDRESS` is the third, deliberately narrower -array transport: one caller-supplied opaque address plus separately completed -pointee rank, shape, element type, and orientation facts. It does not authorize -NumPy extraction, a packed array-buffer ABI, or a native descriptor. Scalar, -fixed-string, and array raw addresses reuse this action and -`ArgumentHandoffMode.OPAQUE_ADDRESS`; their object kind then selects the named -bridge association method. Do not add datatype-specific raw-address actions. - -The binding input and bridge input may have different representations: a C -binding commonly receives `PyObject *`, produces a C scalar or address, and -the bridge then consumes a value or pointer according to its ABI slot. The plan -therefore records the producer/consumer handoff contract explicitly; it does -not assume identical types or actions. This keeps both backend plans in one -coherent editable tree while preventing them from drifting into disconnected -top-level plans. `CBindingGenerator` reads only binding views plus shared -handoff/order facts needed to create C nodes; `FortranBridgeGenerator` reads -only bridge views plus shared native-call order needed to create Fortran nodes. -Neither backend output is an input to the other backend. - -An owner may have only one active backend side when completed policy places the -behavior entirely in one backend, but that ownership must be explicit in the -plan rather than inferred during lowering. - -### Transformation Layer Ownership - -Every representation transformation has one explicit -`TransformationPlan.layer`: `BINDING` or `BRIDGE`. The record also names its -phase (`COPY_IN`, `NATIVE_MUTATION`, `COPY_OUT`, or `CLEANUP`), typed action, -source representation, target representation, and reason. These records are -subordinate to the `ArgumentTransferPlan` or `ResultPlan` that owns the value; -they are not a parallel datatype-policy hierarchy. - -Use the binding layer for transformations involving Python objects or NumPy -semantics: dtype/layout conversion, Python encoding/decoding, reference and -identity handling, copy-back into caller objects, and Python-owned temporary -cleanup. Use the bridge layer for transformations wholly between the ABI and a -native-language representation: Fortran character representation, native -descriptor/result materialization, derived native layout, or native-only -allocation and copy. - -One logical conversion and its inverse/cleanup must stay at one layer. If a -workflow genuinely needs both layers, policy completion records two distinct -transformations separated by a named intermediate ABI representation. A -backend consumes only transformations assigned to it and fails validation if -asked to lower an action owned by the other backend. Method location, datatype, -`intent`, and available local storage never select the transformation layer. -For `COPY_F`, copy-in, conditional copy-out, and cleanup are all binding-owned; -the bridge has no `COPY_F` transformation and reuses its ordinary ORDER_F -association path. - -### Editable Signature And Native Intent Boundary - -The semantic `.pyi` signature is authoritative for the Python-facing call -shape. The source converter may use native `intent` to propose the initial -generated signature, but that proposal is not backend policy. A user may -reorder visible Python arguments, keep a native output dummy as caller-supplied -storage, project it into a Python result, or introduce hidden bridge storage. -After the semantic contract is constructed or edited, completed native-call -slots must account for every required native position exactly once; stored -source `intent` must not silently override that mapping by hiding, exposing, -reordering, allocating, or projecting a Python value. - -Bridge dummies and backend-local variables use the most permissive declaration -that is compatible with the selected ABI, normally no `intent` or an internal -`intent(inout)`-equivalent writable local. The called native procedure enforces -its actual `intent(in)`, `intent(out)`, or `intent(inout)` contract. Required -interoperability attributes such as `value`, optional presence fields, standard -descriptor attributes, and true bridge-output parameters remain explicit ABI -facts; they are not permission for a backend to reconstruct the user-facing -signature from native `intent`. - -The plan includes every fact required for mechanical lowering: - -- owner path and plan-node kind; -- typed Python, native, and result actions; -- semantic datatype, datatype family, and precision/type facts; -- Python/native handoffs and bridge ABI slots; -- native-call slots in their exact order; -- result and output projection; -- ownership, transfer, destruction, mutability, writeback, release - responsibility, storage mode, nullability, and lifecycle ordering; and -- every typed lowering choice required by a supported backend. - -It contains decisions and facts, never generator method names. In particular, -there are no handler-name fields, handler records, or plan-owned handler -registries. The planner uses the completed policy actions already represented -by `PythonBarrierAction`, `NativeBarrierAction`, and `CodegenAction`; a new -typed action is added only when none of those can identify a necessary -mechanical behavior. Free-form string actions are forbidden. - -## Ownership Boundary - -Post-IR policy completion decides object kind, ownership, transfer, -destruction, mutability, writeback, nullability, output projection, release -responsibility, contract-value storage (`stack`, `heap`, or `alias`), getter -behavior, native setter assignment, Python setter exposure, ABI order, and -lifecycle order before planning starts. - -`WrapperPlanner` only projects those completed decisions and datatype facts -into readable editable records. It may traverse owners, preserve declared -order, assign stable owner paths, and wire already-decided producers to -consumers. It must not derive or replace policy from a datatype, `intent`, -decorator spelling, `is_alias`, dotted owner shape, local memory observation, -or a missing field. - -No code after planning may infer, replace, or override semantic policy. Backend -contexts may allocate temporary names and create declarations, error paths, -reference-count operations, and local cleanup statements after selecting a -typed lowering case. Those are emitted-code mechanics, not plan policy. - -`WrapperPlanner.build(module)` returns an editable `ModulePlan`. Maintainers -may edit its ordinary fields to inspect an experiment before generation. A -permanent behavior change belongs in the semantic contract and completed policy -rather than in a backend exception. - -## Generator-Owned Freezing and Validation - -Every consumer freezes the exact object it receives: - -```text -editable ModulePlan -- WrapperGenerator --> frozen ModulePlan -editable backend modules -- source printers --> frozen backend modules -editable GeneratedWrapper -- build integration --> frozen GeneratedWrapper -``` - -At the start of `WrapperGenerator.generate(plan)`, the generator must: - -1. recursively freeze that exact plan object; -2. run the complete binding/bridge plan-consistency validation on the final - edited plan; -3. ask each backend to preflight only its own implementation capability; and -4. recursively lower the validated plan into backend nodes before the printers - consume those nodes. - -Later mutation of the received plan raises `FrozenStageRecordError`. Backend -nodes remain editable until their printer consumes them. The generated wrapper -remains editable until `_build_generated_wrapper_extension(...)` consumes it. - -`WrapperPlanner` does not validate its output. It mechanically projects an -editable plan, which may temporarily be inconsistent while a maintainer edits -it. `WrapperGenerator` owns the private structured validation methods and -is the only validation consumer. There is no standalone validator class or -public validation operation. - -`WrapperGenerator._validate_plan()` is the single plan-consistency gate. -It validates the complete binding/bridge graph after the editable plan has -been frozen and before either backend preflight or visitor runs. The gate stays -small by composing `_plan_diagnostics()` from typed private diagnostics for -namespaces, functions, arguments, results, lifecycle actions, and module -variable getter/setter action families. A cross-view invariant belongs to the -diagnostic for the lowest plan node that contains both views; for example, a -module-variable diagnostic validates Python setter exposure against its native -assignment and bridge setter role. - -`CBindingGenerator.require_supported()` and -`FortranBridgeGenerator.require_supported()` are later backend-local -capability preflights. They may reject a completed action, primitive type, -descriptor kind, or ABI combination that their own backend cannot implement, -but they do not establish whether binding and bridge views agree. Backend -visitors and `_lower_*` methods mechanically consume the decisions owned by -their view. Their exhaustive unmatched-action errors remain defensive -protection for direct backend use; the public generation path must report a -cross-view inconsistency from `_validate_plan()` first. No generator infers -consistency by reading the other backend's plan view. - -Structural validation preserves these invariants: - -- module getter actions and roles agree, and Python setter exposure agrees with - native assignment, bridge setter roles, descriptor kinds, and constant state; -- binding producer and bridge consumer roles agree; -- bridge ABI coverage, positions, and owner roles are complete; -- native-call slot coverage, exact ordering, hidden literals, and hidden - results agree, including hidden-result native and codegen actions; -- direct and hidden result producer/consumer roles agree; -- writeback, cleanup, and release actions use available source roles in their - declared order, and advertised roles exactly match their plan producers; -- positions and symbolic roles are neither duplicate nor missing; and -- external and bind-target requirements are complete. - -## Direct Recursive Lowering - -`WrapperGenerator` owns two private backend visitors: - -```python -c_module, c_header = CBindingGenerator().visit(plan) -fortran_module = FortranBridgeGenerator().visit(plan) -``` - -They are private implementation organization inside direct generation, not -public stages. Both visitors recursively traverse the same plan tree and -return actual C or Fortran nodes (or tuples of actual nodes where a child needs -multiple declarations or statements). - -The recursive shape is: - -```text -ModulePlan - -> binding and bridge module contexts and backend nodes - -> NamespacePlan - -> directly owned FunctionPlan and ModuleVariablePlan records - -> binding/bridge argument transfers, result projection, lifecycle actions - -> complete backend function and namespace nodes - -> complete backend module node -``` - -An argument visitor returns the C or Fortran declarations/statements/parameters -needed for that backend. A result visitor returns the backend result nodes. -Lifecycle visitors return backend writeback, cleanup, or release nodes. Parent -visitors assemble these concrete child results directly into complete syntax -nodes. Do not introduce another wrapper-specific transport model. - -The public orchestration stays visibly direct: - -```python -class WrapperGenerator: - def generate(self, plan: ModulePlan) -> GeneratedWrapper: - plan.freeze() - self._validate_plan(plan) - self._c_generator.require_supported(plan) - self._fortran_generator.require_supported(plan) - - c_module, c_header = self._c_generator.visit(plan) - fortran_module = self._fortran_generator.visit(plan) - - c_source = self._c_printer.doprint(c_module) - c_header_source = self._c_printer.doprint(c_header) - fortran_source = self._fortran_printer.doprint(fortran_module) - return self._generated_wrapper( - plan.owner_path, - c_source, - c_header_source, - fortran_source, - ) -``` - -The generator constructs `GeneratedWrapper` directly from the -printed source plus build metadata. It does not duplicate native build plans, -compiler selection, link ordering, native-support installation, or compilation -policy; those remain in existing build/link orchestration. - -## Direct Lowering Methods - -Each backend visitor dispatches plan nodes by class through -`_visit_`. A visitor method then calls a typed -`_lower_` helper for each completed action family owned by that -backend. The helper uses an explicit, exhaustive action match and calls one -concrete `_lower__` implementation method. For example: - -```python -def _visit_ModuleVariablePlan(self, plan): - return ( - *self._lower_module_getter(plan), - *self._lower_module_setter(plan), - ) - -def _lower_module_getter(self, plan): - match plan.binding.getter_action: - case ModuleGetterAction.CONSTANT_VALUE: - return self._lower_module_getter_constant_value(plan) - case ModuleGetterAction.DIRECT_VALUE: - return self._lower_module_getter_direct_value(plan) - case ModuleGetterAction.NULLABLE_SNAPSHOT: - return self._lower_module_getter_nullable_snapshot(plan) - raise ValueError(...) -``` - -The C binding dispatches only from binding-owned actions, and the Fortran -bridge dispatches only from bridge-owned actions. In particular, native module -setter generation consumes the completed bridge assignment action rather than -the Python setter-exposure action. Post-IR policy completion records -`AssignmentMode.NONE` when no native setter is exposed and -`AssignmentMode.VALUE_COPY` for supported scalar value write-through; bridge -lowering does not reconstruct that choice from the Python setter action. -Backend support checks retain genuine ABI and capability validation; action -dispatch itself raises explicitly for every unsupported value, including an -unsupported alias assignment. - -Do not synthesize implementation method names, use `getattr` to execute -lowering, retain a fallback behavior, or store dispatcher names in the plan. -Do not create extra getter or setter plan nodes solely to gain more -`_visit_` methods. Both visitor and lowering methods return backend -syntax nodes; printers remain the only layer that renders those nodes as source -text. - -Primitive dtype spelling and converter differences live in the intentionally -scalar-specific `PrimitiveScalarTypeRegistry`; they do not duplicate control -flow methods or select semantic policy. - -Within policy, planning, support analysis, validation, and both backend -visitors, family-specific helpers stay in visibly labeled contiguous groups: -scalar helpers, string helpers, and ordinary-array helpers. Put a short section -comment above every such group so maintainers can find one datatype family -without scanning interleaved lowering methods. Generic orchestration remains -outside those groups and dispatches into them through the completed typed -actions. - -## Migration and Route Rules - -The legacy route remains the behavioral oracle until a lane has direct-plan -parity. Route selection is atomic per merged extension: a generation unit uses -either the direct wrapper-plan route or the legacy route. It never combines one -backend from one route with the other backend from the other route. - -The documented public contract is authoritative when it intentionally corrects -legacy behavior. In that case, use the legacy implementation to simplify the -mechanical ABI, conversion, ownership, and cleanup audit, improve the design -where the legacy path is unsafe or unnecessarily complex, and record every -intentional behavioral difference in focused tests. Do not preserve a known -legacy defect merely to obtain byte-for-byte or semantic parity. - -An unsupported owner may select the legacy route before planning. Once the plan -route is selected, planning, validation, lowering, printing, or compilation -failure fails the build; it must not fall back to legacy generation. - -Support reports and rollout gates keep scalar, string, and ordinary-array -input, optional, writeback, direct-result, and hidden-result lanes distinct. -Evidence for one datatype family must not make another family production -eligible accidentally. - -For each lane: - -1. replay an existing passing `tests/wrapper` case through the legacy route and - retain its generated artifacts; -2. record the relevant legacy source paths, ABI/call order, ownership and - cleanup behavior, artifact requirements, and runtime assertions; -3. complete every missing semantic decision before planning; -4. add the smallest required plan record and directly named lowering method; -5. produce the same complete artifact set through the direct route; -6. inspect differences, compile both routes, and run the existing assertions; -7. update checklist evidence only after direct-route parity is proven. - -Generated source is diagnostic evidence, not a byte-for-byte golden. Backend -temporary names and equivalent control flow may differ, but ABI, conversion, -ownership, cleanup, call order, and artifact requirements must remain proven. - -During this migration the full real-library BLAS/LAPACK wrapper corpus is -excluded locally and in CI until final cutover. General native-bundle coverage -remains active. - -## Staged Walkthrough - -`tools/wrapper_plan_staged_walkthrough.py` is the maintained hand-inspection -path. It shows only the source/contract entry, policy completion, plan creation, -a direct edit, direct generation, artifact inspection, build, and runtime use: - -```python -module = ... -complete_semantic_policies(module) - -plan = WrapperPlanner().build(module) -namespace = next(item for item in plan.namespaces if item.python_path == ()) -function = namespace.functions[0] -function.bridge.native_name = "SUB_R8" - -binding = CBindingGenerator() -bridge = FortranBridgeGenerator() -print(function.arguments[0].binding.optional_mode) -print(function.arguments[0].bridge.optional_mode) - -generated_wrapper = WrapperGenerator( - c_generator=binding, - fortran_generator=bridge, -).generate(plan) - -# inspect generated files -# build and run -``` - -It does not expose standalone validation. Printed plan inspection uses the -actual namespace, owner, and completed action records. The backend visitors -make the corresponding explicit action matches visible in their typed lowering -helpers. - -## Required Evidence - -Focused tests must prove: - -- `WrapperPlanner.build(module)` returns a directly mutable plan; -- direct edits to binding and bridge views change the relevant generated C and - Fortran source; -- `WrapperGenerator.generate(plan)` freezes the exact consumed plan; -- module visitors recursively include generated function nodes; -- function visitors recursively include argument, result, and lifecycle nodes; -- directly named backend lowering methods cover every supported plan action; -- unsupported combinations fail explicitly; -- source printers freeze backend module nodes; -- generated artifacts remain editable until build consumption, which freezes - them; -- source and semantic-`.pyi` entries preserve compiled runtime parity; and -- backend lowering does not reconstruct semantic policy. - -Use package-export inspection and focused migration checks to prove removal of -obsolete internal representations; do not preserve tests whose only assertion -is that a removed API is absent. - -## Recovered Roadmap Scope - -The detailed migration queue below is retained from the original roadmap. The -obsolete Phase 0-2 emitter/fragment architecture is replaced by the simplified -direct-plan checklist later in this file; all later semantic lanes, matrix rows, -verification gates, and completion records remain explicit. - -## Existing Wrapper Suite As The Migration Queue - -`tests/wrapper` is the behavioral source and final acceptance suite for this -migration. Migrate its existing generation units one by one; do not create a -parallel wrapper suite or new native source fixtures merely to make the new -route easier to exercise. - -- Phase 0A adds a maintained migration matrix to this file covering every - Python test node under `tests/wrapper`. Each row records whether the test - generates a wrapper, the source/contract generation unit it uses, its - relevant feature lanes, and one status: - `not-applicable`, `deferred-real-library`, `legacy`, `dual-route`, or - `wrapper-plan`. -- Existing source files, contract fixtures, build helpers, runtime assertions, - failure assertions, and ABI assertions are reused as written whenever they - already cover the migrated behavior. Do not copy their behavior into a new - test with a smaller invented source. -- A new native source or contract fixture is allowed only when the audit proves - that accepted production behavior has no existing test. Record that coverage - gap and its owning semantic lane here before adding the fixture; migration - convenience is not sufficient justification. -- Whole-generation-unit routing still applies. An existing test moves to - `dual-route` only when every runtime-required feature in its module is - supported. If a nominally scalar fixture also contains results, strings, - arrays, decorators, module state, or classes, leave it on the legacy route - until those lanes are complete rather than carving out a narrower fixture. -- Dual-route parity reuses the same existing fixture and assertion function for - legacy and wrapper-plan builds through internal test orchestration. Do not - add a public route flag, duplicate the behavioral assertions, or require - byte-identical generated source. -- Once parity passes and production eligibility is widened, that existing test - moves to `wrapper-plan`. Keep deliberate legacy execution only in the - migration parity harness until final cutover. -- The final target is not merely that `tests/wrapper` passes. Every test in the - suite must be represented in the migration matrix, and every test that - generates a runtime wrapper must use the wrapper-plan route after cutover. - Tests that only inspect documentation, layout, parsing, or `.pyi` generation - may be `not-applicable` but must still pass. -- During active migration, ordinary pytest invocations exclude the full BLAS - and LAPACK example projects. Their dedicated lane owns library-scale - verification. General native-bundle tests remain active because they test - linker/build mechanics independently of the full corpora. - -### Wrapper Test Migration Matrix - -Matrix rows use pytest selector patterns. A row ending in `::*` covers every -collected test node in that Python file when all nodes share the same -generation classification. A row ending in `[*]` covers the parametrized nodes -for that test function. The structural layout test expands these selectors -against live `python3 -m pytest --collect-only -q tests/wrapper` output, so a -new wrapper test node must either match an existing row intentionally or add a -new row here before later implementation starts. - -Statuses have the meanings defined above: `legacy` still uses the current -`semantic_ir_to_codegen_ast()` route, `dual-route` runs the same generation -unit and runtime assertions through both implementations, `wrapper-plan` uses -only `WrapperPlan -> WrapperGenerator`, `not-applicable` does not generate -a runtime wrapper, and `deferred-real-library` is reserved for the full BLAS -and LAPACK corpus until Phase 12. - -#### Current Wrapper Route Counts - -These are collected pytest-node counts, not matrix-row counts. The structural -layout test derives them from live `tests/wrapper` collection and fails if this -summary, the exhaustive matrix, and the test tree disagree. - -| Status | Collected nodes | -| --- | ---: | -| `wrapper-plan` | 370 | -| `dual-route` | 0 | -| `legacy` | 0 | -| `not-applicable` | 76 | -| `deferred-real-library` | 0 | - -#### Recorded Route Progression - -This history keeps phase movement visible instead of replacing the previous -snapshot with only the latest totals. Phase 2D moved all 17 dual-route nodes -and 44 legacy nodes to production plan routing, then added two parametrized -plan-route nodes. Phase 2E adds two scalar-only parity nodes, and Phase 2F adds -one isolated direct-return plus hidden-output scalar aggregation node. The -original mixed integration nodes retain their real array/string/object -blockers. - -| Proven checkpoint | `wrapper-plan` | `dual-route` | `legacy` | `not-applicable` | `deferred-real-library` | Total | -| --- | ---: | ---: | ---: | ---: | ---: | ---: | -| Before Phase 2D | 0 | 17 | 178 | 95 | 2 | 292 | -| Phase 2D complete | 63 | 0 | 134 | 95 | 2 | 294 | -| Phase 2E scalar isolation | 65 | 0 | 134 | 95 | 2 | 296 | -| Phase 2F scalar result aggregation | 66 | 0 | 134 | 95 | 2 | 297 | -| Phase 5A required string values | 67 | 0 | 134 | 95 | 2 | 298 | -| Phase 5B fixed string results | 69 | 0 | 134 | 95 | 2 | 300 | -| Phase 5C fixed string writeback | 70 | 0 | 134 | 95 | 2 | 301 | -| Phase 5C assumed/optional string writeback | 71 | 0 | 134 | 95 | 2 | 302 | -| Phase 5D fixed string storage/raw addresses | 72 | 0 | 134 | 95 | 2 | 303 | -| Phase 5 production route reconciliation | 76 | 0 | 130 | 95 | 2 | 303 | -| Phase 6 ordinary arrays | 78 | 5 | 130 | 95 | 2 | 310 | -| Phase 6G raw array addresses | 80 | 5 | 130 | 95 | 2 | 312 | -| Phase 6 `COPY_F` representation copy | 81 | 5 | 130 | 95 | 2 | 313 | -| Phase 7 native handles/descriptors | 88 | 5 | 129 | 96 | 2 | 320 | -| Phase 7 production route reconciliation | 94 | 5 | 123 | 95 | 2 | 319 | -| Phase 8 scalar-derived object lifetimes | 106 | 5 | 123 | 95 | 2 | 331 | -| Phase 8 complete scalar-derived actual/dummy matrix | 213 | 5 | 123 | 95 | 2 | 438 | -| Phase 8H failure, qualified-type, and typed-value closure | 222 | 5 | 123 | 95 | 2 | 447 | -| Phase 11 cross-cutting suite completion | 344 | 0 | 0 | 95 | 2 | 441 | -| Phase 12 canonical cutover | 346 | 0 | 0 | 95 | 0 | 441 | - -Migration is complete only when `legacy`, `dual-route`, and -`deferred-real-library` are all zero. At that point every runtime-generating -node must be `wrapper-plan`; `not-applicable` may remain only for tests that do -not generate a wrapper. Until then, moving a node from `legacy` to `dual-route` -records proven parity, and moving it from `dual-route` to `wrapper-plan` -records final removal of its legacy execution. - -#### Complete Route Ledger - -For a `legacy` row, the feature-lane column identifies what still blocks the -new route. For `dual-route` and `wrapper-plan` rows, it identifies the behavior -already covered by the new generator. - -| Pytest selector | Generation unit | Feature lanes / blockers | Status | -| --- | --- | --- | --- | -| `tests/wrapper/fortran/arrays/test_array_contracts.py::*` | source/generated-.pyi parity or parametrized route | ordinary arrays | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_array_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/wrapper/fortran/arrays/test_array_results.py::test_array_results_follow_data_buffer_and_descriptor_handle_contracts[*]` | production plan route in source/generated-.pyi parity modes | fixed/runtime-shape ordinary array results; owned allocatable descriptor results; namespace preservation | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_array_results.py::test_maybe_unallocated_allocatable_result_preserves_absent_state` | edited semantic `.pyi` contract over the existing array-result native unit | `MaybeUnallocated` direct allocatable vector/matrix result annotations preserve allocated and unallocated result states without changing default always-allocated result handling | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_array_results.py::test_ordinary_array_results_use_canonical_plan` | canonical production output-only plan route | fixed/runtime-shape ordinary array results; ranks one through fifteen; Fortran order; zero-sized results; allocation/copy/release failure paths | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_array_results.py::test_owned_allocatable_results_preserve_handle_state` | canonical reduced owned-result contract | allocated and zero-sized wrapper-owned `CFI_CDESC_T` function-result handles; extraction and release | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_assumed_rank_arrays.py::test_assumed_rank_arguments_dispatch_to_runtime_rank[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_assumed_rank_arrays.py::test_assumed_rank_bridge_dispatches_each_runtime_rank_argument[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_assumed_rank_arrays.py::test_assumed_rank_arrays_use_explicit_plan_branches` | reduced semantic `.pyi` entry over the existing assumed-rank native unit | runtime ranks one through fifteen; mutable storage; rank validation; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_multidimensional_arrays.py::test_rank2_contiguous_contract_requires_fortran_contiguous[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_multidimensional_arrays.py::test_rank2_assumed_shape_accepts_fortran_ordered_strided_views[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_multidimensional_arrays.py::test_rank2_assumed_shape_rejects_non_positive_strides[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_multidimensional_arrays.py::test_rank2_explicit_shape_requires_fortran_contiguous[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_multidimensional_arrays.py::test_rank3_contiguous_contract_requires_fortran_contiguous[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_multidimensional_arrays.py::test_rank3_assumed_shape_accepts_fortran_ordered_strided_views[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/arrays/test_multidimensional_arrays.py::test_dense_strided_and_projected_arrays_use_canonical_plan` | reduced semantic `.pyi` entry over the existing multidimensional native unit | dense/explicit extents; positive-strided views; zero-sized axes; projected output identity; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py::test_init_entry_uses_resolved_parent_name_from_inside_package` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py::test_output_name_override_replaces_entry_inference` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py::test_recursive_graph_preserves_module_and_symbol_aliases_and_ignores_support_imports` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py::test_recursive_graph_reports_cycles_before_codegen` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py::test_recursive_graph_reports_missing_relative_contract_before_native_validation` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py::test_source_build_preserves_modules_and_root_externals` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py::test_complete_general_source_preserves_namespaces_through_canonical_plan[*]` | canonical production plan route | Python namespace hierarchy; native import aliases; scalar inputs/results; void calls | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_entry_can_alias_one_module_procedure_at_the_root` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_entry_rejects_colliding_wildcard_exports` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_entry_wildcard_import_explicitly_flattens_module_leaf` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_generated_pyi_fixture_builds_from_native_object_without_source_reparse` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_generated_pyi_matches_checked_in_fixture` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_mixed_entry_exposes_externals_at_root_and_modules_as_children` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_module_leaf_can_be_the_entry_contract` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_module_variable_runtime_contract[*]` | source/generated-.pyi parity or parametrized route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_mutable_module_variable_default_initializes_native_storage` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_one_entry_preserves_multiple_native_module_namespaces` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_cli_accepts_exactly_one_entry_contract` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_cli_preserves_explicit_ordered_link_items` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_cli_requires_a_native_link_input` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_makefile_manifest_and_replay_workflows` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_manifest_records_pointer_descriptor_interop_requirements` | non-generating: manifest serialization unit | completed native-array build requirements and local standard-descriptor headers | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_python_api_accepts_exactly_one_entry_contract` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_python_api_rejects_a_missing_native_artifact` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_python_api_rejects_invalid_address_contracts_before_codegen[*]` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_python_api_rejects_python_suffix_as_semantic_contract` | non-generating: validation/failure-path assertion | semantic .pyi generation/parsing; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_reduced_entry_generates_only_reachable_module_variable_bindings` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_scale_runtime_contract[*]` | source/generated-.pyi parity or parametrized route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py::test_source_named_root_discovers_and_builds_module_leaf` | direct wrapper/build route | semantic .pyi generation/parsing; build/compile/link orchestration; scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_documented_readme_points_example_builds_and_imports` | direct wrapper/build route | build/compile/link orchestration; module namespace and derived-type inputs/results | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_default_module_name_does_not_collide_with_root_function` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_default_places_artifacts_in_invocation_directory` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_dir_separates_abi_artifact_from_cli_alias` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_names_importable_shared_library` | direct wrapper/build route | build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_generate_sources_cli_writes_wrapper_sources_without_native_outputs` | source-only wrapper generation route | build integration through the completed wrapper plan without compile/link execution | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_internal_preprocessing_mode_still_builds_importable_runtime_wrapper` | direct wrapper/build route | build/compile/link orchestration; scalar inputs/results | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_native_link_plan_serializes_interleaved_item_kinds` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_source_build_reuses_native_plan_for_additional_compile_and_link_inputs` | source-only wrapper generation route | shared source/contract native build plan; implementation compiler flags; supplemental sources; objects; libraries; include and link directories; ordered link items | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_source_build_result_records_structured_native_plan` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_verbose_mode_prints_custom_wrapper_flags` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_verbose_mode_prints_full_direct_build_commands` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_empty_source_list` | non-generating: validation/failure-path assertion | build/compile/link orchestration | `not-applicable` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_generation_verbose_combination[*]` | non-generating: validation/failure-path assertion | build/compile/link orchestration | `not-applicable` | -| `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_missing_source` | non-generating: validation/failure-path assertion | build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/build_from_source/test_compiler_verbose.py::*` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py::*` | direct wrapper/build route | build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/pipeline/test_source_generated_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/fortran/callbacks/end_to_end/test_supported_callback_shapes.py::*` | source/generated-.pyi parity or parametrized route | callbacks/trampolines; derived types/object lifetimes | `wrapper-plan` | -| `tests/fortran/callbacks/end_to_end/test_array_callbacks.py::*` | source/generated-.pyi parity or parametrized route | callbacks/trampolines; ordinary arrays | `wrapper-plan` | -| `tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py::*` | source/generated-.pyi parity or parametrized route | callbacks/trampolines; scalar inputs/results | `wrapper-plan` | -| `tests/fortran/callbacks/pipeline/test_generated_callback_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/wrapper/fortran/derived_types/test_borrowed_finalizers.py::*` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes; classes/methods/properties/overloads | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_constructors_and_finalizers.py::*` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes; classes/methods/properties/overloads | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_derived_layout.py::*` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py::*` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_derived_type_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/wrapper/fortran/derived_types/test_derived_type_methods.py::*` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes; classes/methods/properties/overloads | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_inheritance.py::*` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes; classes/methods/properties/overloads | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py::*` | reduced passing legacy/source artifacts compared with direct typed-plan generation; plain non-target module objects intentionally use the safer member-proxy correction described in Phase 8 | scalar derived arguments/results; optional and by-value inputs; projected identity; owned/borrowed lifecycle; plain/`Aliased` module objects; scalar/string/array/nested/native-handle fields; production routing | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py::*` | reduced direct-plan bound-constructor runtime and artifact proof | explicit bound construction; shared method plan; allocation and owner commit | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py::*` | complete source/generated-contract and direct-plan proof over the canonical scalar-derived matrix fixture; replaces the former isolated descriptor rejection unit; final Phase 8H cross-suite verification remains a separate closure gate | all five actual declarations from module and wrapper origins; all six dummy forms; exact action/error selection; holder, scoped-address, allocation and pointer transactions; mixed multi-argument acquisition and reverse cleanup; distinct module-origin callbacks for qualified types from separate Fortran modules | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_pointers.py::test_caller_created_pointer_crosses_separately_built_extensions` | two independently built semantic-contract extensions | caller-created pointer descriptor identity; cross-extension validation and association | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_pointers.py::test_caller_created_pointer_handle_tracks_native_output_association` | direct semantic-contract wrapper/build route | caller-created pointer storage attachment; output association and descriptor operations | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_pointers.py::test_module_and_derived_pointer_handles_track_native_association[*]` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes; native handles/descriptors | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_pointers.py::test_module_native_array_handles_use_canonical_plan` | canonical reduced module-only contract | borrowed pointer/allocatable handles; descriptor calls; strided extraction; ordinary array actuals; operation permissions | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_pointers.py::test_pointer_array_results_use_owned_descriptors_without_owning_targets[*]` | source/generated-.pyi parity or parametrized route | owned pointer result descriptors; associated and unassociated state; borrowed target lifetime | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_pointers.py::test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime` | direct wrapper/build route | derived types/object lifetimes; native handles/descriptors | `wrapper-plan` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_native_order_contracts.py::test_editable_contract_can_use_native_order_arguments_without_native_call` | direct wrapper/build route | semantic .pyi generation/parsing; raw array addresses completed by Phase 6G; derived result remains Phase 8 | `wrapper-plan` | -| `tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py::test_primitive_array_and_fixed_string_raw_addresses_share_one_native_build` | reduced edited semantic `.pyi` entries over existing scalar, vector, matrix, and fixed-string native routines | raw primitive, numeric-array, and fixed-string addresses; checked fixed-string storage; visible scalar-storage extents; rank one/two; default C and explicit Fortran orientation; mutation; integer-only conversion | `wrapper-plan` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_native_order_contracts.py::test_copy_f_preserves_logical_axes_through_binding_owned_temporary` | reduced edited semantic `.pyi` entries over the existing matrix native routine | explicit C-to-Fortran representation copy; native-input and inout calls; projected original identity; binding-owned copyback and cleanup | `wrapper-plan` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_ownership_contracts.py::*` | direct wrapper/build route | semantic .pyi generation/parsing; native handles/descriptors; derived types/object lifetimes; optional/presence/writeback | `wrapper-plan` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_policy_dispatch_contracts.py::test_contradictory_ownership_contract_fails_before_bridge_generation` | non-generating: policy validation before bridge generation | semantic .pyi generation/parsing; native handles/descriptors; derived types/object lifetimes; optional/presence/writeback | `not-applicable` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_policy_dispatch_contracts.py::test_immutable_array_policy_copies_in_and_returns_replacement` | direct wrapper/build route | semantic .pyi generation/parsing; native handles/descriptors; derived types/object lifetimes; optional/presence/writeback | `wrapper-plan` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_policy_dispatch_contracts.py::test_immutable_scalar_string_array_and_derived_policies_return_replacements` | direct wrapper/build route | semantic .pyi generation/parsing; native handles/descriptors; derived types/object lifetimes; optional/presence/writeback | `wrapper-plan` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_surface_edit_contracts.py::*` | direct wrapper/build route | semantic .pyi generation/parsing; classes/methods/properties/overloads; naming/visibility/dispatch | `wrapper-plan` | -| `tests/wrapper/fortran/edit_pyi_contracts/test_visibility_contracts.py::*` | direct wrapper/build route | semantic .pyi generation/parsing; scalar module visibility and namespace projection | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_compact_blas_like_folder_generates_one_standalone_entry_and_preserves_separate_objects` | direct wrapper/build route | external symbols/native linkage; ordinary arrays | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_external_bind_renames_python_export_without_changing_native_call` | direct wrapper/build route | scalar external symbol; explicit bridge interface; renamed export | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_classic_external_bridge_uses_implicit_declaration_and_no_module_use` | direct wrapper/build route | scalar external symbol; implicit external declaration | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_fixed_form_standalone_procedure_runtime_parity[*]` | source/generated-.pyi parity or parametrized route | scalar external symbol; explicit bridge interface | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_free_form_standalone_procedure_runtime_parity[*]` | source/generated-.pyi parity or parametrized route | scalar external symbol; explicit bridge interface | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_generated_standalone_contracts_are_non_empty_root_fragments` | direct wrapper/build route | external symbols/native linkage | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_handwritten_c_order_flat_contract_passes_rank_preserving_bridge_view` | direct wrapper/build route | external symbols/native linkage; ordinary arrays | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_handwritten_fortran_order_flat_contract_flattens_the_final_python_axes` | direct wrapper/build route | external symbols/native linkage; flat arrays; scalar storage | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_standalone_allocatable_argument_accepts_a_caller_created_handle` | direct wrapper/build route | external symbols/native linkage; native allocatable descriptors | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_optional_flat_contracts_preserve_present_and_absent_calls` | direct wrapper/build route | optional/presence; F-order and C-order flat arrays | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_module_procedure_bridge_uses_native_module_scope` | direct wrapper/build route | external symbols/native linkage | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_namespace_imported_module_rejects_standalone_marker_before_codegen` | non-generating: validation/failure-path assertion | external symbols/native linkage | `not-applicable` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_one_source_with_several_standalone_procedures_exports_each_at_root[*]` | source/generated-.pyi parity or parametrized route | scalar external symbols; explicit bridge interfaces | `wrapper-plan` | -| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_package_entry_rejects_non_standalone_root_declaration_before_codegen` | non-generating: validation/failure-path assertion | external symbols/native linkage | `not-applicable` | -| `tests/wrapper/fortran/function_calls/test_function_call_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/wrapper/fortran/function_calls/test_native_call_examples.py::test_native_call_examples_build_from_generated_pyi_and_native_shared_library` | direct wrapper/build route | native-call projections; ordinary arrays; strings; derived types/object lifetimes | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_native_call_examples.py::test_native_call_examples_cover_scalar_array_string_and_object_projection[*]` | source/generated-.pyi parity or parametrized route | native-call projections; ordinary arrays; strings; derived types/object lifetimes | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_fixed_form_optional_arguments_drive_fortran_present_behavior[*]` | source/generated-.pyi parity or parametrized route | optional/presence; scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_fixed_optional_scalar_plan_matches_all_presence_states` | canonical production plan route | optional/presence; scalar inputs/results; build/artifact integration | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_optional_allocatable_scalar_descriptor_distinguishes_omitted_none_and_value` | canonical production plan route | optional/presence; nullable scalar descriptor; build/artifact integration | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_optional_array_descriptors_preserve_presence_and_storage_state` | canonical reduced optional descriptor contract | omitted/`None` absence; present unallocated/unassociated and allocated/associated handle states; kind/dtype validation | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_optional_arguments_drive_fortran_present_behavior[*]` | source/generated-.pyi parity or parametrized route | optional/presence/writeback | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_optional_array_buffers_preserve_omission_and_identity` | reduced semantic `.pyi` entry over the existing optional native unit | omitted/`None`/present ordinary array storage; mutation; projected identity; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py::test_scalar_copy_in_out_returns_replacement` | canonical production plan route | scalar copy-in/native mutation/copy-out/cleanup; build/artifact integration | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py::test_source_generated_scalar_inout_contract_returns_replacement_and_keeps_namespace` | source/generated-.pyi parity | scalar replacement projection; namespace preservation; semantic .pyi generation/parsing | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_output_arguments.py::test_output_arguments_and_multiple_results_follow_python_projection_rules[*]` | source/generated-.pyi parity | mixed-type multiple-result aggregation; ordinary arrays; strings; derived types/object lifetimes; native handles/descriptors | `wrapper-plan` | -| `tests/wrapper/fortran/function_calls/test_output_arguments.py::test_hidden_ordinary_array_output_uses_canonical_plan` | canonical production output-only plan route | fixed/runtime-shape hidden ordinary array output; zero-sized output; allocation/copy failure paths | `wrapper-plan` | -| `tests/wrapper/fortran/layout_rules/test_wrapper_guide_layout.py::*` | non-generating: wrapper docs/test layout | test/docs layout | `not-applicable` | -| `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_allocatable_inout_arrays_mutate_and_return_the_same_handle[*]` | source/generated-.pyi parity route | module variables/state; native handles/descriptors | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_allocatable_replacement_has_no_native_memory_errors[*]` | source/generated-.pyi parity route | module variables/state; native handles/descriptors | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_caller_created_allocatable_crosses_separately_built_extensions` | two independently built semantic-contract extensions | caller-created allocatable descriptor identity; cross-extension validation and mutation | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_projected_allocatable_descriptor_preserves_same_handle_identity` | canonical reduced owned-result plus projected-descriptor contract | direct persistent descriptor mutation; allocation/reallocation/deallocation; same-handle result identity | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles[*]` | source/generated-.pyi parity with one mixed generation unit | derived class/field handles and parent retention remain Phase 8/9 blockers | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_maybe_unallocated_direct_allocatable_results_preserve_unallocated_state` | edited semantic `.pyi` contract over the existing allocatable module unit | `MaybeUnallocated` direct allocatable result annotation preserves the unallocated result state without changing default always-allocated result handling | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_scalar_descriptor_module_variables_return_copied_optional_values[*]` | production plan route in source/generated-.pyi parity modes | rank-zero allocatable/pointer arguments, writeback, results, and copied nullable module values | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_plain_allocatable_module_array_exposes_current_live_view[*]` | production plan route after the Phase 7 contract correction | plain and `Aliased` module handles return a current live view or `None`; explicit `.copy()` is independent and a fresh extraction follows current native state | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_common_blocks.py::*` | source/generated-.pyi parity or parametrized route | scalar calls with internal common-block storage | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_module_state.py::test_aliased_derived_module_object_borrows_native_state[*]` | source/generated-.pyi parity or parametrized route | module variables/state; derived types/object lifetimes | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_module_state.py::test_scalar_module_variables_use_attributes_and_parameters_have_no_native_setter[*]` | source/generated-.pyi parity or parametrized route | module variables/state | `wrapper-plan` | -| `tests/wrapper/fortran/module_state/test_module_state_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/wrapper/fortran/module_state/test_scalar_module_variable_plan.py::test_whole_scalar_module_variable_behavior_uses_canonical_plan` | canonical production plan route | scalar inputs/results; scalar module variables/state; build/artifact integration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_file_modules_build_one_merged_extension` | direct wrapper/build route | scalar multi-source build/link orchestration; module variables/state | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_file_standalone_procedures_build_one_merged_extension` | direct wrapper/build route | scalar multi-source external symbols and link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_generated_child_modules_are_importable_submodules` | direct wrapper/build route | generated child-module imports and namespace preservation | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_source_generated_contract_build_matches_source_runtime_and_link_order` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state; semantic .pyi generation/parsing | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_source_modified_entry_preserves_modules_and_adds_documented_alias` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state; semantic .pyi generation/parsing | `wrapper-plan` | -| `tests/wrapper/fortran/naming/test_defined_operators.py::*` | source/generated-.pyi parity or parametrized route | naming/visibility/dispatch; classes/methods/properties/overloads; operators; generic dispatch | `wrapper-plan` | -| `tests/wrapper/fortran/naming/test_generic_interfaces.py::*` | source/generated-.pyi parity or parametrized route | naming/visibility/dispatch; classes/methods/properties/overloads; generic dispatch | `wrapper-plan` | -| `tests/wrapper/fortran/naming/test_naming_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/wrapper/fortran/naming/test_phase9_class_overloads.py::*` | reduced direct-plan constructor and method overload runtime proof | class-owned exact predicates; constructor ownership; no speculative calls | `wrapper-plan` | -| `tests/wrapper/fortran/naming/test_visibility_naming.py::test_strict_wrapper_names_reject_python_name_fixes` | direct wrapper/build route | naming/visibility/dispatch; classes/methods/properties/overloads | `wrapper-plan` | -| `tests/wrapper/fortran/naming/test_visibility_naming.py::test_visibility_and_default_python_name_fixing_policy[*]` | source/generated-.pyi parity or parametrized route | naming/visibility/dispatch; classes/methods/properties/overloads | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_duplicate_native_definitions_report_linker_error` | direct wrapper/build route | scalar external symbols; linker failure propagation | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_imported_contracts_resolve_from_one_archive_or_shared_library[*]` | source/generated-.pyi parity or parametrized route | external symbols/native linkage; build/compile/link orchestration | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_incompatible_native_artifact_reports_linker_error` | non-generating: validation/failure-path assertion | external symbols/native linkage; build/compile/link orchestration | `not-applicable` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_missing_module_directory_reports_compile_error` | non-generating: validation/failure-path assertion | external symbols/native linkage; build/compile/link orchestration | `not-applicable` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_missing_symbol_reports_native_link_or_loader_error` | non-generating: validation/failure-path assertion | external symbols/native linkage; build/compile/link orchestration | `not-applicable` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_mixed_module_external_bundle_resolves_all_native_input_kinds` | direct wrapper/build route | scalar module/external symbols; ordered native inputs and library directories | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_required_transitive_named_library_resolves_runtime_symbol` | direct wrapper/build route | scalar external symbol; transitive named library | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_static_archive_dependency_order_resolves_transitive_library` | direct wrapper/build route | scalar external symbol; ordered archive linkage | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_static_archive_groups_resolve_cyclic_archive_dependencies` | direct wrapper/build route | scalar external symbol; archive-group linkage | `wrapper-plan` | -| `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_unavailable_dependent_shared_library_reports_loader_error` | non-generating: validation/failure-path assertion | external symbols/native linkage; build/compile/link orchestration | `not-applicable` | -| `tests/wrapper/fortran/runtime_behavior/test_openmp_runtime.py::*` | direct wrapper/build route | runtime policies/errors/GIL; build/compile/link orchestration | `wrapper-plan` | -| `tests/wrapper/fortran/runtime_behavior/test_runtime_behavior_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/fortran/error_handling/end_to_end/test_status_projection.py::*` | edited-.pyi canonical production plan route with focused semantic and lowering evidence | runtime status projection, errors, cleanup, and GIL envelope | `wrapper-plan` | -| `tests/wrapper/fortran/runtime_behavior/test_runtime_recursion.py::*` | source/generated-.pyi parity or parametrized route | runtime policies/errors/GIL; scalar inputs/results | `wrapper-plan` | -| `tests/fortran/enumerations/end_to_end/test_enum_runtime.py::test_fortran_enums_preserve_integer_runtime_surface[*]` | source/generated-.pyi parity or parametrized route | scalar inputs/results; module variables/state | `wrapper-plan` | -| `tests/fortran/enumerations/semantics/test_enum_semantics.py::test_fortran_enums_preserve_values_in_generated_pyi_contract` | non-generating semantic and contract-emission evidence | scalar inputs/results; module constants/state | `not-applicable` | -| `tests/wrapper/fortran/scalars/test_scalar_boundary_plan.py::*` | scalar-only copied native routines with deliberate legacy/direct-plan parity | primitive scalar kinds; value and `Addr(Arg(i))` inputs; hidden output; copy-in/copy-out; rank-zero storage; raw `Addr(T)`; native slot reordering; direct-plus-hidden result tuple assembly | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_scalar_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | -| `tests/wrapper/fortran/scalars/test_scalar_kinds.py::*` | source/generated-.pyi parity or parametrized route | scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_value_and_bind_c.py::*` | source/generated-.pyi parity or parametrized route | scalar inputs/results; native-call projections | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_verified_baseline.py::test_fmath_scalar_sources_use_canonical_wrapper_plan[*]` | canonical production plan route using the existing fixed- and free-form generation units | scalar inputs/results; native-call projections; Python namespaces; build/artifact integration | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_verified_baseline.py::test_f90_array_wrapper_distinguishes_contiguous_and_strided_contracts[*]` | source/generated-.pyi parity or parametrized route | scalar inputs/results; ordinary arrays | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_verified_baseline.py::test_f90_wrapper_pipeline_builds_importable_extension[*]` | source/generated-.pyi parity or parametrized route | scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_verified_baseline.py::test_fortran_array_wrapper_pipeline_matches_fmath_results_with_contiguous_arrays[*]` | source/generated-.pyi parity or parametrized route | scalar inputs/results; ordinary arrays | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_verified_baseline.py::test_fortran_wrapper_pipeline_builds_importable_extension[*]` | source/generated-.pyi parity or parametrized route | scalar inputs/results | `wrapper-plan` | -| `tests/wrapper/fortran/scalars/test_verified_baseline.py::test_required_array_buffers_use_canonical_wrapper_plan` | reduced semantic `.pyi` entry over the existing `fmath_arrays_f90` native unit | required rank-one dense buffers; exact dtype/rank/order/alignment/writeability; zero length; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_edited_modern_string_contract_wraps_full_axis_spelling_set` | edited semantic `.pyi` contract | strings; fixed/assumed inputs; arrays; mutable string storage | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_legacy_fortran_character_arguments_and_results[*]` | production plan route from source/generated-.pyi parity | fixed-form strings; fixed/assumed inputs; fixed results | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_modern_fortran_character_arguments_and_results[*]` | source/generated-.pyi parity | strings; fixed/assumed inputs; fixed/deferred results; arrays; writeback | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_deferred_allocatable_string_results_use_canonical_plan` | canonical reduced scalar descriptor result contract | runtime length; nullable copy-out; UTF-8 data; allocation failure | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_deferred_character_array_handles_use_canonical_plan` | canonical reduced descriptor-result and projected-descriptor contract | hidden/direct owned deferred-character arrays; runtime `S3`/`S4`/`S5` width; projected identity; nullable rank-zero result | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_fixed_width_character_arrays_use_canonical_plan` | reduced semantic `.pyi` entry over the existing `fstrings_f90` native unit | fixed-width `NPY_STRING` array itemsize; rank/dtype/zero-size validation; native-handle actuals deferred to Phase 7 | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_raw_fixed_width_character_arrays_use_canonical_plan` | reduced edited semantic `.pyi` entry over the existing `fstrings_f90` native unit | raw fixed-width character array address; literal shape; element length; integer-only conversion | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_required_scalar_string_inputs_use_canonical_plan` | reduced semantic `.pyi` entry over the existing `fstrings_f90` native unit | required fixed/assumed scalar string inputs; default/kind-1/`c_char`; UTF-8 length and NUL validation; scalar results | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_arguments.py::test_fixed_string_results_use_canonical_plan` | reduced semantic `.pyi` entry over the existing `fstrings_f90` native unit | direct fixed string results; trailing blanks; default/`c_char`; allocation failure | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_edge_cases.py::test_fortran_character_edge_cases_follow_copy_in_copy_out_policy[*]` | production plan route from source/generated-.pyi parity | strings; fixed/assumed input/output; optional presence; Unicode/NUL handling | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_edge_cases.py::test_fixed_hidden_string_output_uses_canonical_plan` | reduced semantic `.pyi` entry over the existing `fcharacter_edges_f90` native unit | fixed hidden string output; trailing blanks; allocation failure | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_edge_cases.py::test_fixed_string_replacement_and_identity_use_canonical_plan` | reduced edited semantic `.pyi` entry over the existing `fcharacter_edges_f90` native unit | fixed immutable replacement and discarded identity; exact length; trailing blanks; allocation failure | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_character_edge_cases.py::test_assumed_and_optional_string_replacements_use_canonical_plan` | reduced semantic `.pyi` entry over the existing `fcharacter_edges_f90` native unit | assumed-length and optional immutable replacement; empty/omitted/`None`/concrete states; NUL rejection; concrete-only allocation failure | `wrapper-plan` | -| `tests/wrapper/fortran/strings/test_string_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | - -## Incremental Protocol - -For each lane: - -1. Select and run an existing passing `tests/wrapper` generation unit through - the legacy route. Retain and inspect its complete generated artifact set. -2. Trace the current lowering, binding, bridge, node/API-model, printer, - runtime-helper, and build paths that produced those artifacts, and record the - observed behavior and existing tests that make them the migration baseline. -3. Expand this checklist with the lane's exact scope, exclusions, source-path - baseline, required backend behavior, plan fields, and validation invariants. -4. Complete every policy field required by the lane in post-IR policy - completion; do not start its planner while semantic decisions remain - scattered or implicit. -5. Implement the lane's hierarchical plan records, planner visitors, - ABI/handoff specs, generator-owned structural checks, directly named backend - lowering methods, source-printer support, and support-report coverage. -6. Implement the minimum dependency-closed backend slice in - `prik.codegen`: copy small suitable pieces, rewrite oversized legacy - classes as minimal equivalents, and add only the intermediate tests required - by the contract above. -7. Generate the plan from policy-completed semantic IR, invoke the directly - named binding and bridge lowering methods, assemble complete backend modules, - and print complete internal artifacts. -8. Compare the new generated artifacts with the retained legacy artifacts and - explain every material difference before compilation. -9. Compile the internal artifacts before changing production route selection. -10. Run the same eligible existing fixtures and assertions through both routes - and compare compiled runtime behavior, failure paths, native-call mapping, - and artifact requirements. -11. Extend the whole-module support predicate so a generation unit uses the - wrapper-plan route only when all its elements belong to completed lanes. - Keep the old route for generation units containing unsupported lanes. -12. Update every affected `tests/wrapper` migration-matrix row and mark the lane - complete only when the reused parity tests pass and every intentional - difference from the baseline is separately documented. - -Do not start a later lane by guessing. Each lane must define the handoff specs -and consistency checks it needs. - -## Mandatory Expansion Gate For Broad Phases - -Phases 5 through 10 are roadmap envelopes, not complete implementation -checklists. Before implementation starts on one of them, update this file and -split that phase into dependency-ordered sub-lanes. The expansion must be based -on an audit of the live semantic models, completed policies, existing -bridge/binding behavior, decorators, and focused wrapper tests. - -Each expanded sub-lane must state: - -- the exact included and excluded semantic cases; -- the completed-policy fields it consumes and any decisions that still need to - move into post-IR policy completion; -- plan records, action keys, handoff specs, native-call slots, lifecycle phases, - and required generated artifacts; -- binding and bridge handler names and which backend-local helper values they - may create; -- validation invariants across Python input/result, binding handoff, bridge - handoff, native call, writeback, cleanup, ownership, and release; -- the whole-module support-predicate change that makes the sub-lane eligible; -- existing `tests/wrapper` nodes that cover the sub-lane, their migration-matrix - status changes, and dual-route parity evidence against the legacy route; -- the exact legacy source path and consumed behavior for every isolated - primitive, whether it is copied or rewritten, its minimal dependency closure, - baseline evidence, and a reason for every behavior with no legacy source; -- dependencies on earlier lanes and the legacy behavior that can be removed - when the sub-lane is complete. - -Do not mark a broad phase complete from its current envelope items. Mark its -expanded sub-lanes individually, then close the phase only after all live cases -in its audited support matrix are either migrated or explicitly removed from -the product contract. - -## Dependency-Ordered Checklist - -### Foundation and semantic authority - -- [x] Establish the isolated `prik.codegen` package boundary and - visitor infrastructure. -- [x] Complete the first primitive lane in general wrapper policy before - planning, including native-call order, result projection, ownership, and - lifecycle facts. -- [x] Build editable `ModulePlan`, `FunctionPlan`, transfer, result, ABI, - native-slot, and lifecycle records from completed wrapper policy. -- [x] Refactor each cross-boundary owner into explicit binding and bridge child - plans, including module/function/result/lifecycle scope as well as arguments. -- [x] Remove plan-owned method names and handler registries; add typed - datatype-family facts required by direct lowering. -- [x] Keep structural plan validation private to `WrapperGenerator`, with - no planner-time validation or standalone validator class, and verify every - listed invariant after direct plan edits. - -### Direct generator boundary - -- [x] Change `WrapperGenerator.generate` to consume only `ModulePlan`, - freeze it, validate it, validate lowering support, recursively generate - backend nodes, print them, and return artifacts directly. -- [x] Implement recursive `CBindingGenerator` synthesis of complete C modules, - headers, and functions from plan nodes. -- [x] Implement recursive `FortranBridgeGenerator` synthesis of complete - Fortran modules and functions from plan nodes. -- [x] Replace plan-selected method names with directly named backend lowering - methods selected by the visible `_lower_{subject}_{action.value}` rule. -- [x] Ensure backend node printers, artifact construction, and build - consumption retain their distinct freezing boundaries. - -### Scalar parity and route evidence - -- [x] Replay the existing scalar source and semantic-`.pyi` baseline through - the direct generator and compare generated artifacts and runtime behavior. -- [x] Update the staged walkthrough to use only plan editing and the public - generator boundary. -- [x] Retire superseded internal representations, their package exports, - orchestration, validation, documentation, and focused tests. -- [x] Run focused wrapper-codegen and pipeline tests; the walkthrough for both - supported entry choices where practical; `tests/wrapper` excluding LAPACK; - documentation checks; `git diff --check`; the required static-analysis suite; - and `tools/check_codegen_complexity.py`. - -## Phase 3 — Scalar Inout, Optional, And Descriptor-Like Scalars - -Scope: scalar copy-in/copy-out, optional arguments, present-but-null descriptor -values, and scalar allocatable/pointer descriptor boundaries. - -Phase 3 legacy replay audit: - -- `foptional_fixed.f` uses one nullable value pointer at the Bind-C ABI. - Omission and explicit `None` both pass a null pointer because both mean that - the ordinary optional dummy is absent; a concrete scalar uses call-local - storage, and the bridge branches on `c_associated(...)` before calling the - native function with or without the optional keyword. -- The existing optional allocatable-scalar contract uses two independent ABI - pointers. The value pointer is null for explicit `None`, while the presence - pointer is non-null for both `None` and a concrete value. Omission leaves both - null. The bridge therefore distinguishes absent, present-unallocated, and - present-with-value states without inferring presence from the value pointer. -- Immutable scalar replacement uses copy-in storage, native mutation of that - storage, copy-out to a new Python scalar, and scope-owned stack cleanup. The - caller's original NumPy scalar remains unchanged. The audit found no existing - runtime wrapper test for this primitive-scalar `Returns["argument", T]` - contract, so `test_scalar_writeback_plan.py` is the recorded coverage-gap - fixture for this lane. -- The first legacy replay of that coverage gap exposed a duplicate declaration - of the mutable scalar result. The legacy bridge now promotes the copy-in - temporary to the Bind-C function result and removes it from the ordinary - local-declaration set. Both routes compile, and incompatible Python values - fail with `TypeError` before the native call. Stack temporaries and local - allocatable descriptors require no explicit release action; their procedure - scope owns cleanup on normal return. - -The Phase 3 plan records optional mode, nullable value and presence handoffs, -and four ordered scalar replacement phases: `copy_in`, `native_mutation`, -`copy_out`, and `cleanup`. Generator preflight requires the complete phase set, -an existing source handoff, the correct binding/bridge owner for each phase, -and a Python result target for copy-out. Forced whole-module route selection -accepts these completed lanes after the dual-route evidence below. Automatic -production selection still remains on the legacy route under the independent -GIL parity deferral recorded for Phase 2D. - -- [x] Audit and record the legacy copy-in/out, optional presence, nullable - scalar descriptor, cleanup, and failure-path behavior for this lane. -- [x] Add or rewrite only the additional optional/descriptor nodes, API - primitives, local-state helpers, and printer cases required by this lane, - with baseline tests. -- [x] Represent copy-in, native mutation, copy-out, and cleanup as explicit - writeback phases. -- [x] Preserve the three-state optional rule: omitted argument, explicit `None`, - and present concrete value are distinct when the native ABI needs them. -- [x] Represent scalar descriptor presence tokens and nullable value handoffs in - the plan. -- [x] Validate that a writeback consumes an existing binding/bridge handoff and - writes to a Python-visible target or result slot. -- [x] Emit and print complete inout/optional/descriptor-capable modules - internally, then compile and compare both routes for all three presence - states, mutation, writeback, cleanup, ABI, and failures. -- [x] Widen whole-module route eligibility to this lane only after parity, and - complete it before moving arrays or handles to the plan path. - -## Phase 4 — Scalar Module Variables - -Scope: scalar module variables. Derived-type fields remain in Phases 8 and 9 -because their wrapper instance, owner, and property lifecycle must already be -represented before field access can use the plan route. - -Phase 4 legacy replay audit: - -- `fmodule_vars_f90.f90` establishes the ordinary scalar state contract. Its - legacy bridge emits value-returning getters and value-argument setters; - binding accessors run with the GIL held, aliases route to the same native - storage, deletion fails, and contract initializers call the native setter at - import. A `parameter` is instead copied into the Python module dictionary, so - rebinding it is local to that module object and never mutates native storage. - This whole source remains legacy because it also owns `rgb_color` and derived - module objects from later phases. -- The scalar subset of `fscalar_descriptors_f90` establishes nullable - allocatable and pointer reads. The legacy bridge returns null for absent - storage or allocates and copies one detached scalar; the binding converts the - copy, frees it, and rejects descriptor replacement. Its whole source cannot - migrate in Phase 4 because it also contains nullable snapshot-result forms - from a later lane. Allocation failure is deliberately injected with - `PRIK_WRAPPER_FAIL_ALLOC` and preserves the legacy null/`None` surface. -- No existing generation unit contained only the already completed scalar - function lanes plus every Phase 4 getter, setter, constant, descriptor, - initialization, reload, and failure behavior. The bounded - `test_scalar_module_variable_plan.py` whole-module fixture records that - coverage gap; it contains no strings, arrays, classes, or later-phase owner. - -The plan keeps only completed typed facts: Python names, getter and setter -actions, initializer or constant value, datatype family, native name/module, -native assignment, descriptor kind, and handoff roles. Both backends invoke -directly named lowering methods with matching subject/action suffixes wherever -their behavior is shared; datatype and descriptor facts stay method inputs. -The generator validates the complete frozen plan before either backend emits -anything, including binding/bridge getter agreement and the rule that a Python -write-through setter must have a compatible bridge setter role. Forced -whole-module selection now accepts `scalar-module-variables`; automatic -production selection remains independently deferred by the Phase 2D GIL gate. - -- [x] Audit and record the legacy scalar module-variable getter, setter, - rejected replacement, module initialization, and attribute-routing behavior. -- [x] Add or rewrite only the additional module/type nodes, getter/setter API - primitives, initialization nodes, and printer cases required by this lane, - with baseline tests. -- [x] Represent getter behavior, setter exposure, native setter assignment, and - rejected replacement behavior in module-variable plans. -- [x] Add binding actions for Python attribute get/set around scalar values. -- [x] Add bridge actions for scalar module-variable read/write. -- [x] Validate getter/setter pair consistency: a Python setter cannot exist - without a compatible bridge setter handoff. -- [x] Keep ordinary Python module-name rebinding semantics separate from native - module-variable storage. -- [x] Emit and print complete module-variable-capable modules internally, then - compile and compare both routes for get/set behavior, rejection paths, - initialization, cleanup, ABI, and generated artifacts. -- [x] Widen whole-module route eligibility to scalar module variables only after - that parity evidence passes. - -### Phase 3/4 whole-unit namespace correction - -The post-Phase 4 review found that scalar lowering itself matched the legacy -route, but the parity helper unwrapped a sole native child module before making -assertions. That hid a public-surface difference: the legacy route retained -Fortran modules as Python child namespaces while the plan route flattened their -members at the extension root. The old route-selection validation also accepted -colliding procedures from separate native modules and allowed the failure to -reach the Fortran compiler. - -This correction is part of the completed scalar foundation rather than a new -datatype lane: - -- [x] Add a concise `NamespacePlan` beneath `ModulePlan`; place functions and - variables in namespace nodes instead of flattening them into the module. -- [x] Complete Python export paths in post-IR export policy, including - namespace-local keyword and collision fixes, then mechanically group plan - owners by those paths without reconstructing namespace policy in either - backend. -- [x] Generate root, child, and nested Python modules while keeping native - module imports and generated bridge symbols unambiguous. -- [x] Support ordinary scalar subroutines with no projected result through the - existing native call plus Python `None` result path. -- [x] Reject duplicate Python exports and generated symbols before either - backend emits source. -- [x] Use one visible lowering naming rule in both backends: - `_lower_argument_`, `_lower_result_`, - `_lower_writeback_`, `_lower_module_getter_`, - and `_lower_module_setter_`. Do not store method-name strings - in the plan or hide these selections in backend dictionaries. -- [x] Remove scalar prefixes from general wrapper concepts, including the - function, argument, result, native-slot, lifecycle, node, and printer policy - surfaces. Retain scalar naming only for permanently scalar-specific ABI type - facts and actions. -- [x] Update the staged walkthrough to print the namespace tree, typed actions, - and the directly corresponding binding and bridge method names. -- [x] Compile both routes from the existing complete - `contract_mixed_module_external.f90`, `contract_import_graph.f90`, - `contract_multi_module.f90`, `contract_standalone_only.f90`, and - `contract_same_name.f90` fixtures; compare the real extension root and child - namespaces without `_sole_native_module` normalization. - -## Phase 2D — Native Call Runtime Envelope - -This is the next dependency-closed migration lane. Complete it before Phase 5 -so the already proven scalar generation units can move from temporary -`dual-route` evidence to production `wrapper-plan` routing instead of adding -more datatype lanes behind the same runtime gate. - -Scope: the binding-owned runtime envelope around an otherwise completed native -call. This phase includes default GIL release, explicit `@hold_gil`, and native -status/message projection through `@raises(...)`. Status projection is included -because the existing `fruntime_policy_f90` generation unit tests it together -with both GIL modes and whole-generation-unit routing cannot split that module. - -Excluded from this phase: - -- strings, arrays, descriptors, derived types, and callbacks, which remain in - their datatype or callback phases; -- callback re-entry and callback exception/abort behavior, which remain in - Phase 10; -- OpenMP array execution and Makefile-specific behavior, which remain blocked - by the array and cross-cutting build lanes; -- general Python exception translation that is not selected by a completed - native status policy. - -The final runtime oracle is -`tests/fortran/error_handling/end_to_end/test_status_projection.py`, -backed by focused semantic and lowering evidence in the same feature. It proves -that successful status returns produce the declared Python result, failing -status returns raise the selected exception with the native message, cleanup -completes on repeated failures, and emitted C places -`Py_BEGIN_ALLOW_THREADS` / `Py_END_ALLOW_THREADS` only around eligible native -calls. `test_recursive_native_runtime_calls` is the scalar regression unit to -run after the call envelope works. Do not use the OpenMP or callback fixtures -as the first parity unit. - -The plan and generators must follow these boundaries: - -- Post-IR policy completion owns `hold_gil` and the complete native status - error decision, including status source, message source, success value, and - Python exception kind. The planner only projects those completed facts. -- Keep the runtime facts concise and function-owned. Extend the existing - binding-facing function plan rather than adding a second function plan or a - backend dispatcher table. The bridge continues to lower native call slots - and result storage mechanically; it does not decide GIL or Python exception - policy. -- Argument parsing and conversion, Python result construction, status/message - conversion, exception creation, writeback, and Python-owned cleanup always - run with the GIL held. For the default policy, release the GIL immediately - before the bridge call and reacquire it immediately after that call. For - `hold_gil=True`, emit no release region. -- Perform status evaluation and raise the selected Python exception only after - the GIL has been reacquired. Validate before emission that every status and - message projection names an existing native result slot with a compatible - completed handoff. -- Use directly named lowering methods that follow the existing visible naming - rule. Do not infer runtime policy from result types, function names, emitted - locals, or the presence of status-like native arguments. - -The completed legacy audit found one binding-owned envelope in both oracle -builds. The legacy binding parsed and converted Python inputs with the GIL -held, emitted `Py_BEGIN_ALLOW_THREADS` immediately before the bridge call and -`Py_END_ALLOW_THREADS` immediately after it by default, and omitted both -macros for `@hold_gil`. Only after reacquiring the GIL did it convert hidden -status/message outputs, compare status with `success`, construct -`RuntimeError`, suppress those policy outputs from the declared Python result, -and decref converted result objects on both the failure and success paths. A -missing or incompatible status/message name was previously rediscovered from -raw decorator dictionaries and result datatypes in `ir2ast` and the legacy C -binding; Phase 2D moved that decision to typed post-IR completion and left the -legacy route as a dispatch consumer for rollback parity. - -The direct plan route now preserves that ordering with explicit released-call -and held-call lowering methods. Its fixed native message handoff is -bridge-owned null-terminated storage that the binding converts and frees after -the GIL is reacquired. Generated symbol spelling differs from the legacy -artifacts, but the same source and edited-`.pyi` concurrency, exception, -cleanup, and runtime assertions pass. Production cutover also reused the -existing generated-wrapper build path for inferred native module include -directories, native library directories, `.pyi` manifests, verbose timing, -and scalar external explicit interfaces; no legacy retry was added. - -- [x] Audit and record the exact legacy GIL release/hold region, status/message - projection, exception construction, result suppression, cleanup, and failure - behavior from both existing runtime-policy tests. -- [x] Complete the native status error decision in post-IR policy before - planning; retain the already completed `hold_gil` fact as its single source - of truth. -- [x] Extend the concise function plan with only the binding-facing runtime - facts needed for GIL and status-error lowering, and validate all referenced - native result slots before either backend emits source. -- [x] Add direct binding lowering for the released-call and held-call envelopes - plus post-call status projection. Keep the bridge call and result-slot - lowering on their existing paths. -- [x] Replay the semantic-`.pyi` runtime policy through the production wrapper - plan, with focused concurrency, exception, artifact, cleanup, and generated-C - assertions under `tests/fortran/error_handling/`. -- [x] Run `test_recursive_native_runtime_calls` through the wrapper-plan route - as the scalar recursion regression; leave OpenMP and callbacks in their - later lanes. -- [x] After dual-route parity passes, remove the blanket Phase 2D production - deferral. Send each whole generation unit through `wrapper-plan` only after - its feature lanes are complete; do not add fallback or per-function mixed - routing. -- [x] Move the eligible scalar matrix rows from `dual-route` or `legacy` to - `wrapper-plan`, update the live route counts, and prove their default builds - no longer invoke `semantic_ir_to_codegen_ast()`. -- [x] Finish this phase only when the production `wrapper-plan` count is - nonzero and the already completed scalar baseline no longer depends on the - legacy route outside deliberate rollback diagnostics. - -## Phase 2E — Scalar Boundary Completion and Test Isolation — Complete - -Complete the scalar public boundary before stopping this migration lane. This -phase does not begin strings or arrays. It separates scalar evidence from -mixed generation units so whole-unit routing cannot hide whether one scalar -policy is implemented. - -Scope: every supported primitive scalar kind; ordinary Python scalar values; -`Addr(Arg(i))` call-local address projection; projected scalar copy-in/copy-out; -caller-owned rank-zero NumPy storage spelled `T[()]`; caller-supplied integer -raw addresses spelled `Addr(T)`; and visible or hidden scalar `in`, `out`, and -`inout` behavior. For a mixed native fixture whose declarations cannot be -safely sliced, add a small distinctly named scalar-only native test routine -that preserves the policy decision under test. - -The boundary contract remains: - -- `T` accepts a Python/NumPy scalar value. When native code only reads it, the - wrapper converts into call-local storage. When native code writes through an - address projection and the contract projects `Returns["name", T]`, the - wrapper performs copy-in, native mutation, and copy-out to a replacement - Python scalar; the caller's immutable scalar object is not mutated. -- `T[()]` accepts a rank-zero NumPy array with exactly the declared dtype. The - wrapper validates caller storage and passes its data address; native `out` or - `inout` mutation remains visible in that same array and the Python call - returns `None` unless the contract declares another result. -- `Addr(T)` accepts an integer address such as `array.ctypes.data`. The wrapper - converts it to a raw pointer and forwards that same address without copying - or owning the pointee. Mutation is therefore observed through caller-owned - storage. -- `@native_call(...)` controls only native slot order and value/address/result - projection. It does not change which Python representation (`T`, `T[()]`, or - `Addr(T)`) the declared argument accepts. -- Use one necessary-copy rule. For interoperable scalar replacement, the - binding's converted C scalar is the copy-in storage and the bridge passes - that same storage directly to the native routine; after mutation the binding - converts it once to the Python replacement. `c_f_pointer` association for - `T[()]` or `Addr(T)` is not a data copy. A bridge-local data copy is allowed - only when the native representation actually changes, such as descriptor, - string-buffer, or ownership-snapshot construction. -- Enforce that rule with a completed `BridgeDataAction` on every argument, - result, and native-call output slot. `DIRECT_TRANSFER` reuses boundary - storage, `ASSOCIATE_VIEW` may create only a non-owning native view, - `COPY_REPRESENTATION` is the sole bridge data-copy permission and requires a - non-empty policy reason, and `BLOCKED` keeps the whole generation unit off - the plan route. A non-copying action carrying a copy reason is also invalid. - New array, string, or object support must complete this fact before route - eligibility is widened. - -Excluded: simultaneous multiple-result tuple assembly; rank-positive arrays; -strings including fixed status buffers except for already completed Phase 2D -status projection; derived types; callbacks; and any compatibility fallback to -the legacy generator. - -- [x] Record scalar-only tests separately from mixed array/string/derived - generation units in the route ledger; use copied minimal native routines - when fixture declarations are coupled. -- [x] Cover every primitive scalar kind exercised by the scalar runtime suite - through the direct registry and both binding/bridge generators. -- [x] Add direct named binding and bridge lowering for rank-zero numeric/logical - storage using the completed `SCALAR_STORAGE` and `PASS_STORAGE_ADDRESS` - decisions; validate dtype, rank zero, and writability before the native call. -- [x] Add direct named binding and bridge lowering for primitive raw addresses - using the completed `RAW_ADDRESS` and `PASS_RAW_ADDRESS` decisions; accept an - integer address and forward it without copy or ownership inference. -- [x] Prove isolated scalar input, hidden output, copy-in/copy-out `inout`, - caller-storage `out`/`inout`, and raw-address `out`/`inout` behavior through - compiled legacy/direct-plan parity where applicable. -- [x] Prove scalar copy-in/copy-out reuses one binding local and does not add a - redundant bridge-local value copy. -- [x] Prove plan validation rejects an unexplained bridge copy, a copy reason - on a non-copying path, and any still-blocked bridge data action. -- [x] Prove isolated `@native_call` argument mapping, including `Addr(Arg(i))` - and hidden `Return(...)` slots, without arrays determining route selection. -- [x] Move only proven scalar-only nodes to `wrapper-plan`, update collected - route counts, and leave the original mixed integration nodes on their real - datatype blockers. - -## Phase 2F — Multiple Scalar Result Assembly — Complete - -This is result aggregation, not another scalar boundary representation. The -first isolated oracle is the `with_scalar` policy from -`test_output_arguments.py`: one direct primitive scalar function return plus -one hidden primitive scalar output, assembled into a Python tuple in declared -result order. Keep it separate from arrays, strings, derived types, and native -handles before widening the plan route. - -For source-derived contracts, an ordinary non-descriptor `intent(out)` scalar -hidden by Python result projection still selects `PASS_CALL_LOCAL_ADDRESS` -even when no edited `.pyi` `Addr(...)` spelling exists. The hidden-result -projection is itself the completed semantic fact that requires writable -call-local native storage; the binding and bridge must not rediscover that ABI -rule. Rank-zero allocatable/pointer descriptor outputs retain the distinct -Phase 7H descriptor transport and are not rewritten as ordinary addresses. - -The completed representation is an ordered `FunctionWrapperPolicy.results` -tuple and an ordered `FunctionPlan.results` tuple. Each Python-visible result -has its own `ResultPolicy` and `ResultPlan`, including its binding consumer and -`result_position`. A direct native function return has -`source_kind="direct_return"` and no native-call slot. A hidden output has -`source_kind="hidden_output"` and references the exact same mutable -`NativeCallSlotPlan` stored in `FunctionPlan.native_call_slots`. The bridge -uses the sole direct result, when present, to select its function result and -passes every hidden result through its completed output-address slot. It does -not assemble Python results. - -After the native call, the binding converts each result from its completed -source role exactly once. One result is returned directly; two or more are -assembled into a Python tuple in ascending `result_position`. Tuple allocation, -reference transfer, and failure cleanup are binding-local emission details, -not semantic policy. Before either backend emits source, validation requires -result positions to cover `0..N-1` exactly once, at most one direct result, -every hidden result to share its function native-call slot, and every -non-status native output slot to have exactly one binding result consumer. -Phase 2F does not combine these consumers with projected argument writeback; -that broader aggregation remains blocked until it receives its own completed -policy. - -- [x] Add a scalar-only copied native routine and contract for a direct return - plus hidden scalar `Return(...)` slot. -- [x] Represent every Python result as an explicit binding consumer while - preserving the bridge's direct-return and output-address ABI roles. -- [x] Validate contiguous result positions and reject unclaimed outputs before - either backend emits source. -- [x] Prove compiled legacy/direct-plan parity, then update the route counts. - -## Phase 5 — Strings - -Scope: non-descriptor scalar character values, fixed-length strings, assumed- -length call inputs, immutable replacement, mutable rank-zero byte storage, and -raw fixed-length character addresses. Character arrays remain in Phases 6 and -7; allocatable or pointer scalar character values remain in Phase 7; character -fields remain in Phases 8 and 9; character callbacks remain in Phase 10. - -The legacy wrapper is the behavioral oracle for this phase. In particular, -`CPythonBindingGenerator._convert_python_string_value_argument()` and -`_convert_python_string_storage_argument()` define Python conversion, -validation, allocation, and writeback behavior, while -`FortranToCBridgeGenerator._build_string_argument()`, -`_build_string_storage_argument()`, `_convert_raw_string_argument()`, and -`_convert_string_result()` define the bridge representation. The public -contract and observable oracle are -`docs/user/reference/fortran-wrapper.md`, `docs/user/guide/data-types.md`, -`docs/user/reference/semantic-pyi-format.md`, -`tests/wrapper/fortran/strings/test_character_arguments.py`, and -`tests/wrapper/fortran/strings/test_character_edge_cases.py`. Direct-plan -lowering may use different temporary names or an equivalent internal C ABI, -but it must preserve the legacy Python behavior, native argument order, -character payload, length, ownership, cleanup, and result projection. - -Strings use the same completed-policy and planning pipeline as the other -rank-zero scalar families: - -```text -ArgumentPolicy -> ArgumentTransferPlan -> NativeCallSlotPlan -ResultPolicy -> ResultPlan -LifecyclePolicy -> LifecycleActionPlan -``` - -Do not add a parallel string plan hierarchy or plan-owned handler names. -Numeric and logical primitive families share registry-backed lowering because -their generated structure is the same. `DatatypeFamily.STRING` dispatches to -its own directly named binding and bridge lowering methods because character -conversion and ABI structure differ. The existing `STRING_VALUE`, -`STRING_STORAGE`, `PASS_CALL_LOCAL_ADDRESS`, `PASS_STORAGE_ADDRESS`, -`PASS_RAW_ADDRESS`, and generic codegen/lifecycle actions remain authoritative; -add a new typed action only if those completed actions cannot identify a real -semantic choice. - -Every string argument plan records the completed fixed positive character -length or the absence of a fixed length. The binding-to-bridge handoff records -both the payload address and encoded payload length when the bridge needs both; -this is an ABI fact in the existing argument transfer, not a new planning -stage. A fixed `String[n]` Python value must encode to exactly `n` bytes. A -plain `String` input carries its runtime UTF-8 byte length. Embedded NUL is -rejected before the native call. The bridge may copy bytes into Fortran -character storage only when `BridgeDataAction.COPY_REPRESENTATION` and its -non-empty policy reason were completed before planning. - -The phase is split into the following dependency-ordered sub-lanes. - -### Phase 5A — Required Read-Only String Values - -Included: required rank-zero `String[n]` and `String` Python `str` inputs; -default character, kind `1`, and `c_char`; fixed-length exact encoded-byte -validation; assumed-length runtime payload size; embedded-NUL rejection; and -primitive scalar or void results already supported by earlier phases. - -Excluded: writable inputs, projected replacement, optional strings, string -results, mutable `String[n][()]` storage, raw `Addr(String[n])`, arrays, -allocatable/deferred results, fields, and callbacks. - -Completed policy must provide `ObjectKind.STRING`, -`PythonBarrierAction.STRING_VALUE`, -`NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS`, -`CodegenAction.CALL_LOCAL_INPUT`, `StorageMode.STACK`, required presence, -`BridgeDataAction.COPY_REPRESENTATION`, and the reason that C UTF-8 bytes are -materialized as Fortran character storage. Planning projects those facts into -the ordinary argument/native-slot records. The C binding method -`_lower_argument_required_string_value()` validates `str`, extracts UTF-8 plus -byte length, rejects embedded NUL, and enforces a fixed length when present. -The Fortran bridge method `_lower_argument_required_string_value()` receives -the payload address and length, associates a byte view, copies it into one -backend-local character temporary, and passes that temporary in the completed -native-call position. - -Validation requires a string-value Python action, call-local-address native -action, character-buffer handoff, one matching payload-length role, required -presence, no projected result, and a justified representation copy. Whole-unit -eligibility widens only for generation units containing this lane plus already -completed scalar/result/runtime lanes. Replay uses the existing -`fstrings_f90` native object and contract package with a reduced entry that -exports only existing read-only scalar string procedures; both routes run the -same fixed/assumed-length, kind, NumPy-string-scalar, wrong-length, and embedded -NUL assertions. The mixed original string nodes remain `legacy` because their -units also contain string results, writable strings, arrays, and allocatables. - -- [x] Complete Phase 5A policy, ordinary plan projection, validation, named C - and Fortran lowering, reduced-entry dual-route runtime parity, support - predicate, and migration-ledger evidence. - -### Phase 5B — Fixed-Length String Results And Hidden Outputs - -Included: direct fixed-length scalar character results and fixed-length hidden -`intent(out)` results, including trailing blanks. The binding receives a -NUL-terminated C-owned copy, converts the full payload to a Python-owned -`str`, and releases the temporary exactly once. The bridge allocates and fills -that copy only through completed `COPY_REPRESENTATION` policy. Deferred-length -and nullable allocatable or pointer results remain in Phase 7 because their -runtime length and allocation state are descriptor lifecycle facts, not -scalar-string conversion facts. - -Both forms reuse the ordinary ordered `ResultPolicy -> ResultPlan` path and -record the fixed positive `character_length` on the result. A direct native -function result has `source_kind="direct_return"`, -`CodegenAction.COPY_OUT`, no native-call slot, and a bridge function result of -`type(c_ptr)`. The bridge first receives the native value in backend-local -`character(kind=c_char, len=n)` storage, then allocates `n + 1` bytes through -the existing `prik_malloc` interface, copies all `n` characters, appends -`c_null_char`, and returns the pointer. A hidden output has -`source_kind="hidden_output"`, `CodegenAction.COPY_OUT`, -`NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS`, and references the exact same -fixed-length `NativeCallSlotPlan` used by the function. Its existing output -slot receives native character storage, then performs the same justified -allocation and copy after the native call. - -In both cases, binding lowering checks for a null allocation, converts the -NUL-terminated UTF-8 payload with the same observable behavior as the legacy -`Py_BuildValue("s", ...)` path, frees the C allocation exactly once even when -Python conversion fails, and returns the Python-owned `str`. Phase 5B supports -exactly one Python-visible string result per function; mixed or multiple -string result aggregation remains blocked until cleanup of every unconverted -native allocation is explicitly planned. A function that combines a public -fixed string result with native status-error projection is blocked for the -same reason: the status failure path must not bypass the string allocation's -planned release. - -Validation requires a fixed positive length, `ObjectKind.STRING`, Python-owned -copy-return ownership, no Python barrier action, the source-appropriate -codegen/native action, `BridgeDataAction.COPY_REPRESENTATION` with the standard -fixed-string copy reason, and matching result/native-slot lengths for hidden -outputs. Direct results must not carry a native-call slot; hidden results must -share their function slot by identity. The C and Fortran backends dispatch -`DatatypeFamily.STRING` to `_lower_result_fixed_string()` methods instead of -the primitive scalar registry. - -Replay direct results from the existing `fstrings_f90` native object with a -reduced contract entry exporting `char_result_default`, -`char_result_c_char`, `string_result_fixed`, `string_result_padded`, and -`string_result_c_char`. Replay the hidden output from the existing -`fcharacter_edges_f90.make_out` unit through another reduced entry. Run the -same trailing-blank and returned-value assertions through legacy and direct -routes. The original mixed nodes remain `legacy` on deferred results, writable -strings, optionality, or arrays. - -- [x] Complete fixed-length direct and hidden string result policy, result-plan - length facts, allocation/failure cleanup, binding conversion, bridge copy, - validation, legacy/direct parity, support widening, and ledger updates. - -### Phase 5C — Immutable String Output And Inout Replacement - -Included: fixed and assumed-length Python `str` output/inout dummies, including -the pass-by-address mutable native call. Python strings remain immutable: the -binding creates mutable call-local storage; the bridge passes that storage to -the native dummy; a declared `Returns["name", String...]` consumer returns a -replacement string; identity form discards native mutation and returns `None`. -Fixed buffers retain their complete post-call contents and trailing blanks; -assumed-length buffers use the encoded input length. Optional omitted, -explicit-`None`, and concrete-value states are handled here after required -replacement works. - -The first Phase 5C slice is required fixed-length `String[n]` only. A projected -replacement consumes completed `ObjectKind.STRING`, Python-owned -`COPY_RETURN`, `PYTHON_REFCOUNT`, stack contract storage, -`PythonBarrierAction.STRING_VALUE`, -`NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS`, -`CodegenAction.COPY_IN_OUT`, native mutation, result projection, and -`BridgeDataAction.COPY_REPRESENTATION` with the fixed-string replacement copy -reason. The ordinary argument plan records those facts plus the fixed positive -character length, and its binding and bridge views both carry the completed -codegen action. The same mutable native-call slot is referenced throughout; -there is no second output slot. - -The binding validates the input exactly as Phase 5A does, allocates one -`n + 1` byte call-local buffer through `prik_malloc`, copies all `n` encoded -bytes, and appends NUL. Allocation failure raises `MemoryError` before native -execution. The bridge receives the mutable buffer and length, materializes -backend-local `character(kind=c_char, len=n)` storage, passes that storage to -the native dummy, then copies the complete post-call value back into the -binding buffer and restores the NUL terminator. After the call, binding -`_lower_writeback_string()` converts the replacement with the same -`Py_BuildValue("s", ...)` behavior as the legacy route and frees the call-local -buffer exactly once whether conversion succeeds or fails. - -The existing ordered lifecycle records remain authoritative: - -```text -COPY_IN (binding allocation and input copy) - -> NATIVE_MUTATION (bridge-local character call and copyback) - -> COPY_OUT (binding Python replacement conversion) - -> CLEANUP (binding call-local buffer release) -``` - -Validation requires the completed ownership/action facts, fixed result -position, one shared payload/length handoff, matching argument and native-slot -lengths/actions/copy reasons, exactly one complete lifecycle phase set, bridge -copyback ownership only for `COPY_IN_OUT`, and binding cleanup after conversion. -A replacement combined with native status-error projection stays blocked until -the status failure path also releases the mutable buffer. Multiple projected -results remain blocked by the existing single-writeback lane. - -A fixed identity contract uses the already-completed `CALL_LOCAL_INPUT` action, -the same call-local bridge character representation, no lifecycle result, and -returns `None`. Native writes affect only that temporary and are deliberately -discarded. Its binding buffer remains the borrowed read-only UTF-8 input because -the bridge never copies mutation back across the boundary. Assumed-length, -optional, mutable `String[n][()]`, and raw-address forms remain excluded from -this first slice. - -Replay both forms from the existing `fcharacter_edges_f90.fixed_inout` native -unit through a reduced edited contract that exports one projected replacement -and one identity spelling bound to the same native symbol. Run the same exact -length, trailing-blank, input-immutability, returned-value, and allocation -failure assertions through legacy and direct routes before widening the -whole-unit support predicate. - -The second Phase 5C slice keeps the same completed ownership, barrier, -representation-copy, and four-phase lifecycle records while removing the -compile-time-length restriction. For required assumed-length `String`, the -binding-recorded UTF-8 byte length is the native character length and the -replacement allocation size. A zero-byte input is valid: replacement owns a -one-byte NUL-only buffer, the bridge materializes a zero-length character -value, and binding returns the empty Python string after releasing the buffer. -Fixed strings still require the exact declared encoded length. - -Optional string values use the completed `OptionalMode.NULLABLE_VALUE`; they -do not invent a descriptor or reinterpret optionality as semantic nullability. -The binding ABI always carries the string payload pointer and runtime byte -length. Omitted and explicit `None` both send a null pointer with length zero, -so the bridge leaves the native optional dummy absent and a projected -replacement returns `None`. A concrete value is validated before native -execution, including embedded-NUL rejection and any fixed-length constraint. -Projected replacement allocates and owns the mutable `length + 1` buffer only -for that concrete value; identity form borrows the read-only payload and -discards native mutation exactly as the required identity path does. - -The bridge tests pointer association to choose the existing optional native -call branch. Only the present branch associates the payload, creates -`character(kind=c_char, len=runtime_length)` call-local storage, and invokes -the native optional dummy. Copyback is likewise guarded by pointer association, -so an absent optional never touches unassociated storage. Concrete projected -replacement restores the NUL terminator after copying all runtime-length -bytes. Binding then returns the concrete replacement and frees its allocation -exactly once, or returns `None` without calling `free` for the absent states. -Status-error combination and multiple projected replacements remain blocked -by the same explicit cleanup exclusions as the fixed required slice. - -Replay `assumed_inout` and `optional_inout` from the existing -`fcharacter_edges_f90` contract through a reduced entry module. Compare legacy -and direct routes for empty and non-empty assumed-length values, omitted, -explicit-`None`, and concrete optional states, input immutability, embedded-NUL -rejection before native execution, and allocator failure only for concrete -projected replacements. - -- [x] Complete fixed required replacement and discarded-identity policy, - writeback lifecycle, named lowering, cleanup, validation, parity, support - widening, and ledger updates. -- [x] Add assumed-length replacement and optional presence only after the fixed - required path is proven; preserve legacy empty-string and omitted/`None` - behavior and reject embedded NUL before native execution. - -### Phase 5D — Mutable Storage And Raw Fixed-Length Addresses - -Included: `String[n][()]` caller-owned rank-zero NumPy bytes storage and -`Addr(String[n])` caller-supplied integer addresses. The storage path validates -rank zero, dtype `S`, native byte order/alignment where applicable, and -writability before aliasing the caller buffer. The raw-address path does not -own or validate the pointee. Both use the declared fixed length; mutable -deferred-length scalar storage remains blocked. - -Both forms complete policy before planning and share no Phase 5C replacement -lifecycle. `String[n][()]` records `ObjectKind.STRING`, caller ownership, -`IN_PLACE`, caller destruction, alias contract/boundary storage, -`PythonBarrierAction.STRING_STORAGE`, -`NativeBarrierAction.PASS_STORAGE_ADDRESS`, `CodegenAction.IN_PLACE_ARGUMENT`, -native mutation, no result projection, and a fixed positive character length. -`Addr(String[n])` records the same caller ownership, in-place transfer, caller -destruction, mutation, and no result projection, but the contract value itself -uses stack storage while `PythonBarrierAction.RAW_ADDRESS` and -`NativeBarrierAction.PASS_RAW_ADDRESS` preserve the unsafe caller-supplied -address. The raw pointee is never adopted, released, sized, or validated by -prik. This corrects the pre-5D raw-string decision that incorrectly retained -immutable-string call-local ownership despite the completed raw-address -barriers. - -The ordinary argument plan carries the fixed length and uses -`ArgumentHandoffMode.OPAQUE_ADDRESS` for both forms. There is one pointer ABI -field and no runtime length field: the fixed character length comes only from -the completed plan. The binding storage handler accepts exactly a rank-zero -NumPy `NPY_STRING` array whose itemsize is `n`, requires alignment and -writability, and forwards `PyArray_DATA` without allocating or copying. -The raw handler accepts an integer and uses the existing `PyLong_AsVoidPtr` -path; it deliberately does not inspect the pointee, its allocation extent, or -its lifetime. - -The native character scalar is not directly C interoperable, so both forms -record `BridgeDataAction.COPY_REPRESENTATION` with a boundary-specific reason. -The bridge associates the incoming address with exactly `n` -`character(kind=c_char)` bytes, copies them into backend-local -`character(kind=c_char, len=n)` storage, invokes the native dummy, and copies -all `n` post-call bytes back. It does not append NUL, allocate, free, infer -ownership, or create a Python result. These helper locals are emitted-code -details selected by the completed storage/raw policy. - -Optional storage/address arguments and projected returns remain blocked in -this phase. `String[()]` and `Addr(String)` are rejected by the semantic `.pyi` -contract because the bridge has no fixed extent; arrays and callback storage -remain owned by their later lanes. Validation rejects edited plans with a -missing/nonpositive length, the wrong owner/transfer/destruction/storage mode, -an inconsistent barrier or handoff, a runtime length role, an unjustified copy -reason, missing mutation, or result projection before either backend lowers. - -Replay `fixed_inout_storage` and `fixed_inout_raw` from the existing -`fnative_call_examples_f90` edited contract through a reduced entry bound to -the same `fixed_inout` native routine. Compare legacy and direct routes for -complete eight-byte mutation, rank/dtype/itemsize/writability failures, raw -integer type rejection, and lack of Python return. Keep the existing mixed -native-order test on the legacy route because its array and derived-type -neighbors belong to later phases; add the reduced replay as a separate -wrapper-plan ledger node. - -- [x] Complete mutable string-storage and raw-address policy, address handoff, - bridge association/copyback mechanics, validation, legacy/direct parity, - support widening, and ledger updates. - -### Phase 5 Completion - -Descriptor-backed scalar character values are deliberately outside this -phase. A contract such as `String | None` with -`result=Allocatable(Return(...))` carries allocation state, runtime element -length, descriptor ownership, and native release responsibility. It must enter -the direct route only through Phase 7's shared allocatable/pointer descriptor -plan; it remains a rank-zero Python `str | None` result rather than a native -array handle. Phase 5 must not add a character-only descriptor ABI or cleanup -path. - -- [x] Expand the phase under the mandatory expansion gate from the live - policies, legacy binding/bridge implementation, public string contract, and - focused wrapper tests. -- [x] Validate fixed/runtime length sources, payload/length role agreement, - result ownership, writeback consumers, and cleanup responsibility before - either backend emits source. -- [x] Keep string behavior in directly named string lowering methods while - reusing the ordinary scalar planning records and lifecycle flow. -- [x] Finish Phase 5 only when all non-descriptor scalar string sub-lanes are - proven and every affected matrix row is either `wrapper-plan` or blocked by - a later array, descriptor, field, or callback lane recorded in the ledger. - -## Phase 6 — Ordinary Arrays - -Scope: NumPy data-buffer arrays that do not require native descriptor handles. - -The ordinary-array lane borrows or copies NumPy data buffers; it never creates -or consumes a persistent native descriptor handle. `Allocatable[T[...]]`, -`Pointer[T[...]]`, rank-zero allocatable/pointer scalars, and a native handle -used as the actual value for an ordinary array dummy all remain in Phase 7. -Caller-supplied `Addr(T[...])` storage is the distinct Phase 6G follow-up and -must complete before Phase 7. -Derived-type arrays remain in Phase 8, fields in Phases 8 and 9, and callback -arrays in Phase 10. The full BLAS/LAPACK generation unit remains deferred until -final cutover even when individual ordinary-array shapes become supported. - -Whole-generation-unit rollout preserves that Phase 7 boundary. Output-only -ordinary array results and hidden outputs may select the production plan route -now. A generation unit with an ordinary array actual remains on the legacy -route, even after its NumPy-buffer path has direct-route parity, because route -selection cannot know whether a caller will pass a NumPy array or a supported -native descriptor handle. Those reduced array-actual rows remain `dual-route` -with the native-handle caller contract recorded as their sole Phase 7 blocker; -the direct route is forced only by the internal parity harness. - -The public behavior is defined by the NumPy array contract in -`docs/user/reference/fortran-wrapper.md`, the array spelling and metadata rules in -`docs/user/reference/semantic-pyi-format.md`, and the existing array wrapper -tests. The legacy binding validates exact dtype, rank, every expressible -extent, native byte order, alignment, layout/stride requirements, and -writeability for mutable storage before the native call. It does not cast, -byte-swap, repair alignment, de-alias overlapping storage, or silently copy a -rejected layout. Read-only source `intent(in)` storage may remain read-only; -edited `.pyi` array storage is writable unless a completed policy says -otherwise. Zero-sized dimensions are valid when the rest of the contract is -valid. - -Every ordinary array remains in the existing -`ArgumentPolicy -> ArgumentTransferPlan -> NativeCallSlotPlan` or -`ResultPolicy -> ResultPlan` flow. An argument embeds one editable array -handoff spec containing element family, concrete or runtime rank, declared -shape expressions, axis modes, order, contiguity, itemsize when relevant, -writeability, and the exact ABI roles for data, extents, upper bounds, strides, -runtime rank, or itemsize. Policy completion selects -`PythonBarrierAction.ARRAY_STORAGE`, -`NativeBarrierAction.PASS_ARRAY_BUFFER`, and either -`BridgeDataAction.ASSOCIATE_VIEW` for caller storage or an explicit copy action -and reason. Planning must not reconstruct any of those choices from rank, -shape spelling, or datatype. The binding and bridge dispatch only to directly -named array implementation methods selected by those completed facts. - -The binding checks the NumPy object before extracting `PyArray_DATA`, shape, -and element strides. The bridge receives only the fields named by the handoff -spec, associates the pointer with the completed element type and extents, and -constructs a stride slice only when the plan explicitly allows it. C-oriented -flat storage reverses bridge association extents only when the completed order -requires it. Backend-local pointer views and slice expressions are emitted-code -details; dtype, rank, extent, order, stride acceptance, mutation, projection, -copy, and ownership are semantic policy. - -The phase is dependency-ordered as follows. - -### Phase 6A — Required Rank-One Contiguous Buffers - -Included: required concrete-rank-one ordinary arrays with dense contiguous -axes (`T[:]`) for the existing bool, integer, real, and complex primitive -families; caller-owned borrowed/in-place storage; scalar or void neighbors and -results already supported by earlier phases; native position reordering; and -zero-length buffers. The binding requires an exact NumPy dtype, rank one, -native byte order, alignment, contiguity, and writeability only when completed -ownership says native code mutates the storage. It forwards the data address -and runtime extent. The bridge creates one typed rank-one pointer view with -`c_f_pointer` and passes that view in the completed native-call position. - -This slice records `ArgumentHandoffMode.ARRAY_BUFFER` and -`BridgeDataAction.ASSOCIATE_VIEW`; it performs no allocation, element copy, -writeback action, release, or Python result projection. Explicit/fixed extent -expressions, `Flat`, multidimensional order, strided axes, optionality, -projected output identity, array results, character arrays, assumed rank, and -native-handle actuals remain in later sub-lanes. Replay one existing -`fmath_arrays_f90` contiguous routine through a reduced semantic `.pyi` entry -and compare legacy/direct behavior for mutation, dtype, rank, alignment, -byte-order, contiguity, writeability, zero length, and native argument order. - -- [x] Complete required rank-one contiguous array policy, editable handoff - spec, validation, named C/Fortran lowering, reduced legacy/direct parity, - support widening, and ledger evidence. - -Boolean arrays retain an exact one-byte NumPy boundary independently of native -language spelling. Semantic IR records compiler-measured native storage as -`Bool8`, `Bool16`, `Bool32`, or `Bool64`. Post-IR wrapper policy must distinguish -an exact `c_bool` view from an exact-kind representation copy and must record -copy-in and copy-out directions before planning. For copied arrays the bridge -owns the exact-kind temporary; copy-out both converts truth values and writes -canonical zero/one boundary bytes in one traversal. Binding code continues to -validate and forward only `NPY_BOOL` storage and must not infer native logical -kind from a semantic name. - -### Phase 6B — Declared Extents, Flat Storage, And Dense Rank - -Included: fixed and visible-symbol extent expressions, lower-bound-derived -extents, assumed-size `Flat`, ranks two through fifteen, `ORDER_F` and -`ORDER_C`, dense contiguous layout, and zero-sized axes. Shape expressions are -resolved against existing scalar handoff roles before backend emission; the -bridge association order follows the completed layout. Any expression that -cannot be represented by available roles remains blocked rather than being -recomputed in a backend. - -Declaration-expression normalization is shared across module variables, -derived fields, dummy arguments, and results. Generated `.pyi` uses Python -array properties (`a.size`, `a.shape[i]`, and `a.ndim`), while the completed -plan carries role-bound expressions that each backend only renders. - -Order is an exact-storage selector, not an implicit conversion selector. -`ORDER_F` preserves logical axes over Fortran-contiguous storage. `ORDER_C` -passes the original C-contiguous address and reverses bridge extents, so native -Fortran observes the transposed storage view. Preserving the same logical axes -while accepting the opposite layout uses explicit `COPY_F` metadata, never an -inference from order. The owning `ArgumentTransferPlan` records C source order, -F native order, copy-in, conditional copy-out, original-object projection, and -temporary cleanup. The binding performs both copy directions and owns the -NumPy temporary. The bridge receives the temporary through the unchanged -ORDER_F association path and performs neither half of this representation -conversion. - -The initial `COPY_F` lane includes required, concrete-rank, dense numeric -ndarray arguments. It excludes `Flat`, assumed-rank, strided, optional and -character arrays, native descriptor arguments, and handle actuals until each -has separate policy and parity evidence. - -`Flat` is one axis marker and never collapses a multidimensional plan: -`T[:, Flat]` remains rank two in Fortran order, while -`Annotated[T[Flat, :], ORDER_C]` is its C-order orientation. The bridge reverses -only the C-order association extents. For an external assumed-size interface, -an explicit prefix such as `T[3, Flat]` may lower to `a(3, *)`; a runtime-only -prefix uses the standards-valid sequence-associated `a(*)` declaration while -the bridge retains every runtime extent and the completed logical rank. - -- [x] Complete declared-shape evaluation, flat-storage orientation, - multidimensional dense handoff, validation, parity, and ledger evidence. -- [x] Complete explicit C-to-Fortran representation copies through `COPY_F`, - including native-input and inout calls through the same binding-owned copy - lifecycle, projected original identity, temporary cleanup, direct bridge - reuse, validation, and compiled parity. Native `intent` remains owned by the - called procedure and is not duplicated in the semantic `.pyi` or bridge - temporary. - -### Phase 6C — Positive-Strided Ordinary Views - -Included: `::` axes and bounded stride-aware axes, runtime upper bounds and -element strides, Fortran-oriented positive-stride slicing, contiguous views as -a valid special case, and degenerate zero-size strides. Negative, zero on an -addressable axis, incompatible C-oriented, broadcast, and otherwise invalid -layouts fail before the native call. No copy-to-contiguous fallback is inferred. - -- [x] Complete stride roles, upper bounds, positive-stride bridge slices, - layout validation, parity, and ledger evidence. - -### Phase 6D — Output Storage And Projected Identity - -Included: ordinary `intent(out)`/`intent(inout)` caller buffers and -`Returns["name", T[...]]` projections. Native code mutates the same validated -NumPy storage; the binding returns the original Python array object with one -owned reference rather than constructing a second array or copying elements. -Read-only output storage fails before the call. Multiple projections compose -with the existing ordered result aggregation only after every projected array -identity and failure-path reference is planned. - -- [x] Complete in-place output ownership, projected identity/reference - lifecycle, multiple-result aggregation, parity, and ledger evidence. - -### Phase 6E — Ordinary Array Results And Hidden Outputs - -Included: non-allocatable direct array results and hidden output arrays whose -shape and element ownership are fully expressible without persistent native -descriptors. The plan records the producer, every runtime extent, allocation -owner, copy or transfer action, Python NumPy construction, and release on -success and every failure path. Nullable allocatable/pointer results remain in -Phase 7. - -- [x] Complete ordinary result/hidden-output allocation, shape projection, - copy ownership, cleanup, parity, and ledger evidence. - -### Phase 6F — Optional, Assumed-Rank, And Character Buffers - -Included: ordinary optional NumPy arrays, numeric assumed-rank dispatch from -one through fifteen, and fixed-width NumPy bytes character arrays with planned -itemsize. Omitted ordinary optional arrays remain distinct from present -storage. Assumed-rank plans carry a runtime-rank role and validate the supported -range before bridge dispatch. Character arrays use exact `NPY_STRING` itemsize -and remain raw fixed-width bytes; deferred descriptor-backed character values -remain in Phase 7. Fixed-shape character array direct results and hidden -outputs reuse the Phase 6E copy-result path with their itemsize included in -NumPy dtype construction and bridge byte-count calculation. - -- [x] Complete optional presence, assumed-rank dispatch, character itemsize, - validation, parity, and ledger evidence. - -### Phase 6A-F Ordinary-Buffer Completion - -- [x] Expand the phase under the mandatory expansion gate from live semantic - array contracts, legacy binding/bridge lowering, public docs, and focused - wrapper tests. -- [x] Define array handoff specs for every supported data, rank, shape, stride, - order, itemsize, writeability, result, and lifecycle role. -- [x] Validate every completed array policy and handoff role before either - backend emits source. -- [x] Finish Phases 6A-F only when every ordinary-array buffer matrix row is - migrated or remains blocked solely by an explicitly later descriptor, - derived, field, callback, or deferred-real-library lane. - -### Phase 6G — Raw Array Addresses — Complete - -Implementation status: complete. Required raw array addresses now use the -shared completed policy, `ArgumentTransferPlan`, native slot, centralized -validation, and named binding/bridge lowering paths. The dependency-closed -numeric and fixed-character runtime rows have passed compiled legacy/direct -parity and moved to `wrapper-plan`. - -Scope: required Python-visible type-level raw-address array arguments such as -`Addr(Float64[n])`. The caller supplies one Python integer address, prik -forwards it as one opaque C address, and the bridge associates a typed native -array view using rank, shape, element type, and orientation facts completed -before `ir2ast.py`. There is no NumPy object, runtime handle, persistent native -descriptor, data copy, ownership transfer, or automatic release. - -This lane follows Phase 6 because its semantic object kind is -`ObjectKind.NUMPY_ARRAY` and its pointee layout reuses the array shape record. -It remains a distinct transport from an ordinary array buffer. The fixed -dispatch algorithm is: - -1. match `ObjectKind.NUMPY_ARRAY`; -2. match the completed Python barrier action; -3. lower `ARRAY_STORAGE` through the Phase 6A-F buffer path or `RAW_ADDRESS` - through Phase 6G; -4. require the matching native action, handoff mode, bridge data action, and - array-shape facts; and -5. fail validation rather than substituting the other transport. - -The same algorithm already separates scalar and string value, storage, and -raw-address forms. Phase 6G must extend that system; it must not add a parallel -raw-pointer planner, a datatype-based backend branch, or a special function or -module plan. - -#### Public Contract And Explicit Non-Scope - -The maintained public contract is already documented in -`docs/user/reference/semantic-pyi-format.md` and -`docs/user/guide/data-types.md`. Preserve it exactly: - -- `Addr(T[d1, ..., dr])` is depth one and has positive rank; -- the pointee dtype is primitive; -- every extent expression is resolved from literals and visible scalar - arguments or visible rank-zero scalar storage; -- the integer carries no dtype, rank, shape, order, alignment, bounds, - ownership, or lifetime metadata; -- prik cannot prove that the supplied address actually points to compatible, - sufficiently large, live storage; and -- edited semantic `.pyi` raw-address storage is mutable caller storage unless - a completed policy explicitly says otherwise. - -The initial compiled oracle is `Addr(Float64[n])`. Before declaring the lane -complete, audit every public primitive family already accepted by semantic -policy, including bool, integer, real, complex, and fixed-width character -array pointees. Add compiled coverage for a family only when an existing native -routine can prove it without broadening the public contract. A fixed scalar -`Addr(String[n])` remains the completed Phase 5D string path; a rank-positive -`Addr(String[k][n, ...])` is an array path and must carry both the fixed element -length and the resolved array shape. - -Explicitly excluded from this lane are: - -- scalar `Addr(T)`, already completed in Phase 2E; -- fixed scalar `Addr(String[n])`, already completed in Phase 5D; -- NumPy `T[...]` storage, already completed in Phases 6A-F; -- unresolved or assumed shapes such as `Addr(Float64[:])`, assumed rank, - assumed size, and stride-marker shapes; -- optional, nullable, projected, direct-result, and hidden-output raw addresses - unless a separate public-contract audit first proves their intended Python - ownership and absence/result behavior; -- wrapped/derived pointees, pointer graphs deeper than one, and callbacks; -- `Allocatable[T[...]]`, `Pointer[T[...]]`, runtime native handles, and C - descriptors, which belong to Phase 7; and -- any implicit conversion from an ndarray or runtime handle to its address. - -#### One Action Vocabulary, Three Array Transports - -| Contract | Object kind | Python action | Native action | Handoff mode | Bridge data action | -| --- | --- | --- | --- | --- | --- | -| NumPy `T[...]` | `NUMPY_ARRAY` | `ARRAY_STORAGE` | `PASS_ARRAY_BUFFER` | `ARRAY_BUFFER` | `ASSOCIATE_VIEW` | -| Raw `Addr(T[...])` | `NUMPY_ARRAY` | `RAW_ADDRESS` | `PASS_RAW_ADDRESS` | `OPAQUE_ADDRESS` | `ASSOCIATE_VIEW` | -| Native descriptor contract | completed handle kind | completed handle action | `PASS_NATIVE_DESCRIPTOR` | Phase 7 descriptor mode | completed Phase 7 action | - -`ASSOCIATE_VIEW` means the bridge creates a typed, non-owning view; it does not -mean that the Python binding extracted a NumPy buffer. The Python and native -barrier actions remain the authoritative distinction. Do not introduce names -such as `PASS_RAW_ARRAY`, `COPY_RAW_ARRAY`, or datatype-specific address -actions. - -The completed ownership/action tuple for the required mutable public form is: - -- `OwnershipOwner.CALLER`; -- `TransferMode.IN_PLACE`; -- `DestructionPolicy.CALLER`; -- `StorageMode.STACK` for the call-local pointer carrier, not for the pointee; -- `CodegenAction.IN_PLACE_ARGUMENT`; -- `PythonBarrierAction.RAW_ADDRESS`; -- `NativeBarrierAction.PASS_RAW_ADDRESS`; -- `ArgumentHandoffMode.OPAQUE_ADDRESS`; and -- `BridgeDataAction.ASSOCIATE_VIEW` with no copy reason. - -If a retained source-derived contract can be read-only, policy may instead -complete `CALL_LOCAL` / `CALL_LOCAL_INPUT` / `NONE` destruction. Both -backends must consume that completed tuple; neither may infer mutability from -the pointee type or raw-address spelling. A raw array never has copy-in, -copy-out, projected-identity, allocation, destruction, release, or lifecycle -actions in this lane. - -#### Required Policy And Plan Shape - -Keep the feature under the existing `ArgumentTransferPlan`: - -```text -ArgumentTransferPlan - object_kind = NUMPY_ARRAY - binding.python_action = RAW_ADDRESS - bridge.native_action = PASS_RAW_ADDRESS - bridge.handoff_mode = OPAQUE_ADDRESS - bridge.data_action = ASSOCIATE_VIEW - array = ArrayHandoffPlan - native_call_slot = the same referenced NativeCallSlotPlan -``` - -Do not add `RawArrayPlan`, a second native slot, or a raw-address lifecycle -owner. Generalize the existing completed `ArrayHandoffPolicy` and -`ArrayHandoffPlan` only enough to carry raw pointee layout: - -- concrete rank and one shape expression per axis; -- one `data_role` equal to the binding/bridge/native-slot address role; -- `extent_reference_roles` naming the existing visible scalar handoff roles - used by each shape expression; -- the completed orientation used for native pointer association; -- fixed character element length/itemsize when the pointee family is string; - and -- no binding-extracted runtime rank, extent, upper-bound, stride, or itemsize - ABI roles. - -For a raw address, the shape record describes the pointee view; it does not -describe fields packed by the binding. A visible `n` used by -`Addr(Float64[n])` already has its own `ArgumentTransferPlan` and native-call -slot. Reference that role rather than passing a duplicate array extent. A -literal extent requires no extra ABI field. The bridge resolves the shape -expression from those planned native role names. - -Post-IR policy completion must explicitly select multidimensional orientation -before planning. Preserve the current legacy interpretation, including its -default orientation, only after capturing a rank-two artifact/runtime oracle. -Do not leave `ir2ast.py`, a codegen-model `order` default, or the bridge's local -shape reversal to make that decision. - -#### Completed Direct-Plan Seams - -The implementation split completed array policy by Python barrier action, -selected `OPAQUE_ADDRESS` and `ASSOCIATE_VIEW` before lowering, projected raw -pointee layout into the shared array record, omitted packed NumPy-buffer roles, -and added named raw-address checks and association methods to both backends. -Ordinary-array buffer checks remain unchanged and fail closed; neither backend -substitutes one transport for another. - -#### Dependency-Ordered Implementation Slices - -##### Phase 6G1 — Complete Raw Array Policy - -- [x] Make the `NUMPY_ARRAY` boundary validator dispatch on - `PythonBarrierAction` and add a named raw-address branch with the exact - ownership/action tuple above. -- [x] Complete raw pointee rank, shape expressions and their visible-scalar - dependencies, primitive family, fixed character element length, and - orientation before `ir2ast.py`. -- [x] Complete `OPAQUE_ADDRESS` and `ASSOCIATE_VIEW` from the action pair; do - not infer either in a backend. -- [x] Keep unresolved dimensions, unsupported pointee families, optionality, - projection, nullability, and deeper pointer graphs blocked with owner-path - diagnostics. -- [x] Freeze current behavior for zero/negative extent expressions, zero or - negative integer addresses, and integer overflow against the public docs and - legacy conversion before changing any rule. If a rule changes, change it in - policy and public docs, not in one backend. - -Audit result: resolved zero and negative extent expressions remain accepted -without a positivity check; integer zero becomes a null pointer without a -conversion error; negative integers follow `PyLong_AsVoidPtr`; and pointer-size -overflow raises `OverflowError`. Public documentation now states that these are -unsafe caller responsibilities, and tests prove the conversion guard and -generated shape without dereferencing an invalid address. - -##### Phase 6G2 — Project And Validate The Shared Plan - -- [x] Populate the existing `ArgumentTransferPlan.array` and its shared - `NativeCallSlotPlan.array` with one identical raw pointee layout record. -- [x] Reuse the scalar/string address handoff role and - `ArgumentHandoffMode.OPAQUE_ADDRESS`; add no raw-array ABI action. -- [x] Resolve every shape symbol to an existing visible scalar role and reject - unavailable, cyclic, hidden, non-scalar, or result-only dependencies before - lowering. -- [x] Split central array diagnostics by the completed Python action so buffer - validation still requires packed extent/layout roles while raw validation - forbids them. -- [x] Add editable-plan tests that independently corrupt object kind, Python - action, native action, handoff mode, bridge data action, rank, shape, - reference roles, element family, character length, orientation, and native - slot identity. - -##### Phase 6G3 — Reuse Binding Raw-Address Extraction - -- [x] Reuse `_lower_argument_required_raw_address()` for the Python integer - check and `PyLong_AsVoidPtr` conversion. Scalar, string, and array raw - addresses should share this extraction code. -- [x] Emit one `void *` handoff value and no `PyArray_*`, dtype, rank, shape, - layout, writeability, or itemsize checks. -- [x] Keep object-kind-specific logic out of the conversion method; array - shape affects only validation, the bridge view, and native call. -- [x] Preserve the existing conversion rule under which integer zero produces - a null pointer without itself raising a Python conversion error. Prove that - rule without dereferencing the null pointer; runtime tests must never call - native code with an invalid test address. - -##### Phase 6G4 — Add Named Raw Array Bridge Association - -- [x] Add directly named raw-array declaration and association methods in the - array method group. Dispatch to them only for - `NUMPY_ARRAY` / `RAW_ADDRESS` / `PASS_RAW_ADDRESS` / - `OPAQUE_ADDRESS` / `ASSOCIATE_VIEW`. -- [x] Declare one `type(c_ptr), value` bridge parameter and one backend-local - typed pointer view. The local view is an emitted-code helper, not a new plan - owner. -- [x] Associate the view with `c_f_pointer` using only the planned shape and - orientation, then pass that view in the existing native-call slot position. -- [x] Preserve fixed character element length when the pointee is a character - array. Do not pass a runtime itemsize unless a future public contract - explicitly requires one. -- [x] Emit no copy, writeback, allocation, release, descriptor, or NumPy - mechanics. - -##### Phase 6G5 — Prove The Route Before Widening It - -- [x] Retain semantic conversion coverage in - `tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py` - for round-trip, - visible extent sources, primitive pointees, and rejection of unresolved or - wrapped forms. -- [x] Add focused completed-policy tests for every authoritative action and - blocker, plus `array-raw-address-inputs` support classification. -- [x] Add `tests/fortran/raw_addresses/codegen/test_raw_array_lowering.py` for plan - shape, edits, validation, C nodes, Fortran nodes, native order, and the - absence of buffer/descriptor/lifecycle nodes. -- [x] Extract `fill_vector_raw` from - `test_editable_contract_can_use_native_order_arguments_without_native_call` - into a reduced legacy/direct-plan parity test. Cover mutation through a valid - `raw_vector.ctypes.data`, ndarray rejection, wrong Python types, a visible - rank-zero scalar extent, and the established native argument order. -- [x] Prove raw-array native argument reordering in the direct-plan generated - call test. The legacy AST route retains only a projection marker and is not - an oracle for reordered projection-slot lowering. -- [x] Add literal and arithmetic extent-role cases. Add a rank-two runtime - parity case before freezing default/explicit orientation. Add a fixed-width - character-array case if the public family audit retains that contract. -- [x] Keep the broad native-order test `legacy` until its derived-type owner is - migrated; only the reduced raw-array row may move to `wrapper-plan` here. -- [x] Run the focused policy/plan/backend tests, the relevant wrapper test, - documentation checks, wrapper-codegen complexity checker, and required - static-analysis suite before changing route support. - -#### Phase 6G Exit Gate - -- [x] Expand raw array addresses as the explicit next lane using the public - contract, completed semantic policy, legacy binding/bridge primitives, and - the existing compiled `Addr(Float64[n])` oracle. -- [x] Complete Phases 6G1 through 6G5 without changing the public raw-address - contract or introducing a parallel action vocabulary. -- [x] Prove that one maintainer algorithm—object kind, Python action, native - action, handoff mode, data action, then typed shape facts—covers ordinary and - raw arrays without backend inference. -- [x] Move only dependency-closed raw-array test rows after generated-artifact - comparison and compiled legacy/direct parity pass. -- [x] Begin Phase 7 only after this exit gate is complete. Phase 7 must consume - the established distinction among array buffers, raw addresses, and native - descriptors rather than revisiting it. - -## Phase 7 — Native Array Handles And Descriptors - -Implementation status: reopened for the view-only `to_numpy()` contract -correction. The previously completed direct Phase 7A-H slices remain evidence -for unaffected descriptor handoffs, but Phase 7 is not closed again until -plain and `Aliased` module-array handles both return a current live view or -`None` without an implicit copy and the final verification gate is rerun. -Every field, pointer-result, callback, and deferred-real-library exclusion -remains on its later blocker. - -Scope: migrate the existing native descriptor and runtime-handle contract into -the wrapper-plan path without redefining that public contract. The maintained -`native-array-handle-checklist.md` remains the feature-level behavioral oracle; -this section owns only its migration into completed wrapper policy, -`ArgumentTransferPlan`, `ResultPlan`, `ModuleVariablePlan`, subordinate native -slots and lifecycle actions, direct C/Fortran lowering, and production route -selection. - -The shared descriptor family includes: - -- rank-positive `Allocatable[T[...]]` and `Pointer[T[...]]` handle arguments; -- optional-absent array handles, where omission or `None` means the native - optional dummy is absent; -- projected writable descriptors whose mutation must remain attached to the - same caller handle; -- wrapper-owned allocatable array results and hidden outputs; -- borrowed module allocatable and pointer handles plus their generated operation - tables; -- native handles passed as actual values to ordinary `T[...]` dummies without - an implicit `.to_numpy()` call; -- build requirements for standard C descriptors; and -- the remaining rank-zero allocatable/pointer result cases, including nullable - deferred-length scalar character values, which return copied Python values - rather than native-array handle objects. - -Allocatable and Pointer remain separate public contract types but share one -plan and lowering structure. Descriptor kind selects only the operations that -genuinely differ: allocation state versus association state, allowed -shape-changing operations, target lifetime, extraction policy, and release. -Do not create independent allocatable and pointer planner hierarchies. - -For every rank-positive module handle, `to_numpy()` has one public result: -`None` for an unallocated/unassociated native object and a live NumPy view of -the current allocation/target otherwise. Plain and `Aliased` allocatable -module variables use the same behavior. `Aliased` remains semantic metadata -but never selects a detached copy. Users call `.copy()` explicitly for -independent storage; an old live view may become stale after native -deallocation, reallocation, nullification, or reassociation, and a fresh -`to_numpy()` call must inspect current native state. - -### Phase 7 Boundary And Explicit Non-Scope - -The following four boundaries must remain distinct: - -| Python contract | Planned Python input | Native transport | Owner phase | -| --- | --- | --- | --- | -| `T[...]` with a NumPy array | validated NumPy storage | `PASS_ARRAY_BUFFER` | Phase 6 | -| `T[...]` with an allocated/associated native handle actual | validated handle array-data facet | `PASS_ARRAY_BUFFER` | Phase 7A | -| `Allocatable[T[...]]` / `Pointer[T[...]]` | matching runtime handle object | `PASS_NATIVE_DESCRIPTOR` | Phase 7B onward | -| `Addr(T[n, ...])` | caller-supplied integer address | `PASS_RAW_ADDRESS` | Phase 6G prerequisite, not Phase 7 | - -`Addr(Float64[n])` is a supported public semantic `.pyi` contract when -every extent is a literal or an expression over visible scalar arguments or -rank-zero scalar storage. It accepts an integer such as `array.ctypes.data` and -forwards that address without ownership, dtype, alignment, lifetime, or bounds -validation. Parsing, policy completion, printing, and both compiled wrapper -routes support it through the completed -`RAW_ADDRESS` / `PASS_RAW_ADDRESS` selector pair. Do not misclassify this raw -pointer as a NumPy buffer, native handle, or C descriptor while maintaining -Phase 7. - -Other exclusions and dependencies are: - -- ordinary NumPy-only buffer extraction, shape, stride, output identity, and - copy-result behavior already completed in Phase 6; -- caller-supplied raw array addresses, completed separately by the Phase 6G - entry dependency; -- derived-type field attachment, class construction, parent-wrapper creation, - and property orchestration, which require Phases 8 and 9 even though the - shared native-handle plan must already be reusable by those later owners; -- scalar derived module-variable member access and argument compatibility, - which belong to Phase 8. Phase 7 descriptor machinery remains limited to - array handles; scalar derived module allocatables use the exact local - move-out/move-back route specified in Phase 8H and do not consume Phase 7 CFI - descriptor machinery. A failed scalar-object call handoff must not become a - module-access blocker; -- pointer results without completed stable owner storage and target lifetime; -- callback descriptor arguments or results, which remain in Phase 10; -- compiler-private descriptor layout inspection or copying; -- any implicit `.to_numpy()` conversion when a native handle is passed to an - ordinary array dummy; and -- the deferred BLAS/LAPACK generation unit until final cutover. - -### Existing Semantic Authority And Legacy Oracle - -Do not redesign the public feature while migrating it. Reuse these completed -sources of truth: - -- `prik/semantics/native_array_handles.py` defines - `NativeArrayHandlePolicy`, `ArrayInteropPolicy`, handle facts, descriptor - kinds, and completed build requirements. -- `prik/policy/completion.py` completes handle kind, origin, owner, - owner retention, descriptor ownership, getter/setter behavior, output - projection, release, target lifetime, destruction, extraction, interop, - nullability, storage mode, operations, and blockers before `ir2ast.py`. -- `prik/runtime/handles.py` owns the reusable runtime protocol, including - `_native_array_actual_argument_for_binding_positional`, - `_native_array_descriptor_argument_for_binding_positional`, and - `_native_array_descriptor_handoff_for_binding_positional`. Direct lowering - must call these helpers rather than duplicate their Python validation. -- `prik/codegen/bindings/c_to_python.py` is the legacy binding oracle. Its - `_ARRAY_INTEROP_POLICY_DISPATCHER`, `_NATIVE_ARRAY_HANDLE_DISPATCHER`, - descriptor-argument handlers, owned-result handlers, operation wrappers, and - descriptor reader define the currently passing C behavior. -- `prik/codegen/bridges/fortran_to_c.py` is the legacy bridge oracle. Its - corresponding dispatchers, descriptor-argument handlers, module/field - operation generators, and owned-allocatable result helpers define the - currently passing Fortran behavior. -- `prik/pipeline/build.py` already derives native-array build requirements from - completed semantic policy and records them in manifests. The wrapper plan - must carry and emit the matching artifact requirements without rediscovering - them from generated source text. - -The legacy generators are behavioral oracles, not dependencies of -`prik/codegen`. Reuse the runtime helpers and completed semantic -records directly. Rewrite the smallest equivalent node/lowering methods in the -direct generators; do not import legacy binding/bridge generator methods or -legacy codegen-model nodes into the wrapper-plan package. - -### Completed Direct-Plan Shape - -Wrapper policy now carries the completed native-handle and array-actual facts. -`ArgumentTransferPlan`, `ResultPlan`, and `ModuleVariablePlan` distinguish a -NumPy data-buffer transfer, a normal array dummy receiving a handle actual, and -a descriptor-handle transfer. Central validation fails closed when any typed -handoff, operation, role, ownership fact, or required header is inconsistent; -neither backend infers policy from datatype or `descriptor_boundary`. - -### Required Plan Shape - -Keep all descriptor-specific state subordinate to the existing datatype- -varying owners: - -```text -ArgumentTransferPlan - array: ArrayHandoffPlan | None - native_array_actual: NativeArrayActualPlan | None - native_array_handle: NativeArrayHandlePlan | None - handoff: NativeDescriptorHandoffPlan - native_call_slot: NativeCallSlotPlan - -ResultPlan - native_array_handle: NativeArrayHandlePlan | None - native_call_slot: NativeCallSlotPlan | None - -ModuleVariablePlan - native_array_handle: NativeArrayHandlePlan | None - -FunctionPlan - native_call_slots: shared ordered references - lifecycle actions: ordered handle materialization/release references -``` - -`NativeCallSlotPlan` and `LifecycleActionPlan` are not competing top-level -semantic owners. A native slot is the argument/result ABI facet shared by its -owning transfer plan, while lifecycle records are function-wide ordering -indexes back to argument/result roles. Descriptor ownership, release, and -operation policy stay under `ArgumentTransferPlan`, `ResultPlan`, or -`ModuleVariablePlan`. Backend-local CFI storage, copy buffers, and failure -cleanup remain inside the named lowerer selected by those plans. - -`NativeArrayActualPlan` is used only when an ordinary `T[...]` argument permits -a runtime native handle as another source for the existing array-buffer ABI. It -records the explicitly accepted Python source kinds and the shared dtype, rank, -shape, layout, writeability, native-byte-order, alignment, and ABI-role checks. -It never carries descriptor ownership or extraction policy. - -`NativeArrayHandlePlan` is one editable projection of the completed handle -policy. It must contain, using typed values rather than free-form backend -method names: - -- descriptor kind and handle kind; -- origin, owner, owner-retention mode, descriptor ownership, and borrowed state; -- element datatype family, dtype, rank, declared shape, order, and character - element length when applicable; -- getter behavior, Python setter exposure, and native setter assignment; -- output projection and same-handle identity requirements; -- release responsibility, target lifetime, destroy behavior, and storage mode; -- `.to_numpy()` extraction action and allowed generated operations; -- descriptor-interop requirement and required headers; -- nullability and optional-absent-handle behavior; and -- one `NativeDescriptorHandoffPlan` with its ABI form and symbolic roles. - -`NativeDescriptorHandoffPlan` must distinguish these typed ABI forms: - -- `FACT_PACKED_CALL_LOCAL`: a non-projected descriptor argument supplies - validated standard descriptor facts; the binding passes those fields and the - bridge establishes call-local standard C descriptor storage. -- `DIRECT_STANDARD_DESCRIPTOR`: a projected writable handle passes its - persistent standard-descriptor pointer so allocation, deallocation, - reassociation, and shape changes remain attached to that handle. -- `OWNED_RESULT_STORAGE`: an allocatable result is materialized into persistent - wrapper-owned CFI storage and later destroyed by the runtime handle. - -The handoff records the descriptor-pointer role when present, `base_addr`, -`elem_len`, runtime rank, per-axis lower-bound/extent/stride-multiplier roles, -an optional presence role, owner-storage role, and generated-operation roles. -The `NativeCallSlotPlan` and its owning argument or hidden result must reference -the same mutable handoff record; do not duplicate descriptor facts that a -maintainer would need to edit twice. - -Convert the current string-valued completed policy selectors into typed plan -enums or validate and translate them exactly once while building wrapper -policy. Backends must not match raw strings such as `argument_descriptor`, -`projected_handle`, or `pointer_c_descriptor` to choose behavior. - -### Consistent Action Vocabulary - -Reuse the existing orthogonal actions: - -| Case | `ObjectKind` | Python action | Native action | `CodegenAction` | Bridge data action | -| --- | --- | --- | --- | --- | --- | -| Ordinary array with ndarray or handle actual | `NUMPY_ARRAY` | `ARRAY_STORAGE`, with explicitly planned accepted sources | `PASS_ARRAY_BUFFER` | existing Phase 6 input/in-place action | `ASSOCIATE_VIEW` | -| Read-only descriptor handle argument | `NUMPY_ARRAY` | `WRAPPER_INSTANCE` | `PASS_NATIVE_DESCRIPTOR` | `CALL_LOCAL_INPUT` | `ASSOCIATE_VIEW` | -| Writable projected descriptor handle | `NUMPY_ARRAY` | `WRAPPER_INSTANCE` | `PASS_NATIVE_DESCRIPTOR` | `IN_PLACE_ARGUMENT` | `DIRECT_TRANSFER` | -| Owned allocatable handle result | `NUMPY_ARRAY` | `NONE` | `NONE` or hidden `PASS_NATIVE_DESCRIPTOR` | `WRAPPER_INSTANCE` | `COPY_REPRESENTATION` with an ownership-transfer reason | -| Borrowed module handle getter | `NUMPY_ARRAY` | module getter action `NATIVE_ARRAY_HANDLE` | operation-specific | `BORROWED_VIEW` | completed per operation | - -Using `WRAPPER_INSTANCE` for the Python handle is consistent with the existing -action axis: the binding validates and consumes a generated runtime wrapper -object, while `ObjectKind.NUMPY_ARRAY` still identifies its array semantic -family. Add a new Python action only if a proven backend operation cannot be -expressed by this existing pair. Add `ArgumentHandoffMode.NATIVE_DESCRIPTOR` -because descriptor tuples are a genuinely different binding-to-bridge ABI from -`ARRAY_BUFFER`; do not overload the Phase 6 mode. - -Keep rank-zero descriptor values on the scalar or string object-kind route. -Their result action creates a Python scalar/string or `None`, not -`WRAPPER_INSTANCE`, and they must not carry `NativeArrayHandlePlan`. - -### Cross-Backend Validation Invariants - -Before either backend emits source, `_validate_plan()` must reject every one of -these inconsistencies: - -- a descriptor plan whose completed `ObjectKind` is not `NUMPY_ARRAY`; -- `PASS_ARRAY_BUFFER` carrying descriptor ownership or CFI roles; -- `PASS_NATIVE_DESCRIPTOR` carrying ordinary data-buffer handoff roles without - a descriptor handoff; -- a disagreement among handle policy, interop ABI, descriptor kind, handle - kind, argument/result plan, and native-call slot; -- a required handle accepting `None`; -- an optional absent handle without a presence role, or a required handle with - one; -- collapsing optional absence into present-unallocated/present-unassociated - state: an absent handle has null fields and a null presence token, whereas a - present handle may have null `base_addr` but must have a non-null presence - token; -- fact-packed handoff for a projected writable descriptor, or direct persistent - descriptor handoff for a policy that does not permit descriptor mutation; -- direct descriptor handoff without a typed - `_NativeArrayDescriptorHandoff`-compatible runtime operation; -- descriptor dtype, rank, shape, element length, or per-axis field counts that - disagree with the declared handle data facet; -- pointer reassociation, allocation, deallocation, or resize without completed - `PointerPolicy` permission; -- a pointer result without stable owner storage and target lifetime; -- an owned result without wrapper ownership, heap/alias boundary storage, - destroy behavior, owner retention, or a failure-path release action; -- a borrowed module/field handle that claims to destroy native owner storage; -- descriptor-view extraction without its completed C-descriptor build - requirement; -- a C-descriptor header requirement on a generation unit whose completed plans - do not need that interop; and -- any semantic helper temporary represented by a fabricated - `OwnershipDecision`. Call-local CFI variables, decoded-dimension locals, - pointer views, status locals, and operation tables are backend-local emitted - storage inside the already selected method. - -### Phase 7A — Ordinary Array Dummies Accepting Native Handle Actuals - -Included: concrete-rank numeric `T[...]` arguments already supported by Phase 6 -when the runtime value is either a valid ndarray, an allocated allocatable -handle, or an associated pointer handle. The handle path validates the same -dtype, rank, shape, layout, writeability, byte-order, and alignment contract, -then calls the handle's internal `array_actual` operation and packs the existing -Phase 6 pointer/extent/stride ABI. It never calls `.to_numpy()` and never passes -the allocatable/pointer descriptor to the ordinary native dummy. - -Initially excluded: optional, assumed-rank, character, and unsupported -noncontiguous handle actuals. Audit each against live runtime-helper behavior -before widening this sub-lane; a rejected form must remain an explicit blocker, -not silently fall back to `.to_numpy()` or a raw address. - -Those exclusions remain visible as the uncompleted -`array-handle-actuals-excluded` rollout lane. Their direct Phase 6 ndarray -lowerers remain testable with a forced wrapper-plan route, but automatic -production selection stays on the legacy route until each corresponding handle -source has parity evidence. - -Legacy oracle: `CPythonBindingGenerator._native_array_actual_argument_body`, -the normal-array runtime helpers in `prik/runtime/handles.py`, and the existing -Phase 6 bridge array-buffer lowering. Reuse the runtime helpers and bridge ABI; -rewrite only the minimal direct binding call and source-kind branch. - -Plan and lowering requirements: - -- [x] Add `NativeArrayActualPlan` or equivalent accepted-source facts beneath - the existing ordinary `ArgumentTransferPlan`; keep - `PASS_ARRAY_BUFFER`, `ArgumentHandoffMode.ARRAY_BUFFER`, and - `ArrayHandoffPlan` unchanged. -- [x] Make the C binding's named ordinary-array input method call - `_native_array_actual_argument_for_binding_positional` with only planned - validation flags and ABI-field selections. -- [x] Keep the Fortran bridge on the exact Phase 6 array-buffer method; it must - not know whether Python supplied an ndarray or a handle. -- [x] Validate that handle actuals are allocated/associated, have a non-null - data address, and satisfy the same declared contract as ndarray inputs; - preserve allocated/associated zero-length arrays. -- [x] Add the `array-native-handle-actuals` support lane and remove the current - production gate on ordinary array actuals only after reduced compiled parity - proves both runtime source kinds and all rejection paths. -- [x] Reuse the normal-array calls in - `test_module_and_derived_pointer_handles_track_native_association` and - allocatable handle fixtures as the legacy baseline, but extract a class-free, - dependency-closed parity contract so Phase 8 does not determine this lane's - route. - -### Phase 7B — Required Read-Only Descriptor Handle Arguments - -Included: required, non-projected `Allocatable[T[...]]` and -`Pointer[T[...]]` arguments. The Python binding accepts only the matching -runtime handle class. A present unallocated allocatable or unassociated pointer -is still a present descriptor argument and may carry a null `base_addr`. - -The binding uses the existing descriptor runtime helper to obtain validated -standard descriptor facts. The bridge establishes rank-specific call-local CFI -storage from `base_addr`, `elem_len`, rank, and dimension records, then passes -the native allocatable or pointer dummy. This association is an emitted-code -view, not a semantic data copy. - -Legacy oracle: - -- binding `_bind_allocatable_descriptor_argument`, - `_bind_pointer_descriptor_argument`, and - `_bind_fact_packed_native_array_descriptor_argument`; -- bridge `_bridge_allocatable_descriptor_argument`, - `_bridge_pointer_descriptor_argument`, and - `_bridge_native_array_descriptor_argument`; and -- runtime `_native_array_descriptor_argument_for_binding_positional`. - -- [x] Carry the completed `NativeArrayHandlePolicy` and descriptor - `ArrayInteropPolicy` into `ArgumentPolicy`, `ArgumentTransferPlan`, and its - shared native slot. -- [x] Add `ArgumentHandoffMode.NATIVE_DESCRIPTOR` and a - `FACT_PACKED_CALL_LOCAL` descriptor handoff with exact symbolic roles. -- [x] Add directly named C and Fortran descriptor-input methods grouped under - the native-array-handle family; backend-local tuple items and CFI locals may - be created only inside those selected methods. -- [x] Validate matching handle class, descriptor kind, dtype, rank, declared - shape, and element length before the call. Reject ndarray inputs. -- [x] Add separate `allocatable-descriptor-inputs` and - `pointer-descriptor-inputs` support lanes after reduced descriptor-argument - parity passes. -- [x] Replay the descriptor calls in - `test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime` - and the allocatable descriptor fixtures through minimal class-free contracts; - retain the mixed original nodes as legacy until all their later owners migrate. - -### Phase 7C — Optional Absent Descriptor Handles - -Included: `Allocatable[T[...]] | None = ...` and -`Pointer[T[...]] | None = ...` callable arguments. Omission and explicit -`None` both mean native `present(...)` is false. A present handle remains -present even when its descriptor has absent allocation/association state. - -This is a two-level handle-presence contract, not the Phase 3 scalar descriptor -three-state value contract. Do not reuse the value pointer as the presence -token. The runtime helper already produces null fact fields plus null presence -for absence, and a distinct non-null token for every present handle. - -- [x] Project `optional_absent`, `nullable`, presence mode, and the dedicated - presence role from completed handle policy without inspecting the Python - object in planning or bridge code. -- [x] Generate both required and optional fact-packed descriptor calls through - the Phase 7B methods, adding only the planned presence ABI field and native - branch. -- [x] Validate required-versus-optional annotation, field count, presence role, - and the distinction between absent handle and present null `base_addr`. -- [x] Add `optional-native-array-handles` route coverage only after compiled - tests exercise omission, explicit `None`, present allocated/associated, - present unallocated/unassociated, wrong handle kind, and wrong dtype/rank. -- [x] Treat the lack of one isolated compiled optional array-handle fixture as - a coverage gap: create a reduced semantic `.pyi` entry over an existing - native optional descriptor routine instead of inventing behavior from the - runtime-only tests. - -### Phase 7D — Writable And Projected Descriptor Handles - -Included: descriptor arguments whose allocation, deallocation, resize, -reassociation, or nullification must remain visible through the same Python -handle, plus a matching projected result that returns that identical handle. -Allocatable mutation follows completed ownership. Writable pointer descriptor -mutation requires explicit `PointerPolicy` permissions and target-lifetime -facts. - -Fact-packed call-local descriptors are forbidden here because native mutation -would be discarded at return. The binding must request the handle's typed -persistent standard-descriptor pointer and the bridge must pass it directly. -Returning the projection increments/transfers the existing Python reference; it -does not construct a replacement handle or call `.to_numpy()`. - -The direct handoff requires generated persistent standard-descriptor storage. -Wrapper-owned result handles provide it. Borrowed module handles expose current -descriptor facts for read-only calls, but they are not accepted for projected -writable mutation because a reconstructed call-local descriptor would lose the -native descriptor update. - -Legacy oracle: - -- binding `_bind_direct_native_array_descriptor_argument` and - `_bind_projected_native_array_handle_result`; -- bridge descriptor argument dispatch with completed output projection; and -- runtime `_native_array_descriptor_handoff_for_binding_positional`. - -- [x] Add `DIRECT_STANDARD_DESCRIPTOR` handoff and same-handle result identity - to the owning `ArgumentTransferPlan`, shared native slot, `ResultPlan` or - lifecycle consumer, and function-wide result order. -- [x] Reuse `CodegenAction.IN_PLACE_ARGUMENT` and `DIRECT_TRANSFER`; do not add - a descriptor-copy action for same-handle mutation. -- [x] Plan success and failure reference handling so a projected handle is - returned exactly once and borrowed caller storage is never destroyed. -- [x] Validate operation permissions, descriptor ownership, target lifetime, - direct handoff type, result identity, and optional presence before emission. -- [x] Add `projected-native-array-handles` support only after - `test_allocatable_inout_arrays_mutate_and_return_the_same_handle` has a - reduced legacy/direct-plan parity replay covering allocation, reallocation, - deallocation, identity, wrong input types, and native-memory checks. -- [x] Keep writable pointer reassociation blocked unless the completed policy - proves every required permission and lifetime fact; never downgrade it to a - read-only fact-packed call. - -### Phase 7E — Owned Allocatable Results And Hidden Outputs - -Included: allocatable array direct function results and hidden output -descriptors whose completed policy selects `owned_result_descriptor`. -Direct array results preserve allocated, zero-sized, and unallocated state, -including matrices and higher-rank arrays. An allocatable output dummy may -validly remain unallocated and still returns a present `AllocatableArray` handle -whose state lives inside that handle. Pointer handle results remain blocked -until stable owner storage and target lifetime are -explicit. - -For a supported numeric direct allocatable function result, the bridge assigns -the native function expression once into a procedure-local allocatable and then -uses `move_alloc` to transfer its state into the allocatable `intent(out)` dummy -backed by persistent wrapper-owned `CFI_CDESC_T(rank)` storage. The move does -not copy the array payload and preserves an unallocated rank-one result. Do not -insert a collector helper or a second intrinsic assignment. Other -procedure-local storage remains permitted only when representation conversion -genuinely requires it, such as deferred-character byte materialization. The -binding constructs the complete generated operation table and Python handle -only after owner storage is valid. Ownership transfers to the handle exactly -once; every earlier failure path releases persistent storage and any genuinely -required bridge-local allocation. - -Character-element handles carry runtime `elem_len` and declared element-length -policy in the same descriptor record. Because a deferred character width is -unknown until the native result exists, the bridge first copies the bytes and -the binding then establishes and allocates persistent CFI storage with that -runtime width. This is a named lowering method under the same result handle -plan, not a separate string-result ownership hierarchy. - -Legacy oracle: - -- binding `_bind_owned_allocatable_result_handle`, owned-result operation - builders, `_bind_materialized_native_array_handle_result`, and destroy body; -- bridge `_bridge_owned_allocatable_result_handle` plus allocatable result - helper/copy logic; and -- the runtime handle factory and exactly-once `close()`/finalizer protocol. - -- [x] Attach one `NativeArrayHandlePlan` with `OWNED_RESULT_STORAGE` to direct - and hidden `ResultPlan` owners; hidden outputs share their exact descriptor - native slot. -- [x] Use `CodegenAction.WRAPPER_INSTANCE` and an explained - `COPY_REPRESENTATION` only for materialization into persistent owner storage; - source hiddenness remains `source_kind`, not a codegen action. -- [x] Record owner storage, materialization, handle construction, ownership - transfer, destroy behavior, and release responsibility under the result's - typed handle plan. Keep backend-local CFI allocation/copy/free nodes inside - the selected result lowerer rather than fabricating lifecycle policy records. -- [x] Require generated `shape`, `array_actual`, `descriptor`, extraction/state, - allowed mutation, and `destroy` operations before publishing the handle. -- [x] Validate CFI rank, dtype, element length, allocated state, owner - retention, release responsibility, destroy behavior, and all success/failure - paths before emission. -- [x] Add `owned-allocatable-results` and - `owned-allocatable-hidden-outputs` support lanes after reduced parity from - `test_array_results_follow_data_buffer_and_descriptor_handle_contracts`, - `test_output_arguments_and_multiple_results_follow_python_projection_rules`, - and `test_allocatable_module_fields_and_results_expose_lifetime_safe_handles`. -- [x] Keep pointer result tests on their explicit policy blocker; do not make - their matrix rows `wrapper-plan` merely because allocatable results pass. - -### Phase 7F — Borrowed Module Handles And Generated Operations - -Included: rank-positive allocatable and pointer module variables exposed as one -stable borrowed handle object at module initialization. Repeated attribute reads -return the same handle. Replacement assignment is rejected. The generated -operation table accesses current native state and includes only operations -allowed by completed policy. - -Allocatable operations include allocation state, shape, array actual, -descriptor handoff, extraction, deallocation, and resize where allowed. Pointer -operations include association state, shape, array actual, descriptor handoff, -nullification, extraction, and policy-gated allocation/deallocation/resize. -Borrowed module handles retain the Python module and never destroy native-owned -descriptor storage. - -Deferred-character handles also expose runtime `element_length`. Shape-only -`allocate` and `resize` operations are omitted because they cannot state the -new character width; native procedures that declare the width remain the -authoritative mutation path. - -Legacy oracle: the bridge's `_native_array_module_handle`, -`_native_array_module_handle_operations`, and operation-specific module methods; -the binding's `_bind_borrowed_native_array_module_handle`, operation wrappers, -and handle creation; and the current runtime handle factory. - -- [x] Add a native-handle getter action and one `NativeArrayHandlePlan` beneath - `ModuleVariablePlan`; keep Python attribute exposure and native operation - generation in its binding and bridge child views. -- [x] Plan operation roles and export names explicitly while leaving operation - call locals backend-local. Do not store generated method names in the plan. -- [x] Validate stable handle identity, module owner retention, rejected - replacement, descriptor kind, operation completeness, and borrowed/no-destroy - lifecycle. -- [x] Add `allocatable-module-handles` and `pointer-module-handles` support - lanes only after module-only reduced parity covers state changes, zero-length - state, extraction policy, operation permissions, stale-view behavior, and - module lifetime. -- [x] Use `test_allocatable_module_fields_and_results_expose_lifetime_safe_handles`, - `test_plain_allocatable_module_array_exposes_current_live_view`, - and the module portion of - `test_module_and_derived_pointer_handles_track_native_association` as legacy - oracles. Split out field/class assertions, which remain Phase 8/9 work. - -#### Phase 7F Contract Correction — View-Only Module Extraction - -The checked Phase 7F items above record the original migration slice; they do -not close this changed public contract. Complete this correction before Phase -8 implementation. - -- [x] Update public docs, maintainer docs, generated/checked semantic `.pyi` - evidence, and wrapper coverage rows to specify current live view or `None`, - explicit `.copy()`, and the unsupported stale-view window. -- [x] Complete plain and `Aliased` allocatable module arrays as native-owned - borrowed handles with the same extraction result. Keep addressability, - descriptor mechanism, owner retention, mutability, nullability, storage, - operation permissions, and release responsibility as separate completed - facts. -- [x] Remove `read_only_detached_copy` and extraction-only `copy_only` policy, - plan, runtime, binding, and bridge dispatch. Preserve only typed live-view - mechanisms such as contiguous or standard-descriptor views; unsupported - extraction fails instead of copying. -- [x] For a plain allocatable module array, add the completed standard- - descriptor module-state mechanism needed to inspect the current allocation - on each extraction. Keep it beneath `ModuleVariablePlan`; do not retain a - descriptor or data address as if it were permanently current. -- [x] Keep binding/bridge ownership explicit: the bridge exposes current native - descriptor facts without NumPy knowledge, and the binding validates - dtype/rank/shape/strides and creates the NumPy view with its handle owner as - the base. Native-handle argument handoff must not call `to_numpy()`. -- [x] Replace the obsolete read-only-copy test with source/generated-`.pyi` - parity covering plain and `Aliased` live mutation, allocated/unallocated and - associated/unassociated state, fresh extraction after state changes, - explicit-copy independence, stale-view documentation, parent/owned-result - retention, and contiguous/strided pointer views. -- [x] Rerun focused policy/plan/backend/runtime tests, documentation checks, - the wrapper suite excluding LAPACK, the wrapper-codegen complexity checker, - and the required static-analysis suite before closing Phase 7 again. - -### Phase 7G — Pointer Descriptor Extraction And Build Requirements - -Included: pointer `descriptor_view`, `contiguous_view`, and explicitly -unsupported extraction actions already selected by completed policy; standard -descriptor decoding; positive and negative strides; and local build/header -requirements. A `copy_only` `to_numpy()` action is obsolete and must not reach -the corrected plan. - -Descriptor views, the corrected plain allocatable module-state path, and -persistent allocatable owner storage require standard C descriptor support. -Generated code may read `CFI_cdesc_t` through `ISO_Fortran_binding.h` when the -completed plan requests it. It must never guess or expose a compiler-private -descriptor layout. Unsupported toolchains fail planning/build with the -completed owner path and requirement. - -- [x] Carry typed extraction and descriptor-interop actions plus required - headers into handle/module/result plans and rendered artifact metadata. -- [x] Reuse the runtime descriptor-view helper for shape, stride, buffer-window, - dtype, rank, and null-address validation; direct C lowering only decodes the - standard descriptor fields into its expected mapping. -- [x] Add directly named C descriptor-reader and operation-wrapper methods; - decoded dimension objects and mapping temporaries remain binding-local. -- [x] Validate that build requirements equal the union of completed plans, - appear in replayable manifests, and do not leak into wrappers that need only - ordinary buffers or non-CFI borrowed allocatable handles. -- [x] Replay - `test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime` - and `test_pyi_manifest_records_pointer_descriptor_interop_requirements`, plus - focused `tests/runtime/handles`, before enabling - `pointer-descriptor-extraction`. -- [x] Preserve the explicit planning failure when required C descriptor - support is unavailable; no contiguous-copy fallback may be inferred in the - backend. - -### Phase 7H — Remaining Rank-Zero Descriptor Results And Strings - -Phase 3 already owns ordinary and optional scalar descriptor inputs, including -omitted/present-null/present-value state. Phase 4 already owns nullable scalar -descriptor module reads as copied Python snapshots. Do not rebuild those paths -or turn rank-zero descriptors into runtime handle objects. - -Included here: direct scalar descriptor function results, hidden scalar -descriptor outputs, projected scalar descriptor readback, and allocatable or -pointer scalar character results with runtime/deferred length. The Python result -is `T | None` or `String | None`; absent allocation/association returns `None`. -An allocated/associated value is copied exactly once before the call-local or -native descriptor is released. Deferred-length strings use runtime element -length and preserve the existing encoding/byte contract. - -- [x] Add a subordinate scalar-descriptor handoff/result record to the existing - scalar or string `ArgumentTransferPlan`/`ResultPlan`; do not attach - `NativeArrayHandlePlan` or use `ObjectKind.NUMPY_ARRAY` for rank zero. -- [x] Complete result source, descriptor kind, presence, runtime element length, - copy action/reason, release owner, and failure cleanup in wrapper policy before - planning. -- [x] Reuse existing Phase 3 presence records and typed lifecycle ordering; - extend named scalar/string result lowering only for the descriptor producer - and copy/release steps. -- [x] Validate direct versus hidden descriptor source, nullable result spelling, - result ordering, runtime string length, null state, copy count, and cleanup on - conversion/status failure. -- [x] Add isolated legacy/direct parity for numeric allocatable and pointer - results and for `string_result_deferred` from - `test_modern_fortran_character_arguments_and_results`, including an absent - result and non-ASCII encoded data. -- [x] Keep pointer array results blocked even after pointer scalar values pass; - copied scalar readback does not prove array target lifetime. - -### Derived Fields Remain A Recorded Later Dependency - -The shared handle plan must be capable of recording -`borrowed_field_descriptor`, `owner_retention=parent_wrapper`, field operation -roles, and parent-owned destruction behavior. Do not add field/class traversal -or route eligibility in Phase 7. `BindCNativeArrayHandleProperty`, field -operation generation, and the field portions of allocatable/pointer tests remain -legacy oracles for Phases 8 and 9, where the owning wrapper instance and property -lifecycle exist in the plan. - -This boundary prevents Phase 7 from either duplicating future `FieldPlan` -ownership or falsely marking mixed module-and-field generation units supported. - -### Legacy Primitive Inventory And Rewrite Rule - -| Primitive | Legacy source | Direct-plan treatment | -| --- | --- | --- | -| Normal array handle actual | binding `_native_array_actual_argument_body`; runtime normal-array helpers | reuse runtime helper and Phase 6 bridge ABI; rewrite minimal binding nodes | -| Required/optional descriptor argument | binding descriptor argument helpers; bridge descriptor handlers | rewrite named direct methods around shared runtime packer and planned CFI roles | -| Projected writable descriptor | binding direct descriptor handler; bridge descriptor projection | rewrite direct pointer handoff and identity lifecycle; no fact-packed fallback | -| Owned allocatable result | binding owned-result/operation helpers; bridge allocatable result helper | rewrite minimal CFI owner-storage and result lifecycle nodes; assign once locally and transfer the allocation into the CFI-backed output dummy with `move_alloc` | -| Borrowed module handle | binding handle creation/operation wrappers; bridge module operations | rewrite under `ModuleVariablePlan`; reuse runtime factory | -| Pointer descriptor view | binding descriptor reader; runtime view helper | reuse runtime view helper; rewrite only standard-descriptor decoding nodes | -| Scalar descriptor result | legacy scalar descriptor/result conversion | extend existing scalar/string plan route; do not create an array handle | -| Build requirement | semantic `native_array_handle_build_requirements`; build manifest | reuse completed requirements and carry them through rendered artifacts | - -For every primitive, first retain generated legacy C/Fortran/header artifacts -from the cited passing wrapper test. Explain each material direct-plan artifact -difference before compilation. Copy a small legacy method only when it already -matches the direct node API and complexity limit; otherwise rewrite the minimal -equivalent. Do not copy legacy dispatcher classes, scope mutation machinery, or -datatype/policy inference. - -### Route And Test Migration Matrix For Phase 7 - -Mixed rows retain their later derived/field owners. The dependency-closed -Phase 7 rows were split, proved through both routes, and then recorded as -`wrapper-plan` in the complete ledger. - -| Existing node or group | Current role/status | Phase 7 owner and target | -| --- | --- | --- | -| `arrays/test_array_results.py::test_array_results_follow_data_buffer_and_descriptor_handle_contracts[*]` | ordinary/allocatable result generation unit; `wrapper-plan` | Phase 6 ordinary and Phase 7E allocatable results now share the production plan route | -| `../../fortran/pointers/pipeline/test_pointer_build_manifest.py::test_pyi_manifest_records_pointer_descriptor_interop_requirements` | non-generating manifest policy; `not-applicable` | Phase 7G plan/header union is covered by direct generated-artifact tests | -| `derived_types/test_pointers.py::test_module_and_derived_pointer_handles_track_native_association[*]` | module, normal-array actual, and field mix; `legacy` | split Phase 7A/7F module subsets; field subset remains Phase 8/9 | -| `derived_types/test_pointers.py::test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime` | module/field descriptor views; `legacy` | Phase 7B/7G module subset; field owner remains Phase 8/9 | -| `derived_types/test_pointers.py::test_pointer_array_results_use_owned_descriptors_without_owning_targets[*]` | owned pointer-result descriptor support; `wrapper-plan` | replaces the former owner-policy blocker after descriptor ownership and target lifetime became explicit | -| `edit_pyi_contracts/test_ownership_contracts.py::*` | module, field, result lifetime mix; `legacy` | Phase 7E/7F subsets; field/finalizer owners remain Phase 8/9 | -| `function_calls/test_optional_arguments.py::test_optional_allocatable_scalar_descriptor_distinguishes_omitted_none_and_value` | scalar baseline; `wrapper-plan` | reuse Phase 3 behavior; no status change | -| `function_calls/test_output_arguments.py::test_output_arguments_and_multiple_results_follow_python_projection_rules[*]` | mixed scalar/array/string/derived/allocatable outputs; `legacy` | Phase 7E reduced allocatable result; retain mixed row | -| `module_state/test_allocatable_replacement.py::*` | projected same-handle descriptor mutation plus a derived factory generation unit; `legacy` | Phase 7D reduced parity is `wrapper-plan`; the broad factory/class unit remains Phase 8/9 | -| `module_state/test_allocatable_views.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles[*]` | module, result, and derived-field mix; `legacy` | field/class owner retention remains Phase 8/9 | -| `module_state/test_allocatable_views.py::test_scalar_descriptor_module_variables_return_copied_optional_values[*]` | scalar descriptor arguments/results/module state; `wrapper-plan` | source conversion records descriptor kind and argument/return reference before completed Phase 7H policy | -| `module_state/test_allocatable_views.py::test_plain_allocatable_module_array_exposes_current_live_view[*]` | corrected source/generated-`.pyi` production-plan evidence | proves Phase 7F plain/`Aliased` current live-view or `None` parity, native mutation, explicit-copy independence, and fresh extraction after state changes | -| `strings/test_character_arguments.py::test_modern_fortran_character_arguments_and_results[*]` | fixed strings plus deferred allocatable result; `legacy` | Phase 7H reduced deferred-result parity; retain mixed row as needed | -| `edit_pyi_contracts/test_native_order_contracts.py::test_editable_contract_can_use_native_order_arguments_without_native_call` | includes raw `Addr(Float64[n])` plus a derived result; `legacy` | raw-array subset is the completed Phase 6G prerequisite; derived subset remains Phase 8 | - -| Completed sub-lane | Dependency-closed compiled evidence | -| --- | --- | -| Phase 7A, 7B, 7F, and 7G | `derived_types/test_pointers.py::test_module_native_array_handles_use_canonical_plan` | -| Phase 7C | `function_calls/test_optional_arguments.py::test_optional_array_descriptors_preserve_presence_and_storage_state` | -| Phase 7D | `module_state/test_allocatable_replacement.py::test_projected_allocatable_descriptor_preserves_same_handle_identity` | -| Phase 7E numeric | `arrays/test_array_results.py::test_owned_allocatable_results_preserve_handle_state` | -| Phase 7E deferred character | `strings/test_character_arguments.py::test_deferred_character_array_handles_use_canonical_plan` | -| Phase 7H numeric | `scalars/test_scalar_boundary_plan.py::test_scalar_descriptor_results_copy_values_or_none_through_wrapper_plan_route` | -| Phase 7H deferred scalar character | `strings/test_character_arguments.py::test_deferred_allocatable_string_results_use_canonical_plan` and the nullable case in the deferred-character handle test | -| Phase 7H source/default projection | `module_state/test_allocatable_views.py::test_scalar_descriptor_module_variables_return_copied_optional_values[*]` | - -Required focused intermediate coverage includes: - -- completed semantic handle/interop policy projection tests; -- editable plan tests for every descriptor kind, handoff form, operation set, - ownership, optional presence state, and lifecycle edit; -- C and Fortran preflight rejection of mismatched or incomplete descriptor - plans; -- generated artifact assertions for standard descriptor fields, optional - presence, operation functions, owner storage, destroy paths, and local header - requirements; -- runtime helper tests under `tests/runtime/handles` without duplicating their - validation in wrapper-codegen tests; and -- compiled legacy/direct parity for each reduced sub-lane before any migration - ledger or production-route change. - -### Phase 7 Completion - -- [x] Expand Phase 7 under the mandatory gate using the live completed policy, - runtime handle implementation, legacy backends, build integration, public - contract, and focused wrapper tests. -- [x] Complete the shared typed handle, array-actual, and descriptor-handoff - plan records without adding a parallel top-level plan hierarchy. -- [x] Complete every missing semantic selector before `ir2ast.py`; remove - bridge-created `ArrayInteropPolicy` and fabricated semantic ownership choices. -- [x] Finish Phases 7A through 7H individually with legacy artifact capture, - direct lowering, validation, compiled parity, route evidence, and matrix - updates. -- [x] Preserve the completed Phase 6G raw-address boundary while keeping every - derived-field, pointer-result, callback, and deferred-real-library exclusion - on its explicit later blocker. -- [x] Complete the Phase 7F view-only correction for plain and `Aliased` - allocatable module arrays and remove every implicit-copy extraction path. -- [x] Rerun focused policy/plan/backend tests, relevant runtime-handle tests, - documentation checks, the wrapper suite excluding LAPACK, the wrapper-codegen - complexity checker, and the required static-analysis suite after the - correction. -- [x] Close Phase 7 only when every live non-field native-handle/descriptor case - is migrated or explicitly removed from the product contract, and no backend - infers descriptor policy or silently substitutes a data buffer, raw pointer, - `.to_numpy()` extraction, or copy fallback. - -Historical pre-correction evidence: 538 focused semantic/policy/plan/backend/ -runtime tests, 1,133 documentation and layout tests, and all 318 wrapper tests -outside the deferred BLAS/LAPACK file passed. The wrapper-codegen complexity -check, Ruff, formatting, Bandit, Vulture, whitespace, and explicit-base Radon -policy also passed. This evidence remains valid for unaffected sub-lanes but is -not closure evidence for the changed view-only extraction contract. Record a -new success signal after the Phase 7F correction. - -Post-correction closure evidence (2026-07-14): 214 focused runtime-handle, -policy, planning, lowering, legacy-dispatch, and Phase 7 direct-plan tests; -199 complete `tests/codegen` tests; 1,123 documentation tests; 317 -wrapper tests outside the shared real-library parameter plus the BLAS-only -parameter; and zero locally executed LAPACK tests all passed. The wrapper -complexity checker, Ruff lint/format, Bandit, Vulture, whitespace, and the -explicit-`origin/main` Radon policy passed. The required `--base-ref auto` -Radon invocation could not resolve a CI base SHA locally; the explicit base -rerun passed. Advisory full Radon complexity and maintainability reports were -also produced. - -Phase 7 was re-verified again with the final Phase 8 closure run on -2026-07-15: the 704-test semantic/policy/plan/backend regression batch, all 79 -runtime-handle tests, 1,123 documentation tests, and all 326 wrapper tests -outside LAPACK passed. No LAPACK test was run locally. - -## Phase 8 — Derived Types And Object Lifetimes - -Expansion status: complete. Implementation proceeds only after the Phase 7 -view-only correction is re-verified. - -Implementation status: reopened for the complete rank-zero scalar-derived -actual/dummy compatibility matrix in Phase 8H. The previous Phase 8A-I -evidence remains authoritative for unaffected fields and lifecycle paths, but -the old module-allocatable rejection, nonreassociating pointer-only path, -interoperable-only value restriction, and incomplete module-object call routes -are superseded. Phase 8 must not close again until direct, scoped-address, -wrapper-holder, module-transaction, pointer-input, and typed-value actions are -implemented without a fallback and re-verified with multi-argument calls. - -Scope: migrate scalar derived-type storage, arguments, results, borrowed -objects, and field handoffs into the wrapper-plan route. Phase 8 owns the -opaque native-instance substrate and the typed transfers that use it. Phase 9 -owns public constructors, methods, overloads, inheritance, and general -class-surface orchestration built on that substrate. Phase 8 owns public field -descriptors and their typed getters/setters because every live object origin, -including plain module proxies, needs the same readable and writable member -surface. - -Do not begin implementation while a Phase 7 native-array-handle correction is -open. In particular, Phase 8 must consume the final view-only `to_numpy()` -contract: an array-handle extraction is a live view or `None`, and an -independent array is obtained with an explicit `.copy()`. - -`Snapshot[T]` is no longer an active public contract. Plain and `Aliased` -rank-zero derived module variables both expose the normal live generated object -surface. Their lowering mechanisms remain distinct: `Aliased` proves a direct -address-backed borrow, while a plain declaration requires typed module-specific -bridge access and must not fabricate a native address. `Aliased` remains an -addressability and aliasing fact for raw-address legality, pointer association, -C-pointer policy, and direct derived-object handoff; it does not select -array-handle `to_numpy()` behavior. - -### Phase 8 Boundary And Explicit Non-Scope - -The first implementation slice is rank-zero, non-polymorphic derived values. -The runtime wrapper is opaque: the binding carries a native address, ownership -state, and an optional retained Python owner, while the bridge performs typed -native association, assignment, allocation, and destruction. The binding must -not depend on component offsets or reproduce native aggregate layout. - -The following surfaces are in Phase 8: - -- required and optional scalar derived arguments; -- visible `out` and `inout` wrappers whose identity remains caller-visible; -- hidden output and direct-function-result values materialized as - wrapper-owned instances; -- native `value` arguments for an exact rank-zero monomorphic derived type, - using a Fortran bridge-owned typed value copy rather than C-side layout - inference; the native type need not be `bind(C)` when the bridge imports its - exact definition; -- derived `parameter` and other explicit constant-value origins materialized - through the existing wrapper-owned immutable-value path, never as a fallback - for an ordinary mutable module object; -- plain rank-zero native module objects exposed as live module-backed proxies - through typed bridge operations; -- `Aliased` rank-zero native module objects exposed as live borrowed wrappers; -- borrowed nested component wrappers, their public field descriptors, and the - owner-retention facts required by those descriptors; -- Phase 7 allocatable/pointer field-handle plans attached to a derived owner; -- exact destruction, finalization, cleanup, and failure ownership for each of - those origins. - -The following remain outside Phase 8: - -- public default/keyword constructors, explicit `@bind(...)` constructors, - `tp_init`, methods, static methods, overload dispatch, Python inheritance, - and ordinary type-bound surface assembly; these remain Phase 9. Public field - descriptors, getters, and setters are Phase 8 and are not a Phase 9 blocker. - A generated semantic `.pyi` field constructor is therefore a whole-unit - Phase 9 blocker; only an opaque contract that suppresses default construction - may use the direct Phase 8 object route; -- scalar polymorphic dispatch and inheritance even where the legacy route - supports them; Phase 9 owns the class relationship needed to validate the - accepted runtime type set; -- callback-derived arguments and results, adapter procedures, and trampoline - ownership; these remain Phase 10; -- arrays of derived values, whose element layout, construction, destruction, - copy, and partial-failure behavior remain explicit planning errors; -- polymorphic results, mutable polymorphic arguments, `class(*)`, abstract - instantiation, deferred bindings, and allocatable/pointer polymorphic - scalars; -- polymorphic descriptor-backed scalars. Wrapper-owned allocatable and pointer - holders plus scalar derived module ordinary/`TARGET`/allocatable/pointer - variables are supported only by their explicit Phase 8H matrix rows. A - pointer holder owns its association container, never its target by default; - target retention and native release responsibility are completed separately - before lowering. Module allocation and pointer transactions use shared typed - holder addresses in interoperable callbacks, never CFI or a compiler-private - descriptor; -- any other derived origin that cannot use one of the explicit matrix rows. It - remains blocked rather than being silently turned into an address-backed - borrow or detached object; -- C-side aggregate casts, `ctypes` layout promises, compiler-private descriptor - inspection, or direct component offsets; -- ownership of targets reachable through pointer components. A containing - derived wrapper does not own such a target without completed pointer policy; - and -- detached whole-object snapshot classes or recursive member-copy graphs. They - are removed rather than retained as a compatibility path. - -### Public Representation And Lifetime Matrix - -Complete this matrix in post-IR policy before adding planner or backend code. -The rows are distinct origins, not datatype guesses made during lowering. - -| Surface | Python representation | Native handoff/storage | Owner and release | -| --- | --- | --- | --- | -| required `in` argument | existing wrapper instance | pass its opaque wrapper address and associate a typed native view for the call | wrapper remains owned by its existing Python object; call destroys nothing | -| required visible `inout` or caller-supplied `out` | same wrapper instance | pass the same address for native mutation | caller-visible wrapper retains identity; its normal wrapper finalizer remains the sole destroyer | -| optional argument, omitted or `None` | no wrapper instance | explicit absence token/branch; no fabricated native object | no allocation or cleanup | -| optional argument, present | validated wrapper instance | same typed address handoff as the required case | existing wrapper owner remains responsible | -| hidden output | new opaque wrapper object | allocate persistent wrapper-owned native storage before the call and pass its address | wrapper deallocator invokes native-aware destruction exactly once | -| direct function result | new opaque wrapper object | move or copy the native result before its temporary expires into persistent wrapper-owned storage | wrapper deallocator invokes native-aware destruction exactly once | -| constructor-created instance | Phase 9 only | Phase 9 must allocate through the same persistent wrapper-owned storage and native-aware destructor established here | explicitly blocked until Phase 9 class construction orchestration; no Phase 8 fallback constructor | -| native `value` input | existing wrapper instance | exact Fortran bridge passes the typed pointee to the native by-value slot; C never lays out or copies the aggregate | call-local native copy only; wrapper ownership is unchanged | -| plain rank-zero module variable | normal live generated object | module-specific typed getter/setter operations plus a synchronous scoped-address consumer when the object is passed to another procedure | native module owns storage; wrapper retains the module and never destroys storage; a temporary target/address cannot escape its consumer scope | -| `Aliased` or explicit `TARGET` rank-zero module variable | live borrowed wrapper | use `C_LOC` as the sole whole-object handoff; reconstruct the exact typed bridge view without copying | native module owns storage; wrapper never destroys it and rejects replacement | -| derived `parameter` or other explicit constant-value origin | wrapper-owned value copy with an immutable module binding | materialize the native value into persistent wrapper-owned storage | wrapper destroys only its materialized copy; no native module setter; normal writable fields modify only that independent copy | -| nested derived field | live borrowed child wrapper | address/alias of the component through the parent wrapper | child retains parent; child never destroys component storage | -| allocatable scalar derived module origin | nullable live module-backed proxy carrying its runtime origin | scoped-address consumer for payload-only calls; for an allocatable dummy, module-specific interoperable operations move between the module variable and a bridge-local shared typed holder addressed by `C_PTR` | native module owns storage before and after a transaction; successful move-out has exactly one reverse-order move-back; no descriptor crosses C | -| pointer scalar derived module origin | nullable live module-backed proxy carrying its runtime origin | current-target address for payload-only calls; for a pointer dummy, a bridge-local shared typed pointer holder receives the initial association and its address is passed to the module-specific restore operation | native module owns the pointer variable and, by default, its target; final association is restored exactly once after a normally returning native call | -| wrapper-owned allocatable scalar derived result | nullable live generated wrapper backed by one persistent typed allocatable holder per native type | result is moved into `holder%value`; ordinary, target, allocatable, allocatable-target, pointer-input, and value dummies use the explicit compatible matrix actions | each Python wrapper owns one target-capable holder and destroys it exactly once; allocation-state writeback preserves wrapper identity | -| wrapper-owned pointer scalar derived result | nullable live generated wrapper backed by one persistent typed pointer holder per native type | holder component stores current association and is passed directly to a compatible pointer dummy; payload-only calls use its associated target | wrapper owns and destroys only the holder; target ownership stays native unless completed policy retains a known wrapper/module target; destruction nullifies the component and never deallocates an unowned target | -| detached whole-object snapshot | removed | no recursive copy graph or snapshot helper is generated | no compatibility parser, lowering, or fallback; read the live object through normal fields instead | - -`Aliased` remains a public, language-neutral addressability/aliasing fact and -must survive parsing, semantic IR, and printing. For derived module objects it -distinguishes direct-address lowering from module-proxy lowering, not live -versus copied public behavior. It must never be reused to select live versus -copied native-array-handle extraction. - -### Existing Semantic Authority And Legacy Oracle - -Use the current implementation as an oracle, not as permission to preserve its -architecture: - -- `prik/policy/ownership.py` already names `DERIVED_TYPE`, - `PASS_WRAPPER_ADDRESS`, `WRAPPER_INSTANCE`, and `BORROWED_VIEW`, and contains - the current argument/result/module/field owner defaults. Remove the obsolete - derived whole-object snapshot action without disturbing ordinary result - copies, scalar descriptor value copies, or explicit non-object uses of - `snapshot_copy` transfer policy. -- `prik/policy/completion.py` is the only allowed owner of origin, - ownership, transfer, destruction, mutability, nullability, projection, - release, storage, getter/setter, owner-retention, module-object handoff, and - field decisions. It must complete module-proxy policy for plain module - objects and direct-address borrowed policy for `Aliased` module objects. -- `prik/policy/construction.py` must gain a derived-specific policy branch. - Derived values must not continue through primitive-scalar blockers, - primitive result checks, or primitive bridge data-action selection. -- `prik/semantics/ir2ast.py` and the legacy generators remain the generated - artifact oracle. Direct lowering must not call `semantic_ir_to_codegen_ast()` - or reconstruct legacy codegen variables. -- `prik/codegen/bindings/c_to_python.py` contains the existing wrapper-instance - conversion, checked casts, owned/borrowed result construction, owner - retention, and allocator/destructor helpers. Remove recursive snapshot - construction rather than migrating it into the direct route. -- `prik/codegen/bridges/fortran_to_c.py` contains the existing typed wrapper - address conversion, native result materialization, borrowed field/module - access, native-aware destruction, and typed component getters/setters. Reuse - those live member-access mechanics as the artifact oracle while moving every - decision into typed plans. - -Capture complete legacy artifacts before each direct slice. Preserve observable -runtime behavior while replacing backend inference with completed typed plans. -Do not copy the broad legacy generator control flow into `codegen`. - -The existing wrapper tests decompose as follows: - -| Existing test or generation unit | Phase 8 oracle | Required split or later owner | -| --- | --- | --- | -| `function_calls/test_native_call_examples.py::test_native_call_examples_cover_scalar_array_string_and_object_projection[*]` | hidden derived result selected by `Return(...)` | add a reduced object-result entry; retain the mixed unit until every included lane is direct | -| `function_calls/test_output_arguments.py::test_output_arguments_and_multiple_results_follow_python_projection_rules[*]` | hidden derived output and mixed result aggregation | add a reduced derived-output entry; retain the broad unit until its complete tuple is direct | -| `edit_pyi_contracts/test_policy_dispatch_contracts.py::test_immutable_scalar_string_array_and_derived_policies_return_replacements` | edited projected derived replacement | isolate `make_point` as Phase 8 evidence; retain the mixed policy unit until whole-unit eligibility follows | -| `derived_types/test_derived_type_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[*]` | required input, in-place mutation, hidden/direct result, nested borrowed component | reduce first to result-created opaque objects passed back to `point_sum`/`move_point`; field descriptors and nested borrowing are Phase 8, while construction remains Phase 9 | -| `function_calls/test_optional_arguments.py::test_optional_arguments_drive_fortran_present_behavior[*]` | optional derived input and exact type/absence behavior | complete the optional transfer in Phase 8; the existing constructor-dependent broad runtime unit remains Phase 9 until it can route whole | -| `module_state/test_module_state.py::test_aliased_derived_module_object_borrows_native_state[*]` | native-owned borrowed module object and replacement rejection | use as the direct-address oracle; add a reduced plain-module proxy case with the same live field behavior; methods remain Phase 9 | -| `derived_types/test_borrowed_finalizers.py::test_borrowed_child_wrapper_never_finalizes_native_component[*]` | parent retention and exactly-once owner finalization | Phase 8 owns storage/lifetime plans and public field descriptors; constructor/method orchestration remains Phase 9 | -| `module_state/test_allocatable_views.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles[*]` | Phase 7 field handle attached to a derived owner | reuse the existing `NativeArrayHandlePlan` and expose its public property in Phase 8 | -| `derived_types/test_pointers.py::test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime` | pointer field handle and parent lifetime | reuse Phase 7 descriptor extraction; do not move pointer target ownership into Phase 8 | -| `derived_types/test_derived_layout.py::test_bind_c_derived_types_use_accessors_and_fortran_value_copy[*]` | opaque `bind(C)` wrapper, field accessors, and typed native `value` copy | Phase 8 owns the handoff and field properties; constructor orchestration remains Phase 9 | -| former `module_state/contracts/fmodule_derived_snapshot_f90/` snapshot fixture | obsolete detached-object behavior | remove the `Snapshot[box]` fixture and snapshot-only runtime assertions; reuse the native unit only for reduced live module-proxy evidence where applicable | -| `derived_types/test_constructors_and_finalizers.py::*`, `derived_types/test_derived_type_methods.py::*`, and `derived_types/test_inheritance.py::*` | owned-instance finalizer and type facts may inform Phase 8 | production migration remains Phase 9 because the observable unit is constructor/method/property/inheritance owned | -| `tests/fortran/callbacks/end_to_end/test_supported_callback_shapes.py::*` | none | callback-derived transfers remain callback-owned after ordinary derived transfers are complete | - -The plain non-target module-object row has one recorded intentional correction: -the legacy whole-object getter attempts `c_loc` on storage without the required -addressability property and therefore has no passing whole-object artifact. -Phase 8 uses the real source declaration, the passing legacy typed component -getters/setters, and the passing `Aliased` direct-address behavior as its -mechanical oracles, then improves the plain path to a typed member proxy. The -compiled Phase 8 evidence asserts that this proxy never emits a fabricated -whole-object `c_loc` while its reads and writes remain live. - -### Mandatory Phase 8 Migration Algorithm - -Apply this same sequence to every dependency-closed sub-lane. A checked item in -a later step cannot compensate for an incomplete earlier step. - -1. Capture the complete generated artifacts and runtime assertions from one - real passing legacy/source case. Record which constructor/method or - callback assertions remain outside the reduced unit. -2. Complete object kind, origin, ownership, transfer, destruction, mutability, - nullability, projection, storage, release, owner retention, getter/setter, - native assignment, and any module-object mechanism before `ir2ast.py`. -3. Project those facts mechanically into `ArgumentTransferPlan`, `ResultPlan`, - or `ModuleVariablePlan`, with native slots and lifecycle actions remaining - subordinate references. -4. Validate type identity, roles, actions, owners, storage, result positions, - releases, and cross-backend handoffs before either backend emits source. -5. Lower through small named binding and bridge methods selected by typed - object kind and action. Backend-local temporaries remain implementation - details inside the already selected method. -6. Compare binding, bridge, header, and build artifacts with the captured - oracle and explain every intentional difference before compiling. -7. Add focused policy, plan-edit, validation, printer, backend, runtime, - documentation, and source/generated-`.pyi` parity tests. -8. Promote the reduced generation unit only after compiled legacy/direct parity - passes; otherwise retain one exact blocker without a fallback route. - -The maintainer trace is therefore always: - -```text -completed semantic facts - -> typed argument/result/module-variable plan - -> subordinate native slots and lifecycle actions - -> validation - -> binding and bridge lowering - -> generated artifacts - -> compiled runtime evidence -``` - -### Plan Shape And Stable Action Vocabulary - -Do not create a second function plan, a second result hierarchy, or a rendered -derived-plan layer. Extend the existing plan tree as follows: - -- add one explicit derived datatype family or equivalent non-primitive marker - so a derived semantic type never indexes the primitive scalar dtype maps; -- add a concise namespace-owned derived-type definition record containing - canonical semantic/native identity, native scope, Python exports, opaque - runtime type symbol, allocation role, destruction/finalization role, and the - minimal field identities needed by later field plans; -- add one `DerivedHandoffPlan`-style facet, analogous to `ArrayHandoffPlan`, to - `ArgumentTransferPlan`, `ResultPlan`, `ModuleVariablePlan`, and the owning - `NativeCallSlotPlan` only where that transfer needs it; -- give a derived `ModuleVariablePlan` one typed module-object access facet that - records the completed direct-address or opaque-callback mechanism, its - context/address roles, compiler capability, and module-lifetime owner. This - mechanism is subordinate to the module-variable policy and must not change - its public borrowed-wrapper facts; -- keep native slot order, symbolic roles, and ABI positions subordinate to the - owning argument or result transfer; -- represent result destruction, failed-construction cleanup, parent retention, - through transfer-owned `LifecycleActionPlan` records in function-wide - execution order; -- add field-handoff records beneath the owning derived-type definition. Do not - put their ownership decisions into a backend registry; and -- keep `FunctionPlan`, `ModulePlan`, namespace assembly, result ordering, GIL - envelope, and status-error behavior stable. - -Reuse the existing action vocabulary: - -- Python boundary: `WRAPPER_INSTANCE` for accepted live wrapper objects and - `NONE` for native-produced results; -- native boundary: `PASS_WRAPPER_ADDRESS` for opaque live objects and `NONE` - when the bridge itself owns result production; module-backed proxies use a - distinct typed module-origin handoff rather than a fabricated address; -- transfer/codegen: `CALL_LOCAL_INPUT`, `IN_PLACE_ARGUMENT`, - `IDENTITY_OUTPUT`, `WRAPPER_INSTANCE`, and `BORROWED_VIEW` according to the - completed matrix row; -- bridge data: `DIRECT_TRANSFER`, `ASSOCIATE_VIEW`, or - `COPY_REPRESENTATION`, with a completed copy reason only when a real native - representation copy occurs; and -- lifecycle: existing ordered copy-in/native-mutation/copy-out/cleanup phases, - extended only with a genuinely missing release phase/action rather than a - derived-only parallel lifecycle system. - -If one of these actions cannot express a required operation, document the -missing semantic distinction before adding exactly one typed action. Do not use -method-name strings, datatype conditionals, `intent`, `is_alias`, dotted-name -shape, or local temporary existence as hidden dispatch. - -### Binding, Bridge, And Validation Ownership - -| Layer | Owns | Must not own | -| --- | --- | --- | -| post-IR policy | origin, dynamic/static type allowance, owner, transfer, destruction, mutability, projection, nullability, storage, owner retention, getter/setter behavior, and blockers | emitted local names or source syntax | -| wrapper planner | mechanical projection into derived type/handoff facets, native roles, ordered results, and lifecycle indexes | new ownership or lifetime decisions | -| binding lowering | exact Python type checks, opaque wrapper address extraction, Python wrapper allocation, retained-owner references, result aggregation, and Python reference cleanup | native component layout, native assignment, or native finalization semantics | -| bridge lowering | typed association from opaque addresses, exact Fortran-owned `value` calls, native instance allocation/assignment, module/component access, and native-aware destruction/finalization | Python classes, C aggregate layout, reference counting, detached-copy fallback, or ownership inference | -| plan validation | matching type identity, roles, actions, owners, releases, result positions, and cross-backend handoffs before emission | fallback selection | - -Validation must reject at least: - -- a derived transfer without canonical type identity or an exported runtime - wrapper type; -- a wrapper-address slot whose binding and bridge roles or ABI positions differ; -- a primitive scalar action or datatype family applied to `DERIVED_TYPE`; -- a wrapper-owned result without persistent storage, allocator, destroy action, - or failure cleanup; -- a borrowed wrapper with a destroy action, or without its required native - module/parent owner retention; -- a call-local argument that schedules destruction of the caller's wrapper; -- a visible in-place argument projected as a replacement without completed - policy; -- a hidden output or direct result whose native temporary can escape by - address; -- a plain module proxy without complete typed member-path operations, or an - `Aliased` live module borrow without a completed direct-address handoff; -- binding and bridge module-object access roles that disagree; -- an obsolete `Snapshot` contract, recursive detached-copy action, or backend - fallback that manufactures a detached object; -- a derived array or unsupported polymorphic form entering scalar-derived - lowering; and -- any backend request to infer a class, owner, addressability, or release from - semantic datatype or `intent`. - -### Phase 8A — Contract, Origin, And Post-IR Policy Completion - -Complete the semantic contract before defining direct plan records. - -- [x] Inventory every live scalar derived origin from source and semantic - `.pyi`: constructor-created storage, wrapper-owned result, caller-supplied - argument, native module object, and nested component. -- [x] Introduce one typed completed origin/retention representation shared by - class-instance, argument, result, module-variable, and field policy. - Do not encode origins as ad hoc reason strings. -- [x] Keep generated and edited `.pyi` type identity stable across module - namespaces, imported derived types, renamed Python exports, and same-name - types from different native scopes. -- [x] Complete required, optional, visible `out`, visible `inout`, hidden - output, and direct-result ownership without treating `intent` as the final - Python signature. The editable signature and `@native_call(...)` projection - decide visibility and order; policy only ensures the native call is valid. -- [x] Complete wrapper-owned result storage and destruction, borrowed - module/field owner retention, native setter rejection, result projection, - and failure cleanup before `ir2ast.py`. -- [x] Preserve `Aliased` parsing, printing, and source-derived metadata. Use it - for a live derived-module borrow and direct-address legality, but never as a - native-array extraction mode. -- [x] Complete a plain ordinary module object as `owner=NATIVE`, - `transfer=BORROWED_VIEW`, native-owner destruction, module lifetime, module - owner retention, typed member-path access, and replacement rejection. Do not - claim or require a whole-object native address. -- [x] Complete an `Aliased` module object as `owner=NATIVE`, - `transfer=BORROWED_VIEW`, native-owner destruction, alias storage, module - owner retention, direct address acquisition, and replacement rejection. -- [x] Remove the obsolete public `Snapshot` keyword from `prik.contracts`, - parser, printer, generated `.pyi`, semantic IR, policy actions, legacy - generators, documentation, and fixtures. Do not remove unrelated explicit - copy-result or scalar descriptor value-copy policy. -- [x] Complete finite typed member-path traversal for plain module proxies. - Memoize derived type identities so recursive graphs do not expand forever; - require explicit pointer/allocatable association, ownership, and stale-child - policy at recursive descriptor-backed edges. -- [x] Remove the obsolete wrapper-owned pointer-result blocker. Complete a - persistent typed pointer-holder origin whose wrapper owns the holder but not - its target, then keep only arrays of derived values and unsupported - polymorphic forms on exact planning errors. Supported scalar module - allocatable/`TARGET`/pointer origins use only their explicit Phase 8H - actions. -- [x] Add focused parser, printer, source-conversion, ownership, accessor, - policy-completion and planning tests for every active matrix row and - blocker. Assert the deliberate module-proxy versus direct-address mechanism - distinction, their shared live public behavior, and that neither changes a - contained native handle's view-only extraction. - -### Phase 8B — Derived Plan Records And Preflight Validation - -- [x] Add the minimal namespace-owned opaque derived-type definition record and - derived handoff facets described above. Keep all per-call decisions in - `ArgumentTransferPlan`, `ResultPlan`, or `ModuleVariablePlan`. -- [x] Add an explicit derived datatype-family/type-reference representation so - documentation, roles, native slots, lifecycle records, and printers never - fall through primitive scalar maps. -- [x] Project class instance/self policies, native type identity, wrapper type - symbol, native scope, allocator/destroy roles, and finalizer requirements - mechanically from completed semantic policy. -- [x] Project optional presence, input/in-place/output action, native call - position, ownership, storage, owner retention, and result position into the - existing transfer records. -- [x] Share the exact `DerivedHandoffPlan` object with its owning - `NativeCallSlotPlan` where the array/handle lanes already share subordinate - facets; do not duplicate editable state. -- [x] Add recursive validation for the namespace type definitions, arguments, - results, module variables, module-object access facets, field facets, and - lifecycle indexes. -- [x] Make plan edits observable: changing a derived owner, action, type - identity, retained owner, or release must either change both backend - artifacts consistently or fail `_validate_plan()` before source emission. -- [x] Extend support analysis with precise derived lanes and blockers. Do not - remove the blanket class-owner blocker until the minimal opaque type surface - is direct and every remaining Phase 9 dependency is reported separately. -- [x] Add normal-print plan tests and direct generator preflight tests under - `tests/codegen/test_phase8_derived_types.py`. - -### Phase 8C — Minimal Opaque Wrapper Storage And Lifecycle - -This sub-lane creates the runtime substrate needed to return and pass opaque -objects. It does not implement public construction, fields, or methods by -itself; Phase 8F/H add the public field surface on this substrate. - -- [x] Emit one minimal runtime wrapper type per exported semantic derived type, - with an opaque native address, an owned/borrowed state, and an optional - retained Python owner. Keep the public constructor unavailable until Phase 9. -- [x] Generate bridge allocation and destruction helpers from completed type - policy. Native-aware destruction owns allocatable components and supported - finalization; the binding must not free native storage directly. -- [x] Ensure owned allocation, initialization, and result conversion failures - run native destruction and Python cleanup exactly once. -- [x] Ensure borrowed wrappers never run native destruction, including when - their retained owner is released through cyclic or delayed garbage - collection. -- [x] Register the minimal type in the correct exported namespace so result and - module-variable materialization use the same class identity in source and - generated-`.pyi` builds. -- [x] Keep wrapper struct/type declaration, allocation, owner retention, and - destruction methods grouped under a derived-type comment in the binding; - keep native allocate/associate/destroy helpers grouped likewise in the - bridge. -- [x] Add source-printer and artifact tests for owned, borrowed, failed - allocation, failed conversion, and exactly-once native destruction paths. - -### Phase 8D — Wrapper-Owned Hidden Outputs And Function Results - -- [x] Plan hidden `Return(...)` outputs and direct derived function results as - `WRAPPER_INSTANCE` results with persistent wrapper-owned native storage. -- [x] For hidden output, allocate the result wrapper before the native call and - pass its native address at the declared native slot. On failure, destroy it - before returning the Python error. -- [x] For a function result, move or copy the returned native value into - persistent wrapper-owned storage before the native temporary expires. Never - retain an address into a bridge local. -- [x] Preserve result order and mixed-result aggregation through the existing - `ResultPlan` and lifecycle sequence; do not special-case a derived result in - function/module orchestration. -- [x] Reuse the same result type object and destructor for direct results, - hidden outputs, and edited `Returns[...]` projections. -- [x] Add reduced legacy/direct compiled parity over the existing - `make_point` cases in `test_native_call_examples.py`, - `test_output_arguments.py`, and `test_derived_type_boundaries.py`, inspecting - result storage, slot order, allocation failure, and cleanup artifacts. -- [x] Promote only those reduced generation units after both source and - generated-`.pyi` routes return the correct opaque wrapper and finalization is - proved. Field-based assertions remain on Phase 8F/H until their typed member - operations are complete. - -### Phase 8E — Required, Optional, In-Place, And Caller-Supplied Outputs - -- [x] Accept only the exact completed wrapper type for a concrete derived - argument. Subclass acceptance belongs to completed Phase 9 polymorphic - policy, not normal Python `isinstance` convenience. -- [x] Extract the opaque native address in the binding and pass it through the - single planned role. The bridge associates the matching typed native pointer - and calls the native procedure without copying for ordinary reference - arguments. -- [x] Preserve the same Python wrapper identity for visible `inout` and - caller-supplied `out` arguments. Return it only when the edited projection - requests that sole result; otherwise return `None`. Keep a mixed direct or - hidden result plus visible derived writeback on an exact policy blocker until - general mixed result/writeback aggregation is completed; do not let the - direct route select it and then drop the wrapper identity. -- [x] Represent optional omission and explicit `None` as native absence. A - present wrapper follows the same typed handoff as a required input; no empty - wrapper or call-local default object may be fabricated. -- [x] Keep native slot order independent of normalized Python argument order - and preserve user edits to argument visibility and projection. -- [x] Keep an immutable visible derived replacement on its existing exact - blocker because no passing legacy contract defines its native copy and - finalization semantics. Existing hidden/direct derived outputs use the owned - result path completed in Phase 8D; do not mutate an immutable input or invent - a generic object copy merely to remove the blocker. -- [x] Add focused type-error, optional-presence, wrong-wrapper-class, - in-place-identity, caller-supplied-output, projection, and cleanup tests. -- [x] Add reduced compiled parity that creates a `point` through the Phase 8D - result path, passes it to `point_sum`, mutates it through `move_point`, and - observes the new value through another native call without requiring a - constructor; the follow-on Phase 8F evidence also observes public fields. - -### Phase 8F — Module Objects, Components, And Field Owners - -- [x] Plan every eligible plain rank-zero derived module variable as a - native-owned live module proxy with rejected replacement; plan every - supported `Aliased` equivalent as a native-owned direct-address borrowed - wrapper. Both retain the module and have no destroy action. -- [x] Preserve `Aliased` in generated semantic `.pyi` only when supplied by the - native/source contract. Prove its module-proxy-versus-direct-address lowering - meaning while separately proving that both are live and that it does not - affect any contained native handle's view-only extraction. -- [x] Repeated `Aliased` module reads may create separate Python wrappers, but - every wrapper must refer to the same native object and never claim ownership; - repeated plain reads may create separate proxies, but every proxy must - delegate to the same current native module object. -- [x] Plan a nested derived component as a borrowed wrapper whose retained - owner is the containing wrapper. Releasing the parent name must not destroy - the parent while a child wrapper remains live. -- [x] Ensure a borrowed child never invokes its own native finalizer; releasing - the final child/owner reference triggers the containing owned instance's - destruction exactly once. -- [x] Reuse the Phase 7 `NativeArrayHandlePlan` for allocatable/pointer fields, - changing only origin=`derived_field`, owner retention=`parent_wrapper`, and - the completed field operation roles. Do not create a derived-only handle. -- [x] Plan scalar, string, ordinary-array, nested-derived, and native-handle - field getter/setter handoffs beneath the owning type for both address-backed - and module-backed objects. Use typed bridge procedures rather than C layout - offsets. Phase 8 emits both the typed low-level operations and public property - descriptors, including setter exposure completed by semantic policy. -- [x] Traverse nested value components by finite member paths and type identity. - Memoize recursive type definitions; recursive pointer/allocatable edges use - their completed association and owner policy instead of unbounded flattening. -- [x] Preserve pointer-field target ownership and stale-view rules from - completed pointer policy; parent retention does not make the parent own an - external pointer target. -- [x] Add direct plan/backend lifetime tests, then reduced compiled evidence - for the distinct plain proxy and `Aliased` direct-address origins, plus the - borrowed-finalizer, allocatable-field, and pointer-field fixtures, without - promoting constructor or method surfaces that remain Phase 9. - -### Phase 8G — Exact Native `value` Copies And Opaque Layout - -- [x] Preserve `bind(C)`/`sequence`/ordinary derived-type facts and native - `value` transport through generated `Value(Arg(i))`, post-IR policy, and the - derived handoff plan. Do not store this per-call ABI choice on the annotated - Python type. -- [x] For every supported exact rank-zero monomorphic native `value` argument, - keep Python on the opaque wrapper contract. The Fortran bridge imports the - exact native type, reads the typed pointee, and performs the typed call. The - binding and C boundary never cast, lay out, or byte-copy the aggregate. -- [x] Remove the obsolete requirement that the native type itself be - interoperable. Ordinary, `sequence`, and `bind(C)` exact derived types use - the same Fortran-owned typed-value action; polymorphic or unresolved native - types remain exact blockers for type-identity reasons, not layout guesses. -- [x] Keep ordinary reference arguments and all component access on generated - bridge helpers even when a type is `bind(C)`; interoperability does not turn - fields into a public binary-layout promise. -- [x] Replace the obsolete unsupported-aggregate-layout assertions with - policy, plan, artifact, and compiled tests for ordinary, `sequence`, and - `bind(C)` exact typed value calls. Field-property assertions are Phase 8 - evidence; retain only constructor-dependent assertions in - `test_derived_layout.py` for Phase 9 production promotion. - -### Phase 8H — Direct-Address And Module-Proxy Object Access - -This sub-lane supplies the distinct lowering mechanisms for the two completed -module-object origins in Phase 8A/8F: direct address acquisition for an -`Aliased` live borrow, and typed live member access for a plain module proxy. - -- [x] Add one typed module-object access facet beneath `ModuleVariablePlan`. - Record `DIRECT_ADDRESS` or `MODULE_PROXY`, the native object type, member-path - operations, owner/release behavior, and failure behavior. Do not encode a - backend method name. -- [x] Use the direct path only when completed source/semantic facts make the - native address legal. The bridge exposes the opaque address mechanically; - the binding constructs the borrowed wrapper and retains its module owner. -- [x] For a plain module object, use typed per-field bridge getters/setters and - operations selected by the completed member graph. The binding constructs a - module-retaining proxy with no native destroy action; every read observes - current module state and every permitted write updates it. -- [x] Keep the initial direct-address and module-proxy paths rank-zero, - nonallocatable, nonpointer, noncoindexed, and nonpolymorphic; the explicit - descriptor-backed correction below adds only its named storage origins and - call actions. Record exact blockers for - unsupported type parameters, dynamic types, unresolved recursive pointer - ownership, or any member without a complete live operation. Do not switch - mechanisms as a fallback. -- [x] Validate direct-address roles or module-proxy member-operation coverage, - exported wrapper type identity, owner/release behavior, and - replacement rejection before either backend emits source. -- [x] Prove the `Aliased` address/lifetime premise and plain proxy live - read/write behavior in focused compiled source/generated-`.pyi` tests. -- [x] Remove the `Snapshot` contract name, metadata, recursive copy policy, - generated helper classes, documentation, and snapshot-only fixtures. Do not - retain a compatibility parser, printer, alias, or backend fallback. -- [x] Preserve ordinary result materialization, explicit constant-value - materialization, scalar descriptor value copies, ordinary array copy - results, and any unrelated active transfer action. Their copy semantics are - separate from removed whole-object snapshot behavior. -- [x] Replace the former plain-module snapshot fixture with source/generated- - `.pyi` parity and runtime evidence for live scalar, string, ordinary-array, - allocatable/pointer-handle, and nested-derived member paths, including - recursive-edge blockers and parent/module retention. - -#### Phase 8H Contract Correction — Complete Scalar-Derived Call Matrix - -This correction replaces every earlier isolated module-allocatable, stable -pointer-target, direct-address-only, and interoperable-value proposal with one -complete compatibility matrix. It covers exact rank-zero, monomorphic -`type(item)` objects. Phase 9 still owns `class(item)`, inheritance, and dynamic -dispatch; arrays of derived values remain outside this matrix. - -The actual declaration and its runtime origin are independent axes. The five -actual declaration forms are ordinary, `TARGET`, `ALLOCATABLE`, -`ALLOCATABLE,TARGET`, and `POINTER`; each can be module-owned or represented by -wrapper-owned storage where such storage is meaningful. The six native dummy -forms are: - -| Key | Exact native dummy | -| --- | --- | -| `O` | `type(item) :: arg` | -| `T` | `type(item), target :: arg` | -| `A` | `type(item), allocatable :: arg` | -| `AT` | `type(item), allocatable, target :: arg` | -| `P` | `type(item), pointer :: arg` | -| `V` | `type(item), value :: arg` | - -`OPTIONAL`, rank, and qualified type identity remain separate facts. Source -`INTENT` may propose the initial Python projection, but it is not a completed -matrix selector. For the `P` column, `Pointer(Arg(i))` without a matching -projected return selects a call-local pointer input adapter and discards native -reassociation. A matching `Returns[...]` selects association writeback and -therefore requires persistent pointer storage. prik never selects between these -paths from native `INTENT`. - -Use these completed action names. Parenthesized state requirements are runtime -preconditions, not alternative fallback actions: - -| Action | Meaning | -| --- | --- | -| `DIRECT_REFERENCE` | wrapper-owned or direct module address; reconstruct the exact typed object and pass it by reference | -| `SCOPED_REFERENCE` | originating module synchronously invokes a generic address consumer; the native call completes before the temporary target scope returns | -| `HOLDER_REFERENCE` | reconstruct a persistent typed holder and pass its component directly | -| `MODULE_ADDRESS` | originating module returns `C_LOC` for an explicit durable target | -| `ALLOCATABLE_HOLDER` | pass a persistent wrapper-owned allocatable holder component directly, including unallocated state | -| `MODULE_ALLOCATABLE_TRANSACTION` | move between the module variable and a bridge-local shared typed transaction holder through interoperable holder-address operations | -| `POINTEE_REFERENCE` | pass the current target of a pointer holder or module pointer to a nonpointer dummy | -| `POINTER_HOLDER` | pass a persistent wrapper-owned pointer holder component directly so association writeback updates the same holder | -| `MODULE_POINTER_TRANSACTION` | initialize one bridge-local typed pointer holder from the current target and restore its final association through an interoperable holder-address operation | -| `POINTER_INPUT_ADAPTER` | expose a payload through a call-local pointer carrier because the Python contract does not project pointer association writeback | -| `TYPED_VALUE_COPY` | the exact Fortran bridge passes the typed object into the native `VALUE` slot; C never copies aggregate bytes | -| `INCOMPATIBLE` | language-level storage mismatch; raise the specified `TypeError` and never enter native code | - -`[allocated]` means an allocated value is required. `[associated]` means an -associated pointer target is required. `A`, `AT`, and `P` descriptor calls -accept unallocated or disassociated state where the table does not carry one of -those preconditions. - -| Actual declaration | Origin | `O` | `T` | `A` | `AT` | `P` | `V` | -| --- | --- | --- | --- | --- | --- | --- | --- | -| `type(item) :: var` | non-module | `DIRECT_REFERENCE` | `DIRECT_REFERENCE` with call-scoped target | `INCOMPATIBLE` | `INCOMPATIBLE` | `POINTER_INPUT_ADAPTER` | `TYPED_VALUE_COPY` from direct reference | -| `type(item) :: var` | module proxy | `SCOPED_REFERENCE` | `SCOPED_REFERENCE` with call-scoped target | `INCOMPATIBLE` | `INCOMPATIBLE` | scoped `POINTER_INPUT_ADAPTER` | scoped `TYPED_VALUE_COPY` | -| `type(item), target :: var` | non-module | `DIRECT_REFERENCE` | `DIRECT_REFERENCE` with owner target lifetime | `INCOMPATIBLE` | `INCOMPATIBLE` | `POINTER_INPUT_ADAPTER` | direct `TYPED_VALUE_COPY` | -| `type(item), target :: var` | module | `MODULE_ADDRESS` | `MODULE_ADDRESS` with module target lifetime | `INCOMPATIBLE` | `INCOMPATIBLE` | `POINTER_INPUT_ADAPTER` | module-address `TYPED_VALUE_COPY` | -| `type(item), allocatable :: var` | non-module holder | `HOLDER_REFERENCE [allocated]` | `HOLDER_REFERENCE [allocated]` with holder target lifetime | `ALLOCATABLE_HOLDER` | `ALLOCATABLE_HOLDER` | holder `POINTER_INPUT_ADAPTER [allocated]` | holder `TYPED_VALUE_COPY [allocated]` | -| `type(item), allocatable :: var` | module | `SCOPED_REFERENCE [allocated]` | `SCOPED_REFERENCE [allocated]` with call-scoped target | `MODULE_ALLOCATABLE_TRANSACTION` | `MODULE_ALLOCATABLE_TRANSACTION` with call target | scoped `POINTER_INPUT_ADAPTER [allocated]` | scoped `TYPED_VALUE_COPY [allocated]` | -| `type(item), allocatable, target :: var` | non-module holder | `HOLDER_REFERENCE [allocated]` | `HOLDER_REFERENCE [allocated]` with holder target lifetime | `ALLOCATABLE_HOLDER` | `ALLOCATABLE_HOLDER` | holder `POINTER_INPUT_ADAPTER [allocated]` | holder `TYPED_VALUE_COPY [allocated]` | -| `type(item), allocatable, target :: var` | module | `MODULE_ADDRESS [allocated]` | `MODULE_ADDRESS` with module target lifetime | `MODULE_ALLOCATABLE_TRANSACTION` preserving target | `MODULE_ALLOCATABLE_TRANSACTION` preserving target | `POINTER_INPUT_ADAPTER [allocated]` | module-address `TYPED_VALUE_COPY [allocated]` | -| `type(item), pointer :: var` | non-module holder | `POINTEE_REFERENCE [associated]` | `POINTEE_REFERENCE [associated]` with retained target owner | `INCOMPATIBLE` | `INCOMPATIBLE` | `POINTER_HOLDER` | pointee `TYPED_VALUE_COPY [associated]` | -| `type(item), pointer :: var` | module | module `POINTEE_REFERENCE [associated]` | module `POINTEE_REFERENCE [associated]` with native target owner | `INCOMPATIBLE` | `INCOMPATIBLE` | `MODULE_POINTER_TRANSACTION` | module-pointee `TYPED_VALUE_COPY [associated]` | - -An `Aliased` ordinary module object follows `MODULE_ADDRESS` instead of -`SCOPED_REFERENCE`, but its original target-lifetime fact still controls whether -a native pointer may outlive the call. This does not change `Aliased` array-view -semantics. - -The matrix is exhaustive for this Phase 8 scope. Every cell becomes either one -completed action or one deliberate language-level error before lowering. No -backend may infer a different action from datatype, `intent`, module shape, -address presence, or local memory checks. - -The table's `P` entries show the non-projecting input-adapter form. When the -Python contract projects pointer association writeback, replace every -nonpointer `P` cell with `INCOMPATIBLE`; the two pointer-storage rows retain -`POINTER_HOLDER` and `MODULE_POINTER_TRANSACTION`. - -##### Shared Holder And Callback ABI - -Define these support types once per qualified native derived type and import -the same definitions in every producer, origin operation, and consumer: - -```fortran -type :: item_allocatable_holder - type(item), allocatable :: value -end type - -type :: item_pointer_holder - type(item), pointer :: value => null() -end type -``` - -A persistent wrapper-owned holder is allocated through a Fortran pointer and -its opaque holder address is stored by the Python wrapper. Its nonpointer -allocatable component is a targetable subobject of the persistent holder -target, so the same carrier supports both `A` and `AT`; do not invent a second -allocatable-target holder. - -Module allocation and pointer transactions use bridge-local holder objects -declared `TARGET`. The module-specific operations are interoperable -`BIND(C)` procedures taking only `type(C_PTR), value :: holder_address` plus -interoperable status/context values. Each operation reconstructs the exact -shared holder with `C_F_POINTER` and performs `MOVE_ALLOC` or pointer assignment -entirely in Fortran. The binding transports a typed function pointer and an -opaque holder address; no allocatable or pointer descriptor crosses C. - -The old proposal to pass `type(item), allocatable` or `type(item), pointer` -directly through a runtime C callback is removed as noninteroperable. The old -proposal to avoid a transaction holder for module allocation/pointer restore is -also removed. A bridge-local transaction holder is the portable carrier; it is -not a persistent replacement for the originating module variable. - -For a module allocatable transaction, the bridge performs the equivalent of: - -```fortran -type(item_allocatable_holder), target :: transaction - -status = move_out(c_loc(transaction)) -if (status == PRIK_STATUS_OK) then - call native_procedure(transaction%value) - restore_status = move_back(c_loc(transaction)) -end if -``` - -`move_out` executes `move_alloc(module_value, transaction%value)` and -`move_back` executes `move_alloc(transaction%value, module_value)`. A successful -move-out makes the module variable unavailable until restoration. When the -module actual has `TARGET`, both destinations preserve pointer association; -when it lacks `TARGET`, aliases created through a temporary target have only -call lifetime. - -For a module pointer transaction, the bridge initializes -`transaction%value` from the current `C_LOC`/`C_NULL_PTR`, passes that component -to the native pointer dummy, and invokes `restore_pointer(c_loc(transaction))`. -The origin reconstructs the pointer holder and executes -`module_pointer => transaction%value`. The final nullification, -reassociation, allocation, or deallocation is therefore visible in the module -pointer. - -Operation tables use typed C function-pointer fields; do not round-trip a -function pointer through `void *`. The proxy retains its originating extension -until every active scoped call or transaction has unwound. - -##### Pointer Target Ownership - -A pointer holder owns the holder and association variable, not its target. -Default scalar-derived pointer target ownership is native: holder destruction -nullifies the component and deallocates only the holder. It must never -deallocate an unowned target. When final association matches a known module, -parent, or wrapper-owned target, retain that owner in completed policy; an -otherwise durable native target retains the originating extension and remains -the native program's release responsibility. Native code that returns a pointer -to an expired local target violates the contract rather than creating an prik -fallback. - -This completed owner/release rule removes the old wrapper-owned pointer-result -blocker. Reassociation is supported, but it never silently transfers target -ownership to Python. Public documentation must warn that a native pointer saved -through a wrapper-owned target remains valid only while the wrapper and target -allocation remain alive. - -##### Multiple Scalar-Derived Arguments - -Do not generate `2**N` native call branches. Build one call context with one -slot per native argument and an ordered acquisition program: - -1. validate every Python wrapper, exact qualified type, storage capability, - allocation/association precondition, optional presence, and pointer-target - owner before entering any native origin operation; -2. retain all Python/module owners and acquire module transaction guards in a - deterministic total order; -3. deduplicate repeated actual identities so one module allocation or pointer - is checked out once and its holder/address can feed multiple native slots; -4. move out module allocatables in deterministic order, rolling back already - moved values in reverse order if a later acquisition fails; -5. initialize module pointer transaction holders; -6. enter all `SCOPED_REFERENCE` producers as a nested continuation chain, - storing each address in the context; and -7. invoke the native procedure exactly once after every slot is ready, then - unwind scoped producers, pointer restorations, allocation restorations, - guards, and retained owners in reverse order. - -If the same actual appears in multiple slots and any corresponding dummy may -define it while another slot references or defines it, reject the call before -checkout unless completed `INTENT` facts prove the aliasing legal. Read-only -duplicates share one acquisition. Never move the same module allocatable twice -or restore the same module pointer through independent locals. - -The generic scoped-address consumer ABI remains -`consumer(object_address, context) -> status`. The context carries all earlier -addresses, holders, ordinary arguments, result slots, and the first error. A -consumer never retains `object_address`; multiple module variables are handled -by nesting producers, not by generating one origin-module cross product per -native procedure. - -##### Error And Cleanup Contract - -Use one status protocol across scoped consumers and module transaction -operations. Do not raise a Python exception, `longjmp`, or unwind C++ through a -Fortran frame. Record status and any Python exception data in the call context, -return normally through every producer, complete cleanup, and only then raise -in the binding. - -- wrong qualified wrapper type, an incompatible matrix cell, or a known - reassociable pointer dummy receiving nonpointer storage raises `TypeError` - before native entry; -- a required ordinary/target/value/pointer-input actual whose allocatable is - unallocated or pointer is disassociated raises `ValueError` before native - entry; -- `A`, `AT`, and `P` descriptor calls preserve valid unallocated or - disassociated state and do not reinterpret it as optional omission; -- only an omitted Python argument or explicit `None` for an optional contract - selects native absence; a present empty handle never becomes omitted by - accident; -- an active recursive/concurrent transaction raises `RuntimeError` before the - affected origin changes state; -- every successful move-out has exactly one attempted move-back on every - normally returning path, and every native module-pointer call has exactly one - attempted association restore; -- cleanup continues in reverse order after the first restoration failure so - independent origins are not stranded; the first failure is reported with - later cleanup failures attached as context; -- a failed restoration leaves its origin guard poisoned instead of advertising - a usable proxy, and raises `RuntimeError` after all other cleanup attempts; -- conversion, result allocation, and Python-object creation that can fail are - completed before checkout where possible; failures after native return still - restore every transaction before propagating; and -- process termination, `ERROR STOP`, signals, or invalid native pointers are - not recoverable wrapper exceptions. The documentation must state that this - cleanup guarantee covers paths that return through the generated ABI. - -The per-origin guard must be thread-safe, or the binding must prove that the -GIL remains held for the complete transaction and that no callback re-entry is -possible. An unsynchronized Fortran `logical` is not a sufficient concurrency -guard. Internal synchronous address consumers are Phase 8 bridge machinery; -they do not expose the public callback semantics deferred to Phase 10. - -##### Implementation And Proof Checklist - -- [x] Preserve actual declaration attributes, module/non-module origin, - `TARGET` lifetime, allocatable/pointer state, exact type identity, and - pointer-dummy `INTENT` authority through parsing, semantic IR, and edited or - generated `.pyi` round trips. -- [x] Replace the former category/action-only contract with completed facets - capable of representing all six dummy forms and every action in the matrix. - `DerivedDummyCategory` remains the completed declared-form label and - `DerivedCallAction` remains the completed selected-action label; neither is - allowed to stand in for the lifetime, access, failure, cleanup, target-owner, - or release facets. The complete record includes `ALLOCATABLE,TARGET`, typed - value, target lifetime, pointer-input - validation, transaction cleanup, and target owner/release. Remove - `RUNTIME_POINTER_TARGET`, the module-allocatable incompatibility, and all old - fallback/rejection actions they made obsolete. -- [x] Complete every matrix decision in post-IR policy before `ir2ast.py`. - Binding and bridge generation only dispatch named actions; neither backend - inspects datatype, `intent`, module shape, address presence, or allocation - state to select a different mechanism. -- [x] Generate one shared allocatable holder and pointer holder per qualified - native type, with persistent create/destroy helpers and bridge-local - transaction use. Prove source/generated-`.pyi` bundles import the identical - holder definition and reject ABI/type mismatch before reconstruction. -- [x] Generate scoped-address producer operations for plain module objects and - non-`TARGET` allocated module allocatables, direct address operations for - durable module targets, move-out/move-back holder-address operations for - module allocatables, and current-target/restore holder-address operations for - module pointers. -- [x] Implement the ordered multi-argument acquisition/unwind program, - deduplicated origin identity, legal read-only aliasing, reverse rollback, - poisoned restoration failures, and a single final native invocation. -- [x] Implement the exact Python error mapping and optional/empty-state rules - above. Add injected failures before first acquisition, after one of several - acquisitions, during scoped nesting, after native return, and during each - cleanup category. -- [x] Remove the interoperable-`bind(C)` restriction from typed derived - `VALUE` calls. The Fortran bridge must perform the exact typed call without a - C aggregate cast, byte copy, layout promise, or detached-object fallback. -- [x] Support wrapper-owned pointer results with a persistent pointer holder, - native target ownership by default, explicit known-owner retention, direct - association writeback, and holder-only destruction. Remove the old blanket - target-ownership blocker rather than retaining it as a compatibility path. -- [x] Update public and contributor documentation to teach the five actual - declarations, six dummy forms, complete matrix, direct versus scoped - address acquisition, holder and module transactions, `INTENT(IN)` pointer - exception, target lifetime, native pointer-target ownership, multi-argument - nesting, errors, and cleanup. Examples must show more than one scalar-derived - argument and link back to one canonical explanation instead of repeating - incomplete fragments. -- [x] Add one comprehensive native fixture at - `tests/data/fortran/wrapper/fscalar_derived_actual_dummy_matrix_f90.f90`, its - reduced source/generated contract under - `tests/wrapper/fortran/derived_types/contracts/fscalar_derived_actual_dummy_matrix_phase8/`, - focused policy/plan/artifact tests in - `tests/codegen/test_phase8_scalar_derived_actual_dummy_matrix.py`, and - compiled tests in - `tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py`. - Replace the earlier proposed separate module-allocatable and - module-target/pointer fixtures; do not retain tests that assert their old - rejection paths. -- [x] Make that native fixture a complete Fortran module containing all five - module actual declarations, wrapper-owned ordinary/allocatable/pointer - producers, all six dummy forms, both pointer `INTENT(IN)` and reassociable - pointer procedures, two qualified native types with the same short name, - optional arguments, injected operation failures, and state-reset helpers. -- [x] Parameterize policy/plan tests over every matrix cell. Every legal cell - must select its one completed action; every incompatible cell must assert its - exact pre-native `TypeError`; allocated/unallocated and - associated/disassociated states must assert their exact `ValueError`, valid - descriptor call, or optional-absence behavior. -- [x] Compiled tests must exercise mixed calls containing several - scalar-derived arguments. Include at least: multiple nested scoped module - objects; two module allocatable transactions plus a module pointer - transaction; mixed direct, holder, scoped, allocatable, pointer, target, and - value slots in one native call; repeated read-only actual identity; rejected - writable duplicate identity; failure after the first of several checkouts; - reverse restoration; native deallocation/reallocation; pointer - nullification/reassociation/allocation/deallocation; and owner retention. - Phase 8 cannot close if the new compiled procedures test only one derived - argument at a time. -- [x] Run the portable ABI fixture with the supported GNU toolchain and every - available secondary compiler in the development environment. The proof must - cover scoped `C_FUNPTR` consumers, `C_PTR` transaction holders, - `C_F_PROCPOINTER`, holder targetability, target-preserving `MOVE_ALLOC`, and - the accepted-`INTENT(IN)`/rejected-reassociable pointer distinction. - -### Phase 8I — Production Routing, Regression, And Completion - -- [x] Add separate support-report lanes for derived inputs, optional derived - inputs, in-place derived arguments, wrapper-owned derived results, plain - module proxies, `Aliased` borrowed module objects, borrowed field owners, - and the exact typed-value slice. -- [x] Replace the isolated scalar-derived descriptor routes with one - dependency-closed actual/dummy-matrix lane only after every unchecked Phase - 8H row passes. It must cover direct and scoped references, target adapters, - allocatable and pointer holders, module allocation and association - transactions, exact typed values, and multi-argument acquisition/unwind. - No old call-incompatible, nonreassociating-only, or interoperability-only - compatibility route may remain selectable. -- [x] Add one deliberate legacy/direct parity node for every dependency-closed - Phase 8 lane and append it to the production rollout evidence only after its - generated artifacts and runtime behavior match. -- [x] Update the migration matrix row for each reduced unit. Keep broad units - containing constructors, methods, inheritance, or callbacks on - their explicit Phase 9/10 policy limitations until whole-generation-unit - planning is complete. -- [x] Treat source and generated-`.pyi` default field constructors as Phase 9 - class-surface blockers. Do not select the Phase 8 route merely because the - generated constructor was consumed into origin metadata rather than retained - as a semantic method; reduced opaque Phase 8 contracts must explicitly - suppress construction. -- [x] Prove an eligible opaque-derived generation unit selects the production - wrapper-plan route and no longer invokes `semantic_ir_to_codegen_ast()`. -- [x] Keep direct plan edits meaningful across both backends and preserve the - global no-fallback rule when a derived type, owner, release, or field action - is incomplete. -- [x] In every relevant planner, validator, binding generator, and bridge - generator, keep scalar, string, ordinary-array/native-handle, and - derived-type lowering methods in consistent groups with one short comment - above each group. Preserve typed object-kind/action matching; grouping must - not introduce datatype inference or a second dispatcher. -- [x] Run focused parser/printer, ownership/policy, plan/validation, - binding/bridge/printer, and runtime tests; relevant source/generated-`.pyi` - wrapper parity; and regressions for scalar, string, array, and Phase 7 handle - lanes. -- [x] Run the wrapper suite excluding the deferred LAPACK coverage, the wrapper - codegen complexity checker, documentation checks, whitespace check, and the - required static-analysis suite before closing implementation. -- [x] Run the comprehensive Phase 8 scalar-derived actual/dummy matrix policy, - artifact, and multi-argument compiled tests; all retained holder and Phase - 7/8 regressions; the wrapper suite excluding LAPACK; documentation and - whitespace checks; the wrapper complexity checker; and the required static - suite after the replacement route is implemented. -- [x] Close Phase 8 only when every supported rank-zero non-polymorphic derived - input/result/module transfer is direct, both plain module-proxy and `Aliased` - address-backed module-object paths are direct, live member operations and - recursive-edge policy are validated before emission, and - every remaining class-surface/callback/derived-array case has an exact Phase - 9/10 or unsupported-policy blocker. - -### Phase 8 Implementation Evidence - -- Post-IR origin, identity, handoff, ownership, field, lifecycle, and exact - blocker evidence lives in - `tests/codegen/test_phase8_derived_types.py`, with supporting parser, - printer, source-conversion, ownership, and planning suites named in - `tests/wrapper/CHECKLIST_COVERAGE.md`. -- Public-field validation is split into named completed-policy, descriptor, - typed object-kind, and setter checks so no single semantic-policy routine - becomes a second backend-style dispatcher. -- Compiled legacy/source and direct-plan evidence lives in - `tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py`. It covers - required, optional, in-place, caller-supplied output, ordinary and `bind(C)` - typed native `value`, direct/hidden owned result, module-proxy, - direct-address module object, constant value, field, owner-retention, - allocation/cleanup artifact, and exactly-once finalization behavior. -- The former isolated scalar-derived descriptor evidence in - `tests/codegen/test_phase8_scalar_derived_descriptors.py`, - `tests/wrapper/fortran/derived_types/test_scalar_derived_descriptor_plan.py`, - and `tests/data/fortran/wrapper/fscalar_derived_descriptors_f90.f90` is - superseded by the comprehensive policy/artifact and compiled matrix files - named in Phase 8H. They cover all 60 declaration/dummy cells, empty states, - qualified same-short-name identities, `sequence` typed values, holder and - module transactions, multi-origin unwind, pointer target ownership, injected - cleanup failures, and the exact retained incompatibilities. No obsolete - module-allocatable rejection, stable-pointer-only, or wrapper-pointer-result - blocker remains as negative compatibility coverage. -- `prik/pipeline/build.py` registers the dependency-closed Phase 8 support - lanes and their passing production evidence. The automatic-route test - replaces `semantic_ir_to_codegen_ast()` with a failure sentinel and proves an - eligible opaque-derived unit never invokes it. -- Constructors, methods, properties beyond the completed field descriptors, - inheritance, and polymorphic class orchestration remain Phase 9. Public - callbacks remain Phase 10. Arrays of derived values, non-scalar holder member - operations, recursive value edges without completed descriptor policy, - unresolved imported types without an exact runtime definition, immutable visible - derived replacement, and mixed native result plus visible-writeback envelopes - carry exact unsupported-policy blockers instead of selecting a fallback. - Internal synchronous scoped-address consumers and module transactions are - Phase 8 implementation machinery, not deferred public callbacks. - -Historical Phase 8 closure evidence before the module-allocatable and -module-pointer restore redesigns (2026-07-15): all 39 focused Phase 8 -plan/compiled tests, the 711-test -cross-stage regression batch, all 79 runtime-handle tests, 1,133 documentation -and layout tests, and all 329 wrapper tests outside the deferred full -BLAS/LAPACK file passed. The wrapper complexity checker, Ruff lint/format, -Bandit, Vulture, whitespace, and explicit-`origin/main` Radon policy passed; -the advisory full Radon complexity and maintainability reports were also -produced. The required `--base-ref auto` Radon invocation could not resolve -CI-only base-SHA variables locally, and the explicit-base rerun passed. No -LAPACK test was run locally. This evidence does not close the reopened Phase 8H -rows. - -Final Phase 8H/I closure evidence (2026-07-15): the focused Phase 8 plus route- -ledger batch passed 180 tests; the affected cross-stage semantic, lowering, -runtime-handle, planner, and backend batch passed 611 tests; and the complete -wrapper suite outside the deferred combined BLAS/LAPACK file passed 445 tests. -Documentation checks passed 1,123 tests and whitespace validation passed. The -GNU toolchain compiled and ran the complete matrix suite; Intel `ifx` 2026.1.0 -compiled, linked, and ran the same generated ABI for `sequence` typed values, -mixed six-form input, module allocatable/pointer transactions, target-preserving -`MOVE_ALLOC`, and the accepted-input/rejected-reassociable pointer distinction. -The wrapper complexity checker, Ruff lint/format, Bandit, Vulture, explicit- -`origin/main` Radon policy, and advisory Radon complexity/maintainability runs -passed. The CI-only `--base-ref auto` Radon lookup was unavailable locally, so -the required explicit-base rerun was used. No LAPACK test was run locally. - -### Phase 8 Expansion Gate - -- [x] Inventory the live semantic contract, post-IR ownership policy, active - snapshot paths to remove, legacy binding/bridge paths, plan-route blockers, - public docs, checked `.pyi` fixtures, and real wrapper tests. -- [x] Separate origin, owned/borrowed lifetime, module address acquisition, - input/result/field/module-state use, destruction, owner retention, and - recursive member-path access into dependency-ordered Phase 8A-I sub-lanes. -- [x] Record the strict Phase 8/9/10 boundaries and identify reduced existing - native units that can prove opaque transfers without first migrating public - constructors, methods, inheritance, or callbacks. Public field descriptors - are part of Phase 8. -- [x] Begin Phase 8 implementation only from Phase 8A and keep every later - sub-lane blocked on its declared dependencies. - -## Phase 9 — Classes, Constructors, And Methods - -Expansion status: complete. Implementation status: complete. The direct class -path is covered by policy, plan-edit, artifact, compiled runtime, production -routing, and broad non-LAPACK wrapper-suite evidence below. - -Scope: generated Python class objects, namespace registration, default and -keyword constructors, explicit constructor bindings, constructor overloads, -instance and static methods, type-bound dispatch, method overloads, finalizer -attachment, inheritance, and the first supported scalar polymorphic input -dispatch. Phase 9 assembles those public class surfaces on the opaque storage, -field descriptors, handoffs, and lifetime rules completed in Phase 8. - -### Phase 9 Boundary And Explicit Non-Scope - -Phase 9 may compose completed Phase 8 records but must not revisit them. -Constructor and method policy may select how an instance is created or passed; -it may not change object origin, storage kind, field access, owner retention, -release, nullability, native setter assignment, or destruction. A class plan -references the namespace-owned `DerivedTypePlan` and its field plans rather -than copying or rendering them. - -The following surfaces are in Phase 9: - -- one generated Python type object for each public supported semantic class, - with stable native identity and explicit Python base identity; -- an explicitly present or deliberately absent public constructor surface; -- generated default/keyword field initialization for eligible public scalar - fields, including omitted-keyword preservation of native defaults; -- direct `@bind("native_name")` constructors and explicit constructor overload - candidates linked to concrete native procedures; -- passed-object type-bound instance methods, non-type-bound methods attached to - the class by the semantic contract, and supported `@staticmethod` methods; -- class-owned overload sets with exact candidate signatures and deterministic - runtime selection; -- owned-instance finalization through the Phase 8 destroy/release path and - borrowed-instance non-destruction; -- Python inheritance for supported Fortran extension types; and -- scalar, input-only polymorphic calls whose accepted runtime class set and - concrete native dispatch targets are fully enumerated before lowering. - -The following remain outside Phase 9: - -- callbacks, adapters, trampolines, and callable lifetime; these remain Phase - 10 even when a callback argument/result is a derived object; -- module-level generic/operator migration units that do not require a class - surface; those remain in Phase 11, although they may reuse the same overload - candidate and runtime-match vocabulary; -- arrays of derived or polymorphic values, elemental class dispatch, and - partial construction/destruction of array elements; -- polymorphic results, mutable polymorphic dummies, allocatable/pointer - polymorphic scalars, unlimited polymorphism, abstract instantiation, - deferred-binding execution, and runtime extension types not enumerated in - the semantic module; -- any unresolved Phase 8 storage blocker merely because a constructor or - method happens to use that type; Phase 9 reuses the completed allocatable and - pointer holders and must not invent a second storage path; -- generic constructor selection whose candidates are indistinguishable at the - Python boundary; and -- compatibility aliases, synthesized legacy entrypoints, string-built backend - method names, or a fallback from an incomplete class plan to legacy class - lowering. - -### Phase 9 Existing Oracle And Inventory - -The legacy route plus existing source/generated-`.pyi` runtime assertions are -the behavioral oracle. Capture complete binding, bridge, header, and runtime -evidence before each reduced direct-plan slice. Correct unsafe behavior only -when the documented contract says so; do not preserve legacy architecture. - -| Existing unit | Phase 9 behavior to preserve | Required reduced slice | -| --- | --- | --- | -| `derived_types/test_constructors_and_finalizers.py::test_fortran_default_constructor_keywords_and_finalization[*]` | default construction, keyword-only scalar fields, native defaults, invalid-call cleanup, and exactly-once finalization | default/keyword constructor plus owned destroy path | -| `derived_types/test_derived_type_methods.py::test_modern_fortran_derived_type_exposes_class_and_type_bound_methods[*]` | instance methods, explicit binding names, scalar arguments/results, class static factory, and Phase 7 handle fields | split `vector` methods from `vector_store` handle methods and static factory | -| `derived_types/test_borrowed_finalizers.py::test_borrowed_child_wrapper_never_finalizes_native_component[*]` | borrowed child retains parent; only the owned parent finalizes | class assembly over the completed Phase 8 borrowed-field owner path | -| `derived_types/test_derived_layout.py::test_bind_c_derived_types_use_accessors_and_fortran_value_copy[*]` | default class creation and methods coexist with opaque field access and typed native value copy | class surface only; Phase 8 retains layout and handoff ownership | -| `derived_types/test_inheritance.py::test_fortran_extension_types_generate_python_inheritance[*]` | Python subclass relationships, inherited field/method access, overridden methods, unbound base calls, and scalar polymorphic input dispatch | base/extension class graph first, polymorphic call second | -| `tests/fortran/derived_types/semantics/test_pyi_class_semantics.py` and `tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py` | generated versus bound constructors, removed constructors, direct constructor targets, explicit overload links, type-bound root targets, and invalid metadata diagnostics | semantic-policy fixtures before planner/backend work | -| `../../fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py` | edited contracts can remove constructors/methods/candidates and add explicit bindings without resurrecting source declarations | absence/export validation and source/generated/edited parity | -| `naming/test_defined_operators.py` and `naming/test_generic_interfaces.py` | exact candidate matching and Python export naming | reuse candidate-match vocabulary; broad module/generic units remain Phase 11 | - -Inventory these legacy owners without importing them into the direct package: - -- `prik/semantics/ir2ast.py` currently interprets constructor overloads, - passed-object positions, type-bound names, polymorphic variants, and class - insertion. Each semantic decision found there must move into post-IR class - policy before direct lowering. -- `prik/codegen/bindings/c_to_python.py` currently assembles type objects, - constructors, methods, overloads, properties, inheritance, module exports, - and finalizers. Reuse emitted behavior as the oracle, not its broad control - flow or method-name synthesis. -- `prik/codegen/bridges/fortran_to_c.py` currently supplies typed constructor - allocation, passed-object association, method calls, overload interfaces, - and finalization helpers. Direct bridge generation must consume completed - class/method actions and reuse Phase 8 native storage helpers. -- Generated semantic `.pyi` class declarations are a public contract. A - consumed default constructor still counts as a constructor surface and must - remain a whole-unit Phase 9 route requirement. - -### Phase 9 Plan Shape And Action Vocabulary - -Extend the existing namespace plan; do not introduce a rendered-class layer or -a second function plan. - -- Add one namespace-owned `ClassSurfacePlan` (name illustrative, not - prescriptive) that references exactly one `DerivedTypePlan`, its Python - exports, optional base-class identity, constructor plan, ordered methods, - ordered overload sets, type-object slots, and module-registration action. -- Add a `ConstructorPlan` with an explicit kind: `ABSENT`, - `DEFAULT_FIELDS`, `BOUND_PROCEDURE`, or `OVERLOAD_SET`. Record allocation - action, accepted Python parameters, native target/call slots, initialized - fields, omitted-field behavior, cleanup action, and success transition. -- Reuse `FunctionPlan` for each concrete method or constructor target. Add only - a class-call facet recording method kind, passed-object position, self - storage requirement, result attachment, public descriptor flags, and the - owning class identity. -- Add an `OverloadSetPlan` containing public export, overload kind, ordered - concrete candidate references, typed runtime predicates, ambiguity result, - no-match diagnostic, and selected native target. Candidate predicates use - exact dtype/rank/derived-class facts already completed by argument plans. -- Add an `InheritancePlan` containing canonical base identity, storage - compatibility, inherited/overridden method ownership, Python base type - symbol, and module initialization dependency order. -- Add a `PolymorphicDispatchPlan` only for supported scalar input calls. It - enumerates accepted concrete class identities and a concrete `FunctionPlan` - variant for each; it must not rediscover subclasses from runtime object names. -- Keep destructor selection on the referenced Phase 8 derived handoff/release - plan. Phase 9 records only which class slot invokes that existing action and - which constructor failure edges need cleanup. - -Stable semantic action names must describe behavior, not backend function -names. At minimum distinguish: - -- class registration: `CREATE_TYPE`, `SET_BASE`, `READY_TYPE`, `EXPORT_TYPE`; -- construction: `OMIT`, `ALLOCATE_DEFAULT`, `ALLOCATE_AND_ASSIGN_FIELDS`, - `CALL_BOUND_CONSTRUCTOR`, `DISPATCH_CONSTRUCTOR`, `REJECT_CONSTRUCTION`; -- method binding: `INSTANCE`, `STATIC`, and explicit unsupported class-method - policy until a real class-method contract exists; -- passed-object handoff: `WRAPPER_ADDRESS`, `BORROWED_ADDRESS`, or the exact - completed Phase 8 storage action; -- overload selection: `MATCH_EXACT`, `SELECT_CANDIDATE`, `NO_MATCH`, - `AMBIGUOUS`; and -- construction lifecycle: `ALLOCATE`, `INITIALIZE`, `COMMIT_OWNER`, - `CLEANUP_UNCOMMITTED`, `DESTROY_OWNED`. - -### Mandatory Phase 9 Migration Algorithm - -For every dependency-closed sub-lane: - -1. Capture one passing source/generated-`.pyi` legacy unit and its complete - class, binding, bridge, header, and runtime assertions. -2. Complete class export, constructor kind, method kind, passed-object policy, - overload candidates, inheritance, polymorphic accepted set, allocation, - commit, cleanup, and destruction before `ir2ast.py`. -3. Project those facts into the existing namespace, derived-type, function, - lifecycle, and native-slot plans plus the smallest class-specific facets. -4. Validate the complete class graph and every cross-backend symbolic role - before either backend emits source. -5. Lower through small named methods selected only by typed actions. Backend - local temporaries may implement a selected action but cannot choose policy. -6. Compare generated artifacts with the oracle and record intentional - differences before compiling. -7. Add focused policy, plan-edit, validation, printer, binding, bridge, - source/generated-`.pyi`, edited-contract, and compiled runtime tests. -8. Promote production routing only after the reduced unit passes direct-plan - runtime parity and no class-surface fallback remains. - -### Phase 9A — Semantic Class-Surface Completion - -- [x] Add completed post-IR policy records for public class identity, exports, - constructor kind, method kind, passed-object position, overload ownership, - base identity, type-object registration, and construction permissions. -- [x] Preserve explicit absence: an edited `.pyi` that removes `__init__`, a - method, or an overload candidate must produce an absent plan entry and cannot - resurrect source behavior. -- [x] Move any class-surface inference still in `ir2ast.py` into policy - completion. Wrapper planning must report the owner path and exact missing decision. -- [x] Add policy tests for generated, bound, removed, overloaded, inherited, - abstract, and invalid class surfaces before planner changes. - -### Phase 9B — Typed Class Plan And Validation - -- [x] Add the namespace-owned class, constructor, method-call, overload, and - inheritance plan facets described above, each referencing existing Phase 8 - type/field/lifetime plans rather than copying them. -- [x] Project Python/native names and export aliases once. Do not synthesize - backend method names from strings or recover native targets by scanning - emitted functions. -- [x] Validate unique class/type identity, base-before-derived order, one - constructor kind, method ownership, passed-object position, native call-slot - agreement, field-plan identity, lifecycle roles, and module export symbols. -- [x] Add direct plan-edit tests proving invalid constructor, method, base, - overload, or lifecycle references fail before emission in both backends. - -### Phase 9C — Class Creation And Module Registration - -- [x] Emit one Python type object per supported class, attach the completed - Phase 8 field descriptors, set the validated base type, ready the type, and - export every completed Python name in dependency order. -- [x] Keep opaque instance storage identical to Phase 8 wrapper storage. Class - assembly must not add C aggregate layout, component offsets, or a second - native owner field. -- [x] Attach the Phase 8 destruction action only to owning classes; borrowed - proxies and nested objects retain owners and never gain independent destroy - slots. -- [x] Add artifact and compiled reduced tests for an opaque constructible class, - an intentionally nonconstructible class, a borrowed child, and exact module - export identity. - -### Phase 9D — Default And Keyword Field Constructors - -- [x] Build constructor parameters only from fields explicitly eligible in - completed constructor policy. Preserve keyword-only behavior and native - default component initialization for omitted fields. -- [x] Allocate the Phase 8 persistent native instance first, apply validated - field assignments through existing field setter actions, then commit wrapper - ownership only after every step succeeds. -- [x] On parse, conversion, allocation, or field-assignment failure, clean up - the uncommitted native instance exactly once. Failed `tp_init` must not leak, - double-finalize, or expose a partially initialized wrapper. -- [x] Replay `fconstructors_f90` for default, partial, complete, positional, - unknown-keyword, native-default, and finalization-count assertions through - both source and generated-`.pyi` contracts. - -### Phase 9E — Explicit And Overloaded Constructors - -- [x] Represent direct `@bind("native_name")` construction as one constructor - action linked to a concrete function plan. It replaces, rather than wraps or - falls back to, the generated field constructor. -- [x] Represent constructor overloads as an explicit constructor-owned overload - set. Do not combine `@overload` and `@native_call`, and do not reinterpret a - normal method overload as `tp_init`. -- [x] Complete allocation-before-call versus native-produced-instance policy, - result attachment, owner commit, failure cleanup, and exactly-once release for - every candidate before lowering. -- [x] Reject indistinguishable candidates, missing targets, incompatible self - types, mixed constructor kinds, or ambiguous edited declarations during - policy or plan validation, never from candidate trial calls. -- [x] Add isolated semantic and compiled fixtures for direct bound construction, - two distinguishable constructor candidates, no-match, ambiguity, target - failure cleanup, and source/generated/edited-contract parity. - -### Phase 9F — Instance And Static Methods - -- [x] Lower passed-object instance methods from the completed self position and - Phase 8 handoff. Preserve native argument order when `self` is not the first - native slot. -- [x] Support explicit binding names and type-bound root-target metadata without - exporting the private concrete target as a duplicate module function. -- [x] Lower supported static methods without fabricating `self`; attach them to - the type object with their completed export and descriptor flags. -- [x] Reuse ordinary function argument/result plans for scalar, string, array, - handle, and derived transfers. A method cannot widen an unsupported ordinary - call lane. -- [x] Replay reduced `fclasses_f90` vector methods first, then `vector_store` - handle methods and static factory, with exact source/generated-`.pyi` runtime - and artifact parity. - -### Phase 9G — Class-Owned Overload Dispatch - -- [x] Complete ordered candidates and exact runtime predicates for each - class-owned overload set. Candidate selection may inspect only typed Python - argument facts named by the plan, never invoke candidates speculatively. -- [x] Reuse one overload matching vocabulary for constructors, methods, - operators, and later Phase 11 module generics while keeping their owners and - call actions distinct. -- [x] Detect indistinguishable signatures before emission and produce stable - no-match diagnostics listing the public overload and accepted signatures. -- [x] Validate native target, Python export, argument/result plans, passed-object - position, and overload kind across binding and bridge views. -- [x] Add focused method-overload tests for primitive kinds, ranks, derived - subclasses, keyword normalization, exact no-match, and ambiguity; keep broad - defined-operator/module-generic promotion in Phase 11. - -### Phase 9H — Finalization And Constructor Failure Safety - -- [x] Route normal owned-instance deallocation, constructor failure, and - native-constructor failure through the same Phase 8 destroy/release action, - guarded by an explicit uncommitted/committed lifecycle state. -- [x] Prove finalization occurs exactly once for successfully constructed - owners, once for native storage allocated before a rejected constructor call, - and never for borrowed children or native-owned module objects. -- [x] Prove child-to-parent retention survives method/property access and that - deleting the parent first delays only the parent's owning finalizer. -- [x] Replay `fconstructors_f90` and `fborrowed_finalizer_f90`, including forced - Python argument failures and repeated garbage collection. - -### Phase 9I — Inheritance And Scalar Polymorphic Input Dispatch - -- [x] Complete canonical base/extension relationships, storage compatibility, - inherited fields, inherited methods, overrides, Python base symbols, and - module initialization order before planning. -- [x] Construct base and derived wrappers with the same Phase 8 opaque storage - contract while preserving exact runtime type identity and safe unbound base - method calls on derived instances. -- [x] For each supported scalar input-only polymorphic dummy, enumerate the - accepted concrete class identities and one concrete native call variant per - identity. Reject unknown or abstract runtime classes before the native call. -- [x] Keep polymorphic results, mutable dummies, arrays, descriptor-backed - polymorphic scalars, unlimited polymorphism, and unenumerated extensions on - exact blockers; inheritance must not silently widen them. -- [x] Replay `finheritance_f90` for `issubclass`, `isinstance`, inherited field - access, override dispatch, unbound base calls, and base/circle/box - polymorphic inputs through source and generated-`.pyi` routes. - -### Phase 9J — Production Routing, Documentation, And Closure - -- [x] Add support-report lanes for class registration, default constructors, - bound constructors, constructor overloads, instance methods, static methods, - class overloads, finalizers, inheritance, and scalar polymorphic input. -- [x] Add one reduced compiled direct-plan node per dependency-closed lane, then - update its migration-matrix row only after artifact and runtime parity. -- [x] Prove eligible class units select the production wrapper-plan route and - never call `semantic_ir_to_codegen_ast()`; an unsupported class decision must - keep the whole generation unit on one exact blocker without partial fallback. -- [x] Synchronize constructor/method/inheritance user docs, semantic `.pyi` - reference, source map, feature matrix, subject README, and checklist coverage - with the implemented class contract. -- [x] Run focused policy/plan/backend tests, all affected existing class wrapper - nodes through source/generated-`.pyi` modes, the wrapper suite excluding - LAPACK, the wrapper complexity checker, documentation checks, whitespace, - and the required static-analysis suite. -- [x] Close Phase 9 only when every supported constructor/method/inheritance - unit routes directly, all Phase 8 field/storage/lifecycle decisions remain - unchanged, and every remaining callback, derived-array, polymorphic, or - ambiguous-overload case has an exact Phase 10/11 or unsupported-policy - blocker. - -Closure evidence (2026-07-16): focused semantic, lowering, routing, and direct -Phase 8-10 plan tests passed 184 tests after the final policy refactor. The -complete local wrapper suite excluding LAPACK passed 449 tests in source and -generated-contract modes. The wrapper complexity checker, Ruff lint/format, -Bandit, Vulture, explicit-`origin/main` Radon policy, and advisory Radon -complexity/maintainability commands passed. The CI-only `--base-ref auto` -Radon lookup could not resolve outside CI, so the required explicit-base run -was used. No LAPACK test was run locally. - -### Phase 9 Expansion Gate - -- [x] Inventory class creation/destruction, constructor categories, - instance/static/type-bound methods, overloads, inheritance/polymorphism, - decorator effects, module initialization, legacy owners, semantic fixtures, - and passing runtime oracles. -- [x] Define the Phase 8/9/10/11 ownership boundaries and keep all class - implementation rows unchecked. -- [x] Split implementation into dependency-ordered Phase 9A-J sub-lanes with - explicit policy, plan, validation, lowering, artifact, compiled parity, - production routing, documentation, and closure gates. - -## Phase 10 — Callbacks And Trampolines - -Expansion status: complete. Implementation status: complete. Immediate -callbacks are covered by focused policy/plan/artifact tests, existing compiled -runtime oracles, production routing, and broad non-LAPACK wrapper-suite -evidence below. - -Scope: immediate callback argument validation, call-scoped context lifetime, -external Fortran adapter procedures, C trampolines, scalar/string/array/derived -argument and result conversion, permissive reference writeback, same-thread -re-entry and GIL handling, callback cleanup, and the documented fatal error -boundary. - -### Phase 10 Boundary And Explicit Non-Scope - -Phase 10 composes ordinary call transfers completed in Phases 2-9 but does not -reinterpret them. A prototype is interface-facing: it describes the exact -procedure declaration that native Fortran uses, including argument order, -`In`/`Out`/`InOut` direction, value/reference transport, rank, shape, character -length, result representation, and procedure characteristics. Normal wrapper -projection and callback adapter projection remain distinct completed records. - -Named `@prototype` declarations are the single exact native-signature -authority. Annotation use selects a callback signature; call use selects a -directly callable standalone procedure entity. -`In(T)`, `Out(T)`, and `InOut(T)` preserve exact dummy direction, while -`Addr(T)` and `Value(T)` preserve transport independently. `@pure` preserves -the corresponding procedure characteristic. Prototypes are semantic-only -declarations and never become Python runtime exports. - -A pure prototype is not a supported Python callback signature. The callback -adapter calls the Python runtime and therefore cannot satisfy Fortran purity; -post-IR policy must block a prototype used both as a specification function and -as a callback before planning. - -Post-IR policy classifies each use as a callback, a standalone procedure entity, -or a module-procedure call. One shared prototype-signature plan owns the -generated `prik_` abstract-interface symbol and exact characteristics. Lowering -only declares callback adapters or concrete entities with -`procedure(prik_...)`; it does not reconstruct placement, purity, direction, -transport, or declaration mode. Direct prototype calls never fall back to an -implicit external declaration, and `@standalone` is rejected on a prototype as -redundant placement metadata. - -The supported callback contract is deliberately call-scoped: - -- the Python callable is validated and retained before the native call, placed - in one thread-local context stack for that callback site, and released after - the native call returns; -- nested callback-taking calls on the same entering Python thread are allowed; -- each C trampoline validates the entering thread, acquires the GIL, converts - completed adapter arguments, invokes the current Python callable, converts - or copies back results, releases the GIL, and returns to its Fortran adapter; -- `Value(T)` uses value conversion; scalar reference storage, fixed-length - character storage, arrays, and derived objects use permissive copy-in/out - storage already asserted by the runtime tests; and -- a Python exception, invalid callback return, missing context, or cross-thread - invocation prints the Python error and aborts the host process. The direct - path must not fabricate a fallback result or continue native execution. - -The following remain outside Phase 10: - -- stored callbacks, callback registration/unregistration, procedure-pointer - fields, callbacks invoked after the wrapped call, optional dummy procedures, - null procedure pointers, asynchronous callbacks, and cross-thread callback - execution; -- persistent callable ownership, callback teardown during object/library - destruction, and callback use as a synchronization mechanism; -- callbacks whose signature is incomplete, assumed-rank, has a runtime-only - character length, or otherwise lacks the exact ABI facts required by the - adapter and trampoline; -- callback-specific coercion, recovery, exception-result, or argument - reordering policies not present in the public contract or legacy tests; and -- module generic/operator orchestration that merely contains a callback-taking - candidate; its callback transfer may be reusable, but public generic routing - remains Phase 11. - -### Phase 10 Existing Oracle And Inventory - -The public callback guide/reference, generated semantic `.pyi` contracts, and -existing source/generated-`.pyi` runtime assertions are the behavioral oracle. - -| Existing unit | Phase 10 behavior to preserve | Required reduced slice | -| --- | --- | --- | -| `tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py::test_immediate_scalar_dummy_procedure_calls_python_callback[*]` | scalar result/void callbacks, callable validation, balanced references, nested same-thread re-entry, held-GIL wrapper envelope, thread-local context, and fatal callback conversion failures | first context, trampoline, scalar-value, cleanup, and fatal-boundary slice | -| `tests/fortran/callbacks/end_to_end/test_array_callbacks.py::test_immediate_dummy_procedure_converts_array_arguments_and_results[*]` | writable array view, shaped array result, outer-output identity, and reference writeback | array argument/result slice | -| `tests/fortran/callbacks/end_to_end/test_supported_callback_shapes.py::test_immediate_callbacks_cover_all_supported_argument_shapes[*]` | scalar values, fixed strings, arrays, derived values, non-scalar reference writeback, and one combined call envelope | cross-kind and derived closure slice | -| `tests/fortran/callbacks/pipeline/test_generated_callback_contracts.py` | named prototypes, primitive value defaults, explicit primitive `Addr(T)` references, non-primitive `Value(T)` transport, shape, character storage, cross-module identity, and result annotations round-trip exactly | semantic-contract parity slice | -| `tests/fortran/callbacks/semantics/test_pyi_callback_semantics.py` | prototype declarations and references, primitive `Addr(T)`, non-primitive `Value(T)`, exact argument names used by shapes, and invalid prototype transport forms | policy completion before planner work | - -### Phase 10 Plan Shape And Action Vocabulary - -Extend the existing argument/function plans; do not add a second function plan -or embed a legacy AST. - -- Add one `CallbackHandoffPlan` facet to each callable argument. It records the - callable owner, call-scoped lifetime, context symbol, context stack action, - entering-thread rule, GIL rule, Fortran adapter symbol, C trampoline symbol, - ordered callback argument plans, optional result plan, and fatal-error - action. -- Add a `CallbackTransferPlan` for each callback argument/result containing the - semantic type identity, object kind, value/reference ABI, rank/shape/length - roles, Python barrier action, primitive-scalar value projection or non-scalar - reference writeback, borrowed-owner retention, and exact C ABI roles. -- Reuse ordinary scalar, string, array, and derived plan vocabulary where the - representation is identical. The native callback is the caller, so normal - Python-to-native argument projection cannot be silently reused in reverse. -- Add ordered function lifecycle phases `VALIDATE_CALLBACK`, `PUSH_CONTEXT`, - `ENTER_NATIVE`, `POP_CONTEXT`, and `RELEASE_CALLBACK`. Every failure edge - before native entry unwinds acquired references; the fatal trampoline edge - never returns. -- Keep backend-local adapter locals and temporary Python views inside the - selected implementation method. They are emitted-code details, not semantic - policy. - -Stable actions must describe behavior, not generated function names. At -minimum distinguish: - -- callable/context: `VALIDATE_CALLABLE`, `RETAIN_CALLABLE`, `PUSH_CONTEXT`, - `POP_CONTEXT`, `RELEASE_CALLABLE`; -- callback ABI: `VALUE`, `REFERENCE`, `DATA_AND_SHAPE`, - `DATA_AND_LENGTH`, and `DERIVED_ADDRESS`; -- adapter transfer: `COPY_IN`, `COPY_OUT`, `COPY_IN_OUT`, `BORROW_READ_ONLY`, - and `BORROW_WRITABLE`; -- trampoline runtime: `REQUIRE_ENTERING_THREAD`, `ACQUIRE_GIL`, `CALL_PYTHON`, - `RELEASE_GIL`, and `ABORT_WITH_PYTHON_ERROR`; and -- result handling: `RETURN_SCALAR`, `RETURN_ARRAY_ADDRESS`, - `RETURN_DERIVED_ADDRESS`, `RETURN_VOID`, and `REJECT_RESULT`. - -### Mandatory Phase 10 Migration Algorithm - -For every dependency-closed sub-lane: - -1. Capture the documented behavior, one passing source/generated-`.pyi` - legacy unit, and its callback-related binding, bridge, adapter, trampoline, - and runtime assertions. -2. Complete callable validity, signature order, ABI roles, reference - writeback/value isolation, - shape/length dependencies, result handling, context lifetime, thread/GIL - rules, cleanup, and fatal behavior before wrapper planning. -3. Project those facts into the existing function/argument/lifecycle plans plus - the smallest callback-specific facets. -4. Validate the binding, bridge, adapter, and trampoline role graph before - either backend emits source. -5. Lower through typed action dispatch and small named methods. Do not trial a - callback or infer shape/transport from emitted locals. -6. Compare emitted artifacts and behavior with the runtime oracle; document - any safety improvement before changing observable behavior. -7. Add focused policy, editable-plan, validation, binding, bridge, printer, - source/generated-`.pyi`, subprocess-failure, and compiled runtime tests. -8. Promote production routing only after the complete callback-taking - generation unit passes direct-plan parity with no callback fallback. - -### Phase 10A — Semantic Callback Completion - -- [x] Add completed post-IR callback records for callable signature order, - object kind, value/reference ABI, shape/length roles, result representation, - call scope, context lifetime, same-thread rule, GIL rule, cleanup, and - fatal-error behavior. -- [x] Preserve generated and edited named prototypes exactly. Reject an - incomplete prototype reference, invalid prototype `Addr`, optional procedure, - stored/procedure-pointer lifetime, unavailable mandatory native interface, - or unsupported result with the owner path and one exact reason. -- [x] Complete callback signature/result/ownership policy before wrapper - planning; lowering may only project the completed callback record. -- [x] Add policy/planning tests for primitive value defaults, explicit - primitive `Addr(T)` references, non-primitive `Value(T)`, - and retained unsupported forms before planner changes. - -### Phase 10B — Typed Callback Plan And Validation - -- [x] Add callback handoff, transfer, result, context, and lifecycle facets to - the existing function plan and reference ordinary datatype plans instead of - copying them. -- [x] Project adapter/trampoline symbols and ABI roles once. Do not synthesize - backend handler names or rediscover dimension/length dependencies from - emitted variables. -- [x] Validate unique callback sites, exact argument order, role availability, - transport/writeback, dtype/rank/shape/length agreement, derived type identity, - result compatibility, context balance, and validate/push/pop/release order. -- [x] Add direct plan-edit tests proving invalid callback roles, unbalanced - lifecycle, or cross-backend disagreement fail before emission. - -### Phase 10C — Context, Trampoline, GIL, And Scalar Values - -- [x] Emit one thread-local stack per callback site, callable validation and - strong-reference retention before native entry, reverse-order pop/release - after return, and cleanup on every ordinary pre-entry failure. -- [x] Emit one C trampoline and separately linked external Fortran adapter from - the completed ABI; validate the entering thread and context before Python - conversion. -- [x] Acquire/release the GIL inside the trampoline and keep the outer - callback-taking wrapper on the legacy-observed held-GIL envelope. -- [x] Lower void and scalar-value arguments/results first, then replay scalar - callback success, nested re-entry, non-callable rejection, and balanced - reference-count assertions in both build modes. - -### Phase 10D — Primitive Scalar Values And Fixed-String Storage - -- [x] Lower every primitive scalar callback argument as an owned NumPy scalar - value. `Value(T)` changes only the native ABI; scalar reference writeback is - unsupported and must be modeled as a callback result. -- [x] Lower fixed-string references as rank-zero fixed-width bytes storage with - exact length, padding, and writeback. The semantic annotation remains - `String[n]` and carries no native direction. -- [x] Reject runtime-length callback strings before emission; no adapter-local - inference may change the representation. -- [x] Replay the scalar-value and string-storage cases from the combined - callback fixture through source/generated-`.pyi` routes. - -### Phase 10E — Array Arguments And Results - -- [x] Lower array callback arguments from completed dtype, rank, shape, - ordering, contiguity, and alignment facts. Reference arrays expose writable - storage and copy back in adapter order. -- [x] Lower fixed-shape array results through one validated returned-address - ABI and assign them into the native adapter result. Reject incomplete shape - or unsupported ownership before emission. -- [x] Preserve output-array Python identity in the outer ordinary call and do - not add a detached-copy fallback. -- [x] Replay `fcallback_array_f90` plus the combined array-storage callback and - artifact assertions in both build modes. - -### Phase 10F — Derived Arguments And Results - -- [x] Reuse the exact Phase 8/9 type identity, opaque wrapper, owner-retention, - and destroy/release actions for callback-local derived wrappers. Do not - expose aggregate layout or introduce callback-specific storage ownership. -- [x] Borrow callback input wrappers only for the callback invocation; convert - supported callback results to the completed native result storage and - release temporary wrapper ownership exactly once. -- [x] Validate exact runtime class/type identity before using a returned - derived address. Polymorphic, descriptor-backed, or unsupported derived - callback forms retain exact blockers. -- [x] Replay `fcallback_derived_f90` and the combined derived callback after the - Phase 9 constructor/class route is green. - -### Phase 10G — Fatal Errors, Re-entry, And Cleanup - -- [x] Route Python exceptions, argument-call mismatch, invalid callback result, - missing context, and cross-thread entry through one - traceback-plus-`abort()` action. Never return a fabricated value. -- [x] Prove nested same-thread callback calls use stack discipline and restore - the previous callable/context after the inner call. -- [x] Prove ordinary validation or setup failures before native entry release - every retained reference, and successful calls leave the callable reference - count unchanged. -- [x] Run fatal cases in subprocesses for both source/generated-`.pyi` builds - and assert the documented traceback/error text plus nonzero termination. - -### Phase 10H — Production Routing, Documentation, And Closure - -- [x] Add support-report lanes for callback context, scalar value/storage, - fixed strings, arrays, derived values, result conversion, same-thread - re-entry, and fatal errors. -- [x] Add one reduced compiled direct-plan node per dependency-closed lane and - update its migration-matrix row only after artifact and runtime parity. -- [x] Prove eligible callback units select the production wrapper-plan route; - unsupported callback policy must keep the whole generation unit on one - exact blocker. -- [x] Synchronize callback guide/reference, semantic `.pyi` reference, feature - matrix, callback README, source map, and checklist coverage with the direct - implementation. -- [x] Run focused policy/plan/backend tests, every callback wrapper node in - source/generated-`.pyi` modes, the wrapper suite excluding LAPACK, the - wrapper complexity checker, documentation checks, whitespace, and the - required static-analysis suite. -- [x] Close Phase 10 only when every supported immediate callback unit routes - directly, no callback plan falls back after generation starts, and every - stored/optional/asynchronous/cross-thread or incomplete callback form has an - exact retained blocker. Stop before Phase 11. - -Closure evidence (2026-07-16): callback policy, editable-plan validation, -binding/bridge artifacts, scalar/string/array/derived conversion, nested -same-thread re-entry, reference cleanup, and subprocess fatal-boundary tests -all passed through the direct route. The same 184-test focused batch and -449-test non-LAPACK wrapper replay used for Phase 9 closure cover the complete -immediate-callback matrix. Required static checks passed with the explicit -Radon base noted above, and implementation stopped before Phase 11. - -### Phase 10 Expansion Gate - -- [x] Inventory the public callback contract, semantic prototype records, - legacy lowering/codegen owners, source/generated-`.pyi` runtime fixtures, - context lifetime, re-entry/GIL behavior, exception/abort behavior, and every - supported scalar/string/array/derived argument-result combination. -- [x] Define the Phase 9/10/11 boundary and retain explicit blockers for stored, - optional, asynchronous, cross-thread, incomplete-signature, and unsupported - callback forms. -- [x] Split implementation into dependency-ordered Phase 10A-H sub-lanes with - policy, typed plan, validation, lowering, compiled parity, production - routing, documentation, and closure gates. - -## Phase 11 — Cross-Cutting Wrapper Suite Completion - -Implementation status: complete. The pre-Phase-11 ledger contained 236 -wrapper-plan nodes, five dual-route array parity nodes, 113 passing legacy-route -nodes, 95 non-generating nodes, and two deferred real-library nodes. The final -forced-plan sweep passed 435 of 449 non-real-library nodes before obsolete -dual-route artifact assertions were removed; its two shared implementation -gaps were Fortran-ordered strided ndarray validation and static `nopass` method -dispatch, both now resolved through existing policy/runtime paths. - -The ordered output aggregator now combines direct and hidden native results -with visible scalar, string, array, and derived writeback. It converts each -value once in public result order and releases every earlier Python reference -if a later conversion or tuple allocation fails; the former single-result and -"native result plus writeback" blockers are removed. - -Scope: existing wrapper tests whose generation units combine completed semantic -lanes or exercise build and runtime behavior rather than introducing one new -datatype lane. - -Implement in these dependency-ordered waves: - -1. reconcile the five reduced array dual-route nodes and remove stale Phase 7 - exclusion bookkeeping where their completed actual-source policy now permits - production routing; -2. migrate source/semantic-`.pyi` build modes, edited contracts, external - symbols, multiple-source linkage, and independent native bundles through one - shared route and planner; -3. migrate mixed scalar/string/array/handle/derived/module/class generation - units without adding per-test or per-datatype fallback; -4. migrate naming, generic interfaces, defined operators, OpenMP/runtime policy, - and remaining public-surface orchestration; and -5. require the live nondeferred ledger to contain only `wrapper-plan` or - justified `not-applicable` nodes before Phase 12 begins. - -- [x] Reconcile every remaining `legacy` or `dual-route` matrix row by owning - test area: `build_from_source`, `build_from_pyi`, `edit_pyi_contracts`, - `external_routines`, `multiple_files`, `naming`, `runtime_behavior`, and the - full BLAS/LAPACK examples. -- [x] Group remaining rows into dependency-ordered waves by their actual - unsupported owner paths. Do not implement a broad test directory as one - special case and do not add per-test backend fallbacks. -- [x] For every newly discovered semantic or backend gap, expand the applicable - earlier lane or add an explicit sub-lane here, then follow the complete - policy -> plan -> backend -> emission -> compiled parity -> route sequence. -- [x] Prove source-driven and semantic-`.pyi`-driven builds use the same route - selector and wrapper planner while retaining their existing build assertions. -- [x] Prove edited-policy contracts, external symbols, multiple-source builds, - naming/generic interfaces, runtime policies, recursion, OpenMP, and real - library-independent native bundles preserve their existing assertions - through the wrapper-plan route. -- [x] Keep non-wrapper-generating tests, including layout and generated-`.pyi` - checks, marked `not-applicable` to route selection but passing in the same - suite. -- [x] Run every `tests/wrapper` test except - `test_real_blas_lapack.py` locally and in CI as the pre-cutover gate. -- [x] Finish this phase only when every nondeferred matrix row is either - `wrapper-plan` or justified `not-applicable`; no nondeferred row may remain - `legacy` or `dual-route`. BLAS/LAPACK rows remain - `deferred-real-library` until Phase 12. - -Closure evidence (2026-07-16): the Phase 11 ledger contains 344 canonical -wrapper-plan nodes, 95 justified non-generating nodes, two deferred -real-library nodes, and no legacy or dual-route node. The complete local -pre-cutover suite outside the shared BLAS/LAPACK file passed all 439 collected -tests. Mixed outputs use the ordered aggregator, Fortran-ordered strided array -validation reuses the shared array-actual runtime path, and static `nopass` -methods reuse the completed class invocation path; no per-test route or -backend fallback was added. - -## Phase 12 — Cutover And Removal - -Implementation status: complete. Local BLAS evidence is recorded below; -LAPACK execution remains intentionally CI-only. - -Local verification boundary: run the BLAS generation unit locally. Do not run -the LAPACK generation unit locally; make its wrapper-plan invocation runnable -in GitHub Actions and use that job for LAPACK parity and cutover evidence. - -External-interface parameter lists preserve native ABI order, while their -declarations may be topologically ordered from the plan's explicit array -extent-reference roles. This permits a later scalar extent dummy to be -declared before an earlier array dummy without reordering the native call. - -Cutover contract: source builds, semantic-`.pyi` builds, Makefile generation, -manifest replay, and strict-name validation all use completed policy -> -`WrapperPlan` -> `WrapperGenerator`. The build API has no route selector, -rollback flag, or silent fallback; an unsupported owner path fails before any -backend or legacy lowering runs. - -- [x] Re-audit collected Python test nodes under `tests/wrapper` and reconcile - them with the migration matrix. No test may be missing from the matrix. -- [x] After every other migration row is complete, restore the full - `test_real_blas_lapack.py` run and any required native-cache preparation in - local opt-in verification and GitHub Actions. -- [x] Run BLAS locally through the canonical route using its existing contract, - import, ABI, and runtime assertions. Run the equivalent exact LAPACK node in - the dedicated GitHub Actions real-library matrix; do not run it locally. -- [x] Require every wrapper-generating test row to be `wrapper-plan`; no row - remains `legacy`, `dual-route`, or `deferred-real-library`. -- [x] Configure the complete `tests/wrapper` suite in CI with ordinary tests in - the main matrix and the full BLAS/LAPACK nodes in the cached real-library - matrix. -- [x] Confirm no wrapper build lane uses the old - `semantic_ir_to_codegen_ast()` path. The old lowering is no longer a supported - test owner and receives no focused compatibility coverage. -- [x] Remove route support tracking and fallback diagnostics; whole-generation - units now either validate and generate one plan or fail on exact owner-path - support diagnostics before emission. -- [x] Retain rollback only until the live ledger is reconciled, then remove it - in one cutover without compatibility flags or per-function fallback. -- [x] Do not move modified isolated nodes or printers back into the legacy - package during migration. After final cutover, remove the legacy package - pieces proven unused and keep `prik.codegen` as the canonical - generator rather than performing a second package rename. -- [x] Keep semantic `.pyi` emission under `prik.printers` and - retire focused tests of the old semantic AST, bridge, binding, and printer - implementation before deleting the legacy package. -- [x] Remove the temporary legacy route and its route diagnostics after every - live generation unit is supported; do not replace it with compatibility - shims or per-function fallback. -- [x] Remove migration-only dual-route orchestration after the complete existing - wrapper suite proves the wrapper-plan route and legacy rollback is no longer - supported. Keep the existing behavioral fixtures and assertions. -- [x] Keep source printers only for the remaining generated source fragments they - still own, or replace them with narrower emitters once the model layer is no - longer needed. - -Closure evidence (2026-07-16): the final live ledger contains 346 canonical -wrapper-plan nodes, 75 justified non-generating nodes, and zero legacy, -dual-route, or deferred nodes. The complete local suite outside the shared -real-library file passed 419 tests; the exact BLAS full-library node passed -locally; and the exact BLAS and LAPACK nodes are runnable as independent legs -of the cached GitHub Actions real-library matrix. LAPACK was intentionally not -run locally, so its runtime result remains CI evidence. Focused semantic and -compiled class/module policy tests passed 80 tests, all wrapper-codegen tests -passed 352 tests, and documentation plus structural layout checks passed 1,142 -tests. Ruff lint/format, Bandit, Vulture, the wrapper-codegen complexity check, -the Radon policy against explicit base `main`, advisory Radon complexity and -maintainability reports, and `git diff --check` all passed. - -## Verification - -- [x] Documentation changes run - `python3 -m pytest -q tests/docs` - and `git diff --check`. -- [x] Wrapper-plan code changes run the affected existing `tests/wrapper` nodes, - the minimal intermediate contract tests required above, and the required - static-analysis suite from `AGENTS.md`. -- [x] Wrapper-codegen implementation changes pass - `python3 tools/check_codegen_complexity.py` with no handler waiver. -- [x] Runtime wrapper tests cover every changed generated behavior. -- [x] Every migrated lane completed legacy-oracle comparison before cutover; - final tests now exercise only the canonical wrapper-plan route and retain the - existing behavior and ABI-relevant call assertions. -- [x] Structural dependency tests prove complete generator isolation: no - imports from `prik.codegen` to `prik.codegen` or in the reverse - direction. -- [x] BLAS and LAPACK full-library wrapper tests remained excluded locally and in - GitHub Actions throughout Phases 0-11. At the explicit Phase 12 gate, enable - BLAS locally and in GitHub Actions, enable LAPACK only in GitHub Actions, and - keep local LAPACK execution disabled. - -## Completion Record - -- [x] The final report for each lane names the plan actions added, the binding - and bridge handlers they dispatch to, and the handoff specs validated. -- [x] No unsupported wrapper lane uses old lowering/codegen; focused tests now - target completed policy, `WrapperPlan`, `WrapperGenerator`, or compiled - public behavior rather than `ir2ast.py` and `prik.codegen` internals. -- [x] The final cutover report includes the completed `tests/wrapper` migration - matrix and confirms every wrapper-generating row uses the wrapper-plan route. -- [x] The final report includes focused verification commands and results. -- [x] The final report includes the changed-stage breakdown required by - `AGENTS.md` and names every test file added or updated with the behavior it - covers. - -## Post-Cutover Legacy Codegen Removal - -The legacy `prik.codegen` package, `prik/semantics/ir2ast.py`, and the obsolete -`prik/compiling/python_wrapper.py` pipeline are removed together. No alias, -fallback, compatibility import, or rejection-only test preserves that route. - -Required behavior remains with its current owner: completed semantic policy -tests for semantic decisions, `tests/codegen/` for plans and direct -source generation, and compiled `tests/wrapper/` cases for public Python -behavior and native ABI outcomes. Static-analysis baselines cover only source -that remains in the repository. -## Session Continuation Protocol - -The stable continuation prompt is: - -```text -Continue implementing the wrapper-plan migration checklist. -``` - -On continuation: read this checklist and `AGENTS.md`; inspect the dirty -worktree; choose the first unchecked dependency-closed item; replay the -existing passing wrapper test before extending a lane; implement code and tests -together; run required verification; and check items only from live evidence. -Do not reset unrelated user changes, infer missing policy in lowering, or use a -new fallback after direct plan generation starts. diff --git a/docs/developer/source-map.md b/docs/developer/source-map.md index 137ddea8e..caa6991c4 100644 --- a/docs/developer/source-map.md +++ b/docs/developer/source-map.md @@ -21,8 +21,10 @@ current Python package layout. | `prik/pipeline/build.py` | End-to-end Fortran source and semantic `.pyi` extension builds | preprocessing, parser, probes, completed semantic policy, wrapper planning and generation, compilation | | `prik/__init__.py` | Public Python exports | parser public-entrypoint tests and user examples | | `prik/policy/ownership.py` | Central ownership, transfer, destruction, and generated-action policy | policy completion and typed wrapper planning | -| `prik/preprocessing/source.py` | Compiler-backed C and Fortran source expansion, provenance, and dependency facts | parser input preparation | +| `prik/preprocessing/source.py` | Compiler-backed Fortran source expansion, provenance, and dependency facts | parser input preparation | + | `prik/preprocessing/fortran.py` | Native Fortran `INCLUDE` expansion and source mappings | Fortran parser input preparation | | `prik/preprocessing/probes/fortran_types.py` | Fortran kind/storage facts and cache | semantic Fortran conversion and wrapper builds | | `prik/pipeline/type_mapping_report.py` | Cross-stage target datatype mapping examples | semantic and codegen datatype catalogues plus documentation example tests | @@ -44,32 +46,32 @@ change crosses ownership boundaries. | Change area | Open first | Public docs to update | Focused evidence | | --- | --- | --- | --- | | CLI flags, stage selection, output formatting, diagnostics | `prik/cli.py` | `docs/user/reference/cli-commands.md`, `docs/user/getting-started/beginner-workflow.md` | `tests/fortran/command_line_interface/pipeline/`, `tests/docs/test_examples.py` | -| Compiler preprocessing, include paths, macros, and target flags | `prik/preprocessing/source.py` | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/compiler-preprocessing.md`, `docs/developer/fortran-parser-reference.md` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` | -| Datatype probing, semantic normalization, NumPy projection, and mapping reports | `prik/preprocessing/probes/fortran_types.py`, `prik/semantics/scalar_types.py`, `prik/codegen/primitive_scalar_types.py`, `prik/pipeline/type_mapping_report.py` | `docs/developer/internal-architecture/type-system.md`, `docs/user/reference/semantic-ir.md` | `tests/fortran/data_types/` | -| Fortran parser facts and diagnostics | `prik/parsers/fortran/parser.py` | `docs/developer/fortran-parser-reference.md`, `docs/user/examples/recipes/inspect-fortran-api.md` | `tests/fortran/source_parsing/parsing/` | +| Compiler preprocessing, include paths, macros, and target flags | `prik/preprocessing/source.py` | `docs/user/examples/recipes/compiler-preprocessing.md`, `docs/developer/packages/preprocessing.md`, `docs/developer/packages/parsers.md` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` | +| Datatype probing, semantic normalization, NumPy projection, and mapping reports | `prik/preprocessing/probes/fortran_types.py`, `prik/semantics/scalar_types.py`, `prik/codegen/primitive_scalar_types.py`, `prik/pipeline/type_mapping_report.py` | `docs/developer/concepts/datatype-lifecycle.md`, `docs/user/reference/semantic-ir.md` | `tests/fortran/data_types/` | +| Fortran parser facts and diagnostics | `prik/parsers/fortran/parser.py` | `docs/developer/packages/parsers.md`, `docs/user/examples/recipes/inspect-fortran-api.md` | `tests/fortran/source_parsing/parsing/` | | Semantic `.pyi` parsing, conversion, printing, package generation, and round-trip behavior | `prik/parsers/pyi/parser.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py`, `prik/printers/pyi.py` | `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/pyi-contracts/index.md`, `docs/user/examples/recipes/semantic-pyi-contracts.md` | `tests/fortran/semantic_pyi_format/`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py`, `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/semantic_pyi_format/pipeline/` | | Wrapper-planning errors and support claims | `prik/policy/completion.py`, `prik/planning/planner.py` | `docs/user/reference/diagnostic-codes.md`, `docs/user/language-support/feature-matrix.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | | Source-driven Fortran wrapper orchestration | `prik/pipeline/build.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/guide/building-shared-library.md` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py`, `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py` | | Semantic `.pyi` wrapper orchestration from native artifacts | `prik/pipeline/build.py`, `prik/pipeline/pyi.py`, `prik/semantics/pyi2ir.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py`, `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/pyi_contracts/exports_and_modules/`, `tests/fortran/pyi_contracts/functions_and_classes/` | | Ownership, lifetime, output projection, and unsupported wrapper policy | `prik/policy/completion.py`, `prik/policy/ownership.py`, `prik/policy/models.py`, `prik/policy/construction.py`, `prik/planning/planner.py` | `docs/user/guide/memory-management.md`, `docs/user/reference/semantic-pyi-format.md`, `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/semantics/`, feature-local `policy/`, and `tests/fortran/infrastructure/codegen/` | | Immediate callback policy, typed adapters, and trampolines | `prik/policy/models.py`, `prik/policy/construction.py`, `prik/policy/completion.py`, `prik/planning/models.py`, `prik/planning/planner.py`, `prik/codegen/c/binding.py`, `prik/codegen/fortran/bridge.py` | `docs/user/guide/callbacks.md`, `docs/user/reference/semantic-pyi-format.md` | `tests/fortran/callbacks/` | -| Native compilation, binding support, and shared-library linking | `prik/pipeline/build.py`, `prik/compiler/compilers.py`, `prik/compiler/native_support.py` | `docs/user/reference/fortran-wrapper.md`, `docs/developer/build-system.md` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | +| Native compilation, binding support, and shared-library linking | `prik/pipeline/build.py`, `prik/compiler/compilers.py`, `prik/compiler/native_support.py` | `docs/user/reference/fortran-wrapper.md`, `docs/developer/packages/compiler.md`, `docs/developer/packages/pipeline.md` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` | | Public Python exports | `prik/__init__.py` | `README.md`, `docs/user/reference/python-api.md` | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` | | Reference BLAS source ownership, inventory, and numerical validation | `examples/blas/routine_inventory.py`, `examples/blas/tests/test_routine_coverage.py` | `examples/blas/README.md`, `docs/user/examples/blas-wrapper.md` | `examples/blas/tests/test_*.py`, `examples/blas/ci/full_surface.py`, dedicated real-libraries workflow | | Reference LAPACK source ownership, inventory, and numerical validation | `examples/lapack/routine_inventory.py`, `examples/lapack/tests/test_routine_coverage.py` | `examples/lapack/README.md`, `docs/user/examples/lapack-wrapper.md` | `examples/lapack/tests/test_*.py`, `examples/lapack/ci/full_surface.py`, dedicated real-libraries workflow | | FFTPACK public-module boundary, source ownership, and numerical validation | `examples/fftpack/routine_inventory.py`, `examples/fftpack/tests/test_routine_coverage.py` | `examples/fftpack/README.md`, `docs/user/examples/fftpack-wrapper.md` | `examples/fftpack/tests/test_*.py`, `tests/fortran/building_shared_library/end_to_end/real_libraries/test_fftpack_routines.py`, dedicated real-libraries workflow | | MINPACK source ownership, parameter constants, and numerical validation | `examples/minpack/routine_inventory.py`, `examples/minpack/tests/test_routine_coverage.py` | `examples/minpack/README.md`, `docs/user/examples/minpack-wrapper.md` | `examples/minpack/tests/test_*.py`, `tests/fortran/building_shared_library/end_to_end/real_libraries/test_minpack_routines.py`, dedicated real-libraries workflow | | Source navigation documentation | `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md`, package README files | `docs/developer/source-map.md` | `tests/docs/test_reference_and_source_map.py` | +| Generated Fortran bridge | `prik/codegen/fortran/bridge.py`, `prik/printers/fortran.py`, `prik/pipeline/wrapper.py` | `docs/user/reference/fortran-wrapper.md` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` generated-wrapper assertions | +| Generated CPython binding and Python-visible runtime behavior | `prik/codegen/c/binding.py`, `prik/codegen/c/python_surface.py`, `prik/codegen/c/naming.py`, `prik/printers/c.py`, `prik/pipeline/wrapper.py` | `docs/user/reference/fortran-wrapper.md`, `docs/user/reference/python-api.md` | `tests/fortran/infrastructure/codegen/`, `tests/fortran/` | ## Package Map @@ -78,23 +80,23 @@ PRIK_C_DOCS_END --> | --- | --- | --- | --- | | `prik/contracts/` | Public semantic `.pyi` contract vocabulary | `__init__.py` | `tests/fortran/semantic_pyi_format/`, semantic `.pyi` reference | | `prik/compiler/` | Compiler execution, compile objects, vendor profiles, native support installation, and linking | `compilers.py`, `objects.py`, `compiler_profiles.py`, `native_support.py` | compiler and shared-library build tests | -| `prik/preprocessing/` | Compiler-backed source expansion, raw C metadata, native Fortran includes, provenance, and target probes | `source.py`, `c.py`, `fortran.py`, `probes/` | C and Fortran preprocessing and target-probe tests | +| `prik/preprocessing/` | Compiler-backed Fortran source expansion, native includes, provenance, and target probes | `source.py`, `fortran.py`, `probes/fortran_types.py` | Fortran preprocessing and target-probe tests | | `prik/pipeline/` | Semantic `.pyi` loading, cross-stage datatype reporting, plan-to-source wrapper generation, and native build orchestration | `pyi.py`, `type_mapping_report.py`, `wrapper.py`, `build.py` | `.pyi`, datatype report, wrapper generation, and build tests | | `prik/runtime/` | Python runtime objects and bundled native support consumed by generated extensions | `handles.py`, `native_support/` | runtime handle, native-support, and wrapper runtime tests | -| `prik/parsers/` | Public namespace for language and semantic `.pyi` frontends | child parser packages | `tests/fortran/source_parsing/parsing/`, `tests/c/parsing/`, `tests/fortran/semantic_pyi_format/parsing/` | -| `prik/parsers/fortran/` | Fortran lexer, recursive parser, models, type resolver, and parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `type_resolver.py`, `cli.py` | `tests/fortran/source_parsing/parsing/`, `docs/developer/fortran-parser-reference.md` | -| `prik/semantics/` | Language-neutral semantic IR, scalar datatype vocabulary, source-to-IR conversion, `.pyi` conversion, and raw ownership or descriptor metadata | `models.py`, `scalar_types.py`, `fortran2ir.py`, `c2ir.py`, `pyi2ir.py`, `ownership_metadata.py`, `native_array_handles.py` | `tests/fortran/data_types/semantics/`, `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/` | +| `prik/parsers/` | Public namespace for the Fortran and semantic `.pyi` frontends | child parser packages | `tests/fortran/source_parsing/parsing/`, `tests/fortran/semantic_pyi_format/parsing/` | +| `prik/parsers/fortran/` | Fortran lexer, recursive parser, models, type resolver, and parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `type_resolver.py`, `cli.py` | `tests/fortran/source_parsing/parsing/`, `docs/developer/packages/parsers.md` | +| `prik/parsers/pyi/` | Semantic `.pyi` text/file parsing to Python AST | `parser.py` | `tests/fortran/semantic_pyi_format/parsing/`, `docs/user/reference/semantic-pyi-format.md` | +| `prik/semantics/` | Language-neutral semantic IR, scalar datatype vocabulary, Fortran-to-IR conversion, `.pyi` conversion, and raw ownership or descriptor metadata | `models.py`, `scalar_types.py`, `fortran2ir.py`, `pyi2ir.py`, `ownership_metadata.py`, `native_array_handles.py` | `tests/fortran/data_types/semantics/`, `tests/fortran/semantic_ir/semantics/`, `tests/fortran/semantic_pyi_format/semantics/` | | `prik/policy/` | Post-IR ownership, export, wrapper-policy construction, immutable policy models, descriptor-handle policy, and ordered completion | `ownership.py`, `exports.py`, `models.py`, `native_array_handles.py`, `construction.py`, `completion.py` | infrastructure semantics and feature-local policy tests | | `prik/planning/` | Editable backend-neutral wrapper-plan records and mechanical policy projection | `models.py`, `planner.py` | infrastructure and feature-local codegen tests | | `prik/codegen/` | Backend datatype projection, plan-driven docstrings, and direct lowering into C and Fortran syntax nodes | `primitive_scalar_types.py`, `docstrings.py`, `nodes.py`, `c/`, `fortran/` | data-type and infrastructure codegen, feature-local codegen, and end-to-end tests | | `prik/printers/` | Language-specific serialization of C nodes, Fortran nodes, and semantic IR | `c.py`, `fortran.py`, `pyi.py` | source-printer and semantic-contract printer tests | +| `prik/naming/` | Unified public-name and generated-symbol policy for Python, generated C, and generated Fortran targets | `policy.py`, `native_symbols.py` | naming, visibility, and wrapper runtime tests | | `prik/utilities/` | Small shared Python utilities | `strings.py`, `visitor.py` | `tests/fortran/infrastructure/utilities/` and tests that exercise callers | ## Hotspot Index @@ -158,7 +160,6 @@ PRIK_C_DOCS_END --> For source-driven Fortran wrappers, read in this order: - For semantic `.pyi` builds, the parser branch is replaced by: diff --git a/docs/developer/testing-strategy.md b/docs/developer/testing-strategy.md index 06c90a096..ab55332a2 100644 --- a/docs/developer/testing-strategy.md +++ b/docs/developer/testing-strategy.md @@ -2,7 +2,7 @@ title: Testing Strategy audience: developers, contributors prerequisites: repository structure -related: quality-assurance.md, development-workflow.md +related: workflows/quality-assurance.md, workflows/contributing.md status: maintained publication: draft --- @@ -23,7 +23,9 @@ Tests first answer which native input contract they exercise: - `tests/tools/` owns maintainer commands and CI support scripts; - `tests/workflows/` owns exceptional automation-safety checks; - `tests/fortran/` owns Fortran input and semantic `.pyi` wrapper behavior; + Within Fortran, user-visible behavior is feature first and pipeline stage second: @@ -234,3 +236,20 @@ Both use `COVERAGE_PROCESS_START=pyproject.toml`, combine subprocess data with `python3 -m coverage combine`, and retain per-file executed line and branch data. LAPACK remains CI-only unless a maintainer explicitly requests a local run. + +## Fixture Regeneration + +Regenerate broad fixture sets only after a focused test explains the intended +change. Update the narrowest affected owner: + +```bash +python3 tests/fortran/source_parsing/parsing/generate_parser_goldens.py \ + tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 +python3 tests/fortran/semantic_ir/semantics/generate_semantic_fixtures.py +WRAPPER_UPDATE_PYI_FIXTURES=1 python3 -m pytest -q \ + tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py +``` + +Include regenerated artifacts only when the parser, semantic IR, or public +contract representation intentionally changed. Never regenerate a broad set to +hide uncertainty or unrelated drift. diff --git a/docs/developer/ci-cd.md b/docs/developer/workflows/ci.md similarity index 90% rename from docs/developer/ci-cd.md rename to docs/developer/workflows/ci.md index e64c6fc33..2f265247a 100644 --- a/docs/developer/ci-cd.md +++ b/docs/developer/workflows/ci.md @@ -1,13 +1,13 @@ --- -title: CI/CD -audience: maintainers +title: Continuous Integration And Delivery +audience: developers, maintainers, contributors prerequisites: testing strategy -related: ../developer/testing-strategy.md, release-process.md -status: planned-documentation +related: ../testing-strategy.md, quality-assurance.md, release.md +status: maintained publication: draft --- -# CI/CD +# Continuous Integration And Delivery GitHub Actions owns repository quality checks and the reviewed-documentation deployment. The documentation workflow builds the same filtered MkDocs site @@ -20,7 +20,7 @@ directly, avoiding the extra caller and called-workflow name layers produced by nested reusable workflows. Pull requests expose one aggregate required check after those jobs complete. Required status checks must use the exact `workflow / job` context documented in the -[quality-assurance guide](../developer/quality-assurance.md). Because GitHub +[quality-assurance guide](quality-assurance.md). Because GitHub treats a renamed check as a different context, update the repository ruleset whenever either half of that name changes. @@ -34,7 +34,7 @@ current source path instead of matching it to a generated duplicate. PyPI publication is deliberately separate from ordinary push and pull-request workflows. Publishing a GitHub Release triggers a build job, followed by a protected `pypi` environment job that authenticates through OpenID Connect. -See the [release process](release-process.md) for the exact trusted-publisher +See the [release process](release.md) for the exact trusted-publisher identity and approval sequence. ## Test Platforms @@ -114,7 +114,7 @@ with `python3 -m mkdocs serve`. Use pages with their draft warning. The lane index must also be reviewed before a page in that lane can enter the deployed artifact. -## TODO - -- TODO: Document the complete current CI quality gates and scheduled jobs. -- TODO: Link coverage troubleshooting to the maintained quality page. +Coverage troubleshooting and the exact local parity commands live in +[Quality Assurance](quality-assurance.md). Workflow-specific implementation +details remain in `.github/workflows/`; this page records the stable pipeline +contract rather than duplicating every YAML step. diff --git a/docs/developer/workflows/contributing.md b/docs/developer/workflows/contributing.md new file mode 100644 index 000000000..07818838c --- /dev/null +++ b/docs/developer/workflows/contributing.md @@ -0,0 +1,193 @@ +--- +title: Contributing Workflow +audience: developers, maintainers, contributors +prerequisites: repository checkout, Python 3.10 or newer +related: ../architecture.md, quality-assurance.md, ../testing-strategy.md, documentation.md +status: maintained +publication: reviewed +--- + +# Contributing Workflow + +This is the practical workflow for changing PRIK. The root +[`CONTRIBUTING.md`](../../../CONTRIBUTING.md) is the short public entrypoint; +this page supplies the complete contributor sequence without duplicating +package architecture. + +## Prepare The Checkout + +```bash +python3 -m pip install -e ".[qa]" +git config core.hooksPath .githooks +``` + +Create a focused branch and begin with the smallest test owner for the +behavior. Do not start with the full suite while discovering the change. + +## Change Workflow + +1. Identify the public behavior, limitation, or internal invariant. +2. Use the [architecture guide](../architecture.md), + [source map](../source-map.md), or + [feature-to-code map](../feature-to-code-map.md) to find its owner. +3. Read the owning package guide and relevant user contract before editing. +4. Update the documentation contract first when public behavior, ownership, + or limitations change. +5. Add or update focused tests at the earliest stage that proves the behavior. +6. Implement the change in the owning stage and extend downstream stages only + when their representation or mechanism genuinely changes. +7. Run focused verification, then the required static and broader checks. +8. Add a concise **Unreleased** changelog entry for visible behavior, + workflows, examples, supported features, or limitations. + +For policy-sensitive wrapper work, semantic decisions must be complete before +planning. A binding or bridge change should implement a newly selected plan +mechanism, not infer a new policy from datatype, intent, aliases, or storage. + +## Support Evidence Rule + +Documentation may claim support only when current implementation and evidence +prove it. Acceptable evidence includes: + +- a focused test for the contract; +- a maintained golden that proves exact generated representation; +- a checked repository command using a maintained fixture; or +- a compiled/imported/called runtime test for wrapper behavior. + +Parser support does not establish semantic or wrapper support. Compilation +alone does not establish runtime behavior. Unsupported cases should fail at +the earliest stage with enough facts to report a stable diagnostic. + +## Documentation Examples + +Important production files expose small public-API examples under +`if __name__ == "__main__"`; package guides document their exact commands and +outputs. Their centralized execution owner is +[`test_execution_examples.py`](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py). + +Markdown snippets use the repository's checked markers: + +````markdown + +```bash +python3 -m prik parse path/to/example.f90 +``` + + +```text +File: path/to/example.f90 +... +``` +```` + +Use `prik-doc-test: run` when only successful execution is stable. Use +`prik-doc-source` for fixture-backed source blocks. Do not mark placeholder, +checkout-modifying, compiler-environment-dependent, or intentionally failing +commands as executable documentation. + +Run the example documentation checks with: + +```bash +python3 -m pytest -q tests/docs/test_examples.py +``` + +## Selecting Tests + +Use the [testing strategy](../testing-strategy.md) for the authoritative +placement rules. Common starting points are: + +```bash +python3 -m pytest -q tests/fortran/source_parsing/parsing/ +python3 -m pytest -q tests/fortran/semantic_ir/semantics/ +python3 -m pytest -q tests/fortran/infrastructure/semantics/ +python3 -m pytest -q tests/fortran/infrastructure/codegen/ +python3 -m pytest -q tests/fortran/command_line_interface/pipeline/ +python3 -m pytest -q tests/docs +``` + +Use a feature-local `policy/`, `codegen/`, `runtime/`, or `end_to_end/` owner +when the behavior belongs to a documented feature. Compiled tests must import +and call the generated API; build success alone is insufficient. + +## Common Change Routes + +### Add A Fortran Construct + +1. Add the smallest parser example under + `tests/fortran/source_parsing/parsing/` or the feature's parsing owner. +2. Preserve the new source fact in `prik/parsers/fortran/`; add model fields + only when downstream consumers need them. +3. Extend `prik/semantics/fortran2ir.py` and semantic tests only if the + language-neutral contract changes. +4. Complete any new ownership, projection, setter, or support decision in + `prik/policy/` before planning. +5. Extend the plan and named binding/bridge lowering mechanisms only when the + completed behavior needs a new representation. +6. Add feature-local codegen and end-to-end evidence, then update the user + guide and feature matrix. + +Regenerate only an intentionally changed Fortran parser fixture: + +```bash +python3 tests/fortran/source_parsing/parsing/generate_parser_goldens.py \ + tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 +``` + +### Add Semantic `.pyi` Syntax Or Projection + +1. Add syntax tests under `tests/fortran/semantic_pyi_format/parsing/`. +2. Change `prik/parsers/pyi/parser.py` only if raw Python AST parsing changes; + otherwise interpret the syntax in `prik/semantics/pyi2ir.py`. +3. Update `prik/printers/pyi.py` and round-trip tests for emitted syntax. +4. Update semantic models only when the IR needs a new contract fact. +5. Complete new behavior in policy, project it through planning, and add + runtime evidence when the edit affects wrappers. +6. Update the semantic `.pyi` user reference. + +### Add A Code-Generation Backend Or Mechanism + +A new backend is not accepted merely because it prints source. It must consume +the completed shared plan without importing construction rules, define its own +typed representation and printer boundary, fail closed on unsupported action +combinations, preserve shared native slots and lifecycle ordering, and provide +focused generation plus compiled/runtime evidence. Add a backend only after +the shared plan can express its requirements without backend-specific semantic +policy. + +For a mechanism inside an existing backend, start in the narrow specialized +emitter named by the package guide. Do not replace specialized methods with a +flag-driven generic emitter or move semantic decisions down to make the +mechanism easier to generate. + +### Add A Stage-Owned Error + +Report a failure at the first stage with enough facts to explain it. Syntax and +source-processing failures belong to preprocessing/parsing; invalid contracts +belong to semantic conversion; unsafe ownership, ABI, projection, or support +belongs to completed policy; inconsistent plan projection belongs to planning; +an unavailable emitted mechanism belongs to backend preflight. Assert the +stable owner path and reason at that stage rather than forcing a known failure +through native compilation. + +## Pull Request And Review + +Before opening a pull request: + +- keep the change focused and remove superseded implementation/tests/docs; +- explain the problem, stage ownership, solution, and verification; +- identify user-visible behavior and limitations; +- run the applicable focused tests and the required checks from + [Quality Assurance](quality-assurance.md); and +- ensure all required GitHub checks pass before merge. + +Review should verify dependency direction, completed-policy authority, +diagnostic ownership, focused and end-to-end evidence, generated ABI stability, +documentation consistency, and removal of obsolete paths. Reviewers should not +accept a compatibility alias for an intentionally moved internal API unless +the change explicitly requires one. + +## Contribution License + +PRIK is distributed under the MIT License. By submitting a contribution, a +contributor agrees to license it under the same terms and confirms they have +the right to do so, including any required employer authorization. diff --git a/docs/developer/documentation-architecture.md b/docs/developer/workflows/documentation.md similarity index 96% rename from docs/developer/documentation-architecture.md rename to docs/developer/workflows/documentation.md index 447364490..487c2cae5 100644 --- a/docs/developer/documentation-architecture.md +++ b/docs/developer/workflows/documentation.md @@ -2,7 +2,7 @@ title: Documentation Architecture audience: developers, maintainers, contributors prerequisites: repository checkout, documentation metadata standard -related: architecture.md, testing-strategy.md, ../user/index.md +related: ../architecture.md, ../testing-strategy.md, ../../user/index.md status: maintained publication: draft --- @@ -61,7 +61,7 @@ it does not duplicate those guides. | Area | Primary reader | Publication | Content | | --- | --- | --- | --- | | `user/` | People using prik | Documentation website after review | Getting Started, guides, performance benchmarks, tutorials, examples, public reference, support status, FAQ, troubleshooting | -| `developer/` | Developers, maintainers, and future contributors changing or governing prik | Documentation website after review | Architecture, source orientation, design decisions, internal maps, testing, coding standards, feature work, contribution workflows, documentation policy, CI administration, releases, and roadmaps | +| `developer/` | Developers, maintainers, and future contributors changing or governing prik | Documentation website after review | Architecture, package guides, cross-stage concepts, source navigation, testing, contribution and project workflows, design decisions, active roadmaps, and deferred input-language references | Pages use their task and stability for placement within the contributor tree. Implemented architecture, design proposals, roadmaps, and release procedures @@ -169,12 +169,15 @@ docs/ developer/ index.md architecture.md - contributing/ - documentation-architecture.md + source-map.md + feature-to-code-map.md + testing-strategy.md + packages/ + concepts/ + workflows/ design/ - internal-architecture/ roadmap/ - CI, release, source, and workflow pages + deferred/ javascripts/ code-copy.js stylesheets/ diff --git a/docs/developer/workflows/quality-assurance.md b/docs/developer/workflows/quality-assurance.md new file mode 100644 index 000000000..4a7db95fb --- /dev/null +++ b/docs/developer/workflows/quality-assurance.md @@ -0,0 +1,148 @@ +--- +title: Quality Assurance +audience: developers, maintainers, contributors +prerequisites: repository checkout, QA dependencies +related: contributing.md, ../testing-strategy.md, ci.md +status: maintained +publication: draft +--- + +# Quality Assurance + +This page records the active quality stack and commands. Historical rollout +logs and completed tool-adoption checklists belong in Git history, not in the +current contributor workflow. + +## Install + +```bash +python3 -m pip install -e ".[qa]" +python3 tools/check_static_analysis_versions.py +``` + +## Active Cadence + +| Cadence | Evidence | +| --- | --- | +| Inner loop | Smallest owning pytest target and Ruff on changed code | +| Local pre-push | Blocking static analysis, focused documentation smoke, one compiled scalar-wrapper smoke, `tests/tools/`, and `tests/workflows/` | +| Pull request | Static analysis, compiler smoke, Python matrix, project coverage, real libraries, performance/docs, aggregate required check | +| Manual discovery | Deep Hypothesis fuzz profile and advisory complexity reports | +| Dependency change or annual review | Dependency vulnerability review | + +The one stable required ruleset context is: + +```text +Pull Request / Validation · all required checks +``` + +If the workflow or job display name changes, update the repository ruleset; +do not keep an alias job for the old name. + +## Focused And Documentation Checks + +Run the narrowest behavioral owner first: + +```bash +python3 -m pytest -q path/to/owning/tests +``` + +Documentation-only changes that do not alter Python, tests, build +configuration, or tooling use: + +```bash +python3 -m pytest -q tests/docs +git diff --check +``` + +When Python code, test logic, build behavior, or tools change, run the complete +blocking and advisory static suite: + +```bash +python3 -m ruff check . +python3 -m ruff format --check . +python3 tools/check_static_analysis_versions.py +python3 tools/check_codegen_complexity.py +python3 -m bandit -c pyproject.toml -r prik --severity-level medium --confidence-level medium +python3 -m vulture +python3 tools/check_radon_policy.py --base-ref auto +python3 -m radon cc prik -n C -s --total-average +python3 -m radon mi prik -s +``` + +Ruff, Bandit, Vulture, codegen complexity, version checks, and the changed-code +Radon policy are blocking. Full Radon reports are advisory. If automatic Radon +base detection lacks CI SHA metadata locally, rerun with `--base-ref main` and +report that fact. + +## Coverage And Test-Order Reproduction + +Do not run the complete coverage workflow for routine changes. When +investigating a CI coverage failure, mirror subprocess collection exactly: + +```bash +COVERAGE_PROCESS_START=pyproject.toml \ +PYTHONPATH=. \ +python3 -m coverage run -m pytest -q --randomly-seed=1 +python3 -m coverage combine +python3 -m coverage report +``` + +The blocking project coverage target is 90%. Codecov patch status is +informational, but new reachable behavior still needs focused tests. + +Reproduce an order-dependent failure with the seed from CI: + +```bash +python3 -m pytest -q --randomly-seed= +``` + +## Compiler And Property Evidence + +Run a configured alternate-compiler lane with: + +```bash +python3 tools/run_fortran_toolchain_lane.py --compiler=/path/to/ifx +python3 tools/run_fortran_toolchain_lane.py --compiler=/path/to/flang +``` + +`--plan` prints the selected tests without running them. CI currently pins +IFX/ICX 2026.1.1 and Flang/Clang 22.1.8 as evidence versions, not declared +minimum versions. + +Run property and deep fuzz profiles with: + +```bash +python3 -m pytest -q -m property --hypothesis-profile=ci +HYPOTHESIS_PROFILE=fuzz python3 -m pytest -q -m fuzz --hypothesis-show-statistics +``` + +Minimize an actionable fuzz failure and preserve it as a focused regression in +the owning feature/stage suite. + +## Tool Responsibilities + +| Tool | Role | +| --- | --- | +| pytest and coverage.py | Behavioral regression and project coverage | +| pytest-randomly | Stable-seed order-coupling detection | +| Hypothesis | Generated parser, semantic, and codegen invariants | +| Ruff | Linting, formatting, modernization, and bounded McCabe checks | +| Bandit | Medium-confidence/severity security boundary review | +| Vulture | Dead-code detection with narrow exclusions | +| Radon | Blocking changed-hotspot policy plus advisory project reports | +| GitHub Actions | Reproducible shared compiler, platform, library, benchmark, docs, and release evidence | + +Mutation testing and pre-commit are not part of the active stack. The tracked +`.githooks` pre-push hook is the supported local automation boundary. + +## Real Libraries And Verification Limits + +Ordinary local suites exclude `real_library`. BLAS, FFTPACK, and MINPACK may be +run through their documented example workflows. LAPACK wrapper tests remain a +GitHub Actions responsibility unless explicitly requested locally. + +GitHub Actions writes path-aware JUnit reports and prints failed pytest node +IDs at the end of failed matrix logs. The real-library lane builds and tests +the complete maintained BLAS, LAPACK, FFTPACK, and MINPACK examples using their +documented entrypoints. diff --git a/docs/developer/release-process.md b/docs/developer/workflows/release.md similarity index 95% rename from docs/developer/release-process.md rename to docs/developer/workflows/release.md index eca113136..a38c3aee8 100644 --- a/docs/developer/release-process.md +++ b/docs/developer/workflows/release.md @@ -2,7 +2,7 @@ title: Release Process audience: maintainers prerequisites: CI/CD, changelog -related: ci-cd.md +related: ci.md, quality-assurance.md status: maintained publication: reviewed --- @@ -53,7 +53,7 @@ approval. Do not add a PyPI API token or password to GitHub secrets. 1. Choose a version that does not already exist on PyPI. 2. Set `[project].version` in `pyproject.toml`. 3. Move the user-visible entries from **Unreleased** into a versioned section - in the repository-root [`CHANGELOG.md`](../../CHANGELOG.md). + in the repository-root [`CHANGELOG.md`](../../../CHANGELOG.md). 4. Run the focused package checks and the repository's required static analysis. Let GitHub Actions run the complete cross-platform suite. 5. Merge the release preparation through the normal review process and wait @@ -81,7 +81,7 @@ the release. Create a GitHub Release from the exact reviewed commit and use a tag matching the project version, such as `v0.1.0`. Use that version's section from -[`CHANGELOG.md`](../../CHANGELOG.md) as the release notes. Publishing the +[`CHANGELOG.md`](../../../CHANGELOG.md) as the release notes. Publishing the GitHub Release triggers `.github/workflows/publish-to-pypi.yml`. The workflow builds and checks the artifacts in an unprivileged job. A diff --git a/docs/user/examples/recipes/compiler-preprocessing.md b/docs/user/examples/recipes/compiler-preprocessing.md index 9d19e9bbf..e0d20a810 100644 --- a/docs/user/examples/recipes/compiler-preprocessing.md +++ b/docs/user/examples/recipes/compiler-preprocessing.md @@ -2,7 +2,7 @@ title: Use Compiler Preprocessing Options audience: users, developers prerequisites: installation, native project compiler flags -related: ../../../developer/compiler-preprocessing.md, ../../../developer/c-parser-reference.md, ../../../developer/fortran-parser-reference.md +related: ../../../developer/packages/preprocessing.md, ../../../developer/deferred/c-parser.md, ../../../developer/packages/parsers.md status: maintained publication: draft --- @@ -49,5 +49,5 @@ PRIK_C_DOCS_END --> ## Next -- Read the [compiler preprocessing reference](../../../developer/compiler-preprocessing.md) +- Read the [preprocessing package guide](../../../developer/packages/preprocessing.md) for the pipeline model, adapters, diagnostics, and include-exposure policy. diff --git a/docs/user/examples/recipes/inspect-c-api.md b/docs/user/examples/recipes/inspect-c-api.md index d68b2f27b..a4681db0b 100644 --- a/docs/user/examples/recipes/inspect-c-api.md +++ b/docs/user/examples/recipes/inspect-c-api.md @@ -3,7 +3,7 @@ title: Deferred Native API Inspection audience: users, developers prerequisites: installation -related: ../../../developer/c-parser-reference.md +related: ../../../developer/deferred/c-parser.md status: maintained publication: draft --- diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index ecc15f54a..6666c4bf9 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -72,7 +72,7 @@ PRIK_C_DOCS_END --> | Generated reference pages for modules, functions, and classes | Partially supported | [Reference index](../reference/index.md) | [Source map](../../developer/source-map.md) | [Documentation reference checks](../../../tests/docs/test_reference_and_source_map.py), [semantic contract tests](../../../tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py) | Maintained manual references exist for generated functions, modules, classes, and generated file contracts; automated reference inventory generation has not been selected. | diff --git a/docs/user/reference/configuration-files.md b/docs/user/reference/configuration-files.md index a39774434..6cd67f5f3 100644 --- a/docs/user/reference/configuration-files.md +++ b/docs/user/reference/configuration-files.md @@ -2,7 +2,7 @@ title: Configuration Files Reference audience: users, developers prerequisites: packaging, CLI commands -related: cli-commands.md, python-api.md, ../guide/building-shared-library.md, ../../developer/quality-assurance.md +related: cli-commands.md, python-api.md, ../guide/building-shared-library.md, ../../developer/workflows/quality-assurance.md status: maintained publication: draft --- @@ -107,7 +107,7 @@ The coverage contract is: When investigating coverage failures that involve subprocesses, run coverage with `COVERAGE_PROCESS_START=pyproject.toml`, combine parallel data, then report. The maintained workflow is documented in -[Quality Assurance](../../developer/quality-assurance.md#pytest-and-coveragepy). +[Quality Assurance](../../developer/workflows/quality-assurance.md#coverage-and-test-order-reproduction). ## `codecov.yml` diff --git a/docs/user/reference/python-api.md b/docs/user/reference/python-api.md index da4af2c3c..994e3ea7d 100644 --- a/docs/user/reference/python-api.md +++ b/docs/user/reference/python-api.md @@ -2,7 +2,7 @@ title: Python API Reference audience: users, developers prerequisites: installation -related: cli-commands.md, ../../developer/development-workflow.md +related: cli-commands.md, ../../developer/workflows/contributing.md status: maintained publication: draft --- diff --git a/mkdocs.yml b/mkdocs.yml index 8075e6fd9..c4904b85b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -121,55 +121,38 @@ nav: - Contributor Documentation: - Overview: developer/index.md - Architecture: developer/architecture.md - - Development Workflow: developer/development-workflow.md - Source Map: developer/source-map.md - Feature To Code Map: developer/feature-to-code-map.md - - Repository Structure: developer/repository-structure.md - - Compiler Preprocessing Reference: developer/compiler-preprocessing.md - # PRIK_C_DOCS: - C Parser Reference: developer/c-parser-reference.md - - Fortran Parser Reference: developer/fortran-parser-reference.md - - Quality Assurance: developer/quality-assurance.md - - Build System: developer/build-system.md - Testing Strategy: developer/testing-strategy.md - - Coding Standards: developer/coding-standards.md - - Adding A Feature: developer/adding-a-feature.md - - Adding A Fortran Construct: developer/adding-a-fortran-construct.md - - Adding A Code Generation Backend: developer/adding-a-code-generation-backend.md - - Contributing: - - Overview: developer/contributing/index.md - - Contribution Guide: developer/contributing/contribution-guide.md - - Pull Request Workflow: developer/contributing/pull-request-workflow.md - - Review Process: developer/contributing/review-process.md - - Documentation Architecture: developer/documentation-architecture.md - - CI/CD: developer/ci-cd.md - - Release Process: developer/release-process.md + - Source Packages: + - Overview: developer/packages/index.md + - Contracts: developer/packages/contracts.md + - Compiler: developer/packages/compiler.md + - Preprocessing: developer/packages/preprocessing.md + - Parsers: developer/packages/parsers.md + - Semantics: developer/packages/semantics.md + - Policy: developer/packages/policy.md + - Planning: developer/packages/planning.md + - Code Generation: developer/packages/codegen.md + - Printers: developer/packages/printers.md + - Pipeline: developer/packages/pipeline.md + - Runtime: developer/packages/runtime.md + - Naming: developer/packages/naming.md + - Utilities: developer/packages/utilities.md + - Concepts: + - Datatype Lifecycle: developer/concepts/datatype-lifecycle.md + - Workflows: + - Contributing: developer/workflows/contributing.md + - Quality Assurance: developer/workflows/quality-assurance.md + - Continuous Integration And Delivery: developer/workflows/ci.md + - Documentation Architecture: developer/workflows/documentation.md + - Release Process: developer/workflows/release.md - Design: - - Overview: developer/design/index.md - - Parser Architecture: developer/design/parser-architecture.md - - Semantic Analysis: developer/design/semantic-analysis.md - - Runtime Model: developer/design/runtime-model.md - - Error Propagation Model: developer/design/error-propagation-model.md - - Memory Ownership Model: developer/design/memory-ownership-model.md - - Code Generation: developer/design/code-generation.md - - CPython Integration: developer/design/cpython-integration.md - - Multilanguage Runtime Architecture: developer/design/semantic-multilanguage-wrapper-runtime-architecture.md - - Wrapper Design Notes: developer/design/wrapper-design-notes.md - - Internal Architecture: - - Overview: developer/internal-architecture/index.md - - Pipeline Map: developer/internal-architecture/pipeline-map.md - - Wrapper Generation Pipeline: developer/internal-architecture/wrapper-generation-pipeline.md - - AST Design: developer/internal-architecture/ast-design.md - - Semantic Passes: developer/internal-architecture/semantic-passes.md - - Datatype Lifecycle: developer/internal-architecture/type-system.md - - Runtime Layer: developer/internal-architecture/runtime-layer.md - - Ownership Tracking: developer/internal-architecture/ownership-tracking.md - - Dependency Analysis: developer/internal-architecture/dependency-analysis.md - - Error Handling Pipeline: developer/internal-architecture/error-handling-pipeline.md - - Symbol Tables: developer/internal-architecture/symbol-tables.md + - Multilanguage Runtime Architecture: developer/design/multilanguage-runtime.md + - Wrapper Open Decisions: developer/design/wrapper-open-decisions.md + # PRIK_C_DOCS: - Deferred C Parser Reference: developer/deferred/c-parser.md - Roadmaps: - Overview: developer/roadmap/index.md - Language-First Test Suite and Fortran Cleanup: developer/roadmap/fortran-test-suite-cleanup-checklist.md - Documentation Content: developer/roadmap/documentation-content-checklist.md - Semantic .pyi Wrapper: developer/roadmap/semantic-pyi-wrapper-checklist.md - - Native Array Handles: developer/roadmap/native-array-handle-checklist.md - - Wrapper Plan Migration: developer/roadmap/wrapper-plan-migration-checklist.md diff --git a/prik/README.md b/prik/README.md index c317f3263..d46066a74 100644 --- a/prik/README.md +++ b/prik/README.md @@ -40,10 +40,10 @@ printers, bridges, and bindings must not infer missing expression semantics. ## Source Navigation Docs +- `docs/developer/architecture.md` +- `docs/developer/packages/index.md` - `docs/developer/source-map.md` - `docs/developer/feature-to-code-map.md` -- `docs/developer/repository-structure.md` -- `docs/developer/internal-architecture/pipeline-map.md` Keep user-facing support claims in the docs backed by focused tests and, for wrapper behavior, runtime tests that compile, import, call, mutate, and check diff --git a/prik/compiler/README.md b/prik/compiler/README.md index be77b3b7e..241a498ca 100644 --- a/prik/compiler/README.md +++ b/prik/compiler/README.md @@ -85,9 +85,9 @@ policy completion. Those decisions happen before generated sources reach this pa ## Tests And Docs - Wrapper reference: `docs/user/reference/fortran-wrapper.md` -- Build-system docs: `docs/developer/build-system.md` -- Quality and static checks: `docs/developer/quality-assurance.md` +- Compiler package guide: `docs/developer/packages/compiler.md` +- Pipeline package guide: `docs/developer/packages/pipeline.md` +- Quality and static checks: `docs/developer/workflows/quality-assurance.md` - Source navigation: `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` -- Pipeline map: `docs/developer/internal-architecture/pipeline-map.md` - Build-mode tests: `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py` - Runtime ABI tests: `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py` diff --git a/prik/parsers/README.md b/prik/parsers/README.md index 82f96eb46..501b9b098 100644 --- a/prik/parsers/README.md +++ b/prik/parsers/README.md @@ -11,6 +11,7 @@ Cross-language semantic interpretation belongs to `prik.semantics`, while preprocessing and build orchestration belong to `prik.pipeline`. Stable parser convenience functions remain exported from the `prik` package root. -See `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md`, -`docs/developer/c-parser-reference.md`, `docs/developer/fortran-parser-reference.md`, and +See `docs/developer/packages/parsers.md`, `docs/developer/source-map.md`, +`docs/developer/feature-to-code-map.md`, +`docs/developer/deferred/c-parser.md`, and `docs/user/reference/semantic-pyi-format.md` for maintained behavior. diff --git a/prik/parsers/c/README.md b/prik/parsers/c/README.md index 9c0618e3e..adfb653d1 100644 --- a/prik/parsers/c/README.md +++ b/prik/parsers/c/README.md @@ -24,7 +24,7 @@ not own preprocessing. ## Tests And Docs -- Public reference: `docs/developer/c-parser-reference.md` +- Deferred reference: `docs/developer/deferred/c-parser.md` - User recipe: `docs/user/examples/recipes/inspect-c-api.md` - Source navigation: `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` - Parser tests: `tests/c/fixtures/parser/` diff --git a/prik/parsers/fortran/README.md b/prik/parsers/fortran/README.md index 41d4c1585..7ac8ed2c2 100644 --- a/prik/parsers/fortran/README.md +++ b/prik/parsers/fortran/README.md @@ -21,7 +21,7 @@ callers may also use the stable parser functions and models exported from the ## Tests And Docs -- Public reference: `docs/developer/fortran-parser-reference.md` +- Package reference: `docs/developer/packages/parsers.md` - User recipe: `docs/user/examples/recipes/inspect-fortran-api.md` - Source navigation: `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` - Parser tests: `tests/fortran/source_parsing/parsing/` diff --git a/prik/pipeline/README.md b/prik/pipeline/README.md index d5fa5fa63..bb7aa06fc 100644 --- a/prik/pipeline/README.md +++ b/prik/pipeline/README.md @@ -22,4 +22,5 @@ Reusable compiler execution, compile objects, and linking live in complete workflow. For cross-stage navigation, see `docs/developer/source-map.md` and -`docs/developer/feature-to-code-map.md`. +`docs/developer/feature-to-code-map.md`. The canonical package reference is +`docs/developer/packages/pipeline.md`. diff --git a/prik/planning/README.md b/prik/planning/README.md index a98b7caf8..7ccdc61fb 100644 --- a/prik/planning/README.md +++ b/prik/planning/README.md @@ -13,4 +13,5 @@ docstrings, C, Fortran, headers, and the generated Python class facade are rendered by `../codegen/` from the completed plan. For cross-stage navigation, see `docs/developer/source-map.md` and -`docs/developer/feature-to-code-map.md`. +`docs/developer/feature-to-code-map.md`. The canonical package reference is +`docs/developer/packages/planning.md`. diff --git a/prik/policy/README.md b/prik/policy/README.md index c73484829..b1b9216c9 100644 --- a/prik/policy/README.md +++ b/prik/policy/README.md @@ -18,4 +18,5 @@ Raw ownership and pointer-contract metadata belongs to this package through `../planning/planner.py`. For cross-stage navigation, see `docs/developer/source-map.md` and -`docs/developer/feature-to-code-map.md`. +`docs/developer/feature-to-code-map.md`. The canonical package reference is +`docs/developer/packages/policy.md`. diff --git a/prik/policy/ownership.py b/prik/policy/ownership.py index 3d48b2de1..d5234e4a7 100644 --- a/prik/policy/ownership.py +++ b/prik/policy/ownership.py @@ -31,7 +31,7 @@ For example, a normal scalar input commonly resolves to caller-owned, call-local use with no wrapper release action, while an array result commonly resolves to a Python-owned copy released by Python reference counting. See -``docs/developer/internal-architecture/ownership-tracking.md`` for the full +``docs/developer/packages/policy.md`` for the full stage map, supported triples, pointer-policy boundary, and change routes. """ diff --git a/prik/preprocessing/README.md b/prik/preprocessing/README.md index 8c1096697..18e395593 100644 --- a/prik/preprocessing/README.md +++ b/prik/preprocessing/README.md @@ -39,7 +39,7 @@ extension. `prik.compiler` supplies reusable compiler mechanisms; - `tests/c/probes/` - `tests/fortran/source_preprocessing/preprocessing/` - `tests/fortran/data_types/probes/` -- `docs/developer/compiler-preprocessing.md` -- `docs/developer/internal-architecture/type-system.md` +- `docs/developer/packages/preprocessing.md` +- `docs/developer/concepts/datatype-lifecycle.md` - `docs/developer/source-map.md` - `docs/developer/feature-to-code-map.md` diff --git a/prik/printers/README.md b/prik/printers/README.md index 69e368752..383c5fc9a 100644 --- a/prik/printers/README.md +++ b/prik/printers/README.md @@ -16,4 +16,5 @@ generated-wrapper result. `../pipeline/build.py` writes or compiles that result. For cross-stage navigation, see `docs/developer/source-map.md` and -`docs/developer/feature-to-code-map.md`. +`docs/developer/feature-to-code-map.md`. The canonical package reference is +`docs/developer/packages/printers.md`. diff --git a/prik/semantics/README.md b/prik/semantics/README.md index 57fb3e757..0684d68bc 100644 --- a/prik/semantics/README.md +++ b/prik/semantics/README.md @@ -104,8 +104,9 @@ completion remains the next shared stage after those converters produce - `.pyi` reference: `docs/user/reference/semantic-pyi-format.md` - `.pyi` wrapper checklist: `docs/developer/roadmap/semantic-pyi-wrapper-checklist.md` - Source navigation: `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` -- Pipeline map: `docs/developer/internal-architecture/pipeline-map.md` -- Datatype lifecycle: `docs/developer/internal-architecture/type-system.md` +- Architecture: `docs/developer/architecture.md` +- Semantics package guide: `docs/developer/packages/semantics.md` +- Datatype lifecycle: `docs/developer/concepts/datatype-lifecycle.md` - Semantic tests: `tests/fortran/semantic_ir/semantics/` - `.pyi` tests: `tests/fortran/semantic_pyi_format/` - Wrapper behavior that reaches the typed plan: `tests/fortran/` diff --git a/tests/docs/_structure_support.py b/tests/docs/_structure_support.py index 3af4ef060..497d7a6e6 100644 --- a/tests/docs/_structure_support.py +++ b/tests/docs/_structure_support.py @@ -22,8 +22,7 @@ *sorted((DOCS_ROOT / "developer").rglob("*.md")), ] DEFERRED_C_PAGE_PATHS = [ - ROOT / "docs/developer/design/cpython-integration.md", - ROOT / "docs/developer/c-parser-reference.md", + ROOT / "docs/developer/deferred/c-parser.md", ROOT / "docs/user/examples/recipes/inspect-c-api.md", ] MARKDOWN_LINK = re.compile(r"\[[^\]]+\]\(([^)#]+)(?:#[^)]+)?\)") @@ -126,9 +125,7 @@ "user/troubleshooting/index.md", "developer/index.md", "developer/architecture.md", - "developer/contributing/index.md", - "developer/design/index.md", - "developer/internal-architecture/index.md", + "developer/packages/index.md", "developer/roadmap/index.md", ] REQUIRED_REFERENCE_PAGES = [ @@ -242,14 +239,25 @@ "developer/architecture.md", "developer/source-map.md", "developer/feature-to-code-map.md", - "developer/repository-structure.md", ] SOURCE_NAVIGATION_CORPUS = [ "docs/developer/architecture.md", "docs/developer/source-map.md", "docs/developer/feature-to-code-map.md", - "docs/developer/repository-structure.md", - "docs/developer/internal-architecture/pipeline-map.md", + "docs/developer/concepts/datatype-lifecycle.md", + "docs/developer/packages/contracts.md", + "docs/developer/packages/compiler.md", + "docs/developer/packages/preprocessing.md", + "docs/developer/packages/parsers.md", + "docs/developer/packages/semantics.md", + "docs/developer/packages/policy.md", + "docs/developer/packages/planning.md", + "docs/developer/packages/codegen.md", + "docs/developer/packages/printers.md", + "docs/developer/packages/pipeline.md", + "docs/developer/packages/runtime.md", + "docs/developer/packages/naming.md", + "docs/developer/packages/utilities.md", "prik/README.md", "prik/parsers/README.md", "prik/parsers/c/README.md", @@ -322,11 +330,11 @@ "docs/user/reference/python-api.md", "docs/user/reference/semantic-ir.md", "docs/user/reference/semantic-pyi-format.md", - "docs/developer/internal-architecture/type-system.md", - "docs/developer/build-system.md", - "docs/developer/c-parser-reference.md", - "docs/developer/fortran-parser-reference.md", - "docs/developer/quality-assurance.md", + "docs/developer/concepts/datatype-lifecycle.md", + "docs/developer/packages/compiler.md", + "docs/developer/deferred/c-parser.md", + "docs/developer/packages/parsers.md", + "docs/developer/workflows/quality-assurance.md", "docs/user/language-support/feature-matrix.md", ] SOURCE_NAVIGATION_TEST_TARGETS = [ diff --git a/tests/docs/test_metadata_and_visibility.py b/tests/docs/test_metadata_and_visibility.py index 49f1a380d..fae6b6051 100644 --- a/tests/docs/test_metadata_and_visibility.py +++ b/tests/docs/test_metadata_and_visibility.py @@ -65,4 +65,28 @@ def test_deferred_c_pages_are_not_in_site_navigation() -> None: assert "Inspect a C API" not in active_navigation assert "C Parser Reference" not in active_navigation assert any("PRIK_C_DOCS" in line and "inspect-c-api.md" in line for line in lines) - assert any("PRIK_C_DOCS" in line and "c-parser-reference.md" in line for line in lines) + assert any("PRIK_C_DOCS" in line and "deferred/c-parser.md" in line for line in lines) + + +def test_contributor_maps_defer_only_the_c_input_frontend() -> None: + source_map = _visible_documentation_source(ROOT / "docs/developer/source-map.md") + feature_map = _visible_documentation_source(ROOT / "docs/developer/feature-to-code-map.md") + visible = f"{source_map}\n{feature_map}" + + for deferred_owner in ( + "prik/parsers/c/", + "prik/semantics/c2ir.py", + "prik/preprocessing/c.py", + "prik/preprocessing/probes/c_types.py", + "tests/c/", + ): + assert deferred_owner not in visible + + for generated_backend_owner in ( + "prik/codegen/c/binding.py", + "prik/codegen/c/python_surface.py", + "prik/printers/c.py", + "prik/codegen/fortran/bridge.py", + "prik/printers/fortran.py", + ): + assert generated_backend_owner in visible diff --git a/tests/docs/test_reference_and_source_map.py b/tests/docs/test_reference_and_source_map.py index 359f16893..ddcd456c7 100644 --- a/tests/docs/test_reference_and_source_map.py +++ b/tests/docs/test_reference_and_source_map.py @@ -199,6 +199,193 @@ def test_static_site_seed_configuration_exists() -> None: assert (ROOT / "mkdocs.yml").is_file() +def test_contributor_architecture_stays_shallow_and_routes_to_package_guides() -> None: + architecture = (DOCS_ROOT / "developer/architecture.md").read_text(encoding="utf-8") + overview_start = architecture.index("## Repository Structure") + overview_end = architecture.index("## Package-Root Entry Points") + overview = architecture[overview_start:overview_end] + + for direct_child in ( + "├── prik/", + "├── tests/", + "├── docs/", + "├── examples/", + "├── tools/", + "├── compiler/", + "├── preprocessing/", + "├── parsers/", + "├── semantics/", + "├── policy/", + "├── planning/", + "├── codegen/", + "├── printers/", + "├── pipeline/", + "├── runtime/", + "├── naming/", + "└── utilities/", + ): + assert direct_child in overview + assert "│ ├──" not in overview + assert " ├──" not in overview + + for heading in ( + "Package-Root Entry Points", + "End-To-End Workflow", + "Authority And Dependency Rules", + "Package Guide Map", + "Tests And Evidence", + "Where A Change Begins", + "Contributor Documentation Structure", + ): + assert f"## {heading}" in architecture + + package_guides = ( + "contracts", + "compiler", + "preprocessing", + "parsers", + "semantics", + "policy", + "planning", + "codegen", + "printers", + "pipeline", + "runtime", + "naming", + "utilities", + ) + for package in package_guides: + assert f"packages/{package}.md" in architecture + + +def test_every_top_level_source_package_has_one_canonical_guide() -> None: + expected_packages = { + "contracts", + "compiler", + "preprocessing", + "parsers", + "semantics", + "policy", + "planning", + "codegen", + "printers", + "pipeline", + "runtime", + "naming", + "utilities", + } + source_packages = { + path.name for path in (ROOT / "prik").iterdir() if path.is_dir() and not path.name.startswith("_") + } + guide_paths = {path.stem for path in (DOCS_ROOT / "developer/packages").glob("*.md") if path.name != "index.md"} + + assert source_packages == expected_packages + assert guide_paths == expected_packages + + +@pytest.mark.parametrize( + "package", + ( + "contracts", + "compiler", + "preprocessing", + "parsers", + "semantics", + "policy", + "planning", + "codegen", + "printers", + "pipeline", + "runtime", + "naming", + "utilities", + ), +) +def test_package_guide_has_structure_examples_tests_and_change_routes(package: str) -> None: + path = DOCS_ROOT / f"developer/packages/{package}.md" + content = path.read_text(encoding="utf-8") + + assert "../architecture.md" in content + assert "## Purpose And Boundaries" in content + assert "## Local Structure" in content + assert "## Important File" in content + assert "## Execution Example" in content + assert "## Tests" in content + assert "## Change Routes" in content + assert "../../../tests/" in content + assert "python3 prik/" in content + + for target in MARKDOWN_LINK.findall(content): + if target.startswith(("http://", "https://")): + continue + assert (path.parent / target).resolve().exists(), f"{package}: missing linked owner {target}" + + +def test_superseded_contributor_pages_and_completed_roadmaps_are_removed() -> None: + removed_paths = ( + "adding-a-feature.md", + "adding-a-fortran-construct.md", + "adding-a-code-generation-backend.md", + "build-system.md", + "coding-standards.md", + "development-workflow.md", + "repository-structure.md", + "roadmap/native-array-handle-checklist.md", + "roadmap/wrapper-plan-migration-checklist.md", + ) + for relative_path in removed_paths: + assert not (DOCS_ROOT / "developer" / relative_path).exists() + + +def test_contributor_documentation_uses_only_canonical_areas_and_active_roadmaps() -> None: + contributor_root = DOCS_ROOT / "developer" + assert {path.name for path in contributor_root.iterdir() if path.is_dir()} == { + "concepts", + "deferred", + "design", + "packages", + "roadmap", + "workflows", + } + assert {path.name for path in contributor_root.glob("*.md")} == { + "architecture.md", + "feature-to-code-map.md", + "index.md", + "source-map.md", + "testing-strategy.md", + } + + roadmap_root = contributor_root / "roadmap" + roadmap_paths = {path.name for path in roadmap_root.glob("*.md")} + assert roadmap_paths == { + "documentation-content-checklist.md", + "fortran-test-suite-cleanup-checklist.md", + "index.md", + "semantic-pyi-wrapper-checklist.md", + } + for path in roadmap_root.glob("*-checklist.md"): + assert re.search(r"^\s*- \[ \]", path.read_text(encoding="utf-8"), re.MULTILINE), ( + f"{path.relative_to(ROOT)} is complete and belongs in Git history, not active roadmaps" + ) + + +def test_package_guide_execution_commands_have_centralized_contract_tests() -> None: + test_inventory = (ROOT / "tests/fortran/infrastructure/execution_examples/test_execution_examples.py").read_text( + encoding="utf-8" + ) + + for path in sorted((DOCS_ROOT / "developer/packages").glob("*.md")): + if path.name == "index.md": + continue + content = path.read_text(encoding="utf-8") + commands = re.findall(r"^python3 (prik/[A-Za-z0-9_/]+\.py)(?:\s.*)?$", content, re.MULTILINE) + assert commands, f"{path.name}: no direct production-file example" + for command_path in commands: + components = [component.removesuffix(".py").strip("_") for component in command_path.split("/")[1:]] + test_name = f"test_fortran_{'_'.join(components)}_execution_example" + assert f"def {test_name}(" in test_inventory, f"{path.name}: {command_path} lacks {test_name}" + + def test_generated_site_and_distribution_outputs_share_hidden_root() -> None: site_configuration = (ROOT / "mkdocs.yml").read_text(encoding="utf-8") release_workflow = (ROOT / ".github/workflows/publish-to-pypi.yml").read_text(encoding="utf-8") From 6ab4107593cf6df07023f5249210e1ee2c425ec8 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 17:22:06 +0100 Subject: [PATCH 20/22] update developper docs --- CHANGELOG.md | 4 + docs/developer/architecture.md | 223 +-- docs/developer/packages/codegen.md | 47 +- docs/developer/packages/compiler.md | 28 +- docs/developer/packages/contracts.md | 18 +- docs/developer/packages/index.md | 53 +- docs/developer/packages/naming.md | 24 +- docs/developer/packages/parsers.md | 1464 ++----------------- docs/developer/packages/pipeline.md | 30 +- docs/developer/packages/planning.md | 22 +- docs/developer/packages/policy.md | 462 ++---- docs/developer/packages/preprocessing.md | 26 +- docs/developer/packages/printers.md | 22 +- docs/developer/packages/runtime.md | 29 +- docs/developer/packages/semantics.md | 42 +- docs/developer/packages/utilities.md | 28 +- tests/docs/test_reference_and_source_map.py | 32 +- 17 files changed, 565 insertions(+), 1989 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ee32457..919e9eeef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ release tags add a leading `v` to the package version. ### Changed +- Expanded the contributor architecture and package guides into a complete + stage-by-stage tutorial, with every supported Python module, runnable example + result, focused test purpose, and change route recorded and checked against + the source tree. - Moved generated documentation and distribution output under the hidden `.artifacts/` directory in local commands and CI workflows. - Centralized every production-file execution-example output contract in one diff --git a/docs/developer/architecture.md b/docs/developer/architecture.md index 694a2aed2..c690a7476 100644 --- a/docs/developer/architecture.md +++ b/docs/developer/architecture.md @@ -9,27 +9,25 @@ publication: draft # Contributor Architecture Guide -This is the first internal document to read before changing PRIK. It explains -the repository at a shallow level, the complete Fortran wrapper workflow, the -authority of every stage, the package-root entrypoints, and where to find the -detailed package and test documentation. +Read this page before changing PRIK. It gives the complete wrapper path, the +handoff at every stage, and the owner of each top-level directory. Then read +the linked package guide for the file you intend to change. Package guides +explain local modules, runnable examples, and focused tests; this page stays +at the workflow level. ## Repository Structure -The first tree is intentionally shallow. Detailed contents belong in the guide -for the owning folder. - ```text prik/ -├── prik/ # Production Python package -├── tests/ # Feature-first, stage-owned verification +├── prik/ # Production Python package and wrapper stages +├── tests/ # Feature-first and stage-owned verification ├── docs/ # User and contributor documentation sources ├── docs_theme/ # Maintained MkDocs template customizations ├── examples/ # Complete wrapper projects and real libraries ├── benchmarks/ # Performance workloads and publication tooling ├── tools/ # Repository maintenance and quality scripts ├── .github/ # Continuous integration and release workflows -├── .artifacts/ # Hidden generated documentation/distributions +├── .artifacts/ # Hidden generated documentation and distributions ├── pyproject.toml # Package and Python-tool configuration ├── mkdocs.yml # Documentation navigation and site configuration ├── CHANGELOG.md # Visible unreleased and released changes @@ -38,8 +36,7 @@ prik/ └── AGENTS.md # Repository implementation and verification rules ``` -The production package follows the implemented workflow rather than -alphabetical order: +The production package is arranged by stage ownership, not alphabetically: ```text prik/ @@ -62,23 +59,24 @@ prik/ └── utilities/ # Genuinely stage-neutral mechanisms ``` +The deferred C-input frontend is deliberately not part of this published +Fortran workflow. Generated C bindings are part of the supported backend and +remain visible in the code-generation and printer guides. + ## Package-Root Entry Points -Only public entrypoints and genuinely shared stage values live directly in -`prik/`. +Only public entrypoints and values shared across stages live directly in +`prik/`. These modules coordinate work; they do not take ownership from a +stage package. -| File | Essential objects | Role | +| File | Read it when | It receives and produces | | --- | --- | --- | -| `prik/__init__.py` | public exports and `__version__` | Flattens the supported Python API and lazily exposes heavyweight parse, probe, CLI, and build functions. It is not an implementation owner. | -| `prik/__main__.py` | guarded launcher | Delegates `python3 -m prik` to `prik.cli.main()`. Importing it does not run the CLI. | -| `prik/cli.py` | `main()` and stage request handlers | Parses commands, validates cross-option combinations, formats diagnostics, and dispatches to parser or pipeline owners. | -| `prik/stage_values.py` | `StageRecord`, `FrozenStageRecordError` | Supports mutable construction followed by recursive freezing at a consuming-stage boundary. | - -The CLI is an orchestrator, not a semantic authority. A new option may select -or configure a stage, but parser grammar, semantic rules, policy, codegen, and -compiler mechanics remain in their owning packages. +| `prik/__init__.py` | you need the supported Python API | Re-exports public parsing, probe, contract, and build operations without becoming their implementation owner. | +| `prik/__main__.py` | you are tracing `python3 -m prik` | Calls `prik.cli.main()` only when executed as a module. | +| `prik/cli.py` | you are changing a command or option | Turns terminal arguments into validated stage requests and dispatches them to the owning parser or pipeline. | +| `prik/stage_values.py` | a value crosses from a producing to a consuming stage | Provides `StageRecord` and recursive freezing so completed input cannot be mutated downstream. | -Run the public entrypoints directly: +Run the direct entrypoint demonstrations from the repository root: ```bash python3 prik/__init__.py @@ -95,126 +93,142 @@ Frozen consumer input: geometry -> ('scale', 'norm') Mutation rejected: ParserOutput is frozen by its consuming stage ``` -The output shows the stable root API, the real CLI dispatcher, and the explicit -producer-to-consumer freeze boundary. Exact output is maintained by the +The examples show the supported import surface, command dispatcher, and +producer-to-consumer freeze boundary. Their exact output is checked by the [central execution-example tests](../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py). ## End-To-End Workflow -The implemented source-driven Fortran path is: +The normal source-driven Fortran route is a one-way sequence: ```text CLI or Python build request - -> compiler-backed preprocessing and target probing + -> preprocessing and target probes -> Fortran parser facts -> language-neutral semantic IR - -> complete post-IR interoperability policy + -> complete interoperability policy -> backend-neutral wrapper plan - -> C and Fortran syntax-node generation - -> language printers - -> GeneratedWrapper - -> compiler and linker services + -> C and Fortran nodes plus Python facade text + -> C and Fortran source text + -> in-memory generated wrapper + -> native compilation and linking -> importable extension and runtime objects ``` -Semantic `.pyi` input joins at semantic IR construction. It is an editable -contract input, not a second backend or a parser for Fortran source. - -The C parser and C-to-IR frontend are intentionally deferred from the -published contributor workflow until the C input path is mature. Generated C -for the CPython/NumPy binding remains an essential, fully documented part of -the Fortran wrapper backend. +Semantic `.pyi` input enters at semantic-IR construction. It is an editable +contract input that uses the same policy, planning, code-generation, and build +path; it is not a second backend. A type-mapping report follows the same +facts for inspection but does not create a wrapper. + +## Stage Handoffs + +Read the rows top to bottom. Each output is the next row's authoritative +input; a later row may organize or lower it, but may not silently change its +meaning. + +| Stage owner | Receives | Produces | Next owner | +| --- | --- | --- | --- | +| `preprocessing/` | source paths, compiler configuration, target requests | prepared source, provenance, dependency facts, measured target facts | `parsers/`, `semantics/` | +| `parsers/` | prepared Fortran text or `.pyi` text | source parser models or a Python AST, with locations and diagnostics | `semantics/` | +| `semantics/` | frontend facts and measured type facts | `SemanticModule`: stable identities, shapes, provenance, and raw metadata | `policy/` | +| `policy/` | semantic IR plus raw requests | complete immutable choices for ownership, transport, projection, lifecycle, setters, and support | `planning/` | +| `planning/` | policy-complete semantic IR | `ModulePlan` with binding and bridge views, ordering, names, and build requirements | `codegen/` | +| `codegen/` | a validated plan | typed C/Fortran nodes and planned Python facade source | `printers/`, `pipeline/` | +| `printers/` | formed native nodes or semantic IR | C, Fortran, or `.pyi` text | `pipeline/` or caller | +| `pipeline/` | completed stage inputs | generated artifacts, written files, native build requests, and public result records | `compiler/`, `runtime/` | +| `compiler/` | explicit source, object, include, library, and link inputs | recorded or executed native commands and a shared extension | `runtime/` | +| `runtime/` | generated extension operations and completed handle contracts | validated Python handle objects and live NumPy views | Python caller | + +`naming/` supplies deterministic public and native names where planning or +generation needs them. `utilities/` supplies small stage-neutral mechanisms; +neither is a hidden policy stage. `contracts/` supplies the public `.pyi` +vocabulary before parsing begins. ## Authority And Dependency Rules -Each stage may depend on completed output from the stage above it. Authority -does not flow backward. +The handoff table describes data flow. This table describes decision-making. | Stage | May decide | Must not decide | | --- | --- | --- | -| Preprocessing/probes | prepared source, provenance, dependencies, measured target facts | declaration meaning, semantic dtypes, wrapper support | +| Preprocessing and probes | prepared source, provenance, dependencies, measured target facts | declaration meaning, semantic types, wrapper support | | Parsers | syntax facts, source structure, source-located diagnostics | ownership, Python API, lowering | -| Semantic IR | language-neutral identities, shapes, origins, raw contract metadata | completed ownership or emitted mechanisms | -| Policy | exports, object kind, owner, transfer, destruction, storage, writeback, nullability, projections, lifecycle, setters, support | source syntax or backend text | -| Planning | typed projection and organization of completed facts | new semantic decisions or presentation text | -| Codegen | backend-local mechanisms and syntax nodes selected by the plan | fallback policy inference | -| Printers | formatting and serialization | orchestration, filenames, semantic decisions | -| Pipeline | workflow order, artifacts, manifests, compilation scheduling | stage-local grammar or rules | -| Compiler/runtime | native command mechanics and enforcement of completed runtime contracts | wrapper API or lifetime policy selection | - -The most important rule is the policy boundary: before -`WrapperPlanner.build()` begins, every semantic choice needed by binding and -bridge generation must already be explicit. Lower stages dispatch from those -choices and fail closed when a required decision is missing. +| Semantic IR | language-neutral identities, shapes, origins, raw contract metadata | completed lifetime, projections, or emitted mechanisms | +| Policy | exports, object kind, owner, transfer, destruction, storage, writeback, nullability, projections, setters, support | source grammar or backend text | +| Planning | a typed projection and ordering of completed facts | a new semantic decision or presentation text | +| Code generation | plan-selected backend mechanisms and syntax nodes | fallback policy inferred from datatype, `intent`, aliases, or local memory checks | +| Printers | formatting and serialization | orchestration, filenames, or semantic decisions | +| Pipeline | stage order, artifact assembly, manifests, and compilation scheduling | grammar, policy, lowering, or command mechanics | +| Compiler and runtime | native command execution and enforcement of completed runtime contracts | wrapper API or lifetime-policy selection | + +The critical boundary is before `WrapperPlanner.build()`: all semantic choices +needed by binding and bridge generation must be explicit. If a required choice +is absent, downstream code must fail with the owning diagnostic rather than +guess a default. ## Package Guide Map -Each package has one canonical detailed guide containing its local structure, -important files and objects, direct examples with output, focused test links, -change routes, and invariants. +Use one guide at a time after this page. Every guide has the same reading +order: purpose, input/output handoff, complete Python-module tour, runnable +examples, focused tests, change routes, and invariants. -| Package | Brief role | Detailed guide | +| Package | Use it for | Guide | | --- | --- | --- | -| `contracts/` | Public semantic `.pyi` vocabulary | [Contracts](packages/contracts.md) | -| `compiler/` | Native command construction and execution | [Compiler](packages/compiler.md) | -| `preprocessing/` | Source preparation, provenance, includes, and target probes | [Preprocessing](packages/preprocessing.md) | -| `parsers/` | Fortran source and semantic `.pyi` syntax facts | [Parsers](packages/parsers.md) | -| `semantics/` | Language-neutral semantic IR construction | [Semantics](packages/semantics.md) | -| `policy/` | Complete post-IR interoperability decisions | [Policy](packages/policy.md) | -| `planning/` | Mechanical typed wrapper-plan projection | [Planning](packages/planning.md) | -| `codegen/` | Plan-driven backend nodes and Python facade | [Code generation](packages/codegen.md) | -| `printers/` | Serialization of formed representations | [Printers](packages/printers.md) | -| `pipeline/` | Cross-stage wrapper/build workflows and artifacts | [Pipeline](packages/pipeline.md) | -| `runtime/` | Imported-extension handles and bundled native support | [Runtime](packages/runtime.md) | -| `naming/` | Shared public and generated symbol rules | [Naming](packages/naming.md) | -| `utilities/` | Stage-neutral expressions, strings, and visitor dispatch | [Utilities](packages/utilities.md) | - -The [datatype lifecycle](concepts/datatype-lifecycle.md) remains a separate -cross-cutting concept because one native datatype passes through probing, -semantic normalization, policy, codegen mapping, and runtime validation. +| `contracts/` | public semantic `.pyi` names | [Contracts](packages/contracts.md) | +| `compiler/` | native commands, profiles, and support installation | [Compiler](packages/compiler.md) | +| `preprocessing/` | parser input, provenance, includes, and target facts | [Preprocessing](packages/preprocessing.md) | +| `parsers/` | Fortran syntax facts and raw `.pyi` AST | [Parsers](packages/parsers.md) | +| `semantics/` | language-neutral IR and raw metadata | [Semantics](packages/semantics.md) | +| `policy/` | completed interoperability decisions | [Policy](packages/policy.md) | +| `planning/` | deterministic plan projection and ordering | [Planning](packages/planning.md) | +| `codegen/` | binding, bridge, nodes, and Python facade mechanisms | [Code generation](packages/codegen.md) | +| `printers/` | C, Fortran, and `.pyi` serialization | [Printers](packages/printers.md) | +| `pipeline/` | whole-wrapper, contract, report, and build workflows | [Pipeline](packages/pipeline.md) | +| `runtime/` | imported handle behavior and native payload | [Runtime](packages/runtime.md) | +| `naming/` | stable public and generated symbols | [Naming](packages/naming.md) | +| `utilities/` | stage-neutral expression, string, and visitor helpers | [Utilities](packages/utilities.md) | + +The [datatype lifecycle](concepts/datatype-lifecycle.md) follows one datatype +across these owners. It is deliberately separate from the package tours. ## Tests And Evidence -Choose tests by native language, public feature, and owning stage. The -[testing strategy](testing-strategy.md) is canonical for placement and command -selection. Package guides link directly to their focused suites. +Feature tests own behavior; documentation tests own navigation and guide +coverage. Start with the package guide's **Tests And What They Prove** section, +then use the [testing strategy](testing-strategy.md) to choose the narrowest +command for the changed stage. -Direct source-file examples are production-owned `if __name__ == "__main__"` -flows, run from the repository root as: +Direct source-file examples are real production-owned +`if __name__ == "__main__"` flows. Run them from the repository root as: ```bash python3 /.py ``` -Their exact results are grouped in -[`tests/fortran/infrastructure/execution_examples/test_execution_examples.py`](../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py). -Documentation tests own links, navigation, metadata, publication, and package -guide structure; feature and stage tests own the demonstrated behavior. +The central execution inventory checks their stable output. Documentation +tests check guide structure, source coverage, links, metadata, and navigation; +they do not replace feature tests for the behavior the example demonstrates. ## Where A Change Begins -| Change | Start here | Continue only when needed | +| You need to change | Start with | Then inspect | | --- | --- | --- | -| CLI option or dispatch | `prik/cli.py` | selected package and CLI/user documentation | -| Source expansion or provenance | `prik/preprocessing/source.py` | parser boundary tests | -| Fortran syntax fact | `prik/parsers/fortran/` | semantic converter if the IR changes | -| Semantic `.pyi` syntax | `prik/parsers/pyi/parser.py` or `prik/semantics/pyi2ir.py` | printer, policy, and user reference according to meaning | -| Stable semantic model/type | `prik/semantics/` | policy and downstream projections | -| Ownership, projection, setters, or support | `prik/policy/` | planning only to project the completed result | -| Plan representation | `prik/planning/` | binding/bridge generation consumers | -| Emitted native mechanism | narrow `prik/codegen/` owner | matching printer only if representation changes | -| Formatting | matching `prik/printers/` file | golden output tests | -| Build artifact or compilation workflow | `prik/pipeline/build.py` | compiler service when argv mechanics change | -| Runtime handle enforcement | `prik/runtime/handles.py` | policy first if permission/ownership is undecided | - -For exact file ownership use the [source map](source-map.md). When starting -from a documented capability use the [feature-to-code map](feature-to-code-map.md). +| CLI option or dispatch | `prik/cli.py` | selected owner and CLI/user documentation | +| Source expansion or provenance | `prik/preprocessing/source.py` | parser-boundary tests | +| Fortran syntax fact | `prik/parsers/fortran/` | semantic converter if IR changes | +| Semantic `.pyi` syntax | `prik/parsers/pyi/parser.py` | `semantics/pyi2ir.py`, printer, and contract reference according to meaning | +| Stable IR/type fact | `prik/semantics/` | policy and downstream projections | +| Ownership, projection, setters, or support | `prik/policy/` | planning only to project a completed result | +| Plan representation or ordering | `prik/planning/` | binding/bridge consumers | +| Emitted native mechanism | narrow `prik/codegen/` owner | matching printer only if node representation changes | +| Formatting | matching `prik/printers/` module | golden-output tests | +| Artifact or compilation workflow | `prik/pipeline/build.py` | compiler service for argv mechanics | +| Runtime handle enforcement | `prik/runtime/handles.py` | policy first if permission or ownership is undecided | + +For exact file ownership use the [source map](source-map.md). For a documented +feature's supported scope and evidence use the [feature-to-code map](feature-to-code-map.md). ## Contributor Documentation Structure -All developer, maintainer, design, testing, release, and roadmap material lives -in one contributor area: - ```text docs/developer/ ├── index.md @@ -231,5 +245,4 @@ docs/developer/ ``` There is no separate maintainer tree. Completed migration logs and placeholder -pages are not maintained architecture; Git history retains them after their -still-valid decisions have moved to canonical guides. +pages belong in Git history after their durable decisions have moved here. diff --git a/docs/developer/packages/codegen.md b/docs/developer/packages/codegen.md index 074db4db2..0a8832b87 100644 --- a/docs/developer/packages/codegen.md +++ b/docs/developer/packages/codegen.md @@ -21,6 +21,7 @@ ownership, change wrapper support, print final native source, or compile it. ```text prik/codegen/ +├── __init__.py ├── nodes.py ├── primitive_scalar_types.py ├── docstrings.py @@ -28,14 +29,16 @@ prik/codegen/ ├── checks.py ├── visitor.py ├── c/ +│ ├── __init__.py │ ├── binding.py │ ├── python_surface.py │ └── naming.py └── fortran/ + ├── __init__.py └── bridge.py ``` -## Internal Workflow +## What This Stage Receives And Produces ```text validated ModulePlan @@ -46,21 +49,27 @@ validated ModulePlan -> language printers ``` -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `nodes.py` | typed C and Fortran node families | Represents generated native syntax before serialization. | -| `primitive_scalar_types.py` | `PrimitiveScalarTypeRegistry`, `NumpyDtypeRegistry` | Maps resolved semantic scalar identities to explicit C, Fortran, NumPy, CFI, and CPython spellings. | -| `docstrings.py` | `WrapperDocstringBuilder` | Renders Python-facing documentation from the completed plan. | -| `c/binding.py` | `CBindingGenerator` | Lowers binding plan views into CPython/NumPy C nodes. | -| `c/python_surface.py` | `PythonSurfaceContext`, `PythonSurfaceEmitter` | Produces the planned derived-class, holder, and module-proxy Python facade. | -| `fortran/bridge.py` | `FortranBridgeGenerator` | Lowers bridge plan views into `bind(C)` modules, accessors, descriptors, and native calls. | - -`overloads.py` answers structural questions over completed overload plans; -`c/naming.py` owns binding-local generated names; `checks.py` powers the -codegen ownership and complexity gate. Specialized emitter methods are kept -local because they make the selected mechanism auditable. +| [`prik/codegen/__init__.py`](../../../prik/codegen/__init__.py) | Re-exports generators, selected node records, scalar lowering, and generic codegen visitor support. | The supported backend API changes. | +| [`prik/codegen/nodes.py`](../../../prik/codegen/nodes.py) | `StageRecord`-based C and Fortran node families represent source before text serialization. | Existing nodes cannot express a plan-selected native construct. | +| [`prik/codegen/primitive_scalar_types.py`](../../../prik/codegen/primitive_scalar_types.py) | `PrimitiveScalarTypeRegistry` and `NumpyDtypeRegistry` map resolved semantic scalars to C, Fortran, NumPy, CFI, and CPython spellings. | An established semantic scalar needs a backend spelling or dtype projection. | +| [`prik/codegen/docstrings.py`](../../../prik/codegen/docstrings.py) | `WrapperDocstringBuilder` renders public Python documentation from a completed plan. | Plan-derived wrapper documentation changes. | +| [`prik/codegen/overloads.py`](../../../prik/codegen/overloads.py) | `OverloadPlanQueries` answers structural questions about completed overload plans. | Shared overload-plan inspection is needed without re-deciding overload policy. | +| [`prik/codegen/checks.py`](../../../prik/codegen/checks.py) | Shared code-generation validation and complexity-check support. | A codegen invariant or its repository gate changes. | +| [`prik/codegen/visitor.py`](../../../prik/codegen/visitor.py) | `ClassVisitor` and `UnsupportedWrapperCodegenNodeError` provide backend-node dispatch and explicit unsupported-node failure. | Generic codegen visitor behavior changes. | +| [`prik/codegen/c/__init__.py`](../../../prik/codegen/c/__init__.py) | Boundary for C/CPython binding mechanics. | Establishing a deliberate C-backend import API. | +| [`prik/codegen/c/binding.py`](../../../prik/codegen/c/binding.py) | `CBindingGenerator` lowers completed binding-plan views into CPython/NumPy C nodes. | A plan-selected Python boundary, lifecycle, error, or module mechanism changes. | +| [`prik/codegen/c/naming.py`](../../../prik/codegen/c/naming.py) | Binding-local generated names that should not become global naming policy. | A C-binding private symbol convention changes. | +| [`prik/codegen/c/python_surface.py`](../../../prik/codegen/c/python_surface.py) | `PythonSurfaceContext` and `PythonSurfaceEmitter` produce planned classes, holders, and module proxies embedded in the extension. | Generated Python facade behavior changes. | +| [`prik/codegen/fortran/__init__.py`](../../../prik/codegen/fortran/__init__.py) | Boundary for Fortran bridge mechanics. | Establishing a deliberate bridge-backend import API. | +| [`prik/codegen/fortran/bridge.py`](../../../prik/codegen/fortran/bridge.py) | `FortranBridgeGenerator` lowers bridge-plan views into `bind(C)` modules, accessors, descriptors, and native calls. | A plan-selected ABI declaration, conversion, call slot, or native bridge mechanism changes. | + +Specialized emitter methods remain local because each makes the selected +mechanism auditable. Shared code must never reconstruct policy from datatype, +source `intent`, dotted shape, aliases, or local memory checks. ## Execution Examples @@ -178,12 +187,12 @@ Internal procedures: (none) Together the outputs demonstrate that both backends lower one shared plan without asking the other backend to decide policy. -## Tests +## Tests And What They Prove -- [Codegen infrastructure](../../../tests/fortran/infrastructure/codegen/) -- [Feature-local codegen suites](../../../tests/fortran/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) -- `python3 tools/check_codegen_complexity.py` +- [Codegen infrastructure](../../../tests/fortran/infrastructure/codegen/) covers nodes, generators, planning handoffs, and validation. +- [Feature-local codegen suites](../../../tests/fortran/) cover emitted mechanisms for each supported feature. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes every direct module demonstration on this page. +- `python3 tools/check_codegen_complexity.py` protects the generator-complexity policy. ## Change Routes diff --git a/docs/developer/packages/compiler.md b/docs/developer/packages/compiler.md index 61f78f5fb..77e929746 100644 --- a/docs/developer/packages/compiler.md +++ b/docs/developer/packages/compiler.md @@ -21,13 +21,14 @@ or decide wrapper policy. ```text prik/compiler/ +├── __init__.py ├── compiler_profiles.py ├── objects.py ├── compilers.py └── native_support.py ``` -## Internal Workflow +## What This Stage Receives And Produces ```text explicit ObjectFile and link inputs from prik.pipeline @@ -41,14 +42,15 @@ The selected Fortran compiler family supplies its matching C driver and family-specific switches. The pipeline owns dependency-ready batches; the compiler executes one request at a time. -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `compiler_profiles.py` | compiler profile records, `fortran_compiler_family()` | Resolves GNU, Intel, LLVM, NVIDIA, or PGI language families and matching drivers. | -| `objects.py` | `ObjectFile` | Immutable input for one source-to-object command. | -| `compilers.py` | `Compiler` | Constructs, records, executes, and reports native compile/link commands. | -| `native_support.py` | `install_native_support()` | Installs the bundled header runtime and NumPy API-version header into a generated wrapper directory. | +| [`prik/compiler/__init__.py`](../../../prik/compiler/__init__.py) | Deliberately empty package boundary; callers use the owning modules or pipeline APIs. | Establishing a small compiler-package public import surface. | +| [`prik/compiler/compiler_profiles.py`](../../../prik/compiler/compiler_profiles.py) | Profile data and `fortran_compiler_family()` map a Fortran executable to its compatible C driver and flags. | Supporting a compiler family or changing family-specific build settings. | +| [`prik/compiler/objects.py`](../../../prik/compiler/objects.py) | `ObjectFile` is the immutable description of one source-to-object request. | A compilation input needs another explicit field or validation rule. | +| [`prik/compiler/compilers.py`](../../../prik/compiler/compilers.py) | `Compiler` builds, records, runs, and reports compile/link commands; `get_condaless_search_path()` isolates environment lookup. | Command spelling, subprocess execution, or command reporting changes. | +| [`prik/compiler/native_support.py`](../../../prik/compiler/native_support.py) | `install_native_support()` copies the bundled support payload and creates the NumPy API-version header. | The pipeline needs a different support-installation result; edit the payload itself under `runtime/native_support/`. | ## Execution Examples @@ -108,13 +110,13 @@ NumPy version header present: True Together these outputs prove that profile selection, request construction, native command mechanics, and support installation remain separate operations. -## Tests +## Tests And What They Prove -- [Compiler construction tests](../../../tests/fortran/building_shared_library/compiling/) -- [Build pipeline tests](../../../tests/fortran/building_shared_library/pipeline/) -- [Source build modes](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py) -- [Runtime ABI compatibility](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Compiler construction tests](../../../tests/fortran/building_shared_library/compiling/) cover profile selection and compile/link argv. +- [Build pipeline tests](../../../tests/fortran/building_shared_library/pipeline/) cover compiler handoff from a build plan. +- [Source build modes](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py) covers real source-build outcomes. +- [Runtime ABI compatibility](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) covers installed support used by a compiled extension. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the four demonstrations above. ## Change Routes diff --git a/docs/developer/packages/contracts.md b/docs/developer/packages/contracts.md index deb4c2d67..18e55e80c 100644 --- a/docs/developer/packages/contracts.md +++ b/docs/developer/packages/contracts.md @@ -27,7 +27,7 @@ prik/contracts/ The single module is intentional. A semantic contract imports one stable public namespace instead of depending on internal stage packages. -## Internal Workflow +## What This Stage Receives And Produces ```text semantic .pyi text @@ -41,11 +41,11 @@ Some primitive symbols also construct exact NumPy scalar values at runtime. Subscriptions such as `Float64[:, :]` construct declarative contract objects; they do not create semantic IR objects. -## Important File And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `prik/contracts/__init__.py` | `Float64`, `Int32`, `String`, `Addr`, `Allocatable`, `Pointer`, `Returns`, `Arg`, `bind`, `native_call` | Publishes the complete supported semantic `.pyi` vocabulary and the small runtime constructors required by that vocabulary. | +| [`prik/contracts/__init__.py`](../../../prik/contracts/__init__.py) | The complete public vocabulary: scalar and array markers, descriptor markers (`Allocatable`, `Pointer`), metadata expressions, decorators, and the small runtime constructors behind concrete scalar and descriptor contracts. | Adding, removing, or documenting public `.pyi` syntax. This one file is intentionally the stable import namespace; private `_Contract*` classes preserve annotation syntax at runtime. | The canonical public import path is part of the file format. Internal code may interpret these names, but must not replace them with imports from semantics, @@ -68,12 +68,12 @@ The first line proves that a primitive contract scalar has exact NumPy runtime behavior. The second proves that array subscription produces declarative rank and shape syntax for later semantic interpretation. -## Tests +## Tests And What They Prove -- [Contract runtime tests](../../../tests/fortran/data_types/runtime/) -- [Semantic `.pyi` parser tests](../../../tests/fortran/semantic_pyi_format/parsing/) -- [Semantic `.pyi` round-trip tests](../../../tests/fortran/semantic_pyi_format/pipeline/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Contract runtime tests](../../../tests/fortran/data_types/runtime/) protect scalar and descriptor-constructor behavior. +- [Semantic `.pyi` parser tests](../../../tests/fortran/semantic_pyi_format/parsing/) protect recognition of the public vocabulary. +- [Semantic `.pyi` round-trip tests](../../../tests/fortran/semantic_pyi_format/pipeline/) protect loading and re-emission through the shared contract path. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the example output above. ## Change Routes diff --git a/docs/developer/packages/index.md b/docs/developer/packages/index.md index f2051dc4f..33367f5eb 100644 --- a/docs/developer/packages/index.md +++ b/docs/developer/packages/index.md @@ -9,27 +9,38 @@ publication: draft # Source Package Guides -These pages explain the production package one ownership boundary at a time. -Read the [architecture guide](../architecture.md) first for the complete flow, -then open the package that owns the change. +These pages are the file-level companion to the +[architecture guide](../architecture.md). Read the architecture guide once for +the whole flow, then use this table to enter the owner of a change. Do not read +the guides as thirteen alternative pipelines: each describes one handoff in +the same pipeline. -| Package | Canonical guide | Boundary | +| Package | Read it when you need to change | Canonical guide | | --- | --- | --- | -| `prik.contracts` | [Contracts](contracts.md) | Public semantic `.pyi` vocabulary | -| `prik.compiler` | [Compiler](compiler.md) | Native command construction and execution | -| `prik.preprocessing` | [Preprocessing](preprocessing.md) | Source preparation, provenance, and target probes | -| `prik.parsers` | [Parsers](parsers.md) | Fortran and semantic `.pyi` syntax facts | -| `prik.semantics` | [Semantics](semantics.md) | Language-neutral semantic IR | -| `prik.policy` | [Policy](policy.md) | Completed post-IR interoperability decisions | -| `prik.planning` | [Planning](planning.md) | Mechanical projection into wrapper plans | -| `prik.codegen` | [Code generation](codegen.md) | Plan-driven backend nodes and Python facade source | -| `prik.printers` | [Printers](printers.md) | Serialization of formed representations | -| `prik.pipeline` | [Pipeline](pipeline.md) | Cross-stage workflow and artifact orchestration | -| `prik.runtime` | [Runtime](runtime.md) | Imported-extension handle behavior and native payload | -| `prik.naming` | [Naming](naming.md) | Shared public and generated symbol rules | -| `prik.utilities` | [Utilities](utilities.md) | Genuinely stage-neutral mechanisms | +| `prik.contracts` | public semantic `.pyi` syntax | [Contracts](contracts.md) | +| `prik.compiler` | compiler profiles, command argv, or native-support installation | [Compiler](compiler.md) | +| `prik.preprocessing` | parser input, provenance, includes, or target probes | [Preprocessing](preprocessing.md) | +| `prik.parsers` | Fortran syntax facts or raw `.pyi` syntax | [Parsers](parsers.md) | +| `prik.semantics` | the shared semantic graph, types, or raw metadata | [Semantics](semantics.md) | +| `prik.policy` | completed ownership, projection, lifecycle, or support choices | [Policy](policy.md) | +| `prik.planning` | plan representation, ordering, or backend views | [Planning](planning.md) | +| `prik.codegen` | generated binding, bridge, node, or Python-facade mechanism | [Code generation](codegen.md) | +| `prik.printers` | C, Fortran, or `.pyi` text serialization | [Printers](printers.md) | +| `prik.pipeline` | wrapper, contract, report, artifact, or build orchestration | [Pipeline](pipeline.md) | +| `prik.runtime` | imported native handles or bundled native support | [Runtime](runtime.md) | +| `prik.naming` | public-name normalization or generated symbols | [Naming](naming.md) | +| `prik.utilities` | a genuinely stage-neutral helper | [Utilities](utilities.md) | -Each guide uses the same order: purpose and boundaries, local structure, -workflow, important files and objects, direct execution examples with expected -output, test owners, change routes, and invariants. Source-tree `README.md` -files remain short orientation notes and link back to these canonical guides. +Each guide answers the same practical questions: + +1. What does this stage receive and produce? +2. Which module owns the behavior I need to change? +3. Which classes and functions are the important entrypoints? +4. What does each direct-execution example prove? +5. Which tests protect that behavior? + +The directory tour covers every supported Python module under that package, +including package initializers and nested backend packages. The deferred +C-input frontend is intentionally excluded from the published Fortran route. +Source-tree `README.md` files remain short orientation notes and link back to +these canonical guides. diff --git a/docs/developer/packages/naming.md b/docs/developer/packages/naming.md index 8d335da3a..804738932 100644 --- a/docs/developer/packages/naming.md +++ b/docs/developer/packages/naming.md @@ -19,16 +19,26 @@ policy or emitted source syntax. ```text prik/naming/ +├── __init__.py ├── policy.py └── native_symbols.py ``` -## Important Files And Essential Objects +## What This Stage Receives And Produces -| File | Important objects | Responsibility | +```text +raw public or generated identity + occupied namespace + -> normalized public name or bounded native symbol + -> planning and code generation +``` + +## Directory Tour + +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `policy.py` | `NamingPolicy`, `NormalizedPublicName`, `PublicNameRecord`, `GeneratedSymbolRules` | Normalizes Python names, reserves namespaces, and applies target-language symbol rules. | -| `native_symbols.py` | `NativeSymbolNames` | Compacts long owner identities into deterministic compiler-safe fragments. | +| [`prik/naming/__init__.py`](../../../prik/naming/__init__.py) | Re-exports the supported normalization and generated-symbol policy objects. | Changing the package-level naming API. | +| [`prik/naming/policy.py`](../../../prik/naming/policy.py) | `NamingPolicy`, `NormalizedPublicName`, `PublicNameRecord`, and `GeneratedSymbolRules` normalize Python names, reserve namespaces, and apply language rules. | Public-name normalization, collision handling, keyword escaping, or target language symbol rules. | +| [`prik/naming/native_symbols.py`](../../../prik/naming/native_symbols.py) | `NativeSymbolNames` retains owner identity and creates compact, deterministic compiler-safe fragments. | Bounded native-symbol spelling or hash/prefix rules. | ## Execution Examples @@ -56,10 +66,10 @@ The first example distinguishes public namespace allocation from generated target naming. The second preserves a readable prefix while hashing the full owner identity under a compiler symbol limit. -## Tests +## Tests And What They Prove -- [Naming infrastructure](../../../tests/fortran/infrastructure/naming/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Naming infrastructure](../../../tests/fortran/infrastructure/naming/) covers normalization, collisions, and stable generated names. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the two demonstrations above. ## Change Routes diff --git a/docs/developer/packages/parsers.md b/docs/developer/packages/parsers.md index 25edc8acd..753ff6f80 100644 --- a/docs/developer/packages/parsers.md +++ b/docs/developer/packages/parsers.md @@ -11,54 +11,77 @@ publication: draft ## Purpose And Boundaries -`prik/parsers/` owns syntax-level frontends. The Fortran frontend preserves -source units, declarations, visibility, locations, and diagnostics. The -semantic `.pyi` frontend deliberately stops at Python AST. Parsers report what -source says; they do not choose ownership, wrapper support, NumPy lowering, or -generated API behavior. +`prik/parsers/` owns syntax-level facts. The Fortran frontend preserves source +units, declarations, visibility, locations, and diagnostics. The semantic +`.pyi` frontend deliberately stops at a standard Python AST. A parser reports +what its input says; it does not assign a stable semantic type, choose +ownership, decide wrapper support, or emit a Python API. + +The C-input frontend is intentionally deferred from the published contributor +workflow. This guide covers the supported Fortran and semantic-`.pyi` path; +generated C binding remains documented under [code generation](codegen.md). ## Local Structure ```text prik/parsers/ +├── __init__.py ├── fortran/ +│ ├── __init__.py +│ ├── __main__.py +│ ├── cli.py │ ├── lexer.py │ ├── models.py │ ├── parser.py │ ├── type_resolver.py -│ ├── cli.py -│ ├── utils.py -│ └── __main__.py +│ └── utils.py └── pyi/ + ├── __init__.py └── parser.py ``` -## Internal Workflow +## What This Stage Receives And Produces ```text -prepared Fortran text -> logical lines -> parser models -> Fortran-to-IR -semantic .pyi text -> Python ast.Module -> .pyi-to-IR -``` - -The essential Fortran objects are `FortranParser`, `SourceUnit` and its unit -subclasses, `FortranFile`, `FortranProject`, `FortranParseError`, and the -public `parse_fortran_file()` and `parse_fortran_project()` functions. The -semantic `.pyi` frontend exposes `parse_pyi_text()` and `parse_pyi_file()` and -returns a standard `ast.Module`. - -## Important Files - -| File | Responsibility | -| --- | --- | -| `fortran/lexer.py` | Detects source form, strips comments, folds continuations, and preserves logical-line locations. | -| `fortran/models.py` | Defines passive parser models and diagnostics. | -| `fortran/parser.py` | Parses files/projects, resolves structural scope, and assembles source units. | -| `fortran/type_resolver.py` | Preserves parser-level type, kind, and character syntax without target evaluation. | -| `fortran/cli.py` | Formats stable human and JSON parser reports. | -| `pyi/parser.py` | Parses semantic `.pyi` syntax into Python AST without semantic interpretation. | +prepared Fortran text + -> logical lines with original locations + -> Fortran parser models and diagnostics + -> Fortran-to-IR conversion + +semantic .pyi text + -> ast.Module + -> .pyi-to-IR conversion +``` + +Fortran parser models retain source spellings such as `real(kind=...)`, +`intent`, and declaration shapes. Target-dependent kind values arrive from +preprocessing probes and are resolved in semantic conversion, not here. + +## Directory Tour + +| Module | Main entrypoints and contents | Change it when | +| --- | --- | --- | +| [`prik/parsers/__init__.py`](../../../prik/parsers/__init__.py) | Declares the parser frontend namespaces. | The package-level frontend layout changes. | +| [`prik/parsers/fortran/__init__.py`](../../../prik/parsers/fortran/__init__.py) | Internal Fortran-parser package boundary. | Establishing a deliberate Fortran-parser import API. | +| [`prik/parsers/fortran/__main__.py`](../../../prik/parsers/fortran/__main__.py) | Module launcher for `python3 -m prik.parsers.fortran`; delegates to the CLI. | Module-launch behavior changes, not parser semantics. | +| [`prik/parsers/fortran/utils.py`](../../../prik/parsers/fortran/utils.py) | `detect_source_form()` and `split_csv()` are small, grammar-neutral lexical helpers. | Source-form detection or top-level comma splitting changes. | +| [`prik/parsers/fortran/lexer.py`](../../../prik/parsers/fortran/lexer.py) | `strip_comment()` and `preprocess_lines()` remove comments, fold continuations, and retain logical-line locations. | Lexical normalization or source-coordinate retention changes. | +| [`prik/parsers/fortran/models.py`](../../../prik/parsers/fortran/models.py) | Passive parser records including `FortranFile`, `FortranProject`, `FortranModule`, variables, signatures, derived types, enums, shapes, and `FortranParseError`. | A parser-level source fact or diagnostic representation changes. | +| [`prik/parsers/fortran/type_resolver.py`](../../../prik/parsers/fortran/type_resolver.py) | `extract_kind_from_type_spec()` preserves type, kind, and character syntax without measuring its meaning. | Parser-level type-spec spelling extraction changes. | +| [`prik/parsers/fortran/parser.py`](../../../prik/parsers/fortran/parser.py) | `FortranParser`, source-unit records, `parse_fortran_file()`, and `parse_fortran_project()` slice units, build models, resolve parser-level scope, and order projects. | Grammar, declaration extraction, source-unit structure, parser diagnostics, or project ordering changes. | +| [`prik/parsers/fortran/cli.py`](../../../prik/parsers/fortran/cli.py) | `main()` turns parser requests into stable human or JSON reports. | Parser CLI arguments or report presentation changes. | +| [`prik/parsers/pyi/__init__.py`](../../../prik/parsers/pyi/__init__.py) | Re-exports `parse_pyi_text()` and `parse_pyi_file()`. | The supported raw-`.pyi` parser import surface changes. | +| [`prik/parsers/pyi/parser.py`](../../../prik/parsers/pyi/parser.py) | `parse_pyi_text()` and `parse_pyi_file()` validate and return `ast.Module` without semantic interpretation. | Accepted Python syntax or raw parse diagnostics change. | + +Read `fortran/parser.py` by entrypoint, then source-unit scanning, then the +visitor that owns the construct you are changing. Do not add policy or codegen +conditions to a parser visitor: preserve the fact and let the next stage +decide whether it is supported. ## Execution Examples +Logical-line preparation: + ```bash python3 prik/parsers/fortran/lexer.py ``` @@ -71,6 +94,8 @@ line 4: real, intent(in) :: offset line 5: end subroutine shift ``` +Fortran file parsing: + ```bash python3 prik/parsers/fortran/parser.py ``` @@ -81,6 +106,8 @@ Parameter: n = 4 Procedure: scale(values: real[1]) ``` +Type-spec preservation: + ```bash python3 prik/parsers/fortran/type_resolver.py ``` @@ -91,6 +118,8 @@ real(kind=selected_real_kind(15, 307)) -> selected_real_kind(15, 307) character(len=16, kind=c_char) -> len=16, kind=c_char ``` +Parser report formatting: + ```bash python3 prik/parsers/fortran/cli.py ``` @@ -103,6 +132,8 @@ File: geometry.f90 - function norm(value:real[0]) -> real[0] ``` +Raw semantic-`.pyi` parsing: + ```bash python3 prik/parsers/pyi/parser.py ``` @@ -114,1355 +145,36 @@ Argument annotation: Float64 Semantic conversion performed: False ``` -The detailed Fortran reference below records the complete maintained subset, -API behavior, diagnostics, fixtures, and reimplementation constraints. +These outputs are intentionally parse-only. They show preserved source facts, +not a completed `SemanticModule`, wrapper plan, or generated source. -## Tests +## Tests And What They Prove -- [Fortran parser tests](../../../tests/fortran/source_parsing/parsing/) -- [Fortran parser CLI tests](../../../tests/fortran/command_line_interface/pipeline/) -- [Semantic `.pyi` parsing tests](../../../tests/fortran/semantic_pyi_format/parsing/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Fortran parser tests](../../../tests/fortran/source_parsing/parsing/) cover source forms, units, declarations, diagnostics, and project ordering. +- [Fortran parser CLI tests](../../../tests/fortran/command_line_interface/pipeline/) cover parser command dispatch and report output. +- [Semantic `.pyi` parsing tests](../../../tests/fortran/semantic_pyi_format/parsing/) cover raw `.pyi` AST parsing and diagnostics. +- [Semantic IR conversion tests](../../../tests/fortran/semantic_ir/semantics/) prove the downstream Fortran-model handoff. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the five demonstrations above. ## Change Routes -Start lexical/source-coordinate changes in `fortran/lexer.py`, grammar and -model construction in `fortran/parser.py`, report changes in `fortran/cli.py`, -and raw `.pyi` syntax changes in `pyi/parser.py`. Semantic meaning begins in -the matching converter. Parser support alone never establishes wrapper -support. - -## Detailed Fortran Reference - -This document defines the currently supported parser subset, expected behavior, -and practical usage from terminal and Python. - -## 1) Supported features (comprehensive) - -### 1.1 Source forms and preprocessing - -- Free-form Fortran: `.f90`, `.f95`, `.f03`, `.f08` -- Fixed-form Fortran: `.f`, `.for`, `.ftn` -- Free/fixed comment stripping -- Continuation handling for both forms - -### 1.2 Procedure parsing - -- `subroutine` headers -- `function` headers -- Header modifiers: `pure`, `elemental`, `recursive` -- Function `result(...)` parsing (tolerant support for `results(...)`) - -### 1.3 Declaration/argument parsing - -- Intrinsic types: `integer`, `real`, `complex`, `logical`, `character` -- Kind extraction from declaration specs (`kind=...`) -- Attribute extraction: - - `intent(in|out|inout)` - - `optional` - - `value` - - `allocatable` - - `pointer` - - `target` -- Array extraction: - - `dimension(...)` - - variable-level shape syntax (`x(:)`, `x(n)`) - -### 1.4 Modules, imports, and project context - -- Module discovery -- Module variable extraction -- Shared specification-part parsing for module-like scopes (modules, - submodules, programs, and block-data units), preserving original line - numbers while skipping contained procedure bodies where they are not - wrap-relevant -- `use` extraction at module and procedure scope -- Explicit `use` symbol mappings preserve imported `source` names and local - `target` names for renamed imports -- Propagation of module-level `use` imports into contained procedures -- Folder/project parsing with dependency-aware ordering -- Cross-file kind constant resolution (e.g., kinds modules) -- Cached compile-time expression resolution for local/module parameters, - module/program variable shapes, and character lengths - -### 1.5 Derived type parsing - -- `type :: ... end type` and legacy `type name ... end type` discovery -- Parameterized derived-type headers such as `type :: buffer_type(k, n)` - and declarations such as `type(buffer_type(real64, 4))` -- Type attributes (e.g., `abstract`) -- Inheritance (`extends(parent)`) -- Field extraction including shape/pointer/allocatable -- Type-bound procedures: - - `procedure ... :: ...` bindings with attributes (e.g. `pass(self)`, `nopass`) - - `generic ... :: name => target1, target2` - -### 1.6 Parser diagnostics and wrapper planning boundary - -- Parser diagnostics report source-level parse errors and unsupported parser - constructs. -- Parser JSON remains parse-only and does not contain wrapper-plan decisions - or support diagnostics. -- Wrapper builds complete policy from semantic IR and validate the resulting - wrapper plan. Unsupported contracts report the owning plan path and - completed-policy diagnostic. - -## 2) Public API surface - -Supported public API: - -- `parse_fortran_file(source_or_path, filename=None, encoding="utf-8") -> FortranFile` -- `parse_fortran_project(files, encoding="utf-8") -> FortranProject` - -## Parser organization notes - -`prik/parsers/fortran/parser.py` is now intentionally organized into clearly labeled -sections and carries embedded implementation guidance. Start with the thin public -wrappers at the bottom, then read the class from top to bottom: - -- Regex/constants, parser-wide type aliases, private unit dataclasses, and the - compile-time resolver -- `FortranParser` internals grouped by domain: - - public parse entrypoints (`parse_file`, `parse_project`). The supported - module-level API remains the wrappers listed above. - - source-unit visitors for files, modules, submodules, programs, - procedures, interfaces, derived types, and block data - - recursive source-unit slicing (`header`, specification part, execution - part, `contains`) with original line numbers preserved on each slice - - shared declaration parsing for module variables, program/block-data - variables, procedure arguments/results, and derived-type fields - - `_helper_*` methods for scoped parsing, expression resolution, same-level - duplicate checks, and shared specification-part collection -- Thin module-level convenience wrappers that delegate to a shared parser - instance - -Parser methods carry focused docstrings, with examples where a grammar visitor -or lexical helper is easier to understand from a concrete call. - -The Fortran parser is now packaged under `prik.parsers.fortran` rather than a -top-level parser package. The package includes its CLI module, lexer, -JSON-compatible parse models, project parser, type resolver, and utility -helpers. Public callers should use the stable top-level `prik` parser exports -or `prik.parsers.fortran` package imports. - -## Implementation Inventory And Maintenance - -This file is the single maintained Fortran parser reference. It replaces the -older standalone implementation-reference document; parser feature inventory, -testing workflow, and maintenance guard policy live here. - -The implementation inventory is maintained across these surfaces: - -- `prik/parsers/fortran/parser.py` owns source slicing, declaration extraction, - diagnostics, project ordering, dependency resolution, and compile-time - expression resolution. -- `prik/parsers/fortran/models.py` owns parse-only dataclasses and JSON-compatible - parser facts. -- `prik/semantics/fortran2ir.py` owns conversion from parser facts to semantic IR, - including kind mapping, compile-time specialization, storage contracts, - projection metadata, and wrapper-planning inputs. -- `tests/fortran/source_parsing/parsing/` covers parser contracts, source-unit slicing, diagnostics, - project behavior, and fixture regressions. -- `tests/fortran/semantic_ir/semantics/` covers semantic conversion, datatype precision mapping, - wrapper planning, `.pyi` emission, and compile-time specialization. - - - -`parse_file` is the central orchestration path. The scanner first constructs -fully classified direct file-level units, then each class visitor parses only -the stored regions and children it owns. This is the key parser design: each -Fortran grammar unit has a header, a specification region, optional execution -region, optional `contains` region, and retained direct children. The -differences between modules, programs, procedures, derived types, interfaces, -and block data are expressed by small visitor decisions and grammar flags -rather than separate whole-file parsing loops. - -Nested unit boundaries and placement outside execution regions are checked even -when they are not exported as wrapper metadata. Internal procedures inside a -host procedure's `contains` block are structurally sliced, then their -declarations and bodies are skipped. Once an execution boundary is detected, -procedure bodies and standalone included execution fragments are intentionally -skipped. Procedure-local interface blocks are still visited enough to type -callback dummy arguments and to preserve interface metadata. - -### 2.1 Recursive parser sketch - -Small input: - -```fortran -module m - integer, parameter :: n = 4 -contains - subroutine scale(x) - real, intent(inout) :: x(n) - end subroutine scale -end module m -``` - -The parser handles it in this order: - -1. `parse_file` preprocesses the source and asks the stateless - `_SourceUnitScanner.scan_file_units` collaborator to scan at file scope. - The result is one `ModuleUnit` carrying the module name, exact lines, source - locations, classified grammar regions, and retained direct children. The - scanner remains independent of `_ParserScope` and constructed models. -2. the shared `ClassVisitor._visit` dispatcher selects `_visit_ModuleUnit`. -3. `_visit_ModuleUnit` creates a module `_ParserScope` and sends the unit's - already-classified specification lines to `_parse_specification_part`. -4. `_parse_specification_part` uses the shared declaration backend: - `_helper_parse_declaration_line` parses `integer, parameter :: n = 4` into a - typed `_Declaration`, then `_store_declaration` dispatches to the - module-like-variable storage helper, which appends the resulting parameter - variable to `FortranModule.variables`. -5. The module visitor reads the scanner-owned direct children. It finds one - procedure unit, `scale`, and dispatches it to - `_visit_ProcedureUnit`. -6. `_visit_ProcedureUnit` creates a procedure `_ParserScope` and visits only - the stored specification part. The same declaration backend parses - `real, intent(inout) :: x(n)` and sends the typed declaration to the - procedure-symbol storage helper, which updates `x` in the procedure argument - symbol table. - -Scope is always an explicit argument to the shared helpers. That is the reason -two modules can each define `type :: state` without conflict, while two -same-level `module m` declarations or two same-level contained procedures with -the same name are rejected by `_helper_validate_sibling_units`. - -The ownership boundary is deliberate: `_SourceUnitScanner` recognizes unit -openers and terminators, matches nested boundaries, and separates -specification, execution, and `contains` regions. `FortranParser` owns scopes, -model visitors, declaration parsing, sibling validation, and diagnostics that -depend on constructed parser models. Splitting the scanner into another file -would not strengthen that boundary; its private source tuples, grammar records, -and unit classes are all local to this parser module. - -Declaration parsing has its own local ownership boundary. `_Declaration` -records the normalized type spelling and declaration attributes shared by an -entity list. For example, `real(kind=rk), pointer, dimension(:) :: values` -produces one declaration with base type `real`, kind `rk`, pointer enabled, and -shape `[:]`; `values` remains a separate entity name. Storage helpers then turn -that record into procedure symbols, derived-type fields, or module-like -variables. The record is parser-internal and never becomes a second public -parser model or semantic-policy object. - -Each `SourceUnit` is fully classified by the scanner. In addition to its exact -source span, kind, and name, it owns its header, specification, execution, -`contains`, footer, and retained direct child units. A child records whether it -occurred in its parent's specification or `contains` region, so a module-level -interface is not confused with a contained procedure. Children are retained -only when later parser work needs them for model construction or validation. -That includes module-like unit children, interface procedure declarations, -procedure-local interfaces used to type callback dummy arguments, and local -declarative units whose syntax still needs validation. Internal procedures -below a procedure's `contains` statement are structurally scanned but are not -retained as wrapper targets. Execution regions remain opaque. - -While matching one unit's terminator, the scanner keeps a stack of `_OpenUnit` -records. Each record names one unit that has opened but not yet closed and -stores its structural region. The top record is the innermost unit; popping it -after its terminator exposes the containing module, interface, or procedure. -This stack is structural parser state only: it contains no `_ParserScope` and -does not own declarations or parser models. - -End-name validation is strict for structural units whose names define exported -scope boundaries, such as modules, submodules, programs, interfaces, and -derived types. Procedure end-name mismatches are still tolerated while slicing -third-party sources because some accepted fixture code contains copy/paste -procedure end labels; the procedure is closed by unit kind so parsing can -continue, and duplicate procedure names are validated at the sibling scope. - -The only separate specification-line visitors are grammar-specific: -module-like units share `_parse_module_like_spec_line`, procedures use -`_parse_procedure_spec_line` for `implicit`, `external`, `import`, and -local `parameter` handling, and derived types use -`_parse_type_spec_line` for `sequence`, `private`, and type-bound -declaration rules. All three still call the same declaration parser/pusher for -actual declarations. - -Most parser organization changes are structural, but behavior, model-schema, -coverage, or fixture changes should be reflected in this reference. - -Parameter constants expose both `value` and serialized `symbolic_value` when -available. `value` is reserved for a literal/evaluated result after -compile-time folding. If an initializer cannot be evaluated safely, such as -`selected_real_kind(...)`, `value` is `None` and `symbolic_value` preserves the -original initializer for validation, debugging, downstream diagnostics, and -JSON consumers. - -Source-level compile-time resolution consumes those parsed parameter models; -it does not rescan stored source text for a second parameter representation. -The parser first builds one `_CompileTimeSymbols` table whose module entries -have already resolved transitive aliases. For example, module parameters -`word = 4` and `rk = word * 2` produce `{"word": "4", "rk": "8"}`; -`use kinds, only: wp => rk` then exposes `{"wp": "8"}` to the consuming -scope. File parsing and project/CLI parsing use the same table construction and -the same procedure, module-like-variable, and derived-field consumers. - -This resolution is limited to facts visible from source. Compiler-dependent -expressions such as `selected_real_kind(12)` remain symbolic for the later -probe/semantic stages. Imported module expressions in procedure argument -shapes also remain symbolic at file and project boundaries so policy completion -can retain their native spelling and role dependencies. - -Procedure-local parameters may be folded into argument shapes during procedure -finalization. Module-level and `use`-associated parameters used in procedure -argument shapes are kept symbolic in the signature (`x(n)` remains `["n"]`) -and are treated as valid scope references for policy completion. Module/program -variable shapes and parameter values can be resolved through the compile-time -resolver when enough information is available. - -## Reimplementation Guide For Another Parser - -Use the Fortran parser as the reference for any source language with nested -program units, scoped declarations, and a later semantic handoff. The details -are Fortran-specific, but the parser architecture is reusable. - -Recommended frontend responsibilities: - -- Keep one typed model layer for parse-only facts. -- Keep one parser orchestration class with thin public wrappers. -- Slice source into grammar units before parsing declarations. -- Pass scope explicitly into shared helpers rather than using global mutable - parser state for symbol resolution. -- Parse only wrapper-relevant specification facts; skip executable bodies once - they are outside the parser contract. -- Preserve source locations and original line numbers through preprocessing and - recursive slicing. -- Emit parser diagnostics for malformed source, but leave wrappability policy - to semantic policy completion. - -The Fortran data flow is: - -```text -source path or source text - -> compiler/native include preprocessing - -> FortranParser.parse_file(...) - -> classified source units with original line numbers - -> scoped specification parsing - -> FortranFile parser facts - -> directory source discovery when requested - -> each FortranFile parsed exactly once - -> dependency ordering of the existing FortranFile objects - -> FortranProject cross-file resolution and indexes - -> semantics.fortran2ir conversion - -> policy completion, `.pyi`, and the implemented Fortran wrapper stages -``` - -The recursive parsing pattern is: - -1. Construct each unit with its header, grammar regions, and retained direct - children already classified. -2. Parse declarations only from the stored specification part. -3. Recurse only into retained direct children that later parser work needs. -4. Keep procedure execution regions opaque and omit inaccessible internal - procedures from the retained child tree. -5. Validate sibling names and scope-local duplicate declarations. -6. Finalize procedure arguments/results after local declarations and - parameters are known. -7. Resolve source-visible cross-file or imported compile-time aliases through - one project symbol table, while leaving compiler-dependent facts for - semantic conversion and target probing. - -When adding another parser, keep these test layers separate: - -- parser unit tests for grammar slicing and declarations; -- parser fixture tests for stable JSON/model output; -- parser error fixture tests for fatal diagnostic contracts; -- project tests for dependency ordering and cross-file resolution; -- CLI tests for frontend selection, stage dispatch, output files, and debug - behavior; -- semantic conversion tests for parser-to-IR mapping; -- `.pyi` tests for generated and edited interface round trips. - -Executable references: - -- Fortran parser walkthrough: `tests/fortran/source_parsing/parsing/test_developer_tutorial.py` -- Procedure/type parsing: `tests/fortran/source_parsing/parsing/` -- Scope and project behavior: `tests/fortran/modules/parsing/test_scope_handling.py` and - `tests/fortran/modules/parsing/test_project_scope_models.py` -- Fortran fixture workflow: `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` -- Shared CLI behavior: `tests/fortran/command_line_interface/pipeline/` -- Fortran semantic handoff: `tests/fortran/semantic_ir/semantics/` - -## 3) Terminal usage and expected outputs - -### 3.1 Basic CLI invocation - -```bash -python -m prik parse path/to/file.f90 -``` - -Recognizable Fortran files can omit `--language`. Directories require explicit -frontend selection: - -```bash -python -m prik parse path/to/fortran_src --language fortran -``` - -Fortran directories are recursively scanned for `.f`, `.for`, `.ftn`, `.f90`, -`.f95`, `.f03`, `.f08`. - -The Fortran frontend rejects unsupported non-Fortran syntax before -wrapper-focused parsing when it appears outside executable procedure/program -bodies, which are intentionally not represented in the extracted interface. - -The human-readable parse tree keeps scope variables compact by default as -`vars=N`. Add `--show-vars` to print the variables, or `--print-limit N` to -print only the first `N` items in each repeated section. - -### 3.2 Human-readable output example - -Input Fortran (`tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90`): - - -```fortran -module m1 -contains -subroutine add1(n, x) - integer, intent(in) :: n - real(kind=8), intent(inout), dimension(n) :: x -end subroutine add1 -end module m1 -``` - -Command: - - -```bash -python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -Expected output: - - -```text -File: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 - Modules: 1 - - module m1 (vars=0, uses=0) - Procedures: 1 - - subroutine add1(n:integer[0], x:real(8)[1]) -``` - -The same command with `--show-vars` uses the variable-expanded report path. -This fixture currently has no module variables to print, so the output remains -compact: - - -```bash -python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --show-vars -``` - - -```text -File: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 - Modules: 1 - - module m1 (vars=0, uses=0) - Procedures: 1 - - subroutine add1(n:integer[0], x:real(8)[1]) -``` - -For large files: - -```bash -python -m prik parse path/to/file.f90 --show-vars --print-limit 50 -``` - -`--print-limit` applies independently to modules, submodules, programs, block -data units, derived types, fields, procedures, and variables when variables are -shown. Counts such as `Procedures: 80` and `Variables: 657` still show the full -totals even when only the first `N` entries are printed. - -Interpretation: - -- Parsed entities are counted per file. -- Free procedures (outside modules) are shown in top-level `Procedures`. -- Module-contained procedures are nested under each module. -- Empty sections are omitted from the human-readable report. - -More complex example: - -Input Fortran (`mixed_example.f90`): - - - -Command: - -```bash -python -m prik mixed_example.f90 -``` - -```text -File: mixed_example.f90 - Procedures: 1 - - subroutine driver(n:integer[0]) - Modules: 2 - - module math_ops (vars=1, uses=1) - Procedures: 2 - - subroutine saxpy(n:integer[0], a:real[0], x:real[1], y:real[1]) - - function dot(x:real[1], y:real[1]) - - module io_ops (vars=0, uses=0) - Procedures: 1 - - subroutine dump(v:real[1]) -``` - -### 3.3 JSON and semantic output - -Print parser JSON: - -```bash -python -m prik tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --json -``` - -Write parser JSON: - -```bash -python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --json --out report.json -``` - -Expected JSON layout: - -- Top-level object keyed by input path -- Per-file payload with keys: - - `signatures` - - `types` - - `modules` - - `submodules` - - `programs` - - `block_data` - -When `prik parse --json` applies compiler preprocessing, the per-file payload -also contains `preprocessing_recipe`. The CLI applies compiler preprocessing -for file-based parsing; compiler linemarkers remain accepted for provenance. -The recipe records the exact compiler executable or adapter, argv, include -paths, macro flags, standard, extra compiler arguments, working directory, -include graph, source mappings, diagnostics, and optional macro metadata used -to produce the parsed stdout stream. - -Fortran CPP directives are handled by the configured compiler. Native Fortran -`include "file.inc"` statements are then expanded recursively by the -preprocessing layer before the single parser pass. Native INCLUDE is textual -insertion into the current scope; it is not a `use` import from a separately -compiled module. Include lookup is relative to the including file first, then -the configured include directories, duplicate textual inclusion is preserved, -and missing files or cycles produce `INCLUDE_NOT_FOUND` or `INCLUDE_CYCLE` -diagnostics. - -`use` import shape: - - - - - -- A renamed import such as - `use list_input, delete_input => delete_input_list` records both sides: - -```json -"uses": { - "list_input": [ - { - "source": "delete_input_list", - "target": "delete_input" - } - ] -} -``` - - - -### 3.4 Semantic and wrapper-plan output - -Parser output and semantic IR are separate stages. Run parser inspection with: - -```bash -python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -Build a wrapper with the default wrapper stage. If the completed plan cannot -lower a contract, the build reports the precise plan owner and blocker: - -```bash -python -m prik tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -Parser JSON stays parse-only. - -Semantic IR JSON uses the same output channels, but the per-file payload is the -semantic model projection instead of raw parser output: - -```bash -python -m prik semantics tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -Generated `.pyi` text is printed with: - -```bash -python -m prik generate --pyi tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -### 3.5 Parse-error diagnostics and debug mode - -When parsing fails, the CLI prints a compiler-style diagnostic to `stderr` and -exits with status code `1`. By default this output is intended for end users: it -includes the source location, diagnostic code, message, source line, and caret -context, but it does **not** include a Python traceback. - -Example command: - -```bash -python -m prik tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.f90 -``` - -Example diagnostic shape: - -```text -tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.f90:1:1: error[PARSE_DUPLICATE_ARGUMENT]: Duplicate argument name 'x' in procedure 'dup'. - | -1 | subroutine dup(x, y, x) - | ^ -``` - -ANSI color is enabled by default when available; no color flag is needed for -normal use. To disable color explicitly, pass `--no-color` or set the standard -`NO_COLOR` environment variable: - -```bash -python -m prik bad.f90 --no-color -NO_COLOR=1 python -m prik bad.f90 -``` - -For parser development, use `--debug` to re-raise -`FortranParseError` and let Python print the full traceback showing where the -error was raised internally: - -```bash -python -m prik bad.f90 --debug -``` - -The same developer mode can be enabled with the environment variable -`FORTRAN_PARSER_DEBUG=1`: - -```bash -FORTRAN_PARSER_DEBUG=1 python -m prik bad.f90 -``` - -In debug mode, the traceback's final exception message also includes a -`note: parser raised at ...` line with the internal parser file, line, and -function that created the diagnostic. - -## 4) Python usage and expected outputs - -### 4.1 Parse a project directory - -```python -from prik import parse_fortran_project -from pathlib import Path - -project = parse_fortran_project(Path("src")) -print(len(project.files)) -print(len(project.modules)) -``` - -Expected behavior: - -- Recursively discovers supported Fortran source paths. -- Parses each discovered file exactly once into a `FortranFile`. -- Orders those existing file models from dependency providers to consumers. -- Resolves cross-file kinds/imports and returns an indexed `FortranProject`. - -The directory control flow is deliberately explicit: - -```text -parse_project - -> _discover_project_paths - -> _parse_project_files - -> _order_project_files - -> _assemble_project -``` - -In-memory `{filename: source}` input uses `_parse_named_project_sources` -instead of filesystem discovery. Explicit file lists use -`_parse_project_files` and preserve caller order. - -### 4.2 Parse single file and convert it to semantic IR - -```python -from pathlib import Path -from prik import parse_fortran_file -from semantics.fortran2ir import fortran_file_to_semantic_modules - -p = Path("tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90") -code = p.read_text() - -parsed = parse_fortran_file(code, filename=str(p)) -modules = fortran_file_to_semantic_modules(parsed, standalone_module_name=p.stem) -print("procedures", len(parsed.procedures)) -print("semantic modules", len(modules)) -``` - -Expected behavior: - -- `parsed` is a `FortranFile` aggregate model with parsed units and symbols. -- `modules` is the semantic IR projection used by `.pyi` printing and wrapper - planning. - -### 4.3 Structured argument specifications - -Compatibility fields such as `FortranArgument.shape`, `lbound`, `ubound`, and -`kind` remain serialized as strings/lists. For callers that need typed access, -argument and variable models also expose structured helpers: - -- `structured_shape` returns a `FortranShape` containing parsed dimensions. -- Slice-like dimensions such as `1:n:2` are represented as `FortranSlice`. -- Whole-expression function calls such as `lbound(x, 1)` are represented as - `FortranFunctionCall`. -- `kind_expression` and `value_expression` parse `kind` and `value` strings - using the same lightweight expression model. - -Example: - -```python -arg.shape -# ["lbound(src, 2):ubound(src, 2)"] - -dim = arg.structured_shape.dimensions[0] -dim.lower.name -# "lbound" -dim.upper.name -# "ubound" -``` - -### 4.4 Declaration-expression ownership - -The declaration parser preserves balanced Fortran 2008/2018 bound text for all -declaration owners: module variables, derived-type fields, dummy arguments, and -procedure results. Nested calls, array constructors, component references, and -colons inside nested syntax do not split an outer dimension or bound. - -Semantic conversion sends every explicit extent through the shared -`prik.utilities.declaration_expressions` layer. That layer retains the native -spelling in `source_shape` and produces the language-neutral public spelling -used by `.pyi`, including -`size(a)` to `a.size`, `size(a, dim)` to `a.shape[dim - 1]`, and `rank(a)` to -`a.ndim`. Post-IR policy then resolves public scalar and array-property -references to wrapper roles. Binding and bridge generators only render the -completed expression for their target language; they do not infer declaration -semantics. - -`lbound(a, dim)` uses the lower bound declared for that dummy axis rather than -Python's index origin. `ubound(a, dim)` combines that bound with the runtime -extent, and the shared expression layer reduces the common -`ubound-lbound+1` form to `a.shape[dim - 1]`. Direct inquiries preserve the -standard zero-extent results: lower bound one and upper bound zero. - -Parsing and preservation are intentionally broader than wrapper execution. -Valid specification expressions whose value exists only in private native -state remain available as source metadata but produce an explicit policy -blocker when no boundary role can supply them. Calls to user specification -functions also remain in the language-neutral expression. Semantic conversion -resolves each call to a local module procedure, through the declaration owner's -`USE` mappings, or to a concrete procedure interface in the same declaration -scope. It records the visible spelling, original native name, native placement, -and resolved declaration. - -A wildcard import is resolved only when file/project parsing has indexed the -named procedure in exactly one imported module; conversion does not guess from -an unavailable module export list. The `.pyi` loader reconstructs the same -identity from module functions, imports, and `@prototype` declarations. A -prototype is one signature model: annotation use makes it a callback signature, -while call use names a standalone procedure entity. Post-IR policy validates -purity, scalar-integer result, argument association, and accessibility, then -selects either a module `use` or a standalone procedure declaration backed by -the generated abstract interface. A pure prototype cannot also be a Python -callback because its generated adapter calls the Python runtime; that mixed use -is blocked before planning. The binding and bridge consume only that -completed action. Fortran 2023 vector bounds and `RANK` clauses are outside the -parser's advertised Fortran 2008/2018 language modes. - -## 5) Running tests - -Run all tests: - -```bash -PYTHONPATH=. pytest -q -``` - -Run parser-focused tests: - -```bash -python -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 --language fortran --json -PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/ -PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py -PYTHONPATH=. pytest -q tests/fortran/command_line_interface/pipeline/ -``` - -Focused test files by implementation area: - -- Parser walkthrough and expected developer flow: - `tests/fortran/source_parsing/parsing/test_developer_tutorial.py` -- Procedure headers, declarations, derived types, interfaces, and type-bound - procedures: - `tests/fortran/source_parsing/parsing/` -- Function header edge cases: - `tests/fortran/functions/parsing/test_function_headers.py` -- Scope handling and project namespace behavior: - `tests/fortran/modules/parsing/test_scope_handling.py` and - `tests/fortran/modules/parsing/test_project_scope_models.py` -- Preprocessing, native includes, and execution-boundary skipping: - `tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py` -- Parser diagnostics and fatal error contracts: - `tests/fortran/source_parsing/parsing/test_error_handling.py` -- Regression contracts: - `tests/fortran/source_parsing/parsing/` -- Public entrypoints: - `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` -- Parser fixture goldens: - `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` -- Parser error fixture goldens: - `tests/fortran/source_parsing/parsing/test_error_fixture_suite.py` -- Parser JSON shape: - `tests/fortran/source_parsing/parsing/test_json_sanity.py` -- Cached Fortran compiler/type and intrinsic-storage probing: - `tests/fortran/data_types/probes/test_fortran_type_probes.py` -- Shared CLI behavior: - `tests/fortran/command_line_interface/pipeline/` - -When adding or changing a Fortran parser feature, add a focused parser test -near the implementation concern first, then update fixture goldens only when -the serialized parser contract intentionally changes. - -Update golden JSON fixtures: - -```bash -python tests/fortran/source_parsing/parsing/generate_parser_goldens.py -``` - -Update selected fixture(s): - -```bash -python tests/fortran/source_parsing/parsing/generate_parser_goldens.py tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 -``` - -In-test auto-update mode: - -```bash -FORTRAN_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py --confcutdir=tests/ -``` - -Semantic and `.pyi` fixtures have separate generators: - -```bash -python tests/fortran/semantic_ir/semantics/generate_semantic_fixtures.py -WRAPPER_UPDATE_PYI_FIXTURES=1 python3 -m pytest -q tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py -``` - -## 6) Error handling - -All parse failures raise `FortranParseError`, a subclass of `ValueError`. The -exception keeps structured metadata for consumers: - -- `filename` — source path supplied to the parser, if any -- `line_number` — 1-based source line where the error was detected, if known -- `source_line` — original source text for context, if known -- `base_message` — stable error text without location/source context -- `code` — stable, explicit diagnostic category identifier; manually - constructed fallback errors use `PARSE_ERROR`, while grammar rejection uses - `PARSE_INVALID_SYNTAX` - -Diagnostic codes are for programmatic matching in tests, tools, and -documentation. The category name states the failure class directly. The shared -registry is [`diagnostic-codes.md`](../../user/reference/diagnostic-codes.md). - -`str(error)` and `error.format_diagnostic(color=False)` render a -compiler-style diagnostic: - -```text -::1: error[]: - | - | - | ^ -``` - -If no filename is available, the location is rendered as ``. If a line -number or source line is unavailable, that part of the diagnostic is omitted or -shown with `?` as appropriate. Use `error.base_message` when tests or API -consumers need only the message text. - -`format_diagnostic(color=True)` adds ANSI styling. The CLI requests colored -diagnostics by default when available; pass `--no-color` or set `NO_COLOR=1` to -disable ANSI output. On Windows, ANSI console compatibility is enabled through -`colorama` when it is installed. - -For parser development, `format_diagnostic(debug=True)` appends a note with the -internal parser file, line, and function that raised the error. The CLI exposes -this through `--debug` or `FORTRAN_PARSER_DEBUG=1`; normal CLI parse errors intentionally hide Python -tracebacks. - -The sections below list each error category, the triggering condition, and the -exact `base_message` format (with `<...>` placeholders for runtime values). - -### 6.1 Unknown or unsupported type declaration - -Triggered when a declaration line cannot be matched to any known intrinsic type, -`type(...)`, or `character` variant. - -**In a procedure:** - -``` -Unknown or unsupported datatype declaration for procedure '': -``` - -Example Fortran that triggers this: - -```fortran -subroutine bad(x) - weirdtype :: x -end subroutine bad -``` - -Example error: - -``` -bad.f90:2:1: error[PARSE_UNSUPPORTED_DECLARATION]: Unknown or unsupported datatype declaration for procedure 'bad': weirdtype :: x - | -2 | weirdtype :: x - | ^ -``` - -**In a derived type:** - -``` -Unknown or unsupported datatype declaration in type '': -``` - -**In a module:** - -``` -Unknown or unsupported datatype declaration in module '': -``` - -### 6.2 Duplicate declaration - -Triggered when the same symbol is declared more than once in the same scope. - -**In a procedure (arguments and local declarations):** - -``` -Duplicate declaration of symbol '' in procedure ''. -``` - -Example: - -```fortran -subroutine dup(x) - real :: x - integer :: x -end subroutine dup -``` - -Example error: - -``` -dup.f90:3:1: error[PARSE_DUPLICATE_DECLARATION]: Duplicate declaration of symbol 'x' in procedure 'dup'. - | -3 | integer :: x - | ^ -``` - -**PARAMETER constants:** - -``` -Duplicate PARAMETER declaration of symbol '' in procedure ''. -``` - -**In a derived type:** - -``` -Duplicate field '' in derived type ''. -``` - -**In a module:** - -``` -Duplicate variable '' in module ''. -``` - -### 6.3 Duplicate procedure name - -Triggered when the same procedure name appears more than once within the same -module or global scope. -Internal procedures inside separate host `contains` blocks are scoped to their -host and do **not** conflict with each other. - -**Global scope:** - -``` -Duplicate procedure name '' in global scope. -``` - -**Module scope:** - -``` -Duplicate procedure name '' in module ''. -``` - -Example: - -```fortran -subroutine work(n) - integer, intent(in) :: n -end subroutine work - -subroutine work(n) - integer, intent(in) :: n -end subroutine work -``` - -Example error: - -``` -dup.f90:5:1: error[PARSE_DUPLICATE_PROCEDURE]: Duplicate procedure name 'work' in global scope. - | -5 | subroutine work(n) - | ^ -``` - -### 6.4 Duplicate argument name - -Triggered when a procedure's argument list contains the same name more than once. - -``` -Duplicate argument name '' in procedure ''. -``` - -Example: - -```fortran -subroutine dup(x, y, x) - integer, intent(in) :: x - real, intent(in) :: y -end subroutine dup -``` - -Example error: - -``` -dup_arg.f90:1:1: error[PARSE_DUPLICATE_ARGUMENT]: Duplicate argument name 'x' in procedure 'dup'. - | -1 | subroutine dup(x, y, x) - | ^ -``` - -### 6.5 Star-kind declarations - -Legacy `type*N` declarations, such as `real*8`, are accepted in both fixed-form -and modern-extension files. Numeric star declarations preserve their fixed -total storage width for semantic conversion. This matters most for complex -types: `complex*8` is an 8-byte `Complex64`, while modern `complex(kind=8)` is -a compiler kind and is 16 bytes on the documented `gfortran` target. -`DOUBLE PRECISION` and `DOUBLE COMPLEX` retain a compiler-dependent double-kind -expression and use the cached Fortran type probe. For `CHARACTER*N` and -`CHARACTER*(*)`, the star value is a length, not a kind or element storage -width. - -```fortran -subroutine accepted(x) - real*8 :: x -end subroutine accepted -``` - -See the [generated modern and legacy datatype mapping](../../user/reference/semantic-ir.md#generated-linux-x86_64-mapping-example) -for the exact GitHub Actions target results. - -### 6.6 Source-form metadata - -The parser records source-form metadata from the filename and lexer, but does -not reject a construct solely because a `.f77` suffix was used. Grammar-region -validation still applies after preprocessing. - -### 6.7 Implicit none — undeclared argument or result - -Triggered when `implicit none` is active and an argument (or function result) -has no matching type declaration. - -**Argument:** - -``` -Argument '' in procedure '' has no type declaration (implicit none is active). -``` - -**Function result:** - -``` -Function result '' in procedure '' has no type declaration (implicit none is active). -``` - -Example: - -```fortran -subroutine foo(x, y) - implicit none - integer, intent(in) :: x -end subroutine foo -``` - -Example error: - -``` -implicit_none.f90:1:1: error[PARSE_IMPLICIT_NONE_UNDECLARED_SYMBOL]: Argument 'y' in procedure 'foo' has no type declaration (implicit none is active). - | -1 | subroutine foo(x, y) - | ^ -``` - -### 6.8 Unknown datatype for function result - -Triggered when a function result has no resolvable type after parsing (and -`implicit none` prevents implicit typing). - -``` -Unknown datatype for function result '' in procedure ''. -``` - -Example: - -```fortran -function f(x) result(res) - implicit none - real :: x -end function f -``` - -Example error: - -``` -bad.f90:1:1: error[PARSE_UNKNOWN_FUNCTION_RESULT_TYPE]: Unknown datatype for function result 'res' in procedure 'f'. - | -1 | function f(x) result(res) - | ^ -``` - -### 6.9 Unknown datatype for a module variable - -Triggered by `_validate_module_variables` when a parsed module variable still -has `base_type == "unknown"` after declaration parsing. - -``` -Unknown type for variable '' in module ''. -``` - -### 6.10 Unknown datatype for a derived type field - -Triggered by `_validate_derived_type_fields` when a field still has -`base_type == "unknown"`. - -``` -Unknown type for field '' in derived type ''. -``` - -### 6.11 PARAMETER symbol without type in `implicit none` scope - -Triggered when a legacy `PARAMETER (...)` statement names a symbol that has not -been typed and `implicit none` is in effect. - -``` -Unknown datatype for PARAMETER symbol '' in procedure ''. -``` - -Example: - -```fortran - subroutine cst(a) - implicit none - real a - parameter ( zero = 0.0e+0 ) - end -``` - -Example error: - -``` -legacy.f:4:1: error[PARSE_UNKNOWN_PARAMETER_TYPE]: Unknown datatype for PARAMETER symbol 'zero' in procedure 'cst'. - | -4 | parameter ( zero = 0.0e+0 ) - | ^ -``` - -### 6.12 Function result variable shadows an argument - -Triggered when a `result(name)` clause reuses an argument name (and the two -names are different from each other — the special case `result(f)` on a -function named `f` is allowed). - -``` -Function result variable '' in function '' shadows an argument name. -``` - -Example: - -```fortran -function f(res) result(res) - integer, intent(in) :: res -end function f -``` - -Example error: - -``` -shadow.f90:1:1: error[PARSE_RESULT_SHADOWS_ARGUMENT]: Function result variable 'res' in function 'f' shadows an argument name. - | -1 | function f(res) result(res) - | ^ -``` - -### 6.13 Failed to resolve declared argument - -An internal safety check: if a symbol was explicitly declared but its type -could not be applied (a parser regression guard), the following error is raised. - -``` -Failed to resolve declared argument '' in procedure ''. -``` - -## 7) Scope note - -This parser is intentionally wrapper-focused and not a complete Fortran front -end. Unsupported syntax should be surfaced through parser diagnostics or later -semantic policy inputs for incremental parser extension. - - -### External callback dummy declarations - -The parser accepts legacy callback-style declarations inside procedure scopes, including: - -- `external :: cb` (treated as a procedure-typed dummy) -- `real, external :: f` / `integer, external :: g` (typed external function dummies) - -Under `implicit none`, these declarations count as valid argument declarations, so callback arguments are not reported as missing datatype declarations. - -## 8) File, project, and semantic entrypoints - -Use the stable top-level API: - -- `parse_fortran_file(source_or_path, filename=None, encoding="utf-8") -> FortranFile` -- `parse_fortran_project(files, encoding="utf-8") -> FortranProject` - -Lower-level unit parsers are internal `FortranParser` methods. - -Semantic conversion lives in `prik/semantics/fortran2ir.py`. It accepts parsed `FortranFile` -(or selected `FortranModule`) structures and converts metadata into semantic IR -consumed by the `.pyi` printer and current Fortran wrapper/runtime stages. -Compiler-backed shared-CLI semantic stages resolve compiler-dependent kind -expressions, measure numeric and logical intrinsic storage with `storage_size`, -attach those facts to semantic types, and reuse memory and persistent caches. -The shared CLI applies project symbol completion even when the input contains -only one source file. That completion follows explicit renamed `use` -associations through project modules and propagates parent/ancestor -host-associated symbols into submodules before compiler-backed stages run. -This includes both a direct intrinsic rename such as `wp => real64` in a -single-file module and a re-exported chain such as `dp => rk => real64`; the -standalone compiler probe therefore receives the intrinsic expression -(`real64`) rather than a project-local alias that is out of scope in the -generated probe program. -Character declarations are excluded from storage probing: their semantic type -is `String`, while fixed or deferred element length is carried separately from -the declaration or runtime descriptor. The generated mapping report describes -the modeled eight-bit character code unit directly and does not manufacture a -compiler probe fact for character rows. For the maintained GitHub Actions -`gfortran` profile, unqualified `integer`, `real`, and `complex` map to `Int32`, -`Float32`, and `Complex64`; target-changing flags can change those mappings. -Source-driven wrapper builds add the normalized native Fortran compiler flags -to the internal probe configuration, so semantic type facts and native -implementation compilation use the same default-kind profile. The -[generated target datatype mapping](../../user/reference/semantic-ir.md#generated-linux-x86_64-mapping-example) -measures and verifies those storage facts. - -The Fortran probe cache key includes the generated expression source, resolved -compiler binary identity, target flags, includes, macros, requested standard, -working directory, target-related environment, and runner. The persistent -location is `$XDG_CACHE_HOME/prik/fortran_type_probe` or -`~/.cache/prik/fortran_type_probe`; `PRIK_CACHE_DIR` changes the internal cache -root. The standalone `prik probe` command additionally exposes `--cache-dir` -and `--refresh` for explicit inspection runs. - -The standalone probe can create a reusable report containing the exact -compile-time and storage expressions needed by a source: - -```bash -python3 -m prik probe --language fortran --compiler gfortran \ - --expr='selected_real_kind(12)' \ - --expr='storage_size(real(0.0,kind=8))' \ - --out build/fortran-types.json -``` - -The report is an inspection and verification output. Semantic conversion and -wrapper builds measure the facts they need internally from their selected -compiler; the report is not a second semantic-stage input path. A missing -required expression is reported explicitly instead of falling back to an -unrelated target mapping. - -The semantic converter also supports compile-time specialization for values the -parser intentionally leaves symbolic. Use -`collect_semantic_compile_time_requirements(parsed)` to list missing parameter -or kind values, then pass a dictionary such as -`{"selected_real_kind(12)": 8}` to -`fortran_module_to_semantic_module(..., compile_time_values=...)` or -`fortran_file_to_semantic_modules(..., compile_time_values=...)`. Existing -semantic IR can be copied and specialized with -`resolve_semantic_compile_time_values(module, {"n": 64})`. +- Change source form, comments, continuations, or logical locations in + `fortran/utils.py` or `fortran/lexer.py`. +- Change parser facts in `fortran/models.py`; change grammar and source-unit + construction in `fortran/parser.py`. +- Change parser report layout in `fortran/cli.py`. +- Change only raw `.pyi` AST parsing in `pyi/parser.py`; put meaning in + `semantics/pyi2ir.py`. +- If a change needs target kind values, use preprocessing probes; if it needs + ownership, projection, or support, use policy after semantic conversion. + +## Invariants And Common Mistakes + +- Preserve original source locations through lexical and structural parsing. +- Keep parser models passive and source-faithful; do not attach completed + policy to them. +- `parse_fortran_project()` only receives explicit project files; it does not + invent recursive source discovery. +- A construct that parses successfully is not automatically wrapper support. +- The `.pyi` parser returns Python AST. Contract interpretation starts only in + `semantics/pyi2ir.py`. diff --git a/docs/developer/packages/pipeline.md b/docs/developer/packages/pipeline.md index 07613e22f..5603b92ea 100644 --- a/docs/developer/packages/pipeline.md +++ b/docs/developer/packages/pipeline.md @@ -21,13 +21,14 @@ policy, backend lowering, printer formatting, or compiler command mechanics. ```text prik/pipeline/ +├── __init__.py ├── pyi.py ├── type_mapping_report.py ├── wrapper.py └── build.py ``` -## Internal Workflow +## What This Stage Receives And Produces ```text semantic modules or source-build request @@ -41,14 +42,15 @@ semantic modules or source-build request -> WrapperBuildResult ``` -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `pyi.py` | `pyi_*_to_semantic_module()` workflows, `emit_module_stubs()` | Loads text/files/path sets, caches conversion per operation, reconciles external types, and emits stub packages. | -| `type_mapping_report.py` | report builders | Connects target probes, semantic conversion, and codegen dtype projection into an auditable report. | -| `wrapper.py` | `GeneratedSource`, `GeneratedWrapper`, `WrapperGenerator` | Validates/freezes a plan, invokes docstring and backend generation, prints sources, assigns names, and returns one in-memory wrapper artifact. | -| `build.py` | `NativeCompilationUnit`, `NativePrebuiltArtifact`, `NativeLinkItem`, `NativeBuildPlan`, `WrapperBuildResult` | Owns public source/`.pyi` build APIs, file output, native input plans, dependency-ready compilation, linking, manifests, and extension import. | +| [`prik/pipeline/__init__.py`](../../../prik/pipeline/__init__.py) | Package boundary for high-level workflows. | Establishing a deliberate pipeline-level import API. | +| [`prik/pipeline/pyi.py`](../../../prik/pipeline/pyi.py) | `pyi_*_to_semantic_module()` workflows and `emit_module_stubs()` load text, files, and path sets; cache one operation; reconcile external types; and emit stub packages. | `.pyi` batch loading, external-type reconciliation, per-operation cache behavior, or stub-package output changes. | +| [`prik/pipeline/type_mapping_report.py`](../../../prik/pipeline/type_mapping_report.py) | Report builders connect target probes, semantic conversion, and backend dtype projection into an auditable table. | Cross-stage datatype-report content or evidence changes. | +| [`prik/pipeline/wrapper.py`](../../../prik/pipeline/wrapper.py) | `GeneratedSource`, `GeneratedWrapper`, and `WrapperGenerator` validate/freeze a plan, invoke docstring and backend generation, print sources, name artifacts, and return one in-memory wrapper. | Plan-to-rendered-wrapper orchestration changes. | +| [`prik/pipeline/build.py`](../../../prik/pipeline/build.py) | `NativeCompilationUnit`, `NativePrebuiltArtifact`, `NativeLinkItem`, `NativeBuildPlan`, and `WrapperBuildResult` own public build APIs, output, manifests, dependency-ready compilation, linking, and extension import. | Artifact layout, native input plans, build scheduling, manifests, linking, or imported-result behavior changes. | ## Execution Examples @@ -104,14 +106,14 @@ The final example requires configured C and Fortran compilers. It follows the entire public source-build path, imports the resulting extension, and calls its generated Python API. -## Tests +## Tests And What They Prove -- [Pipeline infrastructure](../../../tests/fortran/infrastructure/pipeline/) -- [Semantic `.pyi` pipeline](../../../tests/fortran/semantic_pyi_format/pipeline/) -- [Build pipeline](../../../tests/fortran/building_shared_library/pipeline/) -- [Compilation integration](../../../tests/fortran/building_shared_library/compiling/) -- [End-to-end builds](../../../tests/fortran/building_shared_library/end_to_end/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Pipeline infrastructure](../../../tests/fortran/infrastructure/pipeline/) covers wrapper assembly and cross-stage records. +- [Semantic `.pyi` pipeline](../../../tests/fortran/semantic_pyi_format/pipeline/) covers contract loading, reconciliation, and stub emission. +- [Build pipeline](../../../tests/fortran/building_shared_library/pipeline/) covers files, manifests, and build-plan handoffs. +- [Compilation integration](../../../tests/fortran/building_shared_library/compiling/) covers native command integration. +- [End-to-end builds](../../../tests/fortran/building_shared_library/end_to_end/) covers produced extension behavior. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the four demonstrations above. ## Change Routes diff --git a/docs/developer/packages/planning.md b/docs/developer/packages/planning.md index 43811a76d..66488f312 100644 --- a/docs/developer/packages/planning.md +++ b/docs/developer/packages/planning.md @@ -21,11 +21,12 @@ may not reinterpret source declarations, choose policy, or render text. ```text prik/planning/ +├── __init__.py ├── models.py └── planner.py ``` -## Internal Workflow +## What This Stage Receives And Produces ```text policy-completed SemanticModule @@ -35,12 +36,13 @@ policy-completed SemanticModule -> backend node generation ``` -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `models.py` | `ModulePlan`, function, argument, result, slot, lifecycle, class, overload, binding, and bridge plan records | Defines the typed editable plan tree. | -| `planner.py` | `WrapperPlanner`, `_ClassPolicyCatalog` | Validates completed policy, creates indexes and symbols, and projects the plan deterministically. | +| [`prik/planning/__init__.py`](../../../prik/planning/__init__.py) | Re-exports `WrapperPlanner` and the supported plan records. | A supported planning type or import path changes. | +| [`prik/planning/models.py`](../../../prik/planning/models.py) | `ModulePlan` and typed function, argument, result, slot, lifecycle, class, overload, binding, and bridge records form the editable plan tree. | Lowering needs a new *already completed* fact represented explicitly. | +| [`prik/planning/planner.py`](../../../prik/planning/planner.py) | `WrapperPlanner` validates policy, indexes declarations, allocates names, and projects deterministic binding and bridge views; `_ClassPolicyCatalog` is a validated lookup. | A completed policy fact is projected or ordered incorrectly. | The private class-policy catalogue is a validated lookup, not another semantic authority. The planner does not generate docstrings or source. @@ -99,12 +101,12 @@ The model example demonstrates representation. The planner example follows the real sequence—semantic IR, policy completion, then planning—and shows the stable role connecting binding conversion to the native call slot. -## Tests +## Tests And What They Prove -- [Plan model tests](../../../tests/fortran/infrastructure/codegen/test_plan.py) -- [Planner tests](../../../tests/fortran/infrastructure/codegen/test_planner.py) -- [Feature-local codegen stages](../../../tests/fortran/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Plan model tests](../../../tests/fortran/infrastructure/codegen/test_plan.py) protect plan-record shape and freeze behavior. +- [Planner tests](../../../tests/fortran/infrastructure/codegen/test_planner.py) protect validation, projection, symbols, and order. +- [Feature-local codegen stages](../../../tests/fortran/) protect plan use for each supported feature. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the model and planner outputs above. ## Change Routes diff --git a/docs/developer/packages/policy.md b/docs/developer/packages/policy.md index 5571f14d9..376f069e7 100644 --- a/docs/developer/packages/policy.md +++ b/docs/developer/packages/policy.md @@ -11,16 +11,22 @@ publication: draft ## Purpose And Boundaries -`prik/policy/` is the final semantic authority before planning. It resolves -public exports, object kind, owner, transfer, destruction, mutability, -writeback, nullability, storage, projections, lifecycle actions, descriptor -operations, setter behavior, and support blockers. Planning and generation may -dispatch from these immutable decisions but may not replace them. +`prik/policy/` is the final semantic authority before planning. It turns raw +semantic facts and metadata into complete immutable interoperability decisions: +public exports, object kind, owner, transfer, destruction, storage, mutability, +writeback, nullability, projection, lifecycle, descriptor operations, setter +behavior, and support blockers. + +Planning, binding, bridge, and runtime code consume these decisions. They may +validate and dispatch from them, but may not infer an alternative answer from +datatype, source `intent`, dotted-variable shape, `is_alias`, or local memory +checks. ## Local Structure ```text prik/policy/ +├── __init__.py ├── models.py ├── ownership.py ├── exports.py @@ -29,30 +35,78 @@ prik/policy/ └── native_array_handles.py ``` -## Internal Workflow +## What This Stage Receives And Produces ```text -complete SemanticModule + normalized raw metadata - -> export and graph completion - -> ownership and feature-policy construction +SemanticModule + normalized raw metadata + -> export completion and semantic graph completion + -> ownership, callable, class, result, and descriptor-policy construction -> complete_semantic_policies() - -> immutable policies attached to semantic IR + -> immutable completed policy attached to semantic IR -> WrapperPlanner ``` -## Important Files And Essential Objects +Completion is ordered because later decisions depend on earlier facts. A +blocked decision records its owner path and reason; it is never replaced by a +downstream fallback. + +## Directory Tour + +| Module | Main entrypoints and contents | Change it when | +| --- | --- | --- | +| [`prik/policy/__init__.py`](../../../prik/policy/__init__.py) | Re-exports `complete_semantic_policies()` as the normal policy-stage entrypoint. | The supported policy import surface changes. | +| [`prik/policy/models.py`](../../../prik/policy/models.py) | Immutable records and enums for function, argument, result, slot, lifecycle, class, overload, callback, array, descriptor, status, and transformation policy. | A completed decision needs a durable backend-neutral representation. | +| [`prik/policy/ownership.py`](../../../prik/policy/ownership.py) | Ownership vocabulary, `OwnershipContext`, `OwnershipDecision`, `OwnershipPolicyResolver`, and action dispatchers resolve lifetime triples and fail-closed lowering actions. | Object kind, owner, transfer, destruction, storage, barrier, assignment, or setter selection changes. | +| [`prik/policy/exports.py`](../../../prik/policy/exports.py) | `PythonExportPolicy`, `complete_python_export_policy()`, and `completed_python_exports()` create collision-checked Python placement. | Export namespace, visibility, or collision behavior changes. | +| [`prik/policy/construction.py`](../../../prik/policy/construction.py) | Feature constructors build coherent function, result, native-slot, callback, class, overload, and module-variable policies from completed ownership decisions. | A supported feature needs different completed policy composition. | +| [`prik/policy/completion.py`](../../../prik/policy/completion.py) | `complete_semantic_policies()` runs the dependency-ordered completion pass, attaches outcomes, and validates blockers. | Completion order, cross-declaration completion, or the stage boundary changes. | +| [`prik/policy/native_array_handles.py`](../../../prik/policy/native_array_handles.py) | `NativeArrayHandlePolicy`, interop/handle/projection dispatchers, and build-requirement records complete descriptor operations and selected ABI/build requirements. | Descriptor-backed array behavior, ABI selection, allowed operations, or build headers change. | + +Start with `completion.py` to see the order, follow its call into the focused +resolver or constructor, and finish in `models.py` to confirm the durable +output. Do not begin in code generation when the question is semantic. + +## How To Read A Completed Decision -| File | Important objects | Responsibility | +Policy keeps related questions separate. This makes aliases, copies, views, +and cleanup auditable instead of encoding them in one overloaded `owned` +flag. + +| Question | Main vocabulary | Example answer | | --- | --- | --- | -| `models.py` | `FunctionWrapperPolicy`, argument/result/call-slot/lifecycle/class/callback/array records | Defines immutable backend-neutral completed policy. | -| `ownership.py` | `OwnershipDecision` and ownership vocabulary | Resolves object kind, lifetime triple, storage, and strict lowering actions. | -| `exports.py` | `PythonExportPolicy` | Completes collision-checked Python placement. | -| `construction.py` | `_FunctionPolicyContext` and feature constructors | Builds coherent function, result, native-slot, callback, and class policies. | -| `completion.py` | `complete_semantic_policies()` | Runs completion in explicit dependency order and attaches its results. | -| `native_array_handles.py` | `NativeArrayHandlePolicy` and ABI dispatch records | Completes descriptor operations, array ABI, and selected build requirements. | +| What Python-facing family is this? | `ObjectKind` | `SCALAR`, `STRING`, `NUMPY_ARRAY`, `DERIVED_TYPE` | +| Who owns the represented storage? | `OwnershipOwner` | `CALLER`, `NATIVE`, `WRAPPER`, `TEMPORARY` | +| How does value or storage cross the boundary? | `TransferMode` | `BY_VALUE`, `IN_PLACE`, `COPY_RETURN`, `BORROWED_VIEW` | +| Who releases a resource? | `DestructionPolicy` | `CALLER`, `NATIVE_OWNER`, `WRAPPER_DEALLOC`, `CALL_LOCAL` | +| Where is the contract value stored? | `StorageMode` | `STACK`, `HEAP`, `ALIAS` | +| What does each boundary do? | `PythonBarrierAction`, `NativeBarrierAction`, `CodegenAction` | extract storage, pass a descriptor, copy out, construct a wrapper | +| How may native storage be assigned or exposed? | `AssignmentMode`, `SetterAction` | value copy, alias, write-through, omit setter | + +Read the lifetime triple left to right. For example, +`NATIVE + BORROWED_VIEW + NATIVE_OWNER` means Python observes live native +storage but does not own or release it. `PYTHON + COPY_RETURN + +PYTHON_REFCOUNT` means that PRIK creates an independent Python-owned result. +Only supported combinations are lowered; contradictory or unimplemented +combinations become explicit blockers. + +For every lowering-ready value, policy completion must answer all of the +following before `WrapperPlanner.build()`: + +1. Object kind and public projection. +2. Owner, transfer, destruction, and contract storage mode. +3. Python and native barrier actions, including ordered native call slots. +4. Mutability, writeback, nullability, lifecycle, release responsibility, + getter behavior, native setter assignment, and Python setter exposure. +5. Supported mechanism or an explicit blocked diagnostic. + +The binding and bridge may create local temporary variables inside a selected +implementation method, but those are emitted-code details. They are not a +license to choose a new semantic policy. ## Execution Examples +Completed record immutability: + ```bash python3 prik/policy/models.py ``` @@ -63,6 +117,8 @@ Lifecycle policy: copy_out writeback via copy_in_out Completed record mutation rejected: True ``` +Ownership resolution: + ```bash python3 prik/policy/ownership.py ``` @@ -72,6 +128,8 @@ before: math.scale(value): Float64 semantic IR after: scalar/caller/call_local; scalar_value -> pass_value ``` +Public export completion: + ```bash python3 prik/policy/exports.py ``` @@ -82,6 +140,8 @@ Python export: linear_algebra.scale_value Completed policy type: PythonExportPolicy ``` +Feature-policy construction: + ```bash python3 prik/policy/construction.py ``` @@ -91,6 +151,8 @@ before: math.scale(value): Float64 semantic IR after: direct_transfer; result=native_scalar; native=pass_value ``` +Full ordered completion: + ```bash python3 prik/policy/completion.py ``` @@ -100,6 +162,8 @@ before: math.scale(value): Float64 semantic IR after: math.scale(value): scalar_value -> pass_value ``` +Descriptor-backed array completion: + ```bash python3 prik/policy/native_array_handles.py ``` @@ -111,345 +175,41 @@ Array ABI: descriptor Selected build header: ISO_Fortran_binding.h ``` -These exact outputs show completed immutable decisions rather than generated source. -The completion entrypoint is mandatory for normal planning; individual example -builders exist only to expose their focused ownership boundaries. +The outputs move from raw semantic facts to immutable decisions. They do not +generate source; that begins only after planning. -## Tests +## Tests And What They Prove -- [Policy infrastructure](../../../tests/fortran/infrastructure/semantics/) -- [Native handle policy](../../../tests/fortran/infrastructure/semantics/test_native_array_handles.py) -- [Feature-local policy suites](../../../tests/fortran/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Policy infrastructure](../../../tests/fortran/infrastructure/semantics/) covers policy records, completion order, and general semantic-policy rules. +- [Native handle policy](../../../tests/fortran/infrastructure/semantics/test_native_array_handles.py) covers descriptor policy, allowed operations, and ABI requirements. +- [Feature-local policy suites](../../../tests/fortran/) cover ownership and projection decisions for the supported wrapper features. +- [Planner tests](../../../tests/fortran/infrastructure/codegen/test_planner.py) prove that planning rejects incomplete policy instead of filling it in. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the six demonstrations above. ## Change Routes -- Put reusable immutable output vocabulary in `models.py`. -- Start a new semantic decision in completion and its focused resolver or - constructor. -- Extend strict descriptor dispatch/build selection in +- Add reusable immutable output vocabulary in `models.py` only when it is a + semantic decision that more than one lower stage must consume. +- Change one lifetime or barrier decision in `ownership.py`; retain a blocked + result when no safe supported combination exists. +- Change Python placement in `exports.py`. +- Change the coherent composition of a supported function, class, overload, + callback, result, or module-variable policy in `construction.py`. +- Change dependency order and attachment in `completion.py`. +- Change descriptor operations, ABI, or build requirements in `native_array_handles.py`. -- If a generator guesses policy from datatype, intent, aliases, or local - memory checks, remove the guess and complete the decision here. +- Project an already completed fact in planning; lower an already selected + mechanism in codegen. Neither is a replacement policy owner. ## Invariants And Common Mistakes -- Completion order remains visible and explicit; do not replace it with an - opaque pass registry. -- Blocked policies keep their owner path and reason. -- Shared policy models remain independent of construction implementation. - -## Orthogonal Selector Vocabulary - -Completed policy keeps separate questions separate: - -| Selector | Question | -| --- | --- | -| `ObjectKind` | What kind of value follows this route? | -| result source kind | Is a result direct or projected from a hidden output? | -| `PythonBarrierAction` | How does the binding validate or extract the Python object? | -| `NativeBarrierAction` | What transport crosses the native ABI boundary? | -| `CodegenAction` | What ownership or transfer operation occurs? | -| bridge data action | What representation operation occurs in the bridge? | -| writeback phase | When does mutation, copy-out, cleanup, or release happen? | - -Hiddenness is not an ownership action. Ordinary NumPy buffer handoff and -persistent native descriptor handoff are also distinct ABI choices; neither -backend may substitute one for the other. A datatype family may select an -element spelling only after object-kind/action dispatch. It must never be used -to rediscover whether the overall transfer is scalar, string, array, or native -handle. - -Native source `intent` may seed a generated default Python signature during -source conversion, but the editable signature, `Returns[...]` projection, and -ordered native-call mapping become authoritative. An explicit native-call list -is exhaustive for native dummy positions. Transport overrides such as -primitive `Addr(Arg(i))` or derived `Value(Arg(i))` select ABI transport; -`Returns[...]` selects Python projection and writeback expectation, not the -transport itself. - -## Ownership Resolution Reference - -PRIK represents ownership as a completed semantic contract, not as one label -such as "owned" or "borrowed." A value is lowering-ready only after policy -completion answers separate questions about its representation, storage owner, -boundary transfer, release responsibility, storage form, and generated actions. - -This separation is deliberate. A NumPy view may be a Python object while its -buffer remains native-owned; a generated Python object may control a -wrapper-owned native instance; and a caller-owned array may be mutated in -place without transferring ownership. Combining those cases under one boolean -would make cleanup and alias behavior ambiguous. - -The canonical pipeline is: - -```text -semantic type + semantic use context + explicit metadata - -> OwnershipPolicyResolver - -> immutable OwnershipDecision - -> post-IR policy validation - -> wrapper plan - -> binding and bridge lowering -``` - -The binding and bridge generators consume completed actions. They must not -reconstruct ownership from datatype, source `intent`, rank, allocation flags, -or local memory checks. - -## The Three Lifetime Questions - -Read the central lifetime triple from left to right: - -1. `OwnershipOwner` says who owns the represented storage. -2. `TransferMode` says how the value or storage relationship crosses the - Python/native boundary. -3. `DestructionPolicy` says who releases any owned resource. - -For example: - -```text -PYTHON + COPY_RETURN + PYTHON_REFCOUNT -``` - -means that native output is copied into independent Python-owned storage and -that ordinary Python or NumPy lifetime releases that copy. In contrast: - -```text -NATIVE + BORROWED_VIEW + NATIVE_OWNER -``` - -means that Python observes live native storage without owning or releasing it. -The resolver accepts only implemented triples and converts contradictory or -unsupported combinations into an explicit blocked decision. - -## Completed Policy Vocabulary - -### Object kind - -`ObjectKind` selects the Python-facing representation family before lifetime -or lowering actions are chosen. - -| Value | Meaning | -| --- | --- | -| `SCALAR` | An ordinary scalar Python value or scalar storage cell. | -| `STRING` | A Python string value or mutable native character storage. | -| `NUMPY_ARRAY` | NumPy-compatible array storage, including descriptor-backed arrays. | -| `DERIVED_TYPE` | An opaque native object represented through a generated wrapper. | - -### Storage owner - -| Value | Meaning | -| --- | --- | -| `PYTHON` | Python, NumPy, or a Python-owned capsule owns the represented value or buffer. | -| `CALLER` | The caller supplied the object and retains ownership across the call. | -| `NATIVE` | A Fortran module or another native owner keeps the storage alive and releases it. | -| `WRAPPER` | A generated wrapper or handle owns or controls the native resource. | -| `TEMPORARY` | Generated call-local storage exists only for the current invocation. | -| `UNKNOWN` | No safe owner is known; this is used by fail-closed decisions. | - -### Boundary transfer - -| Value | Meaning | -| --- | --- | -| `BY_VALUE` | An independent scalar-like value crosses the boundary. | -| `IN_PLACE` | Native code reads or writes caller-visible storage without replacement. | -| `COPY_RETURN` | Native output is copied or converted into a fresh Python result. | -| `SNAPSHOT_COPY` | Python receives a detached copy of current native state. | -| `BORROWED_VIEW` | Python observes storage owned elsewhere without taking ownership. | -| `CALL_LOCAL` | Storage or an association exists only for one wrapped call. | -| `WRAPPER_INSTANCE` | Python receives an object that owns or controls a native instance. | -| `BLOCKED` | No supported safe transfer exists; generation must stop. | - -### Destruction responsibility - -| Value | Meaning | -| --- | --- | -| `PYTHON_REFCOUNT` | Python, NumPy, or a Python-owned capsule releases the resource. | -| `CALLER` | The caller retains release responsibility; PRIK must not destroy the object. | -| `WRAPPER_DEALLOC` | A generated wrapper or handle deallocator releases the native resource. | -| `NATIVE_OWNER` | The independent native owner releases the storage. | -| `CALL_LOCAL` | Generated cleanup releases a temporary before the wrapper call ends. | -| `NONE` | This boundary value creates no resource that PRIK must release. | -| `BLOCKED` | Release responsibility is unsafe, contradictory, or unimplemented. | - -`NONE` does not mean that the value has no storage. It means that this wrapper -boundary did not create an owned resource requiring a release action. For -example, a wrapper-owned derived input can use `CALL_LOCAL + NONE` because the -existing wrapper remains responsible for its instance. - -### Contract and boundary storage - -`StorageMode` describes where PRIK keeps the contract value and, separately, -its ABI boundary representation. - -| Value | Meaning | -| --- | --- | -| `STACK` | Direct or call-frame storage with no persistent heap allocation. | -| `HEAP` | Storage whose lifetime extends beyond a native stack value. | -| `ALIAS` | A reference to existing storage; no independent value is owned here. | - -Pointers always use alias storage, allocatables use heap storage, and borrowed -array views use alias storage. Those are storage invariants, not backend -guesses. - -### General lowering action - -| Value | Meaning | -| --- | --- | -| `DIRECT_VALUE` | Convert or return a direct independent value. | -| `CALL_LOCAL_INPUT` | Prepare input storage valid only during the call. | -| `IN_PLACE_ARGUMENT` | Pass mutable caller-visible storage through to native code. | -| `IDENTITY_OUTPUT` | Mutate and project the same supplied object rather than replacing it. | -| `COPY_IN_OUT` | Copy immutable Python input into mutable call storage and return its final value. | -| `COPY_OUT` | Materialize native output as a new Python result. | -| `SNAPSHOT_COPY` | Materialize a detached snapshot of persistent native state. | -| `BORROWED_VIEW` | Expose existing owner-controlled storage. | -| `WRAPPER_INSTANCE` | Construct or return a generated native-object wrapper. | -| `BLOCKED` | Reject lowering because the completed policy is unsupported. | - -### Python-to-wrapper barrier - -| Value | Meaning | -| --- | --- | -| `SCALAR_VALUE` | Read an ordinary Python scalar value. | -| `SCALAR_STORAGE` | Read or create addressable scalar storage. | -| `ARRAY_STORAGE` | Validate and use NumPy-compatible array storage. | -| `STRING_VALUE` | Read an immutable Python string value. | -| `STRING_STORAGE` | Use mutable addressable character storage. | -| `RAW_ADDRESS` | Accept an explicit raw-address contract. | -| `WRAPPER_INSTANCE` | Extract an opaque native instance or descriptor from a wrapper. | -| `NONE` | No Python argument crosses this boundary. | -| `BLOCKED` | Reject Python-boundary lowering. | - -### Wrapper-to-native barrier - -| Value | Meaning | -| --- | --- | -| `PASS_VALUE` | Pass the converted value directly. | -| `PASS_CALL_LOCAL_ADDRESS` | Pass the address of wrapper-created call-local storage. | -| `PASS_STORAGE_ADDRESS` | Pass the address of existing mutable storage. | -| `PASS_RAW_ADDRESS` | Forward the explicitly supplied raw address. | -| `PASS_ARRAY_BUFFER` | Pass a validated array data buffer. | -| `PASS_NATIVE_DESCRIPTOR` | Pass a native allocatable or pointer descriptor. | -| `PASS_WRAPPER_ADDRESS` | Pass the opaque address held by a generated object wrapper. | -| `NONE` | No native argument is required for this value. | -| `BLOCKED` | Reject native-boundary lowering. | - -### Assignment and setter actions - -| Axis | Value | Meaning | -| --- | --- | --- | -| `AssignmentMode` | `NONE` | No native assignment is generated. | -| `AssignmentMode` | `VALUE_COPY` | Copy the incoming value into existing native storage. | -| `AssignmentMode` | `ALIAS` | Associate the destination with existing storage. | -| `SetterAction` | `WRITE_THROUGH` | Expose a Python setter that updates native state. | -| `SetterAction` | `REJECT_REPLACEMENT` | Keep the property readable but reject replacing its storage. | -| `SetterAction` | `OMIT` | Do not expose a Python setter. | - -## Supported Triples - -The resolver's validated triples are the authoritative combinations: - -| Owner + transfer + destruction | Typical use | -| --- | --- | -| `PYTHON + BY_VALUE + PYTHON_REFCOUNT` | Scalar result. | -| `PYTHON + COPY_RETURN + PYTHON_REFCOUNT` | Array or string copied into a Python result. | -| `PYTHON + SNAPSHOT_COPY + PYTHON_REFCOUNT` | Detached view of current native state. | -| `CALLER + CALL_LOCAL + NONE` | Read-only caller value used only during the call. | -| `CALLER + CALL_LOCAL + CALL_LOCAL` | Caller-classified value with wrapper-created call storage that needs local cleanup. | -| `CALLER + IN_PLACE + CALLER` | Caller array mutated without ownership transfer. | -| `NATIVE + BORROWED_VIEW + NATIVE_OWNER` | Live module-state view. | -| `WRAPPER + CALL_LOCAL + NONE` | Existing wrapper instance used for one call without creating a resource. | -| `WRAPPER + IN_PLACE + WRAPPER_DEALLOC` | Existing wrapper-controlled storage mutated in place. | -| `WRAPPER + BORROWED_VIEW + WRAPPER_DEALLOC` | Field storage retained by its parent wrapper. | -| `WRAPPER + WRAPPER_INSTANCE + WRAPPER_DEALLOC` | Generated object or owned descriptor handle. | -| `TEMPORARY + CALL_LOCAL + CALL_LOCAL` | Generated bridge temporary. | - -Not every syntactically possible triple is meaningful. Adding a new triple is -a semantic feature: update the resolver validation, completed wrapper policy, -planner validation, backend lowering, documentation, and focused runtime tests -together. - -## Explicit Overrides and Pointer Policy - -`Ownership(...)`, `Transfer(...)`, and `Destruction(...)` override the general -lifetime triple. The resolver normalizes the metadata, applies storage -invariants, and then validates the resulting combination. An override never -bypasses the normal safety gates. - -`PointerPolicy(...)` is a separate descriptor/target contract with ten fields: - -| Field | Question answered | -| --- | --- | -| `nullable` | May the descriptor be unassociated? | -| `transfer` | How is the pointer or target relationship used at the boundary? | -| `target_owner` | Who owns the target allocation? | -| `lifetime` | What proves the target outlives the Python use? | -| `deallocation` | Which target-release operations are permitted? | -| `shape_source` | Where are rank and extents obtained? | -| `contiguity` | What storage-layout guarantee is available? | -| `reassociation` | Which association-changing operations are permitted? | -| `aliasing` | Is the result a live alias, descriptor, or independent copy? | -| `mutability` | May Python or native code modify the target through this path? | - -The strings are retained so contracts can describe project-specific facts; -policy completion still accepts only mechanisms the current wrapper runtime -can implement. Pointer-array module variables, fields, arguments, and results -are descriptor containers. Their container ownership is fixed by their native -location, so `PointerPolicy` governs extraction and descriptor operations -rather than silently replacing that ownership with the general override. - -## Resolution and Validation Order - -`OwnershipPolicyResolver.decide_semantic_type()` performs these stages in -order: - -1. Normalize the semantic type into immutable storage facts. -2. Select a default decision for the object kind and semantic use context. -3. Apply explicit ownership or pointer metadata. -4. Reject unsupported pointer lifetimes and reassociation. -5. Complete immutable-value policy and validate result projection. -6. Validate the owner/transfer/destruction triple. -7. Derive general, Python-barrier, and native-barrier lowering actions. - -This order prevents an explicit annotation from bypassing a later safety -check, and prevents a backend from selecting an easier but semantically -different implementation. - -## Change and Test Routes - -The main source owners are: - -- `prik/policy/ownership.py`: vocabulary, defaults, overrides, validation, - and completed actions; -- `prik/policy/completion.py`: attachment of decisions to semantic - variables, functions, fields, classes, and module state; -- `prik/policy/construction.py`: completed wrapper-policy records and - cross-feature validation; -- `prik/policy/models.py`: immutable feature-specific completed-policy records; -- `prik/planning/planner.py`: projection into the editable wrapper plan; -- `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py`: strict - dispatch from planned actions into emitted mechanisms. - -The enums documented on this page are the shared ownership vocabulary defined -in `ownership.py`. Feature-specific completed policies also define narrower -mechanical enums in `models.py`, such as derived-object owner -retention/release and native-array descriptor ownership/release. Those values -refine an already completed ownership decision for one implementation family; -they do not form another competing ownership system. - -Start focused verification in -`tests/fortran/infrastructure/semantics/test_ownership.py`. Add feature-specific -policy and runtime evidence under the owning `tests/fortran//` -directory whenever a decision gains a new observable mechanism. The user-facing -lifetime and stale-view rules remain in -[Memory Management](../../user/guide/memory-management.md). - -## Safety Boundary - -Completed ownership policy makes release responsibility explicit; it does not -make every live view memory-safe. Native deallocation, allocatable -reallocation, pointer reassociation, or owner destruction can invalidate an -existing NumPy view. The generated runtime cannot revoke every previously -exported view, so users must copy data that needs to outlive such a native -change and request a fresh view afterward. +- Completion order stays explicit; do not replace it with an opaque pass + registry. +- Raw semantic ownership metadata is a request, not an `OwnershipDecision`. +- Hidden output projection is separate from ABI transport. +- Ordinary NumPy buffer handoff and a persistent native descriptor handoff + are distinct ABI choices. +- A valid source declaration or `.pyi` annotation is not proof of safe + wrapper support. +- If a generator guesses a decision, move that decision into policy completion + and add the focused policy test before changing lowering. diff --git a/docs/developer/packages/preprocessing.md b/docs/developer/packages/preprocessing.md index 91009ff03..4eaa6c678 100644 --- a/docs/developer/packages/preprocessing.md +++ b/docs/developer/packages/preprocessing.md @@ -22,16 +22,18 @@ complete wrapper policy. ```text prik/preprocessing/ +├── __init__.py ├── source.py ├── fortran.py └── probes/ + ├── __init__.py └── fortran_types.py ``` The C preprocessing and target-probe modules remain deferred from the published Fortran contributor workflow. -## Internal Workflow +## What This Stage Receives And Produces ```text original Fortran path + PreprocessingConfig @@ -51,13 +53,15 @@ included files, source mappings, and diagnostics so a build can explain or replay its parser input. Probe cache identity includes the compiler and target configuration; measured facts must not cross targets silently. -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `source.py` | `PreprocessingConfig`, `PreprocessingRecipe`, `PreprocessResult`, `SourceMapping`, `IncludedFile` | Runs compiler preprocessing, collects provenance and dependencies, and coordinates native include expansion. | -| `fortran.py` | `expand_native_fortran_includes()` | Recursively expands native `INCLUDE` statements while preserving original locations and diagnostics. | -| `probes/fortran_types.py` | `FortranTypeProbeRecipe`, `FortranTypeProbeReport` | Compiles and runs target programs for kind expressions, storage widths, logical representations, and compile-time values. | +| [`prik/preprocessing/__init__.py`](../../../prik/preprocessing/__init__.py) | Re-exports the supported source-preparation records, adapters, and entrypoints, including `expand_native_fortran_includes()`. | The supported preprocessing import API changes. | +| [`prik/preprocessing/source.py`](../../../prik/preprocessing/source.py) | `PreprocessingConfig`, `PreprocessingPlan`, `PreprocessingRecipe`, `PreprocessResult`, `SourceMapping`, and `IncludedFile`; builds compiler invocations, runs them, recovers mappings, and retains diagnostics/provenance. | Compiler-preprocessor adapters, recipes, line-marker handling, source provenance, or diagnostics change. | +| [`prik/preprocessing/fortran.py`](../../../prik/preprocessing/fortran.py) | `expand_native_fortran_includes()` expands native `INCLUDE` directives recursively while preserving locations and diagnostics. | Native Fortran include discovery or expansion changes. | +| [`prik/preprocessing/probes/__init__.py`](../../../prik/preprocessing/probes/__init__.py) | Namespace marker for compiler-derived target facts. | A probe-level public import surface is deliberately introduced. | +| [`prik/preprocessing/probes/fortran_types.py`](../../../prik/preprocessing/probes/fortran_types.py) | `FortranTypeProbeRecipe` and `FortranTypeProbeReport` compile and run small target programs for kind expressions, storage widths, logical representations, and compile-time values. | Measured fact generation, validation, cache identity, or probe execution changes. | ## Execution Examples @@ -110,12 +114,12 @@ mapping facts. The probe output is a native kind value, not yet a stable semantic scalar or NumPy dtype. The probe example requires `gfortran` or `f95`. -## Tests +## Tests And What They Prove -- [Fortran preprocessing](../../../tests/fortran/source_preprocessing/preprocessing/) -- [Parser boundary tests](../../../tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py) -- [Fortran target probes](../../../tests/fortran/data_types/probes/test_fortran_type_probes.py) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Fortran preprocessing](../../../tests/fortran/source_preprocessing/preprocessing/) covers adapters, recipes, mappings, dependencies, and diagnostics. +- [Parser boundary tests](../../../tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py) prove that prepared source reaches parsing with preserved facts. +- [Fortran target probes](../../../tests/fortran/data_types/probes/test_fortran_type_probes.py) cover measured type facts and cache separation. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the three demonstrations above. ## Change Routes diff --git a/docs/developer/packages/printers.md b/docs/developer/packages/printers.md index b88ec815e..de8ced5a1 100644 --- a/docs/developer/packages/printers.md +++ b/docs/developer/packages/printers.md @@ -21,25 +21,27 @@ complete policy, or compile output. ```text prik/printers/ +├── __init__.py ├── c.py ├── fortran.py └── pyi.py ``` -## Internal Workflow +## What This Stage Receives And Produces ```text formed C or Fortran node tree -> matching source printer -> native text SemanticModule graph -> PyiPrinter -> editable .pyi ``` -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `c.py` | `CSourcePrinter` | Serializes C translation units, headers, declarations, functions, tables, and statements. | -| `fortran.py` | `FortranSourcePrinter` | Serializes bridge modules, interfaces, declarations, procedures, and statements with free-form line wrapping. | -| `pyi.py` | `PyiPrinter`, `emit_module()`, `_PyiEmissionContext` | Serializes semantic modules and scopes imports, aliases, class names, namespaces, and default order for one emission. | +| [`prik/printers/__init__.py`](../../../prik/printers/__init__.py) | Re-exports `CSourcePrinter`, `FortranSourcePrinter`, `PyiPrinter`, and `emit_module()`. | The supported printer import surface changes. | +| [`prik/printers/c.py`](../../../prik/printers/c.py) | `CSourcePrinter` serializes C translation units, headers, declarations, functions, tables, and statements. | C syntax layout, escaping, or formatting changes. | +| [`prik/printers/fortran.py`](../../../prik/printers/fortran.py) | `FortranSourcePrinter` serializes bridge modules, interfaces, declarations, procedures, and free-form wrapped statements. | Fortran source layout or line-wrapping changes. | +| [`prik/printers/pyi.py`](../../../prik/printers/pyi.py) | `PyiPrinter`, `emit_module()`, and `_PyiEmissionContext` serialize semantic modules and scope imports, aliases, namespaces, and defaults for one emission. | Editable contract spelling or emission-context behavior changes. | The fact that code generation calls a printer at the end of wrapper rendering does not make printing part of codegen ownership. `pipeline/wrapper.py` @@ -98,11 +100,11 @@ The native examples prove that punctuation and layout are added to already formed nodes. The `.pyi` example proves that required contract imports and native identity are derived without attaching wrapper policy. -## Tests +## Tests And What They Prove -- [Printer infrastructure](../../../tests/fortran/infrastructure/printers/) -- [Semantic `.pyi` round trips](../../../tests/fortran/semantic_pyi_format/) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Printer infrastructure](../../../tests/fortran/infrastructure/printers/) covers native syntax serialization and formatting. +- [Semantic `.pyi` round trips](../../../tests/fortran/semantic_pyi_format/) cover contract emission and re-parsing. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the three rendered examples above. ## Change Routes diff --git a/docs/developer/packages/runtime.md b/docs/developer/packages/runtime.md index b03636c0e..2326758d1 100644 --- a/docs/developer/packages/runtime.md +++ b/docs/developer/packages/runtime.md @@ -21,6 +21,7 @@ completed behavior; they do not decide ownership or invent missing operations. ```text prik/runtime/ +├── __init__.py ├── handles.py └── native_support/ ├── __init__.py @@ -28,7 +29,7 @@ prik/runtime/ └── LICENSE ``` -## Internal Workflow +## What This Stage Receives And Produces ```text generated extension operation dictionary @@ -40,13 +41,15 @@ generated extension operation dictionary The native-support initializer only makes the payload locatable. The compiler installs it into a generated `binding_support/` include directory. -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Path | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `handles.py` | `NativeArrayHandleBase`, `AllocatableArray`, `PointerArray` | Adapts generated descriptor operations into stable Python APIs and live NumPy views. | -| `native_support/prik_binding.h` | capsule, descriptor, validation, conversion, and release helpers | Supplies the header-only native runtime used by generated bindings. | -| `native_support/__init__.py` | package marker | Makes the payload discoverable without creating another runtime API. | +| [`prik/runtime/__init__.py`](../../../prik/runtime/__init__.py) | Package boundary for Python runtime support. | A small supported runtime import surface is deliberately introduced. | +| [`prik/runtime/handles.py`](../../../prik/runtime/handles.py) | `NativeArrayHandleBase`, `AllocatableArray`, and `PointerArray` validate generated operations, retain owners, and produce policy-permitted live NumPy views. | Handle protocol, validation, retention, descriptor conversion, or Python operation behavior changes. | +| [`prik/runtime/native_support/__init__.py`](../../../prik/runtime/native_support/__init__.py) | Locates the bundled native-support payload without creating another Python runtime API. | Payload discovery changes. | +| `runtime/native_support/prik_binding.h` | Bundled native capsule, descriptor, validation, conversion, and release support compiled into generated bindings. | A generated binding requires changed native support; also inspect `compiler/native_support.py` installation. | +| `runtime/native_support/LICENSE` | License text distributed with the native payload. | The payload licensing changes. | ## Execution Example @@ -70,14 +73,14 @@ storage is live, not a detached snapshot. The native payload intentionally has no standalone Python example: it is compiled only as part of a generated binding. -## Tests +## Tests And What They Prove -- [Allocatable runtime tests](../../../tests/fortran/allocatables/runtime/) -- [Pointer runtime tests](../../../tests/fortran/pointers/runtime/) -- [Memory-management runtime tests](../../../tests/fortran/memory_management/runtime/) -- [Runtime infrastructure](../../../tests/fortran/infrastructure/runtime/) -- [Compiled runtime compatibility](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Allocatable runtime tests](../../../tests/fortran/allocatables/runtime/) cover allocatable operations and NumPy views. +- [Pointer runtime tests](../../../tests/fortran/pointers/runtime/) cover pointer association and views. +- [Memory-management runtime tests](../../../tests/fortran/memory_management/runtime/) cover release and ownership enforcement. +- [Runtime infrastructure](../../../tests/fortran/infrastructure/runtime/) covers generated-operation protocols. +- [Compiled runtime compatibility](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) covers the payload in a real extension. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the handle demonstration above. ## Change Routes diff --git a/docs/developer/packages/semantics.md b/docs/developer/packages/semantics.md index 525bf4dcc..496f9dbb1 100644 --- a/docs/developer/packages/semantics.md +++ b/docs/developer/packages/semantics.md @@ -21,6 +21,7 @@ wrappers, or emit source. ```text prik/semantics/ +├── __init__.py ├── models.py ├── scalar_types.py ├── fortran2ir.py @@ -35,7 +36,7 @@ prik/semantics/ The deferred C-to-IR path is intentionally excluded from the published Fortran contributor workflow. -## Internal Workflow +## What This Stage Receives And Produces ```text Fortran parser models + measured target facts ─┐ @@ -45,20 +46,23 @@ semantic .pyi AST ──────────────────── -> prik.policy completion ``` -## Important Files And Essential Objects +## Directory Tour -| File | Important objects | Responsibility | +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `models.py` | `SemanticModule`, `SemanticFunction`, `SemanticClass`, `SemanticArgument`, `SemanticType`, array/storage contracts, `SemanticOrigin` | Defines the language-neutral graph. | -| `scalar_types.py` | `SemanticScalarSpec` and scalar catalogue | Defines stable scalar identities and intrinsic family/storage facts without backend spellings. | -| `fortran2ir.py` | `FortranToIRConverter` | Resolves Fortran models and probed facts into semantic IR. | -| `pyi2ir.py` | `convert_pyi_to_ir()` | Interprets parsed Python AST as an editable semantic contract. | -| `ownership_metadata.py` | normalized ownership and pointer request setters | Stores unresolved frontend requests for later policy completion. | -| `native_array_handles.py` | `NativeArrayHandleFacts` | Separates descriptor, array-data, and element facets. | -| `native_contract.py` | `NativeContractIssue` and validation helpers | Prepares and validates source-free native placement and ABI facts. | - -`metadata.py` and `pyi_metadata.py` are passive shared-key registries. Combined -multi-file `.pyi` loading belongs to `prik/pipeline/pyi.py`. +| [`prik/semantics/__init__.py`](../../../prik/semantics/__init__.py) | Re-exports supported Fortran conversion and `.pyi` conversion entrypoints. | The supported semantic-conversion import API changes. | +| [`prik/semantics/models.py`](../../../prik/semantics/models.py) | `SemanticModule`, `SemanticFunction`, `SemanticClass`, `SemanticArgument`, `SemanticType`, storage/array contracts, and `SemanticOrigin` form the shared language-neutral graph. | A downstream consumer needs a new language-neutral fact. | +| [`prik/semantics/scalar_types.py`](../../../prik/semantics/scalar_types.py) | `SemanticScalarSpec` and the scalar catalogue give stable identities and intrinsic family/storage facts without backend spelling. | Stable scalar vocabulary or intrinsic facts change. | +| [`prik/semantics/fortran2ir.py`](../../../prik/semantics/fortran2ir.py) | `FortranToIRConverter` combines parser models and measured facts into semantic IR; public helpers handle files, modules, and projects. | A Fortran source fact needs a different semantic interpretation. | +| [`prik/semantics/pyi2ir.py`](../../../prik/semantics/pyi2ir.py) | `convert_pyi_to_ir()` interprets parsed Python AST as an editable semantic contract and reconciles external type references. | A supported `.pyi` construct needs semantic meaning. | +| [`prik/semantics/metadata.py`](../../../prik/semantics/metadata.py) | Passive keys shared by semantic owners. | A generic semantic metadata key or its canonical spelling changes. | +| [`prik/semantics/pyi_metadata.py`](../../../prik/semantics/pyi_metadata.py) | Passive keys specific to `.pyi` interpretation. | Parsed `.pyi` metadata needs a canonical key. | +| [`prik/semantics/ownership_metadata.py`](../../../prik/semantics/ownership_metadata.py) | Normalizes raw ownership and pointer requests without resolving them. | A frontend request needs preservation before policy completion. | +| [`prik/semantics/native_array_handles.py`](../../../prik/semantics/native_array_handles.py) | `NativeArrayHandleFacts` keeps descriptor, data, and element facets separate. | Semantic description of a native descriptor-backed array changes. | +| [`prik/semantics/native_contract.py`](../../../prik/semantics/native_contract.py) | `NativeContractIssue` and helpers prepare and validate source-free native placement and ABI facts. | Native contract validation or diagnostics change. | + +Combined multi-file `.pyi` loading belongs to `prik/pipeline/pyi.py`. Completed +ownership, projection, and lowering actions belong to `policy/`, never here. ## Execution Examples @@ -133,13 +137,13 @@ Invalid contract issue: pyi_native_type_missing at math.broken.value These examples show stable semantic representation and raw contract facts. None contains a completed binding or bridge action. -## Tests +## Tests And What They Prove -- [Semantic IR conversion](../../../tests/fortran/semantic_ir/semantics/) -- [Semantic `.pyi` behavior](../../../tests/fortran/semantic_pyi_format/) -- [Datatype semantics](../../../tests/fortran/data_types/semantics/) -- [Native handle semantics](../../../tests/fortran/infrastructure/semantics/test_native_array_handles.py) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Semantic IR conversion](../../../tests/fortran/semantic_ir/semantics/) covers Fortran-model conversion and graph shape. +- [Semantic `.pyi` behavior](../../../tests/fortran/semantic_pyi_format/) covers contract interpretation and external references. +- [Datatype semantics](../../../tests/fortran/data_types/semantics/) covers stable type and storage facts. +- [Native handle semantics](../../../tests/fortran/infrastructure/semantics/test_native_array_handles.py) covers descriptor/data/element separation. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the seven stage demonstrations above. ## Change Routes diff --git a/docs/developer/packages/utilities.md b/docs/developer/packages/utilities.md index 61f95783d..eaddbe2c5 100644 --- a/docs/developer/packages/utilities.md +++ b/docs/developer/packages/utilities.md @@ -19,18 +19,28 @@ semantic policy, syntax grammar, and workflow orchestration. ```text prik/utilities/ +├── __init__.py ├── declaration_expressions.py ├── strings.py └── visitor.py ``` -## Important Files And Essential Objects +## What This Stage Receives And Produces -| File | Important objects | Responsibility | +```text +stage-owned caller facts + -> reusable expression, local-name, or visitor mechanism + -> requesting stage +``` + +## Directory Tour + +| Module | Main entrypoints and contents | Change it when | | --- | --- | --- | -| `declaration_expressions.py` | `ResolvedDeclarationExtent`, `DeclarationExpressionCall`, `ArrayExpressionSource` | Translates, validates, resolves, evaluates, and renders declaration extents across explicit stage boundaries. | -| `strings.py` | collision-safe local-name helpers | Allocates deterministic local names without owning a public naming policy. | -| `visitor.py` | `ClassVisitor` | Provides exact-class and intentional MRO fallback dispatch shared by independent visitors. | +| [`prik/utilities/__init__.py`](../../../prik/utilities/__init__.py) | Package boundary for small stage-neutral mechanisms. | Establishing a deliberate package-level utility API. | +| [`prik/utilities/declaration_expressions.py`](../../../prik/utilities/declaration_expressions.py) | `ResolvedDeclarationExtent`, `DeclarationExpressionCall`, and `ArrayExpressionSource` translate, validate, resolve, evaluate, and render declaration extents at explicit handoffs. | An extent representation or its stage-owned translation changes. | +| [`prik/utilities/strings.py`](../../../prik/utilities/strings.py) | Collision-safe local-name helpers allocate deterministic temporary identifiers. | Generic local name allocation changes; public name policy belongs in `naming/`. | +| [`prik/utilities/visitor.py`](../../../prik/utilities/visitor.py) | `ClassVisitor` provides exact-class dispatch with intentional MRO fallback. | Shared generic dispatch changes, not a stage's visitor methods. | ## Execution Examples @@ -67,11 +77,11 @@ Exact handler: literal:42 MRO fallback: expression:Expression ``` -## Tests +## Tests And What They Prove -- [Utility infrastructure](../../../tests/fortran/infrastructure/utilities/) -- [Declaration-expression semantics](../../../tests/fortran/arrays/semantics/test_declaration_expression_utilities.py) -- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) +- [Utility infrastructure](../../../tests/fortran/infrastructure/utilities/) covers local-name and visitor behavior. +- [Declaration-expression semantics](../../../tests/fortran/arrays/semantics/test_declaration_expression_utilities.py) covers role resolution and expression rendering. +- [Direct execution inventory](../../../tests/fortran/infrastructure/execution_examples/test_execution_examples.py) fixes the three demonstrations above. ## Change Routes diff --git a/tests/docs/test_reference_and_source_map.py b/tests/docs/test_reference_and_source_map.py index ddcd456c7..0298a5f89 100644 --- a/tests/docs/test_reference_and_source_map.py +++ b/tests/docs/test_reference_and_source_map.py @@ -231,6 +231,7 @@ def test_contributor_architecture_stays_shallow_and_routes_to_package_guides() - for heading in ( "Package-Root Entry Points", "End-To-End Workflow", + "Stage Handoffs", "Authority And Dependency Rules", "Package Guide Map", "Tests And Evidence", @@ -308,9 +309,10 @@ def test_package_guide_has_structure_examples_tests_and_change_routes(package: s assert "../architecture.md" in content assert "## Purpose And Boundaries" in content assert "## Local Structure" in content - assert "## Important File" in content + assert "## What This Stage Receives And Produces" in content + assert "## Directory Tour" in content assert "## Execution Example" in content - assert "## Tests" in content + assert "## Tests And What They Prove" in content assert "## Change Routes" in content assert "../../../tests/" in content assert "python3 prik/" in content @@ -321,6 +323,32 @@ def test_package_guide_has_structure_examples_tests_and_change_routes(package: s assert (path.parent / target).resolve().exists(), f"{package}: missing linked owner {target}" +def test_package_guides_cover_every_supported_python_module() -> None: + deferred_c_input_modules = { + path.relative_to(ROOT).as_posix() + for root in ( + ROOT / "prik/parsers/c", + ROOT / "prik/preprocessing/c.py", + ROOT / "prik/preprocessing/probes/c_types.py", + ROOT / "prik/semantics/c2ir.py", + ) + for path in (root.rglob("*.py") if root.is_dir() else (root,)) + } + + package_guides = { + path.stem: path for path in (DOCS_ROOT / "developer/packages").glob("*.md") if path.name != "index.md" + } + for package, guide_path in package_guides.items(): + documented = guide_path.read_text(encoding="utf-8") + source_modules = { + path.relative_to(ROOT).as_posix() + for path in (ROOT / "prik" / package).rglob("*.py") + if path.relative_to(ROOT).as_posix() not in deferred_c_input_modules + } + missing = sorted(path for path in source_modules if path not in documented) + assert not missing, f"{guide_path.name}: undocumented supported modules: {missing}" + + def test_superseded_contributor_pages_and_completed_roadmaps_are_removed() -> None: removed_paths = ( "adding-a-feature.md", From 8d2c4e8c9061bb2da85b7e0f6f6a19bd3baa393c Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 17:53:01 +0100 Subject: [PATCH 21/22] modify root imports --- CHANGELOG.md | 6 + README.md | 8 +- docs/developer/architecture.md | 13 +- docs/developer/deferred/c-parser.md | 23 +- docs/developer/packages/parsers.md | 2 +- .../recipes/use-python-inspection-apis.md | 12 +- docs/user/reference/fortran-wrapper.md | 2 +- docs/user/reference/python-api.md | 262 +++--------------- prik/__init__.py | 138 +-------- prik/parsers/c/README.md | 4 +- prik/parsers/fortran/__init__.py | 35 ++- prik/semantics/fortran2ir.py | 2 +- tests/c/_support/cli.py | 2 +- tests/c/cli/test_c_cli_argument_contract.py | 2 +- tests/c/cli/test_c_cli_output_contract.py | 2 +- tests/c/cli/test_c_cli_stage_dispatch.py | 2 +- tests/c/parsing/test_c_cli_skeleton.py | 2 +- tests/c/parsing/test_c_public_api_skeleton.py | 4 +- tests/docs/test_examples.py | 2 +- tests/fortran/_support/fixture_outputs.py | 2 +- tests/fortran/_support/parser_procedures.py | 2 +- tests/fortran/_support/printer_models.py | 2 +- .../test_allocatable_contract_printing.py | 2 +- ...st_allocatable_module_contract_printing.py | 2 +- ...st_allocatable_output_contract_printing.py | 2 +- .../test_fortran_allocatable_semantics.py | 2 +- .../test_array_declarations_and_shapes.py | 2 +- .../arrays/policy/test_array_shape_policy.py | 2 +- .../arrays/semantics/test_array_semantics.py | 2 +- .../pipeline/test_root_build_api.py | 41 +++ .../parsing/test_callback_declarations.py | 2 +- .../test_fortran_callback_semantics.py | 6 +- .../pipeline/_support.py | 2 +- .../pipeline/test_argument_contract.py | 2 +- .../pipeline/test_output_contract.py | 2 +- .../pipeline/test_stage_dispatch.py | 4 +- .../test_default_logical_scalar_lowering.py | 2 +- .../parsing/test_declarations_and_shapes.py | 5 +- .../test_scalar_kind_parsing_properties.py | 2 +- .../probes/test_fortran_type_probes.py | 13 +- .../test_fortran_scalar_semantics.py | 2 +- .../semantics/test_types_and_storage.py | 2 +- .../parsing/test_derived_field_syntax.py | 2 +- .../parsing/test_derived_procedure_syntax.py | 2 +- .../parsing/test_derived_type_declarations.py | 2 +- .../parsing/test_derived_type_errors.py | 2 +- .../parsing/test_derived_type_properties.py | 2 +- .../parsing/test_derived_type_scopes.py | 2 +- .../test_parameterized_derived_types.py | 2 +- .../parsing/test_type_bound_diagnostics.py | 2 +- .../semantics/test_derived_type_identity.py | 2 +- .../test_fortran_derived_semantics.py | 2 +- .../test_imported_derived_semantics.py | 6 +- .../parsing/test_enum_diagnostics.py | 2 +- .../enumerations/parsing/test_enum_syntax.py | 2 +- .../semantics/test_enum_semantics.py | 2 +- .../parsing/test_fortran_diagnostics.py | 2 +- .../parsing/test_function_headers.py | 2 +- ...est_procedure_and_interface_regressions.py | 2 +- ...an_conversion_procedures_and_interfaces.py | 2 +- .../test_fortran_function_semantics.py | 2 +- .../parsing/test_generic_interface_syntax.py | 2 +- .../test_fortran_generic_semantics.py | 2 +- .../codegen/test_refactoring_goldens.py | 2 +- .../test_execution_examples.py | 4 - .../runtime/test_handle_lifecycle.py | 9 - .../parsing/test_project_scope_models.py | 2 +- .../modules/parsing/test_scope_handling.py | 2 +- .../test_module_contract_semantics.py | 2 +- .../semantics/test_modules_and_imports.py | 4 +- .../parsing/test_optional_declarations.py | 2 +- .../test_optional_fortran_semantics.py | 2 +- .../parsing/test_pointer_declarations.py | 2 +- .../semantics/test_pointer_semantics.py | 2 +- .../policy/test_class_surface_policy.py | 2 +- .../semantics/test_compile_time_values.py | 2 +- .../test_fortran_conversion_properties.py | 2 +- ...test_semantic_specialization_properties.py | 2 +- .../test_calls_and_policy_metadata.py | 2 +- .../pipeline/test_classes_and_methods.py | 2 +- .../pipeline/test_modern_example.py | 2 +- .../test_pyi_printer_imports_and_packages.py | 2 +- .../pipeline/test_types_and_declarations.py | 2 +- .../semantics/test_calls_and_projections.py | 2 +- .../semantics/test_imports_and_packages.py | 2 +- .../semantics/test_types_and_values.py | 2 +- .../parsing/generate_error_goldens.py | 4 +- .../parsing/generate_parser_goldens.py | 2 +- .../test_declaration_and_interface_edges.py | 2 +- .../test_declaration_and_scope_regressions.py | 2 +- .../test_derived_types_and_program_units.py | 6 +- .../parsing/test_error_fixture_suite.py | 2 +- .../parsing/test_error_handling.py | 2 +- .../parsing/test_fortran_fixture_suite.py | 2 +- ...ortran_parser_procedures_and_interfaces.py | 5 +- .../parsing/test_fortran_parser_properties.py | 5 +- .../parsing/test_parser_benchmarks.py | 2 +- .../parsing/test_public_entrypoints.py | 8 +- ...test_real_world_interaction_regressions.py | 2 +- ...source_form_and_diagnostics_regressions.py | 5 +- .../preprocessing/test_parser_boundaries.py | 2 +- .../test_preprocessing_properties.py | 5 +- .../parsing/test_character_declarations.py | 2 +- .../parsing/test_character_length_parsing.py | 2 +- .../test_fixed_form_character_parsing.py | 2 +- .../test_fortran_string_semantics.py | 2 +- .../test_subroutine_argument_projection.py | 2 +- 107 files changed, 263 insertions(+), 534 deletions(-) create mode 100644 tests/fortran/building_shared_library/pipeline/test_root_build_api.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 919e9eeef..934e6c305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,12 @@ release tags add a leading `v` to the package version. ### Changed +- Reduced the root `prik` API to its version and normal-user build entrypoints; + parser, semantic, probe, runtime, and planning tools now use their owning + package import paths. +- Made `prik` an import-only package boundary by removing its direct-script + demonstration; command and stage-value examples remain available from their + owning modules. - Expanded the contributor architecture and package guides into a complete stage-by-stage tutorial, with every supported Python module, runnable example result, focused test purpose, and change route recorded and checked against diff --git a/README.md b/README.md index 54ee763ce..184d9bf41 100644 --- a/README.md +++ b/README.md @@ -539,11 +539,9 @@ strings, focused tests, and already-preprocessed inputs. ```python -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file parsed = parse_fortran_file( "subroutine ping(n)\n" @@ -45,7 +45,7 @@ PRIK_C_DOCS_END --> - - - - - -## Fortran parser API - -| Symbol | Purpose | -| --- | --- | -| `parse_fortran_file` | Parses one Fortran file into a `FortranFile`. | -| `parse_fortran_project` | Parses multiple Fortran files into a `FortranProject`. | -| `FortranFile` | Parsed Fortran file model. | -| `FortranProject` | Parsed Fortran project model. | -| `FortranModule` | Parsed module model. | -| `FortranSubmodule` | Parsed submodule model. | -| `FortranProgram` | Parsed program model. | -| `FortranBlockData` | Parsed block-data unit model. | -| `FortranDerivedType` | Parsed derived-type model. | -| `FortranInterface` | Parsed interface model. | -| `FortranProcedureSignature` | Parsed function or subroutine signature model. | -| `FortranArgument` | Parsed procedure argument model. | -| `FortranParseError` | Error raised for Fortran parse failures. | - -## Semantic conversion API - -| Symbol | Purpose | -| --- | --- | -| `fortran_file_to_semantic_modules` | Converts a parsed Fortran file to semantic module models. | -| `fortran_project_to_semantic_modules` | Converts a parsed Fortran project to semantic module models. | -| `fortran_module_to_semantic_module` | Converts one parsed Fortran module to one semantic module. | -| `collect_semantic_compile_time_requirements` | Collects semantic values that must be known at compile time. | -| `resolve_semantic_compile_time_values` | Resolves collected compile-time requirements. | - - - -Semantic conversion is the boundary between parser models and wrapper-facing -contracts. Run the default wrapper build to complete policy and validate -whether the wrapper plan supports the contract. - -## Semantic `.pyi` contract API - -| Symbol | Purpose | -| --- | --- | -| `parse_pyi_text` | Parses semantic `.pyi` source text into Python AST. | -| `parse_pyi_file` | Loads and parses one semantic `.pyi` file into Python AST. | -| `convert_pyi_to_ir` | Converts parsed semantic `.pyi` AST to semantic IR. | -| `pyi_text_to_semantic_module` | Parses inline semantic `.pyi` text and converts it to semantic IR. | -| `pyi_file_to_semantic_module` | Converts one semantic `.pyi` file to semantic IR. | -| `pyi_paths_to_semantic_modules` | Converts semantic `.pyi` files or directories to semantic IR and reconciles imports. | - -Editable `.pyi` files are a contract surface. User-private declarations in a -`.pyi` file are distinct from source-private Fortran declarations omitted from -generated stubs. - -## Stub emission API - -| Symbol | Purpose | -| --- | --- | -| `emit_module_stubs` | Emits semantic Python `.pyi` text from semantic module models. | -| `opaque_dependency_modules` | Computes opaque dependency modules needed for emitted stubs. | - -## Native array handle API - -| Symbol | Purpose | -| --- | --- | -| `NativeArrayHandleBase` | Common runtime base for generated native array descriptor handles. | -| `AllocatableArray` | Runtime object for a native allocatable array descriptor. | -| `PointerArray` | Runtime object for a native pointer array descriptor. | - -Generated wrappers use these handle classes when an allocatable or pointer array -descriptor is exposed as a Python object. Users can test for these classes when -they need to distinguish descriptor handles from ordinary NumPy arrays. Borrowed -handles do not own native storage. Owned handles expose `close()` and `closed`; -their finalizer attempts generated owner-storage destruction at most once. - -`Allocatable[T[...]]()` creates an owned, initially unallocated -`AllocatableArray`. `Pointer[T[...]]()` creates an owned, initially -unassociated `PointerArray`. The dtype and rank come from the annotation. On -the first writable descriptor call, the generated wrapper attaches -compiler-compatible persistent storage to the same handle. Closing an -allocatable handle also releases any allocation it still owns; closing a -pointer handle releases only its descriptor, not an associated target. - -`p1.associate(p2)` makes `p1` refer to the same target as `p2`, or makes -`p1` unassociated when `p2` is unassociated. It replaces any current -association of `p1` without copying or deallocating target storage. - -Owned writable handles carry a versioned record defined by prik's bundled -native binding support. Separately built prik extensions can accept the same -handle without linking to each other when their prik handle ABI and Fortran -compiler/runtime ABIs are compatible. Each receiving wrapper validates the -record's version, size, descriptor kind, dtype, and rank before direct -descriptor use. - -These classes are array-only. Scalar `Allocatable[T]` and `Pointer[T]` -projections remain ordinary `T | None` values and never produce an -`AllocatableArray` or `PointerArray`. - -`to_numpy()` returns `None` when the descriptor is currently unallocated or -unassociated. Otherwise, it returns a live NumPy view of the current allocation -or pointer target and never an automatic detached copy. Results must match the -handle's declared dtype and rank. Contiguous-view policy rejects non-contiguous -storage, while descriptor-view extraction can expose positive or negative -strides when generated standard descriptor support is available. Unsupported -descriptor extraction fails explicitly. Reallocation, deallocation, pointer -reassociation, or nullification may make an older view stale; accessing a stale -view is unsupported and may crash. Call `.copy()` explicitly when independent -storage is required, and call `to_numpy()` again to inspect current state. - -When a generated wrapper accepts a handle for an ordinary `T[...]` argument, it -uses an internal native array-actual handoff rather than an implicit -`to_numpy()` call. Parameters annotated as `Allocatable[T[...]]` or -`Pointer[T[...]]` use descriptor handoff and require the matching handle class; -plain NumPy arrays are for ordinary array-data parameters. - -## Wrapper build API - -| Symbol | Purpose | -| --- | --- | -| `build_fortran_extension` | Builds a Python extension from semantic Fortran source inputs plus optional native-only sources, artifacts, compiler flags, include paths, libraries, and ordered link items. | -| `build_pyi_extension` | Builds a Python extension from semantic `.pyi` contracts plus explicit native artifacts. | -| `build_pyi_extension_from_manifest` | Replays a saved semantic `.pyi` wrapper build manifest, either building directly or regenerating `Makefile.prik`. | -| `WrapperBuildResult` | Result model returned by wrapper build functions; `import_module()` explicitly loads its built extension. | -| `NativeBuildPlan` | Structured native implementation compile/link plan attached to a wrapper build result. | -| `NativeCompilationUnit` | Native source compilation unit and produced object recorded in a native build plan. | -| `NativePrebuiltArtifact` | Caller-supplied native object, archive, or shared library recorded in a native build plan. | -| `NativeLinkItem` | One ordered object, archive, shared library, named library, or linker argument in a native link plan. | - -Fortran source wrapper builds own the normal source-to-extension workflow and -may augment their positional semantic sources with the same native compile and -link inputs used by contract builds. Semantic `.pyi` wrapper builds require at -least one explicit native implementation input such as native Fortran sources, -objects, libraries, or ordered link items. Inspect -`WrapperBuildResult.native_build_plan` when a caller needs the native -compilation units, produced objects, prebuilt artifacts, module/include -directories, library directories, or ordered native link items separately from -the semantic contract paths. Semantic `.pyi` build results also expose a -normalized replay `manifest`; Makefile mode writes that manifest to -`/prik-build.json` before generating `Makefile.prik`. - -When a program needs the generated extension immediately, call -`result.import_module()`. It loads `result.shared_library` under -`result.module_name` without changing `sys.path` and returns the imported -module. The method requires that the shared-library file already exists, so a -direct build can import at once and a Makefile result can import after `make` -has produced the extension. - -## Target type probing - -| Symbol | Purpose | -| --- | --- | -| `FortranTypeProbeError` | Error raised for Fortran type probing failures. | -| `FortranTypeProbeReport` | Report model for Fortran type probing. | -| `build_fortran_type_probe_source` | Builds the source used to probe Fortran type properties. | -| `fortran_type_probe_expressions` | Produces expressions used by the Fortran type probe. | -| `probe_fortran_type_expressions` | Runs Fortran type probes for selected expressions. | -| `evaluate_fortran_type_requirements` | Evaluates semantic requirements against a Fortran type probe report. | - -These helpers expose compiler-target measurement. Semantic-to-NumPy projection -is an internal code-generation concern consumed through completed wrapper -plans, not a public conversion API. The CLI type-probe flags are documented in -[CLI Commands Reference](cli-commands.md). - -## Current boundaries - -- Parser functions do not run CLI path expansion or command-line preprocessing - validation. -- Generated module, function, class, and configuration references document the - wrapper output surface; this page remains the maintained inventory for - `prik.__all__`. - - +The functions return `prik.pipeline.build.WrapperBuildResult`. Import result +models and native-build plan records from `prik.pipeline.build` only when you +need to inspect or construct those advanced values. + +## Advanced Package Imports + +| Need | Import from | Main entrypoints | +| --- | --- | --- | +| Fortran source facts and diagnostics | `prik.parsers.fortran` | `parse_fortran_file`, `parse_fortran_project`, `FortranParser`, parser models, `FortranParseError` | +| Raw semantic `.pyi` syntax | `prik.parsers.pyi` | `parse_pyi_text`, `parse_pyi_file` | +| Semantic conversion | `prik.semantics.fortran2ir` or `prik.semantics.pyi2ir` | Fortran conversion helpers or `convert_pyi_to_ir` | +| `.pyi` loading and stub emission | `prik.pipeline.pyi` | `pyi_*_to_semantic_module`, `emit_module_stubs` | +| Build records and results | `prik.pipeline.build` | `WrapperBuildResult`, `NativeBuildPlan`, `NativeCompilationUnit`, `NativePrebuiltArtifact`, `NativeLinkItem` | +| Target type probing | `prik.preprocessing.probes.fortran_types` | probe source, requirements, expressions, and report/error types | +| Runtime descriptor handles | `prik.runtime.handles` | `NativeArrayHandleBase`, `AllocatableArray`, `PointerArray` | +| Semantic `.pyi` vocabulary | `prik.contracts` | scalar, array, ownership, and native-call contract markers | +| CLI implementation | `prik.cli` | `main()`; shell users should run `python3 -m prik` instead | + +The [Fortran wrapper reference](fortran-wrapper.md) documents the normal build +functions. The [package guides](../../developer/packages/index.md) explain +advanced module responsibilities and their focused tests. + +## Current Boundaries + +- Root imports are intentionally small and do not load parser or semantic + implementation modules. +- A parser success is only a source fact. Semantic conversion, policy + completion, planning, and generation are separate stages. +- The C-input frontend is deferred from the published workflow. Its internal + parser package is not a root API. diff --git a/prik/__init__.py b/prik/__init__.py index 4cc31e9eb..0c6d75ef3 100644 --- a/prik/__init__.py +++ b/prik/__init__.py @@ -1,70 +1,16 @@ -"""Public PRIK API.""" +"""Normal-user PRIK build entrypoints. + +Import advanced parser, semantic, probe, runtime, and planning APIs from the +package that owns them instead of from this root facade. +""" from importlib import import_module from importlib.metadata import version as _distribution_version -from prik.parsers.c.models import CFile, CParseError, CProject -from prik.parsers.c.parser import parse_c_file, parse_c_project -from prik.parsers.fortran.models import ( - FortranArgument, - FortranBlockData, - FortranDerivedType, - FortranFile, - FortranInterface, - FortranModule, - FortranParseError, - FortranProcedureSignature, - FortranProgram, - FortranProject, - FortranSubmodule, -) -from prik.parsers.fortran.parser import parse_fortran_file, parse_fortran_project -from prik.parsers.pyi import parse_pyi_file, parse_pyi_text -from prik.semantics.fortran2ir import ( - collect_semantic_compile_time_requirements, - fortran_file_to_semantic_modules, - fortran_module_to_semantic_module, - fortran_project_to_semantic_modules, - resolve_semantic_compile_time_values, -) -from prik.semantics.c2ir import ( - CToIRConverter, - c_file_to_semantic_module, - c_file_to_semantic_modules, - c_function_to_semantic_function, - c_parameter_to_semantic_argument, - c_project_to_semantic_module, - c_project_to_semantic_modules, - c_struct_to_semantic_class, - c_type_to_semantic_type, -) -from prik.semantics.pyi2ir import convert_pyi_to_ir -from prik.pipeline.pyi import ( - emit_module_stubs, - opaque_dependency_modules, - pyi_file_to_semantic_module, - pyi_paths_to_semantic_modules, - pyi_text_to_semantic_module, -) -from prik.runtime.handles import AllocatableArray, NativeArrayHandleBase, PointerArray __version__ = _distribution_version("prik") -_CLI_EXPORTS = {"main"} -_FORTRAN_TYPE_PROBE_EXPORTS = { - "FortranTypeProbeError", - "FortranTypeProbeReport", - "build_fortran_type_probe_source", - "evaluate_fortran_type_requirements", - "fortran_type_probe_expressions", - "probe_fortran_type_expressions", -} -_WRAPPING_EXPORTS = { - "NativeBuildPlan", - "NativeCompilationUnit", - "NativeLinkItem", - "NativePrebuiltArtifact", - "WrapperBuildResult", +_BUILD_EXPORTS = { "build_fortran_extension", "build_pyi_extension", "build_pyi_extension_from_manifest", @@ -72,84 +18,16 @@ def __getattr__(name: str): - if name in _CLI_EXPORTS: - module = import_module("prik.cli") - return getattr(module, name) - if name in _FORTRAN_TYPE_PROBE_EXPORTS: - module = import_module("prik.preprocessing.probes.fortran_types") - return getattr(module, name) - if name in _WRAPPING_EXPORTS: + """Load a public build entrypoint only when a caller requests it.""" + if name in _BUILD_EXPORTS: module = import_module("prik.pipeline.build") return getattr(module, name) raise AttributeError(f"module 'prik' has no attribute {name!r}") __all__ = ( - "AllocatableArray", - "CFile", - "CParseError", - "CProject", - "CToIRConverter", - "FortranArgument", - "FortranBlockData", - "FortranDerivedType", - "FortranFile", - "FortranInterface", - "FortranModule", - "FortranParseError", - "FortranProcedureSignature", - "FortranProgram", - "FortranProject", - "FortranSubmodule", - "FortranTypeProbeError", - "FortranTypeProbeReport", - "NativeArrayHandleBase", - "NativeBuildPlan", - "NativeCompilationUnit", - "NativeLinkItem", - "NativePrebuiltArtifact", - "PointerArray", - "WrapperBuildResult", "__version__", "build_fortran_extension", - "build_fortran_type_probe_source", "build_pyi_extension", "build_pyi_extension_from_manifest", - "c_file_to_semantic_module", - "c_file_to_semantic_modules", - "c_function_to_semantic_function", - "c_parameter_to_semantic_argument", - "c_project_to_semantic_module", - "c_project_to_semantic_modules", - "c_struct_to_semantic_class", - "c_type_to_semantic_type", - "collect_semantic_compile_time_requirements", - "convert_pyi_to_ir", - "emit_module_stubs", - "evaluate_fortran_type_requirements", - "fortran_file_to_semantic_modules", - "fortran_module_to_semantic_module", - "fortran_project_to_semantic_modules", - "fortran_type_probe_expressions", - "main", - "opaque_dependency_modules", - "parse_c_file", - "parse_c_project", - "parse_fortran_file", - "parse_fortran_project", - "parse_pyi_file", - "parse_pyi_text", - "probe_fortran_type_expressions", - "pyi_file_to_semantic_module", - "pyi_paths_to_semantic_modules", - "pyi_text_to_semantic_module", - "resolve_semantic_compile_time_values", ) - - -if __name__ == "__main__": - parsed = parse_fortran_file("subroutine ping()\nend subroutine ping\n", filename="ping.f90") - procedure = parsed.procedures[0] - - print(f"PRIK {__version__}") - print(f"Public parser result: {procedure.kind} {procedure.name} from {parsed.filename}") diff --git a/prik/parsers/c/README.md b/prik/parsers/c/README.md index adfb653d1..f8b51ca9c 100644 --- a/prik/parsers/c/README.md +++ b/prik/parsers/c/README.md @@ -5,8 +5,8 @@ preserves declarations and diagnostics, and feeds semantic conversion. It does not own runtime wrapping of user-supplied C libraries. Its canonical import namespace is `prik.parsers.c`. The stable convenience -functions `prik.parse_c_file` and `prik.parse_c_project` remain available from -the package root. +functions `parse_c_file` and `parse_c_project` are imported from that package, +not from the root facade. ## Entry Points diff --git a/prik/parsers/fortran/__init__.py b/prik/parsers/fortran/__init__.py index 3ee3d60ee..a1736a315 100644 --- a/prik/parsers/fortran/__init__.py +++ b/prik/parsers/fortran/__init__.py @@ -1 +1,34 @@ -"""Internal Fortran parser implementation package.""" +"""Supported Fortran source-parser API.""" + +from .models import ( + FortranArgument, + FortranBlockData, + FortranDerivedType, + FortranFile, + FortranInterface, + FortranModule, + FortranParseError, + FortranProcedureSignature, + FortranProgram, + FortranProject, + FortranSubmodule, +) +from .parser import FortranParser, parse_fortran_file, parse_fortran_project + + +__all__ = ( + "FortranArgument", + "FortranBlockData", + "FortranDerivedType", + "FortranFile", + "FortranInterface", + "FortranModule", + "FortranParseError", + "FortranParser", + "FortranProcedureSignature", + "FortranProgram", + "FortranProject", + "FortranSubmodule", + "parse_fortran_file", + "parse_fortran_project", +) diff --git a/prik/semantics/fortran2ir.py b/prik/semantics/fortran2ir.py index e109ace87..910c1b6c3 100644 --- a/prik/semantics/fortran2ir.py +++ b/prik/semantics/fortran2ir.py @@ -3087,7 +3087,7 @@ def collect_semantic_compile_time_requirements( wrappers below. Example: - >>> from prik import parse_fortran_file + >>> from prik.parsers.fortran import parse_fortran_file >>> parsed = parse_fortran_file("module m\\ninteger, parameter :: rk = selected_real_kind(12)\\nend module") >>> reqs = collect_semantic_compile_time_requirements(parsed) >>> reqs[0]["symbol"] diff --git a/tests/c/_support/cli.py b/tests/c/_support/cli.py index 1eb2bf5cf..ffc35c3b7 100644 --- a/tests/c/_support/cli.py +++ b/tests/c/_support/cli.py @@ -4,7 +4,7 @@ import types -from prik import cli as prik_cli +import prik.cli as prik_cli class _MainParserError(Exception): diff --git a/tests/c/cli/test_c_cli_argument_contract.py b/tests/c/cli/test_c_cli_argument_contract.py index 875df1364..195ae4a7b 100644 --- a/tests/c/cli/test_c_cli_argument_contract.py +++ b/tests/c/cli/test_c_cli_argument_contract.py @@ -5,7 +5,7 @@ import pytest -from prik import cli as prik_cli +import prik.cli as prik_cli from tests.c._support.cli import ( _MainParserError, _install_main_parser, diff --git a/tests/c/cli/test_c_cli_output_contract.py b/tests/c/cli/test_c_cli_output_contract.py index e15d44a02..f9942b7cf 100644 --- a/tests/c/cli/test_c_cli_output_contract.py +++ b/tests/c/cli/test_c_cli_output_contract.py @@ -2,7 +2,7 @@ import types -from prik import cli as prik_cli +import prik.cli as prik_cli from tests.c._support.cli import ( _install_main_parser, _main_args, diff --git a/tests/c/cli/test_c_cli_stage_dispatch.py b/tests/c/cli/test_c_cli_stage_dispatch.py index 62db51b34..0811e535d 100644 --- a/tests/c/cli/test_c_cli_stage_dispatch.py +++ b/tests/c/cli/test_c_cli_stage_dispatch.py @@ -4,7 +4,7 @@ import pytest -from prik import cli as prik_cli +import prik.cli as prik_cli from tests.c._support.cli import ( _install_main_parser, _main_args, diff --git a/tests/c/parsing/test_c_cli_skeleton.py b/tests/c/parsing/test_c_cli_skeleton.py index 814cf62a4..ca91da197 100644 --- a/tests/c/parsing/test_c_cli_skeleton.py +++ b/tests/c/parsing/test_c_cli_skeleton.py @@ -12,7 +12,7 @@ from prik.parsers.c import CParseError from prik.parsers.c import cli as c_parser_cli -from prik import cli as prik_cli +import prik.cli as prik_cli from prik.preprocessing import PreprocessingConfig CONTRACT_IMPORT = "from prik.contracts import Int32\n\n" diff --git a/tests/c/parsing/test_c_public_api_skeleton.py b/tests/c/parsing/test_c_public_api_skeleton.py index d176d89e4..34fa19f96 100644 --- a/tests/c/parsing/test_c_public_api_skeleton.py +++ b/tests/c/parsing/test_c_public_api_skeleton.py @@ -94,8 +94,8 @@ def test_parse_c_file_accepts_inline_source_and_returns_typed_model(): assert [fn.name for fn in parsed.functions] == ["add"] -def test_prik_exports_c_file_and_project_entrypoints_like_fortran(): - from prik import CFile, CProject, parse_c_file, parse_c_project +def test_c_package_exports_file_and_project_entrypoints(): + from prik.parsers.c import CFile, CProject, parse_c_file, parse_c_project parsed = parse_c_file("int add(int left, int right);\n", filename="api.h") project = parse_c_project({"api.h": "int add(int left, int right);\n"}) diff --git a/tests/docs/test_examples.py b/tests/docs/test_examples.py index 33e8f1384..be422a7ae 100644 --- a/tests/docs/test_examples.py +++ b/tests/docs/test_examples.py @@ -14,7 +14,7 @@ import pytest -from prik import pyi_text_to_semantic_module +from prik.pipeline.pyi import pyi_text_to_semantic_module ROOT = Path(__file__).parents[2] diff --git a/tests/fortran/_support/fixture_outputs.py b/tests/fortran/_support/fixture_outputs.py index c552c539f..ed8e53d06 100644 --- a/tests/fortran/_support/fixture_outputs.py +++ b/tests/fortran/_support/fixture_outputs.py @@ -2,7 +2,7 @@ from dataclasses import asdict from pathlib import Path -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.semantics.fortran2ir import fortran_module_to_semantic_module FORTRAN_ROOT = Path(__file__).resolve().parents[1] diff --git a/tests/fortran/_support/parser_procedures.py b/tests/fortran/_support/parser_procedures.py index 660bc1936..ab1d888df 100644 --- a/tests/fortran/_support/parser_procedures.py +++ b/tests/fortran/_support/parser_procedures.py @@ -1,4 +1,4 @@ -from prik import parse_fortran_file, parse_fortran_project +from prik.parsers.fortran import parse_fortran_file, parse_fortran_project def collect_project_procedure_signatures(files): diff --git a/tests/fortran/_support/printer_models.py b/tests/fortran/_support/printer_models.py index 264c2e176..5d8157068 100644 --- a/tests/fortran/_support/printer_models.py +++ b/tests/fortran/_support/printer_models.py @@ -3,7 +3,7 @@ from prik.contracts import CONTRACT_SYMBOLS -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.semantics.fortran2ir import ( diff --git a/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py b/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py index 0c559061a..46bea4e1d 100644 --- a/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py +++ b/tests/fortran/allocatables/pipeline/test_allocatable_contract_printing.py @@ -2,7 +2,7 @@ from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def _generate_pyi(source: str) -> str: diff --git a/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py b/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py index 3ab6ab7b9..d6fa9aa0f 100644 --- a/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py +++ b/tests/fortran/allocatables/pipeline/test_allocatable_module_contract_printing.py @@ -2,7 +2,7 @@ from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text diff --git a/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py b/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py index bd7133586..be5e4bcb7 100644 --- a/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py +++ b/tests/fortran/allocatables/pipeline/test_allocatable_output_contract_printing.py @@ -2,7 +2,7 @@ from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def _generate_pyi(source: str) -> str: diff --git a/tests/fortran/allocatables/semantics/test_fortran_allocatable_semantics.py b/tests/fortran/allocatables/semantics/test_fortran_allocatable_semantics.py index 34337b4ef..f9fe57322 100644 --- a/tests/fortran/allocatables/semantics/test_fortran_allocatable_semantics.py +++ b/tests/fortran/allocatables/semantics/test_fortran_allocatable_semantics.py @@ -6,7 +6,7 @@ ) from prik.semantics.models import ProjectionMapping from tests.fortran._support.semantic_conversion import array_contract -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_converter_preserves_allocatable_target_metadata(): diff --git a/tests/fortran/arrays/parsing/test_array_declarations_and_shapes.py b/tests/fortran/arrays/parsing/test_array_declarations_and_shapes.py index 4ee517576..d353979a6 100644 --- a/tests/fortran/arrays/parsing/test_array_declarations_and_shapes.py +++ b/tests/fortran/arrays/parsing/test_array_declarations_and_shapes.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.parsers.fortran.models import ( FortranUseMapping, FortranVariable, diff --git a/tests/fortran/arrays/policy/test_array_shape_policy.py b/tests/fortran/arrays/policy/test_array_shape_policy.py index 853c25105..e03105d54 100644 --- a/tests/fortran/arrays/policy/test_array_shape_policy.py +++ b/tests/fortran/arrays/policy/test_array_shape_policy.py @@ -12,7 +12,7 @@ ) from prik.policy.completion import complete_semantic_policies from prik.policy.models import DeclarationCallableAction -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_array_extent_reference_requires_a_visible_scalar_argument(): diff --git a/tests/fortran/arrays/semantics/test_array_semantics.py b/tests/fortran/arrays/semantics/test_array_semantics.py index 431490c57..75e96b557 100644 --- a/tests/fortran/arrays/semantics/test_array_semantics.py +++ b/tests/fortran/arrays/semantics/test_array_semantics.py @@ -10,7 +10,7 @@ ) from prik.semantics.models import SemanticExpressionCallable from prik.printers import PyiPrinter -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text diff --git a/tests/fortran/building_shared_library/pipeline/test_root_build_api.py b/tests/fortran/building_shared_library/pipeline/test_root_build_api.py new file mode 100644 index 000000000..2aa56d9a2 --- /dev/null +++ b/tests/fortran/building_shared_library/pipeline/test_root_build_api.py @@ -0,0 +1,41 @@ +"""Public root-facade contract for normal wrapper builds.""" + +import pytest + +import prik +from prik.pipeline.build import build_fortran_extension, build_pyi_extension, build_pyi_extension_from_manifest + + +def test_root_facade_exposes_only_version_and_build_entrypoints(): + assert prik.__all__ == ( + "__version__", + "build_fortran_extension", + "build_pyi_extension", + "build_pyi_extension_from_manifest", + ) + assert prik.build_fortran_extension is build_fortran_extension + assert prik.build_pyi_extension is build_pyi_extension + assert prik.build_pyi_extension_from_manifest is build_pyi_extension_from_manifest + + for removed_name in ( + "parse_fortran_file", + "FortranParseError", + "pyi_text_to_semantic_module", + "AllocatableArray", + "FortranTypeProbeReport", + ): + with pytest.raises(AttributeError, match=removed_name): + getattr(prik, removed_name) + + +def test_root_build_entrypoints_support_direct_imports(): + """Normal users can import the documented build functions from ``prik``.""" + from prik import ( + build_fortran_extension, + build_pyi_extension, + build_pyi_extension_from_manifest, + ) + + assert build_fortran_extension is prik.build_fortran_extension + assert build_pyi_extension is prik.build_pyi_extension + assert build_pyi_extension_from_manifest is prik.build_pyi_extension_from_manifest diff --git a/tests/fortran/callbacks/parsing/test_callback_declarations.py b/tests/fortran/callbacks/parsing/test_callback_declarations.py index 7fc4c63d4..92154b377 100644 --- a/tests/fortran/callbacks/parsing/test_callback_declarations.py +++ b/tests/fortran/callbacks/parsing/test_callback_declarations.py @@ -1,6 +1,6 @@ """Callback declaration parsing.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_nested_interface_marks_dummy_as_procedure(): diff --git a/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py b/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py index cb12b2699..30de6fe85 100644 --- a/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py +++ b/tests/fortran/callbacks/semantics/test_fortran_callback_semantics.py @@ -1,13 +1,11 @@ """Tests split by stable ownership concept from `test_compile_time_values.py`.""" -from prik import ( - parse_fortran_project, -) +from prik.parsers.fortran import parse_fortran_project from prik.printers import emit_module from prik.semantics.fortran2ir import FortranToIRConverter from prik.semantics.native_contract import native_contract_issues from tests.fortran._support.semantic_conversion import get_function -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text diff --git a/tests/fortran/command_line_interface/pipeline/_support.py b/tests/fortran/command_line_interface/pipeline/_support.py index 9f06ea833..42427e5ca 100644 --- a/tests/fortran/command_line_interface/pipeline/_support.py +++ b/tests/fortran/command_line_interface/pipeline/_support.py @@ -1,7 +1,7 @@ import types from pathlib import Path -from prik import cli as prik_cli +import prik.cli as prik_cli TEST_FILE = Path(__file__).parents[2] / "source_parsing" / "parsing" / "fixtures" / "general" / "basic_subroutine.f90" diff --git a/tests/fortran/command_line_interface/pipeline/test_argument_contract.py b/tests/fortran/command_line_interface/pipeline/test_argument_contract.py index aea1bbace..e83b11de2 100644 --- a/tests/fortran/command_line_interface/pipeline/test_argument_contract.py +++ b/tests/fortran/command_line_interface/pipeline/test_argument_contract.py @@ -8,7 +8,7 @@ import pytest -from prik import cli as prik_cli +import prik.cli as prik_cli from prik.preprocessing import PreprocessingError from tests.fortran.command_line_interface.pipeline._support import ( TEST_FILE, diff --git a/tests/fortran/command_line_interface/pipeline/test_output_contract.py b/tests/fortran/command_line_interface/pipeline/test_output_contract.py index 46125dd56..26517d30c 100644 --- a/tests/fortran/command_line_interface/pipeline/test_output_contract.py +++ b/tests/fortran/command_line_interface/pipeline/test_output_contract.py @@ -14,7 +14,7 @@ import prik import pytest -from prik import cli as prik_cli +import prik.cli as prik_cli from prik.parsers.fortran import cli as fortran_parser_cli from prik.preprocessing import ( PreprocessingConfig, diff --git a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py b/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py index 486227f40..8e4049cdc 100644 --- a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py +++ b/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py @@ -10,8 +10,8 @@ import pytest -from prik import FortranParseError -from prik import cli as prik_cli +from prik.parsers.fortran import FortranParseError +import prik.cli as prik_cli from prik.parsers.fortran import cli as fortran_parser_cli from prik.preprocessing import ( PreprocessingConfig, diff --git a/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py b/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py index c773351ac..6f8c78598 100644 --- a/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py +++ b/tests/fortran/data_types/codegen/test_default_logical_scalar_lowering.py @@ -1,6 +1,6 @@ """Default-logical scalar kind adaptation through completed wrapper plans.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik.policy.completion import complete_semantic_policies from prik.policy.models import BridgeDataAction, ScalarLogicalABI diff --git a/tests/fortran/data_types/parsing/test_declarations_and_shapes.py b/tests/fortran/data_types/parsing/test_declarations_and_shapes.py index 2e210bf61..6efa2da40 100644 --- a/tests/fortran/data_types/parsing/test_declarations_and_shapes.py +++ b/tests/fortran/data_types/parsing/test_declarations_and_shapes.py @@ -1,10 +1,7 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" import pytest -from prik import ( - parse_fortran_file, - parse_fortran_project, -) +from prik.parsers.fortran import parse_fortran_file, parse_fortran_project from tests.fortran._support.parser_procedures import ( COMPILE_TIME_EXPRESSION_SOURCE, collect_project_procedure_signatures, diff --git a/tests/fortran/data_types/parsing/test_scalar_kind_parsing_properties.py b/tests/fortran/data_types/parsing/test_scalar_kind_parsing_properties.py index 990161271..b9e03acf0 100644 --- a/tests/fortran/data_types/parsing/test_scalar_kind_parsing_properties.py +++ b/tests/fortran/data_types/parsing/test_scalar_kind_parsing_properties.py @@ -5,7 +5,7 @@ given, strategies as st, ) -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from tests.fortran._support.parser_properties import _FORTRAN_SCALAR_TYPES diff --git a/tests/fortran/data_types/probes/test_fortran_type_probes.py b/tests/fortran/data_types/probes/test_fortran_type_probes.py index 922011739..9920cea01 100644 --- a/tests/fortran/data_types/probes/test_fortran_type_probes.py +++ b/tests/fortran/data_types/probes/test_fortran_type_probes.py @@ -13,8 +13,8 @@ collect_semantic_compile_time_requirements, fortran_module_to_semantic_module, ) -from prik import parse_fortran_file as parse_fortran_source -from prik import parse_fortran_project +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_project from prik.preprocessing.probes.fortran_types import ( FortranTypeProbeRecipe, FortranTypeProbeReport, @@ -86,15 +86,6 @@ def test_fortran_type_probe_wraps_long_intrinsic_import_lists(): assert all(len(line) <= 120 for line in source.splitlines()) -def test_prik_public_api_lazily_exposes_type_probe_symbols_and_rejects_unknown_names(): - import prik - - assert prik.FortranTypeProbeError is FortranTypeProbeError - assert prik.FortranTypeProbeReport is FortranTypeProbeReport - with pytest.raises(AttributeError, match="not_exported"): - _ = prik.not_exported - - def test_fortran_type_probe_rejects_statement_injection(): with pytest.raises(FortranTypeProbeError, match="single initialization expression"): build_fortran_type_probe_source(["selected_real_kind(12); stop"]) diff --git a/tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py b/tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py index de3493984..1add7b3ef 100644 --- a/tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py +++ b/tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py @@ -11,7 +11,7 @@ collect_fortran_type_storage_requirements, fortran_type_storage_expression, ) -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_intrinsic_builtin_kinds_map_to_semantic_types(): diff --git a/tests/fortran/data_types/semantics/test_types_and_storage.py b/tests/fortran/data_types/semantics/test_types_and_storage.py index c6128f438..893b27c7b 100644 --- a/tests/fortran/data_types/semantics/test_types_and_storage.py +++ b/tests/fortran/data_types/semantics/test_types_and_storage.py @@ -21,7 +21,7 @@ get_function, has_constraint, ) -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_converter_visitor_and_compatibility_methods_cover_public_paths(): diff --git a/tests/fortran/derived_types/parsing/test_derived_field_syntax.py b/tests/fortran/derived_types/parsing/test_derived_field_syntax.py index 7f254c857..4ddc11784 100644 --- a/tests/fortran/derived_types/parsing/test_derived_field_syntax.py +++ b/tests/fortran/derived_types/parsing/test_derived_field_syntax.py @@ -2,7 +2,7 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file def test_derived_type_field_default_initializers_are_preserved(): diff --git a/tests/fortran/derived_types/parsing/test_derived_procedure_syntax.py b/tests/fortran/derived_types/parsing/test_derived_procedure_syntax.py index 8d3fe7337..4c421aa18 100644 --- a/tests/fortran/derived_types/parsing/test_derived_procedure_syntax.py +++ b/tests/fortran/derived_types/parsing/test_derived_procedure_syntax.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_subroutine_derived_type_arguments_are_parsed(): diff --git a/tests/fortran/derived_types/parsing/test_derived_type_declarations.py b/tests/fortran/derived_types/parsing/test_derived_type_declarations.py index c3da3443e..132b923c3 100644 --- a/tests/fortran/derived_types/parsing/test_derived_type_declarations.py +++ b/tests/fortran/derived_types/parsing/test_derived_type_declarations.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_derived_type_fields_and_methods_detection(): diff --git a/tests/fortran/derived_types/parsing/test_derived_type_errors.py b/tests/fortran/derived_types/parsing/test_derived_type_errors.py index fec9944d6..be6864a99 100644 --- a/tests/fortran/derived_types/parsing/test_derived_type_errors.py +++ b/tests/fortran/derived_types/parsing/test_derived_type_errors.py @@ -1,6 +1,6 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file # --------------------------------------------------------------------------- diff --git a/tests/fortran/derived_types/parsing/test_derived_type_properties.py b/tests/fortran/derived_types/parsing/test_derived_type_properties.py index 6eef82dc7..7c2523e50 100644 --- a/tests/fortran/derived_types/parsing/test_derived_type_properties.py +++ b/tests/fortran/derived_types/parsing/test_derived_type_properties.py @@ -5,7 +5,7 @@ given, strategies as st, ) -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from tests.fortran._support.parser_properties import ( _FORTRAN_IDENTIFIER_STEMS, _FORTRAN_SCALAR_TYPES, diff --git a/tests/fortran/derived_types/parsing/test_derived_type_scopes.py b/tests/fortran/derived_types/parsing/test_derived_type_scopes.py index c37a781d1..57195533e 100644 --- a/tests/fortran/derived_types/parsing/test_derived_type_scopes.py +++ b/tests/fortran/derived_types/parsing/test_derived_type_scopes.py @@ -1,4 +1,4 @@ -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_module_symbol_tables_keep_derived_type_fields_scoped_to_type(): diff --git a/tests/fortran/derived_types/parsing/test_parameterized_derived_types.py b/tests/fortran/derived_types/parsing/test_parameterized_derived_types.py index aaa58ec45..5d6319a85 100644 --- a/tests/fortran/derived_types/parsing/test_parameterized_derived_types.py +++ b/tests/fortran/derived_types/parsing/test_parameterized_derived_types.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from tests.fortran._support.parser_procedures import COMPILE_TIME_EXPRESSION_SOURCE diff --git a/tests/fortran/derived_types/parsing/test_type_bound_diagnostics.py b/tests/fortran/derived_types/parsing/test_type_bound_diagnostics.py index 567025bde..603293fde 100644 --- a/tests/fortran/derived_types/parsing/test_type_bound_diagnostics.py +++ b/tests/fortran/derived_types/parsing/test_type_bound_diagnostics.py @@ -1,7 +1,7 @@ """Tests split by stable ownership concept from `test_source_form_and_diagnostics_regressions.py`.""" import pytest -from prik import FortranParseError +from prik.parsers.fortran import FortranParseError from prik.parsers.fortran.models import FortranDerivedType from prik.parsers.fortran.parser import FortranParser diff --git a/tests/fortran/derived_types/semantics/test_derived_type_identity.py b/tests/fortran/derived_types/semantics/test_derived_type_identity.py index d71f6b8aa..c855d47aa 100644 --- a/tests/fortran/derived_types/semantics/test_derived_type_identity.py +++ b/tests/fortran/derived_types/semantics/test_derived_type_identity.py @@ -2,7 +2,7 @@ from prik.semantics.fortran2ir import fortran_module_to_semantic_module from tests.fortran._support.semantic_conversion import get_function -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_procedure_local_derived_type_rename_uses_origin_type_identity(): diff --git a/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py b/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py index debc6603f..43768d33f 100644 --- a/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py +++ b/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py @@ -22,7 +22,7 @@ ) from prik.semantics.native_contract import native_contract_issues from tests.fortran._support.semantic_conversion import get_class -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text diff --git a/tests/fortran/derived_types/semantics/test_imported_derived_semantics.py b/tests/fortran/derived_types/semantics/test_imported_derived_semantics.py index 9c86449bb..5cfe6ea0d 100644 --- a/tests/fortran/derived_types/semantics/test_imported_derived_semantics.py +++ b/tests/fortran/derived_types/semantics/test_imported_derived_semantics.py @@ -1,9 +1,7 @@ """Tests split by stable ownership concept from `test_compile_time_values.py`.""" from dataclasses import asdict -from prik import ( - parse_fortran_project, -) +from prik.parsers.fortran import parse_fortran_project from prik.parsers.fortran.models import ( FortranArgument, FortranDerivedType, @@ -25,7 +23,7 @@ SemanticVariable, ) from tests.fortran._support.semantic_conversion import get_function -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_converter_preserves_imported_derived_contexts_through_dispatch_paths(): diff --git a/tests/fortran/enumerations/parsing/test_enum_diagnostics.py b/tests/fortran/enumerations/parsing/test_enum_diagnostics.py index 4166b7e5d..d2f9eaa7d 100644 --- a/tests/fortran/enumerations/parsing/test_enum_diagnostics.py +++ b/tests/fortran/enumerations/parsing/test_enum_diagnostics.py @@ -2,7 +2,7 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file from prik.parsers.fortran.parser import FortranParser from tests.fortran._support.parser_regressions import _unit diff --git a/tests/fortran/enumerations/parsing/test_enum_syntax.py b/tests/fortran/enumerations/parsing/test_enum_syntax.py index e8b55682e..463da4f7e 100644 --- a/tests/fortran/enumerations/parsing/test_enum_syntax.py +++ b/tests/fortran/enumerations/parsing/test_enum_syntax.py @@ -2,7 +2,7 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file def test_valid_enum_subunit_accepts_optional_separator_and_multiple_enumerators(): diff --git a/tests/fortran/enumerations/semantics/test_enum_semantics.py b/tests/fortran/enumerations/semantics/test_enum_semantics.py index 9e979251b..74dac6ace 100644 --- a/tests/fortran/enumerations/semantics/test_enum_semantics.py +++ b/tests/fortran/enumerations/semantics/test_enum_semantics.py @@ -3,7 +3,7 @@ from pathlib import Path -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.printers import emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module diff --git a/tests/fortran/error_handling/parsing/test_fortran_diagnostics.py b/tests/fortran/error_handling/parsing/test_fortran_diagnostics.py index f4a90c4ac..5c4174e1d 100644 --- a/tests/fortran/error_handling/parsing/test_fortran_diagnostics.py +++ b/tests/fortran/error_handling/parsing/test_fortran_diagnostics.py @@ -1,6 +1,6 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file # --------------------------------------------------------------------------- diff --git a/tests/fortran/functions/parsing/test_function_headers.py b/tests/fortran/functions/parsing/test_function_headers.py index 222bb34c6..6121568c5 100644 --- a/tests/fortran/functions/parsing/test_function_headers.py +++ b/tests/fortran/functions/parsing/test_function_headers.py @@ -2,7 +2,7 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file def test_typed_function_result_headers_are_parsed_from_inline_fortran(): diff --git a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py index f61fb111f..901400bb7 100644 --- a/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py +++ b/tests/fortran/functions/parsing/test_procedure_and_interface_regressions.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_source_form_and_diagnostics_regressions.py`.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.parsers.fortran.models import ( FortranArgument, FortranProcedureSignature, diff --git a/tests/fortran/functions/semantics/test_fortran_conversion_procedures_and_interfaces.py b/tests/fortran/functions/semantics/test_fortran_conversion_procedures_and_interfaces.py index 301670ec5..609d7c97e 100644 --- a/tests/fortran/functions/semantics/test_fortran_conversion_procedures_and_interfaces.py +++ b/tests/fortran/functions/semantics/test_fortran_conversion_procedures_and_interfaces.py @@ -14,7 +14,7 @@ SemanticType, ) from tests.fortran._support.semantic_conversion import get_function -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.semantics import models as semantic_models diff --git a/tests/fortran/functions/semantics/test_fortran_function_semantics.py b/tests/fortran/functions/semantics/test_fortran_function_semantics.py index 346ee408d..7901e5b7b 100644 --- a/tests/fortran/functions/semantics/test_fortran_function_semantics.py +++ b/tests/fortran/functions/semantics/test_fortran_function_semantics.py @@ -7,7 +7,7 @@ from prik.semantics.models import ProjectionMapping from tests.fortran._support.semantic_conversion import get_function from prik.semantics.metadata import PROJECTED_OUTPUT_METADATA -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_missing_intent_scalar_uses_conservative_replacement_projection(): diff --git a/tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py b/tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py index 3d7c5a598..84a261fb8 100644 --- a/tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py +++ b/tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py @@ -4,7 +4,7 @@ import pytest -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from tests.fortran._support.parser_procedures import ( parse_fortran_interfaces, parse_fortran_module, diff --git a/tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py b/tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py index 09acbfbe6..c223fd023 100644 --- a/tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py +++ b/tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py @@ -8,7 +8,7 @@ fortran_module_to_semantic_module, ) from prik.semantics.metadata import BIND_TARGET_METADATA -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source OPERATOR_F90_SOURCE = Path(__file__).parents[1] / "end_to_end" / "fixtures" / "foperators_f90.f90" diff --git a/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py b/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py index ed15fe979..44507bdf7 100644 --- a/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py +++ b/tests/fortran/infrastructure/codegen/test_refactoring_goldens.py @@ -18,7 +18,7 @@ import pytest -from prik import parse_fortran_project +from prik.parsers.fortran import parse_fortran_project from prik.pipeline.wrapper import WrapperGenerator from prik.planning import WrapperPlanner from prik.pipeline.pyi import emit_module_stubs diff --git a/tests/fortran/infrastructure/execution_examples/test_execution_examples.py b/tests/fortran/infrastructure/execution_examples/test_execution_examples.py index 5452e5d10..9a51ab5b7 100644 --- a/tests/fortran/infrastructure/execution_examples/test_execution_examples.py +++ b/tests/fortran/infrastructure/execution_examples/test_execution_examples.py @@ -29,10 +29,6 @@ def _run_example(relative_path: str, *arguments: str) -> str: return completed.stdout -def test_fortran_root_init_execution_example(): - assert _run_example("prik/__init__.py") == ("PRIK 0.2.1\nPublic parser result: subroutine ping from ping.f90\n") - - def test_fortran_root_cli_execution_example(): assert _run_example("prik/cli.py", "--version") == "prik 0.2.1\n" diff --git a/tests/fortran/memory_management/runtime/test_handle_lifecycle.py b/tests/fortran/memory_management/runtime/test_handle_lifecycle.py index ddb4a1520..e186c9a33 100644 --- a/tests/fortran/memory_management/runtime/test_handle_lifecycle.py +++ b/tests/fortran/memory_management/runtime/test_handle_lifecycle.py @@ -3,11 +3,9 @@ import ctypes import gc import numpy as np -import prik import pytest from prik.runtime.handles import ( AllocatableArray, - NativeArrayHandleBase, PointerArray, _native_array_descriptor_handoff_for_binding, _native_array_handle_from_generated_ops, @@ -19,13 +17,6 @@ ) -def test_runtime_handle_classes_are_public_api_exports(): - assert prik.AllocatableArray is AllocatableArray - assert prik.NativeArrayHandleBase is NativeArrayHandleBase - assert prik.PointerArray is PointerArray - assert {"AllocatableArray", "NativeArrayHandleBase", "PointerArray"} <= set(prik.__all__) - - def test_generated_handle_factory_adapts_private_operations_to_runtime_protocol(): owner = object() value = np.arange(3, dtype=np.float64) diff --git a/tests/fortran/modules/parsing/test_project_scope_models.py b/tests/fortran/modules/parsing/test_project_scope_models.py index 4f1d3c78e..8a00c141c 100644 --- a/tests/fortran/modules/parsing/test_project_scope_models.py +++ b/tests/fortran/modules/parsing/test_project_scope_models.py @@ -2,7 +2,7 @@ import pytest -from prik import FortranParseError, parse_fortran_file, parse_fortran_project +from prik.parsers.fortran import FortranParseError, parse_fortran_file, parse_fortran_project from prik.parsers.fortran.parser import FortranParser diff --git a/tests/fortran/modules/parsing/test_scope_handling.py b/tests/fortran/modules/parsing/test_scope_handling.py index f21a9cbde..3e5867c6c 100644 --- a/tests/fortran/modules/parsing/test_scope_handling.py +++ b/tests/fortran/modules/parsing/test_scope_handling.py @@ -1,7 +1,7 @@ import pytest from prik.parsers.fortran.models import FortranParseError -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_same_argument_name_in_different_procedures_is_allowed(): diff --git a/tests/fortran/modules/semantics/test_module_contract_semantics.py b/tests/fortran/modules/semantics/test_module_contract_semantics.py index 77e518211..3a346410c 100644 --- a/tests/fortran/modules/semantics/test_module_contract_semantics.py +++ b/tests/fortran/modules/semantics/test_module_contract_semantics.py @@ -1,7 +1,7 @@ """Semantic contracts for module state and common blocks.""" from prik.semantics.fortran2ir import fortran_module_to_semantic_module -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_module_common_block_storage_stays_internal(): diff --git a/tests/fortran/modules/semantics/test_modules_and_imports.py b/tests/fortran/modules/semantics/test_modules_and_imports.py index 92f750c3b..33601bd02 100644 --- a/tests/fortran/modules/semantics/test_modules_and_imports.py +++ b/tests/fortran/modules/semantics/test_modules_and_imports.py @@ -17,9 +17,9 @@ get_function, has_constraint, ) -from prik import parse_fortran_project +from prik.parsers.fortran import parse_fortran_project from prik.semantics.fortran2ir import fortran_project_to_semantic_modules -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_converter_normalizes_wrapped_types_and_resolves_wildcard_imports(): diff --git a/tests/fortran/optional_arguments/parsing/test_optional_declarations.py b/tests/fortran/optional_arguments/parsing/test_optional_declarations.py index ec2a655b5..3e1c0dc4f 100644 --- a/tests/fortran/optional_arguments/parsing/test_optional_declarations.py +++ b/tests/fortran/optional_arguments/parsing/test_optional_declarations.py @@ -4,7 +4,7 @@ import pytest -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file FIXTURES = Path(__file__).parents[1] / "end_to_end" / "fixtures" diff --git a/tests/fortran/optional_arguments/semantics/test_optional_fortran_semantics.py b/tests/fortran/optional_arguments/semantics/test_optional_fortran_semantics.py index 436e1bc31..c7799bb7e 100644 --- a/tests/fortran/optional_arguments/semantics/test_optional_fortran_semantics.py +++ b/tests/fortran/optional_arguments/semantics/test_optional_fortran_semantics.py @@ -8,7 +8,7 @@ get_function, ) from prik.semantics.metadata import PROJECTED_OUTPUT_METADATA -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_optional_argument(): diff --git a/tests/fortran/pointers/parsing/test_pointer_declarations.py b/tests/fortran/pointers/parsing/test_pointer_declarations.py index c764e6924..67e9e6cf8 100644 --- a/tests/fortran/pointers/parsing/test_pointer_declarations.py +++ b/tests/fortran/pointers/parsing/test_pointer_declarations.py @@ -1,6 +1,6 @@ """Fortran pointer declaration parsing.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_scalar_array_and_optional_pointer_attributes_are_parsed(): diff --git a/tests/fortran/pointers/semantics/test_pointer_semantics.py b/tests/fortran/pointers/semantics/test_pointer_semantics.py index 15c418f08..645f0e1ed 100644 --- a/tests/fortran/pointers/semantics/test_pointer_semantics.py +++ b/tests/fortran/pointers/semantics/test_pointer_semantics.py @@ -12,7 +12,7 @@ ) from prik.semantics.metadata import NATIVE_ARRAY_DESCRIPTOR_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA from prik.semantics.native_array_handles import native_array_descriptor_kind -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_fortran_pointer_arrays_and_scalars_preserve_descriptor_semantics(): diff --git a/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py b/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py index 48bc04150..1dcb44256 100644 --- a/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py +++ b/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py @@ -4,7 +4,7 @@ import pytest -from prik import pyi_text_to_semantic_module +from prik.pipeline.pyi import pyi_text_to_semantic_module from prik.pipeline.pyi import pyi_file_to_semantic_module from prik.policy.completion import complete_semantic_policies from prik.policy.models import ClassInvocationKind, OverloadMatchKind diff --git a/tests/fortran/semantic_ir/semantics/test_compile_time_values.py b/tests/fortran/semantic_ir/semantics/test_compile_time_values.py index 1e03c2b25..ad91de716 100644 --- a/tests/fortran/semantic_ir/semantics/test_compile_time_values.py +++ b/tests/fortran/semantic_ir/semantics/test_compile_time_values.py @@ -32,7 +32,7 @@ SemanticType, ) from tests.fortran._support.semantic_conversion import get_function -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.semantics import models as semantic_models diff --git a/tests/fortran/semantic_ir/semantics/test_fortran_conversion_properties.py b/tests/fortran/semantic_ir/semantics/test_fortran_conversion_properties.py index 15309f474..7749534c8 100644 --- a/tests/fortran/semantic_ir/semantics/test_fortran_conversion_properties.py +++ b/tests/fortran/semantic_ir/semantics/test_fortran_conversion_properties.py @@ -3,7 +3,7 @@ import pytest from dataclasses import asdict from hypothesis import given -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.semantics.fortran2ir import fortran_file_to_semantic_modules from tests.fortran._support.semantic_properties import fortran_scalar_subroutines diff --git a/tests/fortran/semantic_ir/semantics/test_semantic_specialization_properties.py b/tests/fortran/semantic_ir/semantics/test_semantic_specialization_properties.py index be53ed0b5..b28f416e0 100644 --- a/tests/fortran/semantic_ir/semantics/test_semantic_specialization_properties.py +++ b/tests/fortran/semantic_ir/semantics/test_semantic_specialization_properties.py @@ -6,7 +6,7 @@ given, strategies as st, ) -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.semantics.fortran2ir import ( fortran_file_to_semantic_modules, resolve_semantic_compile_time_values, diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py b/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py index 38e417d8c..0478b1d37 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py @@ -1,7 +1,7 @@ """Tests split by stable ownership concept from `test_imports_and_packages.py`.""" import pytest -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.printers import ( PyiPrinter, emit_module, diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py b/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py index 4d3605527..d61695840 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py @@ -1,7 +1,7 @@ """Tests split by stable ownership concept from `test_imports_and_packages.py`.""" import pytest -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.pipeline.pyi import emit_module_stubs from prik.printers import PyiPrinter, emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py b/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py index d3af8deed..1b8219b59 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py @@ -1,6 +1,6 @@ from pathlib import Path -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik.printers import emit_module diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py index 1cf49d52f..f112c09e8 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py @@ -2,7 +2,7 @@ import prik import pytest -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.printers import ( PyiPrinter, emit_module, diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py b/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py index 7fffcab18..d3ea0d06b 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py +++ b/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py @@ -1,7 +1,7 @@ """Tests split by stable ownership concept from `test_imports_and_packages.py`.""" import pytest -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source from prik.printers import ( PyiPrinter, emit_module, diff --git a/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py b/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py index be7d7d446..5e0de068c 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py @@ -3,7 +3,7 @@ import ast import pytest from dataclasses import asdict -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.printers import emit_module from prik.contracts import CONTRACT_SYMBOLS from prik.semantics.fortran2ir import fortran_file_to_semantic_modules diff --git a/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py b/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py index 56e463e61..04e35b422 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py @@ -2,7 +2,7 @@ import pytest from pathlib import Path -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.printers import emit_module from prik.pipeline.pyi import ( pyi_file_to_semantic_module, diff --git a/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py b/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py index 95205da28..f5d42cc94 100644 --- a/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py +++ b/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py @@ -2,7 +2,7 @@ import ast import pytest -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.printers import emit_module from prik.pipeline.pyi import pyi_text_to_semantic_module from prik.semantics.fortran2ir import fortran_file_to_semantic_modules diff --git a/tests/fortran/source_parsing/parsing/generate_error_goldens.py b/tests/fortran/source_parsing/parsing/generate_error_goldens.py index dd81fe39a..fde55268c 100644 --- a/tests/fortran/source_parsing/parsing/generate_error_goldens.py +++ b/tests/fortran/source_parsing/parsing/generate_error_goldens.py @@ -34,7 +34,7 @@ def parse_fortran_modules(source, filename=None): def _parse_fortran_file(source, filename=None): - from prik import parse_fortran_file + from prik.parsers.fortran import parse_fortran_file return parse_fortran_file(source, filename=filename) @@ -51,7 +51,7 @@ def _get_parser_for_fixture(fixture: Path) -> str: def _serialize_error_fixture(fixture: Path) -> dict: - from prik import FortranParseError + from prik.parsers.fortran import FortranParseError source = fixture.read_text(encoding="utf-8") parser_name = _get_parser_for_fixture(fixture) diff --git a/tests/fortran/source_parsing/parsing/generate_parser_goldens.py b/tests/fortran/source_parsing/parsing/generate_parser_goldens.py index 551efcc70..24ed3604a 100644 --- a/tests/fortran/source_parsing/parsing/generate_parser_goldens.py +++ b/tests/fortran/source_parsing/parsing/generate_parser_goldens.py @@ -31,7 +31,7 @@ def _parser_filename_for_fixture(fixture: Path) -> str: def _serialize_fixture(fixture: Path) -> dict: - from prik import parse_fortran_file + from prik.parsers.fortran import parse_fortran_file source = fixture.read_text(encoding="utf-8") parsed = parse_fortran_file(source, filename=_parser_filename_for_fixture(fixture)) diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_interface_edges.py b/tests/fortran/source_parsing/parsing/test_declaration_and_interface_edges.py index 700d36157..8c407d474 100644 --- a/tests/fortran/source_parsing/parsing/test_declaration_and_interface_edges.py +++ b/tests/fortran/source_parsing/parsing/test_declaration_and_interface_edges.py @@ -4,7 +4,7 @@ from prik.parsers.fortran.models import FortranModule from prik.parsers.fortran.parser import FortranParser, _ParserScope -from prik import FortranParseError, parse_fortran_file, parse_fortran_project +from prik.parsers.fortran import FortranParseError, parse_fortran_file, parse_fortran_project def test_legacy_star_kind_and_declarations_without_double_colon_are_resolved(): diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py index 3a149da7e..d5d9bbb66 100644 --- a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py @@ -2,7 +2,7 @@ import pytest from pathlib import Path -from prik import FortranParseError +from prik.parsers.fortran import FortranParseError from prik.parsers.fortran.models import ( FortranArgument, FortranDerivedType, diff --git a/tests/fortran/source_parsing/parsing/test_derived_types_and_program_units.py b/tests/fortran/source_parsing/parsing/test_derived_types_and_program_units.py index a13cc7a15..c7844d0de 100644 --- a/tests/fortran/source_parsing/parsing/test_derived_types_and_program_units.py +++ b/tests/fortran/source_parsing/parsing/test_derived_types_and_program_units.py @@ -1,11 +1,7 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" import pytest -from prik import ( - FortranParseError, - parse_fortran_file, - parse_fortran_project, -) +from prik.parsers.fortran import FortranParseError, parse_fortran_file, parse_fortran_project from tests.fortran._support.parser_procedures import ( parse_fortran_block_data_unit, parse_fortran_module, diff --git a/tests/fortran/source_parsing/parsing/test_error_fixture_suite.py b/tests/fortran/source_parsing/parsing/test_error_fixture_suite.py index 2a1a592ec..d161f5b73 100644 --- a/tests/fortran/source_parsing/parsing/test_error_fixture_suite.py +++ b/tests/fortran/source_parsing/parsing/test_error_fixture_suite.py @@ -4,7 +4,7 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file def parse_fortran_procedures(source, filename=None): diff --git a/tests/fortran/source_parsing/parsing/test_error_handling.py b/tests/fortran/source_parsing/parsing/test_error_handling.py index 952c2ea19..65221dce5 100644 --- a/tests/fortran/source_parsing/parsing/test_error_handling.py +++ b/tests/fortran/source_parsing/parsing/test_error_handling.py @@ -1,6 +1,6 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file # --------------------------------------------------------------------------- diff --git a/tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py b/tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py index 673248cc4..69c6aed3a 100644 --- a/tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py +++ b/tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py @@ -5,7 +5,7 @@ import pytest -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def parse_fortran_modules(source, filename=None): diff --git a/tests/fortran/source_parsing/parsing/test_fortran_parser_procedures_and_interfaces.py b/tests/fortran/source_parsing/parsing/test_fortran_parser_procedures_and_interfaces.py index c36954bbe..0368eea6f 100644 --- a/tests/fortran/source_parsing/parsing/test_fortran_parser_procedures_and_interfaces.py +++ b/tests/fortran/source_parsing/parsing/test_fortran_parser_procedures_and_interfaces.py @@ -1,10 +1,7 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" import pytest -from prik import ( - parse_fortran_file, - parse_fortran_project, -) +from prik.parsers.fortran import parse_fortran_file, parse_fortran_project from prik.parsers.fortran.models import ( FortranFunctionCall, FortranSlice, diff --git a/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py b/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py index 860dad4bc..246860810 100644 --- a/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py +++ b/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py @@ -3,10 +3,7 @@ import pytest from contextlib import suppress from hypothesis import given -from prik import ( - FortranParseError, - parse_fortran_file, -) +from prik.parsers.fortran import FortranParseError, parse_fortran_file from prik.pipeline.pyi import emit_module_stubs from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text from prik.semantics.fortran2ir import fortran_file_to_semantic_modules diff --git a/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py b/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py index 60952bc81..734cfd0bd 100644 --- a/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py +++ b/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py @@ -8,7 +8,7 @@ from prik.semantics.fortran2ir import fortran_file_to_semantic_modules from prik.pipeline.pyi import emit_module_stubs -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file pytestmark = pytest.mark.skip(reason="Benchmarks are parked until benchmark adoption resumes.") diff --git a/tests/fortran/source_parsing/parsing/test_public_entrypoints.py b/tests/fortran/source_parsing/parsing/test_public_entrypoints.py index 7291250a9..d485c3d75 100644 --- a/tests/fortran/source_parsing/parsing/test_public_entrypoints.py +++ b/tests/fortran/source_parsing/parsing/test_public_entrypoints.py @@ -2,9 +2,7 @@ import pytest -from prik.parsers.fortran.parser import FortranParser -from prik import FortranParseError, parse_fortran_file, parse_fortran_project -from prik.parsers.fortran.parser import FortranParser as PackageFortranParser +from prik.parsers.fortran import FortranParseError, FortranParser, parse_fortran_file, parse_fortran_project from prik.semantics.fortran2ir import fortran_file_to_semantic_modules @@ -57,8 +55,8 @@ def test_parser_public_entrypoint_aliases_and_singular_contracts_use_inline_sour ) -def test_prik_package_contains_fortran_parser_and_semantics_subpackages(): - parsed_fortran = PackageFortranParser().parse_file( +def test_fortran_parser_package_exports_the_supported_parser_api(): + parsed_fortran = FortranParser().parse_file( """ subroutine work(n) integer, intent(in) :: n diff --git a/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py b/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py index 545c46902..1b2f780fc 100644 --- a/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py @@ -1,6 +1,6 @@ """Minimized parser regressions extracted from former third-party sources.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file from prik.parsers.fortran.lexer import preprocess_lines, strip_comment from prik.parsers.fortran.models import FortranProcedureSignature from prik.parsers.fortran.parser import FortranParser, _SourceUnitScanner diff --git a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py index 8642efd23..db6f3e357 100644 --- a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py +++ b/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py @@ -2,10 +2,7 @@ import pytest from pathlib import Path -from prik import ( - FortranParseError, - parse_fortran_file, -) +from prik.parsers.fortran import FortranParseError, parse_fortran_file from prik.parsers.fortran.models import ( FortranArgument, FortranDerivedType, diff --git a/tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py b/tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py index 297ecd5c5..671ac5392 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py +++ b/tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py @@ -6,7 +6,7 @@ import pytest -from prik import FortranParseError, parse_fortran_file +from prik.parsers.fortran import FortranParseError, parse_fortran_file def test_fortran_lexer_strip_comment_preserves_directives_and_quoted_bangs(): diff --git a/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py b/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py index 828649ffb..be29896c1 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py +++ b/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py @@ -8,10 +8,7 @@ strategies as st, ) from pathlib import Path -from prik import ( - FortranParseError, - parse_fortran_file, -) +from prik.parsers.fortran import FortranParseError, parse_fortran_file from prik.preprocessing import ( PreprocessingConfig, preprocess_source, diff --git a/tests/fortran/strings/parsing/test_character_declarations.py b/tests/fortran/strings/parsing/test_character_declarations.py index 51073bcf5..e75ebccb7 100644 --- a/tests/fortran/strings/parsing/test_character_declarations.py +++ b/tests/fortran/strings/parsing/test_character_declarations.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_legacy_character_and_star_kind_declarations_from_inline_fortran(): diff --git a/tests/fortran/strings/parsing/test_character_length_parsing.py b/tests/fortran/strings/parsing/test_character_length_parsing.py index f6d6b0014..dbef8de95 100644 --- a/tests/fortran/strings/parsing/test_character_length_parsing.py +++ b/tests/fortran/strings/parsing/test_character_length_parsing.py @@ -1,6 +1,6 @@ """Declaration parsing, interfaces, and less common scope edges.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_character_entity_lengths_and_assumed_bounds_are_preserved(): diff --git a/tests/fortran/strings/parsing/test_fixed_form_character_parsing.py b/tests/fortran/strings/parsing/test_fixed_form_character_parsing.py index 34bbe7905..f039c627d 100644 --- a/tests/fortran/strings/parsing/test_fixed_form_character_parsing.py +++ b/tests/fortran/strings/parsing/test_fixed_form_character_parsing.py @@ -1,6 +1,6 @@ """Tests split by stable ownership concept from `test_procedures_and_interfaces.py`.""" -from prik import parse_fortran_file +from prik.parsers.fortran import parse_fortran_file def test_fixed_form_character_star_length_is_parsed(): diff --git a/tests/fortran/strings/semantics/test_fortran_string_semantics.py b/tests/fortran/strings/semantics/test_fortran_string_semantics.py index b31bf0913..e7d28deaf 100644 --- a/tests/fortran/strings/semantics/test_fortran_string_semantics.py +++ b/tests/fortran/strings/semantics/test_fortran_string_semantics.py @@ -2,7 +2,7 @@ from prik.semantics.fortran2ir import fortran_module_to_semantic_module from tests.fortran._support.semantic_conversion import get_function -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_scalar_character_inout_is_projected_as_replacement_return(): diff --git a/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py b/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py index 482622bcd..da42c8fe0 100644 --- a/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py +++ b/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py @@ -4,7 +4,7 @@ from prik.semantics.models import ProjectionMapping from tests.fortran._support.semantic_conversion import get_function from prik.semantics.metadata import PROJECTED_OUTPUT_METADATA -from prik import parse_fortran_file as parse_fortran_source +from prik.parsers.fortran import parse_fortran_file as parse_fortran_source def test_primitive_scalar_inout_stays_visible_and_projects_replacement_return(): From 3432196b0b3b4aeb239b11e9d0ffe29aef013948 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 12 Aug 2026 18:16:49 +0100 Subject: [PATCH 22/22] move stage_values to utilities --- CHANGELOG.md | 2 ++ README.md | 6 ++--- docs/developer/architecture.md | 22 +++++++------------ docs/developer/packages/utilities.md | 22 ++++++++++++++++++- .../recipes/use-python-inspection-apis.md | 2 +- prik/README.md | 8 +++---- prik/codegen/nodes.py | 2 +- prik/pipeline/wrapper.py | 2 +- prik/planning/models.py | 2 +- prik/printers/c.py | 2 +- prik/printers/fortran.py | 2 +- prik/{ => utilities}/stage_values.py | 2 +- .../pipeline/test_generated_wrapper_build.py | 2 +- .../test_execution_examples.py | 4 ++-- .../pipeline/test_wrapper_generator.py | 2 +- .../test_pyi_printer_imports_and_packages.py | 8 +++---- 16 files changed, 52 insertions(+), 38 deletions(-) rename prik/{ => utilities}/stage_values.py (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 934e6c305..fdf85d720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ release tags add a leading `v` to the package version. - Reduced the root `prik` API to its version and normal-user build entrypoints; parser, semantic, probe, runtime, and planning tools now use their owning package import paths. +- Moved stage-record freezing from `prik.stage_values` to + `prik.utilities.stage_values`; the root module path was removed. - Made `prik` an import-only package boundary by removing its direct-script demonstration; command and stage-value examples remain available from their owning modules. diff --git a/README.md b/README.md index 184d9bf41..b7fa93d0e 100644 --- a/README.md +++ b/README.md @@ -519,8 +519,8 @@ explicit build directories, depending on the command mode. ## Python API -Public entrypoints cover Fortran extension builds, parsing, semantic -conversion and `.pyi` emission: +Root entrypoints cover normal Fortran extension builds. Advanced parsing, +semantic conversion, and `.pyi` emission use their owning packages: ```python from prik import build_fortran_extension @@ -540,7 +540,7 @@ strings, focused tests, and already-preprocessed inputs.