From 7219708b766616941833cda792f224d80ff48971 Mon Sep 17 00:00:00 2001 From: said Date: Tue, 18 Aug 2026 18:58:18 +0100 Subject: [PATCH 01/26] update docs --- AGENTS.md | 18 + CHANGELOG.md | 7 + README.md | 42 +- .../native-entrypoint-adoption-checklist.md | 97 +++- docs/user/faq/index.md | 4 +- docs/user/guide/building-shared-library.md | 9 + docs/user/guide/index.md | 8 + docs/user/guide/strings.md | 11 +- docs/user/index.md | 12 + docs/user/language-support/feature-matrix.md | 41 +- docs/user/language-support/index.md | 16 +- docs/user/reference/cli-commands.md | 466 ++++++------------ docs/user/reference/diagnostic-codes.md | 213 +++++--- docs/user/reference/index.md | 46 +- docs/user/reference/python-api.md | 84 ++-- prik/codegen/fortran/bridge.py | 18 +- prik/planning/models.py | 1 + prik/planning/planner.py | 1 + prik/policy/construction.py | 49 ++ prik/policy/models.py | 1 + .../codegen/test_string_input_lowering.py | 66 +++ .../policy/test_string_wrapper_policy.py | 105 ++++ 22 files changed, 838 insertions(+), 477 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 18f2596ee..ca30e8e30 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,6 +80,24 @@ the selected plan requires a genuinely new emitted-code mechanism; those generators should otherwise keep reusing and dispatching existing planned paths. +To answer an ABI question, or to decide whether something belongs in the +binding or in the Fortran bridge, first ask: **how would this work for a +`bind(C)` procedure, where there is no bridge at all?** A direct entrypoint has +only the binding and the user's C ABI symbol, so whatever the direct route must +do is binding-owned by definition. The bridge then owns exactly the remainder: +the work that makes an ordinary non-`bind(C)` procedure reachable through that +same completed plan. Deriving the boundary this way keeps one shared entrypoint +contract for both routes instead of two parallel designs. + +The question is still decisive when the form cannot be `bind(C)` at all. A +Fortran type that no interoperable interface can declare — a deferred-length +`character(len=:)` dummy, for example, which the standard rejects in a +`bind(C)` interface because character dummies there must have length 1 — proves +that a generated Fortran adapter is mandatory rather than optional, and names +what that adapter has to construct: the non-interoperable local the native +dummy requires. Record that reasoning with the completed policy so the bridge +implements a decided mechanism rather than rediscovering it. + After every implementation task, the final summary must include a breakdown of the stages that actually changed. Relevant stages include parsing, semantic IR construction, post-IR policy completion, wrapper planning/direct lowering, binding diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a81b5a0..71220433a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ release tags add a leading `v` to the package version. ### Added +- Added wrapper support for read-only deferred-length scalar character + arguments (`character(len=:), allocatable, intent(in)`). The generated + Fortran adapter now builds the allocatable local the native dummy requires + instead of a fixed-length temporary the compiler rejected. The C ABI is + unchanged: the binding still passes a byte buffer and a length. Mutable + `intent(inout)` and `pointer` deferred-length arguments now stop at policy + with a diagnostic instead of failing in the Fortran compiler. - Added a native-entrypoint adoption roadmap for selective direct Fortran `bind(C)` calls and the initial direct-only C wrapper backend, including conservative starter-contract defaults for ambiguous C pointers. diff --git a/README.md b/README.md index 5191002e8..196252872 100644 --- a/README.md +++ b/README.md @@ -210,13 +210,45 @@ charts below come from the latest successfully deployed benchmark snapshot. ## Current limitations -PRIK does not yet support: +PRIK rejects these forms rather than wrapping them unsafely. Most fail before +code generation with a diagnostic naming the boundary and the reason. -- arrays of derived types; -- procedure pointers, including procedure-pointer module variables and callbacks - retained after the wrapped call; or +**Types and arrays** + +- arrays of derived types, and assumed-type `type(*)` arrays; +- character arrays that cannot be represented as a fixed-width NumPy bytes + dtype, and mutable or pointer deferred-length scalar character arguments + (`character(len=:)` with `intent(inout)` or `pointer`); read-only + `allocatable, intent(in)` arguments and `allocatable, intent(out)` results + are supported; +- quad precision — `real(16)` and `complex(16)` — which has no portable NumPy + dtype. Everything narrower is supported, including all `logical` kinds. + +**Procedures and polymorphism** + +- procedure pointers, including procedure-pointer module variables, and + callbacks retained after the wrapped call returns; - polymorphic outputs, mutable polymorphic arguments, polymorphic arrays, - unlimited polymorphism (`class(*)`), abstract types, and deferred bindings. + unlimited polymorphism (`class(*)`), abstract types, and deferred bindings; +- constructor overload sets whose candidates are ambiguous or incomplete. + +**Storage and ownership** + +- pointer target deallocation and writable reassociation, which stay gated + behind explicit completed policy. + +Scalar allocatable and pointer *arguments* are supported — they cross the +boundary as values (`Float64 | None`) rather than as array handles, so there is +no rank-zero handle form such as `Allocatable[Float64]()`. + +**Builds** + +- dependency-graph discovery, prebuilt module-path resolution, and external + library discovery. Pass sources, objects, and libraries in the order you + want them built and linked. + +The [language feature matrix](https://pynumlab.github.io/prik/user/language-support/feature-matrix/) +records the full support status of every feature with its evidence. ## Installation & Quick Start diff --git a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md index d5f2dbd70..dbba76416 100644 --- a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md +++ b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md @@ -807,44 +807,81 @@ blocked by completed policy before planning and source generation. ### Stage 0 — C Language And Contract Inputs -- [ ] Add C source conversion and authoritative source-free C semantic - contracts while preserving `source_language = "c"` on semantic modules, - native inputs, and build records. -- [ ] Treat a C procedure as C ABI by language identity. Do not require or +#### Current Stage 0 Status (2026-08-18) + +Stage 0 is **partially implemented**. The C frontend, semantic conversion, and +language-owned test suite exist and pass (497 collected; 496 passed, one parked +benchmark skip). Generated starter contracts match the defaults recorded below. +No build path accepts a C input, so nothing compiles or imports a C-backed +extension yet. + +Verified present: C source conversion in `prik/semantics/c2ir.py`; +`source_language = "c"` on semantic modules, functions, and arguments; +`native_language` validated as `"c"` or `"fortran"` in +`prik/semantics/pyi2ir.py`; and `void` versus value returns, pointer depth, +`const` provenance, structs, unions, opaque records, enum constants, and +typedef-resolved scalars in generated contracts. + +Verified absent: any `native_language` or C-source parameter on +`build_pyi_extension` and `prik/pipeline/build.py`; a C input route in the CLI; +and `tests/c//policy/`, `codegen/`, and `end_to_end/` evidence owners. + +- [x] Add C source conversion preserving `source_language = "c"` on semantic + modules, declarations, and arguments. +- [ ] Emit authoritative source-free C semantic contracts. Function-pointer + parameters currently serialize as the `CFunctionPointer` placeholder built by + `prik/semantics/c2ir.py`, which `prik.contracts` does not export and the + generated import line omits, so such a contract is not hand-editable. Either + promote the placeholder into the public contract vocabulary or block the + operation with a documented diagnostic. Do not leave a spelling that only + PRIK's own `.pyi` parser accepts. +- [ ] Preserve `source_language = "c"` on native inputs and build records. + `build_pyi_extension` accepts only `native_fortran_sources` with a Fortran + `input_compiler`, and the CLI documents Fortran inputs only. +- [x] Treat a C procedure as C ABI by language identity. Do not require or synthesize `@native_abi("c")`; that decorator remains the source-free Fortran spelling for an original `bind(C)` procedure. -- [ ] Preserve C symbols, `void` versus value returns, typedef-resolved scalar +- [x] Preserve C symbols, `void` versus value returns, typedef-resolved scalar types, pointer depth, qualifiers, structs, and function-pointer facts needed by completed policy. Do not infer ownership, nullability, or aggregate layout - merely from pointer or typedef syntax. -- [ ] Add language-owned parsing, semantic-contract, and diagnostic tests + merely from pointer or typedef syntax. Function-pointer facts are retained as + origin provenance behind the placeholder named above. +- [x] Add language-owned parsing, semantic-contract, and diagnostic tests under `tests/c/` without importing Fortran-specific fixture helpers. #### Conservative C Starter-Contract Defaults -C source conversion must preserve only what the declaration proves. The -generated starter contract is deliberately low-level; it must not guess -whether a pointer denotes one scalar, an array, an output, owned storage, or a -retained address. +A C declaration cannot prove what a one-level pointer denotes. `double *x` is +equally a scalar passed by reference and a pointer to the first element of an +array, and no amount of signature inspection distinguishes them. Only the +library's author knows, so the starter contract commits to the safest reading — +**one scalar passed by reference** — and the user promotes it to an array by +editing the semantic `.pyi`. That edit is the intended workflow, not a +workaround: it is where the contract earns its place. + +Everything the declaration *does* prove is preserved exactly. Conversion still +must not infer rank, shape, direction, nullability, ownership, or lifetime. | C declaration | Default generated semantic `.pyi` | Preserved meaning | | --- | --- | --- | | `T value` | `value: T` | Primitive scalar passed by value. | -| `T *value` | `value: Addr(T)` | Unrefined mutable one-level pointer with no invented rank or shape. | -| `const T *value` | `value: Addr(T)`, with `const` retained in origin and policy facts | Unrefined read-only one-level pointer; `const` does not make it a scalar or array. | +| `T *value` | `value: T` with `@native_call([Addr(Arg(i))])` | One scalar passed by reference. The user refines it to array storage in the contract. | +| `const T *value` | `value: T` with `@native_call([Addr(Arg(i))])`, with `const` retained in origin and policy facts | Same handoff as `T *`; `const` is recorded as provenance and does not by itself change the public contract. | | `T **value` | `value: Addr[2](T)` | Two native pointer levels; support may remain policy-blocked after serialization. | | return `T` | `-> T` | Direct primitive scalar result. | | return `T *` | `-> Addr(T)` | Raw pointer result with no invented ownership, lifetime, NumPy storage, or destruction policy. | -An authoritative semantic `.pyi` supplies the missing API meaning. It may -refine `Addr(T)` to `T[()]` for caller-provided rank-zero scalar storage, -`T[n]` or `T[:]` for proved array storage, or retain `Addr(T)` intentionally -as a raw address. `Addr(Arg(i))` requests the address of call-local scalar -storage, while a matching `Returns["name", T]` requests mutation readback. -Direction uses the explicit `In`, `Out`, or `InOut` contract, and nullability -uses an explicit `| None`; neither is inferred from pointer syntax. - -The source default must not infer an array from an adjacent extent parameter, +An authoritative semantic `.pyi` supplies the API meaning the declaration could +not. It may promote the by-reference scalar default to `T[n]` or `T[:]` for +proved array storage, keep `T[()]` for caller-provided rank-zero storage, or +restate `Addr(T)` deliberately as a raw address. `Addr(Arg(i))` requests the +address of call-local scalar storage, while a matching `Returns["name", T]` +requests mutation readback. Direction uses the explicit `In`, `Out`, or `InOut` +contract, and nullability uses an explicit `| None`; neither is inferred from +pointer syntax. + +The by-reference scalar default is the only reading conversion may assume. The +source default must still not infer an array from an adjacent extent parameter, infer output behavior from a parameter name, interpret non-`const` as input/output, or interpret `char *` as a string. C parameter array syntax still decays to a pointer at the ABI; retain its dimensions as source provenance and @@ -855,6 +892,22 @@ operation eligible: completed policy must block any pointer contract whose ownership, lifetime, nullability, transfer, or result behavior remains unsafe or unsupported. +- [x] Settle the one-level pointer default (decided 2026-08-18). A C signature + cannot distinguish a by-reference scalar from a pointer to a first array + element, so conversion emits the by-reference scalar and the user promotes it + to an array in the semantic `.pyi`. Current conversion output already matches + every row of the table above; the table was corrected to record the decision. +- [ ] Add fixture evidence for every row of the table above. The present + round-trip check re-parses generated text with PRIK's own `.pyi` parser, so + it accepts a contract that a user could not import, and its unknown-type + guard matches only the literal `Unknown`. A pointer-default change must fail + a focused test instead of silently rewriting every generated C contract. +- [ ] Prove the promotion path end to end once C builds exist: one fixture + where a `T *` parameter stays a by-reference scalar, and one where an edited + contract promotes the same native procedure to a NumPy array argument. This + pair is the user-facing demonstration that the contract, not the signature, + owns the Python API. + ### Stage 1 — Direct-Only C Policy - [ ] Reuse `NativeEntrypointAction.DIRECT_C_ABI` for supported C operations diff --git a/docs/user/faq/index.md b/docs/user/faq/index.md index ca4b05ccb..3727b2d03 100644 --- a/docs/user/faq/index.md +++ b/docs/user/faq/index.md @@ -88,7 +88,9 @@ PRIK also covers important Fortran features: supported [pointer forms](../guide/pointers.md), native errors as [Python exceptions](../guide/error-handling.md), and [overloaded procedures](../guide/generic-interfaces.md). PRIK is currently -alpha, so check the linked guides for exact limitations. The +alpha, so check the linked guides for exact limitations, or the +[language feature matrix](../language-support/feature-matrix.md) for every +supported and blocked form in one table. The [performance results](../performance.md) cover only their measured runtime and clean-build workloads. diff --git a/docs/user/guide/building-shared-library.md b/docs/user/guide/building-shared-library.md index bc846ed29..e60a73ee0 100644 --- a/docs/user/guide/building-shared-library.md +++ b/docs/user/guide/building-shared-library.md @@ -142,3 +142,12 @@ example. This workflow requires GNU Make. The shared library is not universal. It must match the target machine's operating system and architecture, Python and NumPy, and required compiler libraries. Rebuilding it on the target machine is the safest choice. + +## Every build option + +This page covers the common build paths. For the complete option surface — +native sources, objects, libraries, ordered link items, wrapper compiler flags, +and manifest replay — see the +[CLI commands reference](../reference/cli-commands.md), or run +`python3 -m prik --help-build`. To drive the same builds from Python instead of +a shell, see the [Python API reference](../reference/python-api.md). diff --git a/docs/user/guide/index.md b/docs/user/guide/index.md index d7d0f855b..bc7bcb1a0 100644 --- a/docs/user/guide/index.md +++ b/docs/user/guide/index.md @@ -64,4 +64,12 @@ the complete rules in one place. --- +**Checking whether a feature is supported** + +Each page below documents its own limitations. For the complete picture in one +table — including unsupported and partially supported forms — see the +[language feature matrix](../language-support/feature-matrix.md). + +--- + Start with **[Data Types](data-types.md)**. diff --git a/docs/user/guide/strings.md b/docs/user/guide/strings.md index bb115818a..402910074 100644 --- a/docs/user/guide/strings.md +++ b/docs/user/guide/strings.md @@ -248,8 +248,15 @@ b'Xlpha ' - `String[8][()]` and `String[8][count]` require dtype `S8`. - A dummy without `intent` uses the conservative `intent(inout)` behavior. -Mutable deferred-length scalar storage is not supported. Use a fixed-width -buffer or an immutable replacement result. +Deferred-length scalar storage (`character(len=:)`) is supported in two +places: a read-only `allocatable, intent(in)` argument, and an +`allocatable, intent(out)` result, which PRIK projects as a returned string. + +Two forms are blocked before code generation. A mutable +`allocatable, intent(inout)` argument is rejected because the native procedure +may reallocate it to a length the caller's buffer cannot hold. A +`character(len=:), pointer` argument is rejected because the adapter has no +target to associate. Use a fixed-width buffer for both. ## Next diff --git a/docs/user/index.md b/docs/user/index.md index 12413020e..2d2f79a0c 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -24,3 +24,15 @@ standalone wrapper, the first module wrapper, and the beginner edit-build-test loop. The User Guide covers supported Fortran wrapper features, runtime behavior, and extension builds. Performance presents the reproducible PRIK and f2py comparison. + +## Then + +- [Language Support](language-support/index.md) — whether PRIK wraps a given + Fortran feature, with the evidence behind each claim. +- [Reference](reference/index.md) — the exact CLI, Python API, generated-wrapper, + and `.pyi` contract surfaces. +- [Examples](examples/index.md) — complete wrappers for BLAS, LAPACK, FFTPACK, + and MINPACK. +- [Troubleshooting](troubleshooting/index.md) — installation, compiler, build, + and runtime problems. +- [FAQ](faq/index.md) — short answers to common questions. diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index d09eb6bdf..35e69044f 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -19,6 +19,29 @@ the current repository. Runtime wrapper support requires compiled, imported, and called wrapper tests. Parser or semantic support alone is listed as inspection-only or partial support. +## At A Glance + +**Fortran wrapping works end to end** for scalars, arrays, strings, functions, +subroutines, modules, derived types, and module state. Build from source with +one command, or edit the generated `.pyi` contract to reshape the Python API +without changing the native code. + +| You want to wrap | Status | +| --- | --- | +| Scalar arguments and results, all documented kinds | Supported | +| NumPy arrays — rank, shape, layout, strides, in-place mutation | Supported | +| Functions, subroutines, modules, module variables and constants | Supported | +| Derived types with fields, methods, constructors, finalizers | Supported | +| Optional arguments, generic interfaces, defined operators | Supported | +| Fixed-width character strings | Supported | +| Python callbacks passed into Fortran | Supported, call-scoped only | +| Allocatable arrays and pointer arrays | Supported / partially supported | +| Arrays of derived types, procedure pointers, `class(*)` | Unsupported | +| Wrapping user-supplied C libraries at runtime | Not implemented | + +The detailed rows below add the owning docs, source route, evidence, and exact +limitation for each feature. + ## Status Meanings | Status | Meaning | @@ -35,20 +58,20 @@ inspection-only or partial support. | --- | --- | --- | --- | --- | --- | | Scalar functions, subroutines, and baseline arrays | Supported | [Functions](../guide/wrapping-functions.md), [subroutines](../guide/wrapping-subroutines.md) | [Wrapper pipeline](../../developer/architecture.md#build-architecture) | [Verified baseline tests](../../../tests/fortran/data_types/end_to_end/test_verified_baseline.py) | Native scalar arguments require exact NumPy dtypes where documented. | | Generic procedure interfaces | Supported | [Generic interfaces](../guide/generic-interfaces.md) | [Feature route](../../developer/feature-to-code-map.md#feature-routes) | [Generic interface tests](../../../tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py) | Defined operators and assignment are tracked separately. | -| Defined operators and assignment overloads | Supported | [Defined operators](../guide/generic-interfaces.md#defined-operators) | [Bridge and binding generation](../../developer/codebase-map.md#cross-stage-hotspots) | [Defined operator tests](../../../tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py) | Supported operators are those covered by the wrapper guide and runtime tests. | +| Defined operators and assignment overloads | Supported | [Defined operators](../guide/generic-interfaces.md) | [Bridge and binding generation](../../developer/codebase-map.md#cross-stage-hotspots) | [Defined operator tests](../../../tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py) | Supported operators are those covered by the wrapper guide and runtime tests. | | Output arguments and multiple results | Supported | [Subroutine projection](../guide/wrapping-subroutines.md) | [Ownership and lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Calls and results tests](../../../tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py), [function result tests](../../../tests/fortran/functions/end_to_end/test_documented_function_journeys.py) | Tuple ordering and caller-provided array behavior follow the wrapper guide. | | Optional arguments | Supported | [Optional arguments](../guide/optional-arguments.md) | [Binding generation](../../developer/codebase-map.md#cross-stage-hotspots) | [Optional argument tests](../../../tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py) | Unsupported optional combinations fail during wrapper planning. | | Allocatable array handles, descriptor arguments, and owned results | Supported | [Allocatables](../guide/allocatables.md) | [Ownership policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Allocatable runtime tests](../../../tests/fortran/allocatables/end_to_end/test_allocatable_handles.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py) | Array module/field handles borrow their owner; result handles own persistent descriptor storage. Wrapper-owned scalar-derived allocatables use typed holders; module scalar allocatables use reversible `move_alloc` transactions for compatible dummies. | | Pointer scalar projections and array handles | Partially supported | [Pointers](../guide/pointers.md) | [Ownership policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Pointer handle tests](../../../tests/fortran/pointers/end_to_end/test_pointer_handles.py), [pointer policy tests](../../../tests/fortran/pointers/policy/test_pointer_ownership_policy.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py) | Descriptor arguments, module/field handles, strided views, wrapper-owned pointer-array results and outputs, scalar-derived pointer holders, and module pointer reassociation transactions are supported. Target deallocation and writable reassociation remain policy-gated. | -| Array-valued function results | Supported | [Array results](../guide/arrays.md#array-results) | [Array lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Array result tests](../../../tests/fortran/arrays/end_to_end/test_array_results.py) | Ownership and dtype/shape behavior are limited to documented array result forms. | +| Array-valued function results | Supported | [Array results](../guide/arrays.md#mutation-and-results) | [Array lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Array result tests](../../../tests/fortran/arrays/end_to_end/test_array_results.py) | Ownership and dtype/shape behavior are limited to documented array result forms. | | NumPy array argument contracts | Supported | [Arrays](../guide/arrays.md) | [Bridge and binding generation](../../developer/codebase-map.md#cross-stage-hotspots) | [Array contract tests](../../../tests/fortran/arrays/end_to_end/test_array_contract_validation.py), [multidimensional tests](../../../tests/fortran/arrays/end_to_end/test_layout_and_strided_arrays.py) | Wrong dtype, rank, shape, contiguity, alignment, or mutability is rejected. | | Derived-type scalar boundaries and methods | Supported | [Derived types](../guide/wrapping-derived-types.md) | [Class lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Derived boundary tests](../../../tests/fortran/derived_types/end_to_end/test_derived_boundaries.py), [method tests](../../../tests/fortran/derived_types/end_to_end/test_type_bound_methods.py) | Derived-type arrays and some polymorphic forms are not included. | | Default and keyword constructors with finalizers | Supported | [Constructors and finalizers](../guide/wrapping-derived-types.md#key-concepts) | [Ownership policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Constructor/finalizer tests](../../../tests/fortran/derived_types/end_to_end/test_default_constructors_and_finalizers.py), [borrowed finalizer tests](../../../tests/fortran/derived_types/end_to_end/test_borrowed_components.py) | Construction commits ownership only after initialization; borrowed wrappers never run an owning finalizer. | | Generic constructor interfaces and overloaded runtime initialization | Supported | [Constructors](../guide/wrapping-derived-types.md#custom-constructor) | [Class policy and lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Edited class surface tests](../../../tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py), [class policy tests](../../../tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates require distinguishable completed Python signatures; incomplete or ambiguous sets are blocked before emission. | | Module variables, constants, saved state, and common-block procedure state | Supported | [Wrapping modules](../guide/wrapping-modules.md) | [Module state route](../../developer/feature-to-code-map.md#feature-routes) | [Module state tests](../../../tests/fortran/modules/end_to_end/test_module_variables_and_state.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py), [common-block tests](../../../tests/fortran/modules/end_to_end/test_common_blocks.py) | Common-block storage is not exported as Python variables. Rank-zero derived module objects use direct, scoped, allocation-transaction, or pointer-transaction handoff selected before lowering. | | Fortran enum constants | Supported | [Enumerations](../guide/enumerations.md) | [Semantic constants route](../../developer/codebase-map.md#cross-stage-hotspots) | [Enum runtime tests](../../../tests/fortran/enumerations/end_to_end/test_enum_runtime.py), [enum semantic tests](../../../tests/fortran/enumerations/semantics/test_enum_semantics.py), [enum diagnostics](../../../tests/fortran/enumerations/parsing/test_enum_diagnostics.py) | No Python `Enum` or `IntEnum` classes are generated. | -| Scalar character arguments, results, and fields | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character argument tests](../../../tests/fortran/strings/end_to_end/test_character_boundaries.py), [edge-case tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype; mutable scalar deferred-length storage is blocked. | -| Scalar kind coverage | Supported | [Data types](../guide/data-types.md) | [Fortran type probe](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py) | Wider real, complex, and explicit logical storage is blocked without portable NumPy mapping. | +| Scalar character arguments, results, and fields | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character argument tests](../../../tests/fortran/strings/end_to_end/test_character_boundaries.py), [edge-case tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Deferred-length `character(len=:)` scalars are supported as read-only `allocatable, intent(in)` arguments and as `allocatable, intent(out)` results; mutable `intent(inout)` and pointer deferred length are blocked before generation. | +| Scalar kind coverage | Supported | [Data types](../guide/data-types.md) | [Fortran type probe](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py) | Quad precision (`real(16)`, `complex(16)`) is blocked because it has no portable NumPy dtype. All `logical` kinds are supported and adapt to one-byte NumPy Booleans at the boundary. | | Caller-ordered multi-source builds, Makefiles, verbose mode, and output placement | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Multi-source tests](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py), [compiler verbose tests](../../../tests/fortran/building_shared_library/compiling/test_compiler_verbose.py) | prik does not discover, reorder, or resolve all external source dependencies. | | Visibility, naming, keyword escaping, and collision policy | Supported | [Visibility and naming](../reference/fortran-wrapper.md#visibility-naming-and-the-python-surface) | [Naming policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Visibility/naming tests](../../../tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_naming.py) | Strict mode rejects names that default mode can normalize. | | Immediate call-scoped Python callbacks | Supported | [Callbacks](../guide/callbacks.md) | [Callback bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Callback plan tests](../../../tests/fortran/callbacks/codegen/test_callback_planning.py), [scalar callback tests](../../../tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py), [array callback tests](../../../tests/fortran/callbacks/end_to_end/test_array_callbacks.py), [combined shape tests](../../../tests/fortran/callbacks/end_to_end/test_supported_callback_shapes.py) | Direct wrapper-plan generation supports entering-thread callbacks only. Stored, optional, asynchronous, or cross-thread callbacks are unsupported. | @@ -78,16 +101,20 @@ PRIK_C_DOCS_END --> ## Unsupported Or Blocked Forms +prik blocks these before code generation and reports the boundary and the +reason, rather than emitting a wrapper that could lose precision, corrupt +memory, or outlive its native storage. + | Feature | Status | User docs | Source owner | Evidence | Limitations | | --- | --- | --- | --- | --- | --- | | Unproved pointer lifetime and ownership-changing operations | Unsupported | [Pointer safety](../guide/pointers.md#safety-checklist) | [Ownership policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Pointer policy tests](../../../tests/fortran/pointers/policy/test_pointer_ownership_policy.py), [pointer runtime tests](../../../tests/fortran/pointers/runtime/test_pointer_handle_protocol.py) | Native targets must outlive every handle use; allocation, target deallocation, resize, and writable reassociation require explicit completed policy. | | Persistent callbacks and procedure pointers | Unsupported | [Callback limitations](../guide/callbacks.md#important-limitations) | [Callback route](../../developer/codebase-map.md#cross-stage-hotspots) | [Callback policy tests](../../../tests/fortran/callbacks/policy/test_callback_policy.py), [scalar callback tests](../../../tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py) | Callbacks are valid only during the wrapped call. | | Advanced multi-source dependency discovery and external-library integration | Unsupported | [Multiple source files](../guide/building-shared-library.md#multiple-source-files) | [Build orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Multi-source tests](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py) | prik does not infer dependency graphs, prebuilt module paths, or external library discovery. | -| Blocked array forms | Unsupported | [Unsupported array forms](../guide/arrays.md#unsupported-forms) | [Array policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Array semantic tests](../../../tests/fortran/arrays/semantics/test_array_semantics.py), [diagnostics](../reference/diagnostic-codes.md) | Assumed type `type(*)`, arrays of derived types, and character arrays not representable as fixed-width bytes need missing runtime contracts. | +| Blocked array forms | Unsupported | [Arrays](../guide/arrays.md) | [Array policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Array semantic tests](../../../tests/fortran/arrays/semantics/test_array_semantics.py), [diagnostics](../reference/diagnostic-codes.md) | Assumed type `type(*)`, arrays of derived types, and character arrays not representable as fixed-width bytes need missing runtime contracts. | | Unsupported polymorphic forms | Unsupported | [Inheritance limits](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/codegen/test_class_surfaces.py) | Results, mutable dummies, arrays, polymorphic allocatable/pointer scalars, and `class(*)` are blocked. | | Ambiguous or incomplete constructor overload sets | Unsupported | [Constructor limitations](../reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | [Constructor route](../../developer/codebase-map.md#cross-stage-hotspots) | [Constructor semantic tests](../../../tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py), [class-plan validation tests](../../../tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates must have distinguishable exact runtime signatures and compatible native-owner lifecycles. | -| Character arrays and mutable deferred-length character storage | Partially supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character edge tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Fixed and allocatable deferred element length maps to dtype itemsize; Unicode/object arrays and mutable scalar deferred-length storage are unsupported. | -| Wider-than-supported real, complex, and logical storage | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | prik blocks rather than silently losing precision or Boolean storage semantics. | +| Character arrays and caller-supplied deferred-length character storage | Partially supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character edge tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Fixed and allocatable deferred element length maps to dtype itemsize; Unicode/object arrays are unsupported. Deferred-length `character(len=:)` scalars work as read-only `allocatable` arguments and `allocatable, intent(out)` results; mutable `intent(inout)` and pointer deferred length are blocked. | +| Quad-precision real and complex storage | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | `real(16)` and `complex(16)` have no portable NumPy dtype, so prik blocks them rather than silently narrowing to 64-bit. Narrower real, complex, integer, and all logical kinds are supported. | +`--help` is a curated overview; `--help-build` is the exhaustive build surface. +Each subcommand has its own help — `parse --help`, `semantics --help`, +`generate --help`, `probe --help` — describing that stage's role for shared +flags such as `--compiler` and `-I`. -## Command shapes +`prik --version` and `python3 -m prik --version` print the same value as +`prik.__version__`. -```bash -python3 -m prik INPUT [INPUT ...] [BUILD OPTIONS] -python3 -m prik {parse,semantics,generate,probe} [OPTIONS] ... -``` +When `rich-argparse` is installed, prik uses its colored help formatter +automatically. Install it with `python3 -m pip install 'prik[pretty]'`, or from +an editable checkout with `python3 -m pip install -e '.[pretty]'`. Plain +`argparse` help is the deterministic fallback; `--no-color` or `NO_COLOR` +selects it explicitly. + +## Input selection + +The default build accepts either one or more Fortran source `INPUT` values, or +exactly one semantic `.pyi` entry contract — never both. With +`--build-manifest PATH`, omit positional input entirely. -The default compiled build accepts one or more Fortran source `INPUT` values, -or exactly one semantic `.pyi` entry contract. Do not mix those two input -forms. When `--build-manifest PATH` is supplied, omit positional input -entirely. In the second form, select one of the four command names shown in -braces; `COMMAND` is not a literal command or input. Inspection and -contract-generation commands advertise their own supported frontend languages -in their focused help; compiled wrapper generation is currently Fortran-only. -The concise top-level help lists `INPUT` under `positional arguments:` and the -common flags under `build options:`. All help section headings use lowercase -for the same presentation in plain and colored output. Full build help and -every source-taking subcommand use the same concise section style. Positional -`INPUT` values appear under `positional arguments:`. Full build help puts -`--language` and manifest selection under `input selection:`, while -source-taking subcommands use `input options:` for their corresponding -controls. Output and diagnostic controls always have separate groups. Each -subcommand describes shared compiler and include flags in terms of that -subcommand's actual stage rather than copying the default-build wording. -Accordingly, full default-build help advertises `--language {fortran}` only; -`parse`, `semantics`, `generate --pyi`, and `probe` advertise -`--language {fortran,c}` because those paths currently support both frontends. +| Option | Purpose | +| --- | --- | +| `paths` | Source files, `.pyi` files, or directories. Omit only with `--build-manifest`. | +| `--version` | Prints the installed PRIK version and exits. | +| `--language fortran` | Selects the frontend explicitly when suffix inference is unavailable. | +| `--build-manifest PATH` | Replays a saved `prik-build.json`. It does not generate one. | +| `--jobs N` | Limits concurrent compiler processes. The default uses available CPUs. | -The top-level help intentionally lists only common build options. Run -`python3 -m prik --help-build` for the complete build surface. Each subcommand -has its own options; use `parse --help`, `semantics --help`, `generate --help`, -or `probe --help` after `python3 -m prik` to see only the options relevant to -that command. The concise build list covers output naming and location, build -compiler and include-directory selection, native compile flags such as `-O3`, -native libraries, compiler job limits, and verbose build output. -Command-specific help describes the stage-specific role of shared flags; for -example, `parse --help` explains -that `--compiler` and `-I` configure preprocessing. The concise build help does -not mislabel them as preprocessing-only options. It also keeps short examples -for a basic source build, an explicitly named extension, and semantic contract -generation; `--help-build` labels its basic build, semantic-contract build, -and manifest-replay examples separately. Both help levels reuse the canonical -`points.f90` and `geometry` naming from the -[derived-type guide](../guide/wrapping-derived-types.md#complete-example), -which contains a complete source, build, import flow, and expected result. - -The full build help uses the following two forms: +Compiled wrapper builds are Fortran-only, so the default build advertises +`--language {fortran}`. The `parse`, `semantics`, `generate --pyi`, and `probe` +paths advertise `--language {fortran,c}` because they support both frontends. -```text -usage: python3 -m prik INPUT [INPUT ...] - [OUTPUT OPTIONS] [COMPILER OPTIONS] [WRAPPER OPTIONS] - [NATIVE OPTIONS] [DIAGNOSTIC OPTIONS] - python3 -m prik --build-manifest PATH [MANIFEST OVERRIDES] -``` +Directories are expanded recursively in deterministic path order. -Its groups are exhaustive rather than curated: `input selection` contains the -frontend and manifest selectors; `output options` contains the module name, -build directory, and structured-result selection; `compiler options` contains -every compiler and preprocessing control; `wrapper options` contains generated -wrapper naming and compiler behavior; `native options` contains native sources, -flags, objects, libraries, directories, and ordered link items; and -`diagnostic options` contains verbose, color, and traceback controls. The -default output directory shown there is `./__prik__`. - -`--build-manifest PATH` reads an existing `prik-build.json` and replays the -saved build; it does not generate a manifest. Manifest replay accepts only -overrides that the replay implementation consumes: -`--out`, `--compiler`, `-I`/`--include-dir`, `--jobs`, `--json`, `--verbose`, -`--no-color`, and `--debug`. The manifest owns its output -directory, input language, preprocessing recipe, wrapper behavior, native -inputs, and link plan, so replay rejects flags from those areas instead of -silently ignoring them. + -| Command | Purpose | -| --- | --- | -| no subcommand | Builds and imports one extension path from Fortran source or a semantic `.pyi` contract. | -| `parse` | Prints parser facts and diagnostics. | -| `semantics` | Prints language-neutral semantic IR. | -| `generate` | Generates `.pyi` contracts, wrapper sources, or a Makefile build without compiling an extension. | -| `probe` | Probes compiler-target datatype facts as JSON or a Markdown mapping table. | +## Wrapper builds -## Input selection +A positional Fortran source is both a semantic input and a native +implementation source. A `.pyi` is only the semantic contract, so it needs at +least one explicit native input: `--native-fortran-sources`, `--native-objects`, +`--native-library`, or `--native-link-item`. | Option | Purpose | | --- | --- | -| `paths` | Source files, `.pyi` files, or directories. Omit only when using `--build-manifest`. | -| `--version` | Prints the installed PRIK version and exits. | -| `--language fortran` | Selects the Fortran frontend explicitly when suffix inference is unavailable. | -| `--jobs N` | Limits concurrent compiler processes to `N`; the default uses the CPUs available to prik. | +| `--out NAME` | Python module name, `PyInit_` symbol, and stable `NAME.so` alias. Accepts `NAME` or `NAME.so`, and requires a value. | +| `--out-dir DIR` | Where generated artifacts and the ABI-suffixed extension are built. Default `./__prik__`. | +| `--compiler COMPILER` | The input-language compiler used for the whole build: preprocessing, datatype measurement, native and bridge compilation, and linking. Default `gfortran`. | +| `-I DIR`, `--include-dir DIR` | Build-wide include directory. Repeat to preserve search order. | +| `--strict-wrapper-names` | Rejects Python names that would need escaping or a collision suffix. | +| `--no-compile-input-sources` | Treats positional sources as semantic inputs only. Requires an explicit native input. | +| `--native-fortran-sources PATH ...` | Compiles extra native sources without exposing them as public API. | +| `--native-compile-flags FLAG ...` | Flags for native implementation compilation. | +| `--native-objects PATH ...` | Links object files, static archives, or shared libraries. | +| `--native-library NAME ...` | Links system libraries by name — `--native-library openblas` passes `-lopenblas`. | +| `--native-link-item KIND:VALUE ...` | Ordered link items. `KIND` is `object`, `archive`, `shared-library`, `library`, or `arg`. | +| `--native-library-dir DIR ...` | Library search directories and runtime paths. | +| `--wrapper-compiler-debug` | Uses the compiler debug profile instead of release. | +| `--wrapper-fortran-flags FLAG ...` | Flags for generated Fortran bridge compilation. | +| `--wrapper-c-flags FLAG ...` | Flags for generated binding compilation and extension linking. | + +Build rules worth knowing: + +- prik selects the generated binding compiler from its own profile; + `--compiler` controls the input-language side. +- `--native-compile-flags` also applies to internal datatype measurement for + source builds, so target-changing flags such as `-fdefault-integer-8` affect + both native compilation and the semantic wrapper types. +- Native input options accept multiple values and may be repeated; supplied + source, artifact, and link-item order is preserved. For values starting with + `-`, use the equals form: `--native-compile-flags="-O3 -fopenmp"`. +- Source-driven builds may add native sources, objects, and libraries to + complete the link. These augment the positional sources without becoming + semantic inputs. +- Manifest replay accepts only `--out`, `--compiler`, `-I`/`--include-dir`, + `--jobs`, `--json`, `--verbose`, `--no-color`, and `--debug`. The manifest + owns output directory, input language, preprocessing recipe, wrapper + behavior, native inputs, and link plan, so other flags are rejected rather + than silently ignored. ## Parse and semantics -Inspection is selected by a subcommand rather than a stage flag. Compact usage -lines leave the complete command-specific option inventory to the groups below -them: - ```bash python3 -m prik parse INPUT [INPUT ...] [OPTIONS] python3 -m prik semantics INPUT [INPUT ...] [OPTIONS] - -python3 -m prik parse points.f90 -python3 -m prik semantics points.f90 ``` -Parse-report controls such as `--show-vars` and `--print-limit` appear only in -`prik parse --help`. Target datatype measurement is internal to semantic -conversion and wrapping. Use the separate `prik probe` command only when you -want to inspect or save the measured target facts yourself. +| Option | Purpose | +| --- | --- | +| `--show-vars` | Includes module, submodule, program, and block-data variables in human-readable parse reports. | +| `--print-limit N` | Shows at most `N` items per repeated section in human-readable parse reports. | -The parse examples distinguish basic inspection, a detailed report, and an -alternate frontend. The semantics examples distinguish basic conversion, an -alternate frontend, and writing the combined semantic IR to a named JSON file. +`semantics` always emits JSON. With no `--out` it prints the combined report; +`--out PATH` writes that report to `PATH`; bare `--out` writes one `.json` +beside each input source. -`semantics` always writes its language-neutral report as JSON. With no `--out`, -it prints the combined report to standard output. `--out PATH` writes that -combined report to `PATH`; `--out` without a path writes one `.json` file beside -each input source. +Target datatype measurement happens automatically inside semantic conversion. +Use `probe` only when you want to inspect those facts yourself. ## Generate `generate` requires exactly one output mode: ```bash -python3 -m prik generate (--pyi | --sources | --makefile) - INPUT [INPUT ...] [OPTIONS] -python3 -m prik generate (--sources | --makefile) - --build-manifest PATH [OVERRIDES] +python3 -m prik generate (--pyi | --sources | --makefile) INPUT [INPUT ...] [OPTIONS] +python3 -m prik generate (--sources | --makefile) --build-manifest PATH [OVERRIDES] ``` | Mode | Purpose | | --- | --- | | `--pyi` | Writes the editable semantic `.pyi` contract. | -| `--sources` | Writes wrapper source files without compiling native objects or an extension. | -| `--makefile` | Writes wrapper sources, the replay manifest when applicable, and `Makefile.prik` without compiling. | +| `--sources` | Writes wrapper sources without compiling. | +| `--makefile` | Writes wrapper sources, the replay manifest when applicable, and `Makefile.prik`. | ```bash python3 -m prik generate --pyi points.f90 --out contracts @@ -190,45 +165,22 @@ python3 -m prik generate --sources points.f90 --out-dir build python3 -m prik generate --makefile points.f90 --out-dir build ``` -These examples reuse `points.f90` from the -[derived-type guide](../guide/wrapping-derived-types.md#complete-example). - -These modes are mutually exclusive. Source and Makefile generation still run -the preprocessing and semantic-policy stages needed to produce a valid wrapper -plan; they skip native object compilation and extension linking. Their -generated commands use the build-wide `--compiler` and `-I` contract. In -`--pyi` mode those same options apply only to source preprocessing and datatype -measurement because no native build is generated. - -The help page presents `generation modes` immediately after the standard -`options` group, then `positional arguments`, `input options`, compiler and -frontend-specific include controls, wrapper and native controls, output, -diagnostics, and examples. `native options` keeps native sources, compiler -flags, objects, libraries, library directories, and ordered link items -together, matching `--help-build`. `--build-manifest` reads an existing -manifest and regenerates wrapper artifacts; it is not a contract-generation -input. +`--sources` and `--makefile` still run preprocessing and semantic policy to +produce a valid wrapper plan; they skip object compilation and linking, and +use `--out-dir`. `--pyi` uses `--out` for its contract package, and there +`--compiler` and `-I` affect only preprocessing and datatype measurement. + +In `.pyi` Makefile mode, prik writes `/prik-build.json` first, then +generates `/Makefile.prik` from that manifest. ## Probe -`probe` uses `--language fortran` and compiler-oriented flags instead of nested -language commands. JSON is the default; `--format markdown` prints the target -datatype mapping table. Its help examples distinguish basic native probes, a -human-readable mapping table, ABI-affecting compiler flags that change default -kinds, and a cross-target probe run through a target runner. Pass each raw -compiler flag separately, for example -`--compiler-arg=-fdefault-real-8 --compiler-arg=-fdefault-integer-8`. +JSON is the default; `--format markdown` prints the target datatype mapping +table. ```bash python3 -m prik probe --language {fortran,c} --compiler COMPILER [OPTIONS] -``` - - -```bash python3 -m prik probe --language fortran --compiler gfortran-13 ``` @@ -240,42 +192,37 @@ PRIK_C_DOCS_END --> | Option | Purpose | | --- | --- | -| `--language fortran` | Selects the Fortran target probe. | - -| `--compiler COMPILER` | Selects the exact native or cross compiler. | -| `--format {json,markdown}` | Chooses the machine-readable report or mapping table. | -| `--expr EXPR` | Adds a Fortran integer expression to the JSON probe; repeat for more expressions. | -| `--runner ARG` | Adds one cross-target runner command item; repeat for multiple arguments. | -| `--cache-dir PATH` | Selects reusable probe storage. | -| `--refresh` | Ignores reusable results and probes the target again. | -| `--out PATH` | Writes the probe report instead of printing it. | - -Compiler preprocessing flags are accepted for JSON probes. Markdown mappings -accept compiler arguments, runner, cache, and refresh options because they -measure the standard mapping table rather than an individual preprocessed -source expression. +| `--language {fortran,c}` | Selects the target probe. | +| `--compiler COMPILER` | The exact native or cross compiler. | +| `--format {json,markdown}` | Machine-readable report, or the mapping table. | +| `--expr EXPR` | Adds a Fortran integer expression to the JSON probe. Repeat for more. | +| `--runner ARG` | Adds one cross-target runner command item. Repeat for more. | +| `--cache-dir PATH` | Reusable probe storage. | +| `--refresh` | Ignores reusable results and probes again. | +| `--out PATH` | Writes the report instead of printing it. | + +Pass each raw compiler flag separately, for example +`--compiler-arg=-fdefault-real-8 --compiler-arg=-fdefault-integer-8`. Markdown +mappings accept compiler, runner, cache, and refresh options because they +measure the standard table rather than one preprocessed expression. ## Compiler preprocessing -These options control compiler preprocessing before Fortran parsing. - - +These options control preprocessing before parsing. | Option | Purpose | | --- | --- | -| `--preprocessor-adapter {auto,gnu-fortran,command-template}` | Selects the Fortran compiler adapter or a custom command template. | -| `--compiler COMPILER` | Uses an exact compiler or preprocessor executable. Defaults to `gfortran` for Fortran. | +| `--preprocessor-adapter {auto,gnu-fortran,command-template}` | Selects the compiler adapter or a custom command template. | +| `--compiler COMPILER` | An exact compiler or preprocessor executable. Defaults to `gfortran` for Fortran. | | `--preprocess-template TEMPLATE` | Runs a custom command-template preprocessor. | -| `-I DIR`, `--include-dir DIR` | Adds an include directory during compiler preprocessing. | +| `-I DIR`, `--include-dir DIR` | Adds an include directory. | | `-D NAME[=VALUE]`, `--define NAME[=VALUE]` | Defines a preprocessing macro. | | `-U NAME`, `--undef NAME` | Undefines a preprocessing macro. | -| `--std STANDARD` | Passes a Fortran language standard such as `f2008` or `f2018`. | -| `--compiler-arg ARG` | Passes one raw compiler preprocessing argument. Repeat for multiple arguments. | +| `--std STANDARD` | Passes a language standard such as `f2008` or `f2018`. | +| `--compiler-arg ARG` | Passes one raw compiler argument. Repeat for more. | + +Use the equals form when a value starts with `-`, for example +`--compiler-arg=-target`. - - -Use `--compiler-arg=-target` style spelling when the value itself starts with -`-`. - @@ -309,124 +250,19 @@ PRIK_C_DOCS_END --> | `--private-include PATH_OR_PATTERN` | Forces matched included files to be private in wrapper output. | PRIK_C_DOCS_END --> -## Parse report controls - -| Option | Purpose | -| --- | --- | -| `--show-vars` | Includes module, submodule, program, and block-data variables in human-readable Fortran parse reports. | -| `--print-limit N` | Shows at most `N` items per repeated section in human-readable parse reports. | - -## Wrapper builds - -With no subcommand, recognizable Fortran source, semantic `.pyi` input, or a -saved manifest builds a wrapper. A positional Fortran source is both a semantic -input and a native implementation source. A `.pyi` is only the semantic -contract, so it requires at least one explicit native implementation input. -Generation without compilation belongs to the `generate` subcommand. - -| Option | Purpose | -| --- | --- | -| `--compiler COMPILER` | Selects the input-language compiler used throughout a wrapper build: preprocessing, datatype measurement, native and generated-bridge compilation, and extension linking. The default is `gfortran`; the generated binding continues to use prik's binding-compiler profile. | -| `-I DIR`, `--include-dir DIR` | Adds a build-wide compiler include directory. Source builds use it during preprocessing; source and `.pyi` builds use it for native and generated wrapper compilation. Repeat to preserve search order. | -| `--strict-wrapper-names` | Rejects Python wrapper names that require escaping or collision suffixes. | -| `--build-manifest PATH` | Reads an existing semantic `.pyi` wrapper build manifest and replays its saved build. It does not generate the manifest. | -| `--no-compile-input-sources` | Treats positional Fortran sources as semantic inputs only. Requires an explicit native input; `--native-fortran-sources` remain compiled hidden implementation sources. | -| `--native-fortran-sources PATH [PATH ...]` | Compiles additional native Fortran implementation sources without using them as semantic inputs. | -| `--native-compile-flags FLAG [FLAG ...]` | Adds compiler flags to native implementation source compilation. Native source compilation is currently Fortran-only. | -| `--native-objects PATH [PATH ...]` | Links one or more native object, static archive, or shared library paths into the extension. | -| `--native-library NAME [NAME ...]` | Links system libraries by name. For example, `--native-library openblas` passes `-lopenblas` to the linker. | -| `--native-link-item KIND:VALUE [KIND:VALUE ...]` | Adds ordered extension link items. `KIND` is `object`, `archive`, `shared-library`, `library`, or `arg`. | -| `--native-library-dir DIR [DIR ...]` | Adds native library search directories and runtime paths for extension linking. | - -Important boundaries: - -- `parse`, `semantics`, `generate`, and `probe` are the only subcommands. -- For compiled wrapper builds, `--out NAME` selects the Python module name, - `PyInit_` symbol, JSON `module_name`, and stable `NAME.so` alias in the - current directory. Use `--out-dir DIR` to choose where generated artifacts - and the ABI-suffixed extension are built. Give `--out` an explicit path to - place the stable alias elsewhere. -- Wrapper `--out` requires a value and accepts `NAME` or `NAME.so`. -- `generate --sources` and `generate --makefile` use `--out-dir`; `generate - --pyi` uses `--out` for its contract package. -- `.pyi` wrapper builds require at least one native implementation input such - as `--native-fortran-sources`, `--native-objects`, `--native-library`, or - `--native-link-item`. -- Source-driven builds accept individual Fortran files or directories. - Directories are expanded recursively in deterministic path order. -- `--no-compile-input-sources` keeps positional Fortran sources as semantic inputs - but removes them from native compilation. It requires an explicit native - implementation through `--native-fortran-sources`, `--native-objects`, - `--native-library`, or `--native-link-item`. Sources passed through - `--native-fortran-sources` are still compiled without becoming public API. -- Source-driven builds may use the same native source, object, library, - include-directory, library-directory, and ordered-link options to complete - the extension build. These inputs augment the positional implementation - sources; they do not become semantic wrapper inputs. -- In a wrapper build, `--compiler` is a build input rather than a - preprocessing-only setting. It selects the input-language compiler command - used for preprocessing and datatype measurement, then for native source and - generated bridge compilation, and finally for extension linking. prik still - selects the generated binding compiler from its compiler profile. -- `-I DIR` is build-wide: prik preserves the supplied order in preprocessing - and in native, bridge, and binding compilation. Use it for source includes, - compiler-produced module files, and native interface directories. -- `--native-compile-flags` compiles the native implementation. The public name - identifies the native compilation phase rather than the current source - language; native source compilation is currently Fortran-only. - `--wrapper-fortran-flags` compiles the generated Fortran bridge, and - `--wrapper-c-flags` compiles the generated binding and supplies additional - extension-link flags. -- For source-driven builds, prik also applies `--native-compile-flags` to its - internal datatype measurement. Target-changing flags such as - `-fdefault-integer-8` or `-fdefault-real-8` therefore affect both native - compilation and the semantic wrapper types without separate probe options. -- Native input options accept one or more values per occurrence and may also be - repeated. prik preserves the supplied source, artifact, and link-item order. - For compiler flags or prefixed library names that start with `-`, group them - with the equals form, for example `--native-compile-flags="-O3 -fopenmp"` or - `--native-library="-lblas -llapack"`. -- In `.pyi` Makefile mode, prik writes `/prik-build.json` first and - generates `/Makefile.prik` from that manifest. -- `--build-manifest PATH` reads a saved manifest and rebuilds from it; it does - not generate the manifest. `generate --makefile - --build-manifest PATH` regenerates `Makefile.prik` without positional - contracts or repeated native flags. Replay may override only `--out`, - `--compiler`, `-I`/`--include-dir`, `--json`, `--verbose`, `--no-color`, and - `--debug`; all other build settings come from the - manifest. - - - ## Output and diagnostics | Option | Purpose | | --- | --- | -| `--json` | Selects JSON instead of the default human-readable output for commands that support both formats. Semantic reports are always JSON and therefore do not expose this flag. | -| `--out [PATH]` | Writes command output, selects a generated `.pyi` package directory, or names the wrapper Python module and final `.so`. | -| `--out-dir DIR` | Selects the wrapper build output directory. The default is `./__prik__`. | -| `--verbose` | Announces and completes binding, bridge, and header source-text generation in order, then each written artifact, source/object compilation pair, and final extension path before printing the exact compiler or linker command; it times each non-writing operation and reports total build time last. | -| `--wrapper-compiler-debug` | Uses the compiler debug profile for direct wrapper builds instead of the default release profile. | -| `--wrapper-fortran-flags FLAG...` | Appends flags to generated Fortran bridge compilation commands. | -| `--wrapper-c-flags FLAG...` | Appends flags to generated binding compilation and extension-link commands. | +| `--json` | Selects JSON where both formats exist. Semantic reports are always JSON and do not expose this flag. | +| `--out [PATH]` | Command output, generated `.pyi` package directory, or the wrapper module and final `.so`. | +| `--out-dir DIR` | Wrapper build output directory. Default `./__prik__`. | +| `--verbose` | Announces each generation, artifact, and compile step with its exact compiler or linker command, times each operation, and reports total build time last. | | `--no-color` | Disables ANSI color in parse diagnostics. | -| `--debug` | Re-raises command failures so Python prints a traceback. | +| `--debug` | Re-raises failures so Python prints a traceback. | -When `rich-argparse` is installed, prik uses its colored help formatter -automatically. Install the optional UI dependencies for a published package -with `python3 -m pip install 'prik[pretty]'`, or from an editable source -checkout with `python3 -m pip install -e '.[pretty]'`. Plain `argparse` help -remains the deterministic fallback, and `--no-color` or `NO_COLOR` selects it -explicitly. - -Use `--out` for command output, generated `.pyi` contract packages, or -the wrapper Python module and final `.so`. Use `--out-dir` for wrapper build artifacts. -Wrapper build JSON includes generated artifact paths, -`native_build_plan`, the structured native compile/link plan for the extension, -and for semantic `.pyi` builds the normalized replay `manifest`. +Wrapper build JSON includes generated artifact paths, `native_build_plan`, and +for semantic `.pyi` builds the normalized replay `manifest`. ## Checked workflows @@ -439,9 +275,9 @@ and for semantic `.pyi` builds the normalized replay `manifest`. | Print semantic IR | `python3 -m prik semantics path/to/file.f90` | | Emit a semantic `.pyi` contract directory | `python3 -m prik generate --pyi path/to/file.f90 --out contracts` | | Build a Fortran wrapper | `python3 -m prik path/to/file.f` | -| Build a Fortran wrapper with native compiler and link flags | `python3 -m prik path/to/file.f90 --native-compile-flags="-O3 -fopenmp" --wrapper-c-flags=-fopenmp` | +| Build with native compiler and link flags | `python3 -m prik path/to/file.f90 --native-compile-flags="-O3 -fopenmp" --wrapper-c-flags=-fopenmp` | | Build from a semantic contract and native object | `python3 -m prik contracts/module.pyi --native-objects build/module.o -I build` | -| Build a Fortran wrapper with an explicit module and `.so` name | `python3 -m prik path/to/file.f90 --out my_extension` | +| Build with an explicit module and `.so` name | `python3 -m prik path/to/file.f90 --out my_extension` | | Generate wrapper sources only | `python3 -m prik generate --sources dependency.f90 api.f90 --out-dir build` | | Generate an editable Makefile | `python3 -m prik generate --makefile dependency.f90 api.f90 --out-dir build` | | Generate a `.pyi` replay manifest and Makefile | `python3 -m prik generate --makefile contracts/module.pyi --native-fortran-sources native/module.f90 --out-dir build --json` | @@ -452,10 +288,12 @@ and for semantic `.pyi` builds the normalized replay `manifest`. | Parse with compiler preprocessing | `python3 -m prik path/to/api.h --language c --parse --compiler clang-18 -I include -D API_EXPORT= --std c11` | PRIK_C_DOCS_END --> +The `points.f90` examples reuse the source from the +[derived-type guide](../guide/wrapping-derived-types.md#complete-example), +which has a complete source, build, import, and result flow. + ## Related pages -- Use [Python API Reference](python-api.md) when calling prik from Python. -- Use [Fortran Wrapper Reference](fortran-wrapper.md) for wrapper - build workflows. -- Use [Semantic .pyi Format](semantic-pyi-format.md) when editing wrapper - contracts. +- [Python API Reference](python-api.md) — the same workflows from Python. +- [Fortran Wrapper Reference](fortran-wrapper.md) — build workflows in depth. +- [Semantic .pyi Format](semantic-pyi-format.md) — editing wrapper contracts. diff --git a/docs/user/reference/diagnostic-codes.md b/docs/user/reference/diagnostic-codes.md index b5e61f7d9..c3b37d0a7 100644 --- a/docs/user/reference/diagnostic-codes.md +++ b/docs/user/reference/diagnostic-codes.md @@ -9,97 +9,164 @@ publication: draft # Diagnostic Codes -Diagnostic codes are stable category identifiers for users, tests, and tooling. -They are not source line numbers, occurrence counters, or process exit statuses. - -Categories use explicit symbolic names such as `PARSE_INVALID_SYNTAX` and -`C_UNRESOLVED_INCLUDE`. The name describes the failure class directly. - -## Fatal Parser Errors - -Fatal parser errors stop parsing and are rendered by the CLI without a Python -traceback unless `--debug` is used. - -| Code | Frontend | Meaning | -| --- | --- | --- | -| `PARSE_ERROR` | Fortran | Fallback for a manually constructed or defensive Fortran parse error without a narrower category. | -| `PARSE_INVALID_SYNTAX` | Fortran | Syntax cannot be consumed in a modeled Fortran grammar region. | -| `PARSE_WRONG_ENTRYPOINT` | Fortran | A singular public parser API was called for a different source-unit kind. | -| `PARSE_AMBIGUOUS_ENTRYPOINT` | Fortran | A singular public parser API matched more than one source unit. | -| `PARSE_EXPECTED_UNIT` | Fortran | An internal unit visitor received the wrong source-unit kind. | -| `PARSE_MISSING_UNIT_END` | Fortran | A source unit has no closing statement. | -| `PARSE_MISMATCHED_UNIT_END` | Fortran | A named source-unit closing statement does not match its opener. | -| `PARSE_UNEXPECTED_UNIT_END` | Fortran | A closing statement appears while another nested unit is active. | -| `PARSE_DUPLICATE_UNIT` | Fortran | A scope contains duplicate named source units of the same kind. | -| `PARSE_DUPLICATE_PROCEDURE` | Fortran | A scope contains duplicate procedure names. | -| `PARSE_MALFORMED_HEADER` | Fortran | A module or procedure header is unsupported or malformed. | -| `PARSE_UNSUPPORTED_RESULT_TYPE` | Fortran | A function header contains an unsupported result-type prefix. | -| `PARSE_DUPLICATE_DECLARATION` | Fortran | A procedure symbol is declared more than once. | -| `PARSE_UNKNOWN_PARAMETER_TYPE` | Fortran | A `PARAMETER` symbol has no declared type where one is required. | -| `PARSE_DUPLICATE_PARAMETER` | Fortran | A procedure contains duplicate `PARAMETER` declarations. | -| `PARSE_DUPLICATE_SYMBOL` | Fortran | A file or project scope contains a duplicate symbol. | -| `PARSE_UNSUPPORTED_OPENMP_DIRECTIVE` | Fortran | A modeled specification region contains an unsupported OpenMP directive. | -| `PARSE_MISSING_DERIVED_TYPE_END` | Fortran | A derived-type declaration has no matching closing statement. | -| `PARSE_EXECUTABLE_IN_SPECIFICATION` | Fortran | An executable statement appears in a non-executable specification region. | -| `PARSE_UNSUPPORTED_DECLARATION` | Fortran | A declaration-shaped line uses an unsupported datatype form. | -| `PARSE_UNSUPPORTED_TYPE_BOUND_DECLARATION` | Fortran | A derived-type `contains` region has an unsupported binding declaration. | -| `PARSE_UNRESOLVED_ARGUMENT_TYPE` | Fortran | A defensive invariant could not apply a declared argument type. | -| `PARSE_UNKNOWN_FUNCTION_RESULT_TYPE` | Fortran | A function result has no resolvable datatype. | -| `PARSE_IMPLICIT_NONE_UNDECLARED_SYMBOL` | Fortran | `implicit none` requires a missing argument or result declaration. | -| `PARSE_MISSING_FUNCTION_RESULT` | Fortran | A defensive invariant found a function without a result variable. | -| `PARSE_RESULT_SHADOWS_ARGUMENT` | Fortran | A function result name shadows an argument. | -| `PARSE_DUPLICATE_VARIABLE` | Fortran | A module-like scope contains conflicting duplicate variable declarations. | -| `PARSE_UNKNOWN_VARIABLE_TYPE` | Fortran | A module variable still has an unknown datatype after parsing. | -| `PARSE_DUPLICATE_FIELD` | Fortran | A derived type contains duplicate fields. | -| `PARSE_UNKNOWN_FIELD_TYPE` | Fortran | A derived-type field still has an unknown datatype after parsing. | -| `PARSE_DUPLICATE_ARGUMENT` | Fortran | A procedure argument list repeats a name. | -| `PARSE_PREPROCESSING_REQUIRED` | Fortran | Raw CPP directives require compiler preprocessing before parser entry. | -| `PARSE_INTERNAL_STATE` | Fortran | A defensive internal parser invariant was violated. | +When prik rejects your source, it prints a stable code in brackets. Look that +code up here to find out what class of problem it is. + +```text +points.f90:5:1: error[PARSE_MISSING_UNIT_END]: Missing end module for module 'points'. + | +5 | module points + | ^ +``` + +The code is a category identifier — not a line number, a counter, or an exit +status. Codes are stable across releases, so you can match on them in scripts +and tests. + +Add `--debug` to any command to re-raise the failure with a Python traceback. +Add `--no-color` if the highlighting is hard to read. + +## Parser errors + +These stop parsing. All are Fortran-frontend codes. + +### Unit and block structure + +A source unit or block is not closed correctly, or contains something that +cannot appear where it does. + +| Code | Meaning | +| --- | --- | +| `PARSE_INVALID_SYNTAX` | Syntax cannot be consumed in a modeled grammar region. | +| `PARSE_MISSING_UNIT_END` | A source unit has no closing statement. | +| `PARSE_MISMATCHED_UNIT_END` | A named closing statement does not match its opener. | +| `PARSE_UNEXPECTED_UNIT_END` | A closing statement appears while another nested unit is active. | +| `PARSE_MISSING_DERIVED_TYPE_END` | A derived-type declaration has no matching closing statement. | +| `PARSE_EXECUTABLE_IN_SPECIFICATION` | An executable statement appears in a specification region. | + +### Duplicate names + +The same name is declared twice where prik needs one definition. + +| Code | Meaning | +| --- | --- | +| `PARSE_DUPLICATE_UNIT` | A scope contains duplicate named source units of the same kind. | +| `PARSE_DUPLICATE_PROCEDURE` | A scope contains duplicate procedure names. | +| `PARSE_DUPLICATE_DECLARATION` | A procedure symbol is declared more than once. | +| `PARSE_DUPLICATE_SYMBOL` | A file or project scope contains a duplicate symbol. | +| `PARSE_DUPLICATE_PARAMETER` | A procedure contains duplicate `PARAMETER` declarations. | +| `PARSE_DUPLICATE_VARIABLE` | A module-like scope contains conflicting duplicate variable declarations. | +| `PARSE_DUPLICATE_FIELD` | A derived type contains duplicate fields. | +| `PARSE_DUPLICATE_ARGUMENT` | A procedure argument list repeats a name. | + +### Unresolved types + +prik could not determine a datatype it needs. Adding an explicit declaration +usually fixes these. + +| Code | Meaning | +| --- | --- | +| `PARSE_IMPLICIT_NONE_UNDECLARED_SYMBOL` | `implicit none` requires a missing argument or result declaration. | +| `PARSE_UNKNOWN_PARAMETER_TYPE` | A `PARAMETER` symbol has no declared type where one is required. | +| `PARSE_UNKNOWN_VARIABLE_TYPE` | A module variable still has an unknown datatype after parsing. | +| `PARSE_UNKNOWN_FIELD_TYPE` | A derived-type field still has an unknown datatype after parsing. | +| `PARSE_UNKNOWN_FUNCTION_RESULT_TYPE` | A function result has no resolvable datatype. | +| `PARSE_UNRESOLVED_ARGUMENT_TYPE` | A declared argument type could not be applied. | + +### Unsupported forms + +The syntax is valid Fortran, but outside the modeled subset. Check the +[language feature matrix](../language-support/feature-matrix.md). + +| Code | Meaning | +| --- | --- | +| `PARSE_MALFORMED_HEADER` | A module or procedure header is unsupported or malformed. | +| `PARSE_UNSUPPORTED_DECLARATION` | A declaration-shaped line uses an unsupported datatype form. | +| `PARSE_UNSUPPORTED_RESULT_TYPE` | A function header contains an unsupported result-type prefix. | +| `PARSE_UNSUPPORTED_TYPE_BOUND_DECLARATION` | A derived-type `contains` region has an unsupported binding declaration. | +| `PARSE_UNSUPPORTED_OPENMP_DIRECTIVE` | A modeled specification region contains an unsupported OpenMP directive. | +| `PARSE_MISSING_FUNCTION_RESULT` | A function has no result variable. | +| `PARSE_RESULT_SHADOWS_ARGUMENT` | A function result name shadows an argument. | + +### Preprocessing required + +| Code | Meaning | +| --- | --- | +| `PARSE_PREPROCESSING_REQUIRED` | Raw CPP directives need compiler preprocessing before the parser runs. | + +### API misuse and internal invariants + +You will normally see these only when calling the parser API directly. + +| Code | Meaning | +| --- | --- | +| `PARSE_WRONG_ENTRYPOINT` | A singular parser API was called for a different source-unit kind. | +| `PARSE_AMBIGUOUS_ENTRYPOINT` | A singular parser API matched more than one source unit. | +| `PARSE_EXPECTED_UNIT` | An internal unit visitor received the wrong source-unit kind. | +| `PARSE_INTERNAL_STATE` | A defensive internal parser invariant was violated. | +| `PARSE_ERROR` | Fallback for a parse error with no narrower category. | + + -## Preprocessing Diagnostics +## Preprocessing errors + +These happen before the parser sees the source, while running the compiler as a +preprocessor. Compiler stderr is preserved in the message. -Compiler-backed preprocessing failures are rendered by the CLI without a -Python traceback unless `--debug` is used. They occur before the parser consumes -the expanded source. +```text +: error[PREPROCESSOR_NOT_FOUND]: preprocessor not found: nosuchcompiler +``` | Code | Meaning | | --- | --- | -| `PREPROCESSOR_NOT_FOUND` | The configured compiler/preprocessor executable could not be started. | -| `PREPROCESSOR_FAILED` | The compiler/preprocessor returned a non-zero status, timed out, or could not be executed. Compiler stderr is preserved. | -| `INVALID_COMPILER_ARGUMENTS` | The preprocessing configuration is invalid, such as a malformed macro name or unusable compile database entry. | +| `PREPROCESSOR_NOT_FOUND` | The configured compiler or preprocessor could not be started. | +| `PREPROCESSOR_FAILED` | The preprocessor returned a non-zero status, timed out, or could not run. | +| `INVALID_COMPILER_ARGUMENTS` | The preprocessing configuration is invalid, such as a malformed macro name. | | `UNSUPPORTED_COMPILER_CAPABILITY` | The selected adapter was asked for metadata it cannot provide. | -| `PROVENANCE_UNAVAILABLE` | Expanded source was produced, but the adapter cannot provide accurate source mappings. | -| `INCLUDE_NOT_FOUND` | A native Fortran `include "..."` target could not be resolved or read. | -| `INCLUDE_CYCLE` | Recursive native Fortran INCLUDE expansion found a cycle. | +| `PROVENANCE_UNAVAILABLE` | Source expanded, but the adapter cannot provide accurate source mappings. | +| `INCLUDE_NOT_FOUND` | A Fortran `include "..."` target could not be resolved or read. | +| `INCLUDE_CYCLE` | Recursive Fortran `INCLUDE` expansion found a cycle. | + +## Wrapper planning errors + +These come from the wrapper build, after the source parsed and its semantic +policy completed. **They do not carry a bracketed code.** Instead they name the +declaration and the specific policy that has no supported lowering: -## Wrapper Planning Errors +```text +prik: error: Semantic function 'm3.make' has unsupported wrapper policy: +result is an unsupported array of derived values; result has no completed +bridge data action +``` -Wrapper planning errors are emitted by the default wrapper build after semantic -policy completion. The owner path identifies the declaration whose completed -policy has no supported lowering. +The quoted owner path locates the declaration. The reasons after the colon +identify a missing completed policy or an unsupported combination of completed +policies. Either reshape the native declaration, or check whether the form is +supported at all in the +[language feature matrix](../language-support/feature-matrix.md). -Reasons identify a missing completed policy or an unsupported -completed-policy combination. These are build-stage diagnostics rather than a -separate inspection report; see -[Error Handling](../guide/error-handling.md#wrapper-planning-errors) for the -repair workflow. +See [Error Handling](../guide/error-handling.md) for the repair workflow and +how these map to Python exceptions at runtime. ```python import prik -sorted(prik.__all__) +print(sorted(prik.__all__)) +``` + + +```text +['__version__', 'build_fortran_extension', 'build_pyi_extension', 'build_pyi_extension_from_manifest'] ``` ## Root API | Symbol | Use it for | | --- | --- | -| `__version__` | Read the installed PRIK distribution version. | -| `build_fortran_extension` | Build an extension from Fortran source plus optional native-only inputs. | -| `build_pyi_extension` | Build an extension from semantic `.pyi` contracts plus explicit native implementation inputs. | -| `build_pyi_extension_from_manifest` | Replay a saved semantic-`.pyi` build manifest or generate its Makefile. | +| `__version__` | The installed PRIK distribution version. | +| `build_fortran_extension` | Build from Fortran source, plus optional native-only inputs. | +| `build_pyi_extension` | Build from semantic `.pyi` contracts, plus explicit native implementation inputs. | +| `build_pyi_extension_from_manifest` | Replay a saved `.pyi` build manifest, or generate its Makefile. | + +## Building an extension -For normal builds, import directly from the root: +Every build entrypoint returns a `WrapperBuildResult`. Call `import_module()` +on it to load the extension without editing `sys.path`: + ```python +from pathlib import Path +from tempfile import TemporaryDirectory + from prik import build_fortran_extension -result = build_fortran_extension("solver.f90", output_dir="build/solver") -module = result.import_module() +source = Path("tests/fortran/building_shared_library/end_to_end/fixtures/native/fruntime_abi_f90.f90") +with TemporaryDirectory() as output_dir: + build = build_fortran_extension(source, output_dir=output_dir) + print(build.module_name) + print(type(build).__module__ + "." + type(build).__name__) +``` + + +```text +fruntime_abi_f90 +prik.pipeline.build.WrapperBuildResult ``` -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. +Import `WrapperBuildResult` and the native-build plan records from +`prik.pipeline.build` only when you need to inspect or construct them. + +## Advanced package imports -## Advanced Package Imports +Reach past the root facade when you need a single stage rather than a build. | 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` | +| Semantic conversion | `prik.semantics.fortran2ir`, `prik.semantics.pyi2ir` | Fortran conversion helpers, `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 | +| Target type probing | `prik.preprocessing.probes.fortran_types` | probe source, requirements, expressions, report and 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. +| CLI implementation | `prik.cli` | `main()` — shell users should run `python3 -m prik` instead | -## Current Boundaries +## Boundaries -- Root imports are intentionally small and do not load parser or semantic - implementation modules. +- Root imports stay 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. + completion, planning, and generation are separate stages that can each + reject input the parser accepted. +- The C frontend is inspection-only and is not part of the root API. + +## Related pages + +- [CLI Commands](cli-commands.md) — the same workflows from a shell. +- [Fortran Wrapper Reference](fortran-wrapper.md) — build options in depth. +- [Package guides](../../developer/packages/index.md) — module responsibilities + and their focused tests. diff --git a/prik/codegen/fortran/bridge.py b/prik/codegen/fortran/bridge.py index d4626ee25..e3baa494d 100644 --- a/prik/codegen/fortran/bridge.py +++ b/prik/codegen/fortran/bridge.py @@ -4432,11 +4432,24 @@ def _string_value_declarations(self, plan: FunctionPlan) -> tuple[FortranDeclara "character(kind=c_char)", ("pointer", "dimension(:)"), ), - FortranDeclaration(name, f"character(kind=c_char, len={name}_length)"), + self._string_value_declaration(argument, name), ) ) return tuple(declarations) + @staticmethod + def _string_value_declaration(plan: ArgumentTransferPlan, name: str) -> FortranDeclaration: + """Declare the native character local selected by completed bridge policy. + + A deferred-length dummy is not interoperable, so no ``bind(C)`` interface + could declare it and the adapter must build the allocatable local the + native procedure requires. Every other character input keeps its + fixed-length local. + """ + if plan.bridge.deferred_character_length: + return FortranDeclaration(name, "character(kind=c_char, len=:)", ("allocatable",)) + return FortranDeclaration(name, f"character(kind=c_char, len={name}_length)") + def _string_value_initializers( self, plan: FunctionPlan, @@ -4465,6 +4478,7 @@ def _string_value_initializer_nodes( if plan.bridge.codegen_action is CodegenAction.COPY_IN_OUT else f"{name}_bytes" ) + mold = f"repeat(' ', {name}_length)" if plan.bridge.deferred_character_length else name return ( FortranCall( "c_f_pointer", @@ -4474,7 +4488,7 @@ def _string_value_initializer_nodes( CodeExpression(f"[{extent}]"), ), ), - FortranAssignment(name, CodeExpression(f"transfer({source}, {name})")), + FortranAssignment(name, CodeExpression(f"transfer({source}, {mold})")), ) def _string_value_finalizers( diff --git a/prik/planning/models.py b/prik/planning/models.py index d87c802ea..af019f5db 100644 --- a/prik/planning/models.py +++ b/prik/planning/models.py @@ -855,6 +855,7 @@ class BridgeArgumentPlan(StageRecord): codegen_action: CodegenAction data_action: BridgeDataAction copy_reason: str | None + deferred_character_length: bool = False @dataclass diff --git a/prik/planning/planner.py b/prik/planning/planner.py index e4f02f21d..853e94c2d 100644 --- a/prik/planning/planner.py +++ b/prik/planning/planner.py @@ -1796,6 +1796,7 @@ def _bridge_argument_plan(policy: ArgumentPolicy) -> BridgeArgumentPlan: codegen_action=policy.codegen_action, data_action=policy.bridge_data_action, copy_reason=policy.bridge_copy_reason, + deferred_character_length=policy.deferred_character_length, ) @staticmethod diff --git a/prik/policy/construction.py b/prik/policy/construction.py index c8834d52b..273339a74 100644 --- a/prik/policy/construction.py +++ b/prik/policy/construction.py @@ -2393,6 +2393,7 @@ def _argument_policy( python_visible=decision.python_visible, result_position=boundary.result_position, character_length=_character_length(argument.semantic_type), + deferred_character_length=_uses_deferred_character_local(argument.semantic_type, decision), array=array_policy, native_array_actual=_native_array_actual_policy(argument, decision, array_policy), native_array_handle=_native_array_handle_wrapper_policy( @@ -3991,6 +3992,7 @@ def _scalar_or_string_argument_shape_blockers( string_value = _is_plan_string_value_type(argument.semantic_type) if not (_is_first_lane_scalar_type(argument.semantic_type) or string_value): blockers.append(f"argument {argument.name!r} is not a first-lane primitive scalar") + blockers.extend(_deferred_character_blockers(argument, decision)) if not decision.python_visible: blockers.append(f"argument {argument.name!r} is not Python-visible") expected_kind = ObjectKind.STRING if string_value else ObjectKind.SCALAR @@ -4993,6 +4995,53 @@ def _runtime_status_plan_blockers(policy: NativeStatusErrorPolicy | None) -> tup return tuple(blockers) +def _has_deferred_character_length(semantic_type: models.SemanticType) -> bool: + """Return whether one character value declares a deferred length parameter. + + A ``character(len=:)`` dummy is not interoperable, so no ``bind(C)`` + interface can declare it and the generated Fortran adapter must build the + local the native dummy requires. Assumed length (``character(len=*)``) is + a different form and stays fixed-length here. + """ + return semantic_type.metadata.get("fortran_character_length") == ":" + + +def _uses_deferred_character_local( + semantic_type: models.SemanticType, + decision: OwnershipDecision, +) -> bool: + """Return whether the adapter must build an allocatable deferred-length local. + + Only a read-only allocatable dummy is supported. A pointer dummy needs a + pointer actual the adapter has nothing to target, and a mutable dummy may be + reallocated to a different length than the caller's buffer holds; both are + blocked by :func:`_deferred_character_blockers`. + """ + return bool( + _has_deferred_character_length(semantic_type) + and semantic_type.metadata.get("fortran_allocatable") + and decision.codegen_action is CodegenAction.CALL_LOCAL_INPUT + ) + + +def _deferred_character_blockers( + argument: models.SemanticArgument, + decision: OwnershipDecision, +) -> tuple[str, ...]: + """Restrict deferred-length character arguments to the supported read-only lane.""" + if not _has_deferred_character_length(argument.semantic_type): + return () + label = f"argument {argument.name!r}" + if argument.semantic_type.metadata.get("fortran_pointer"): + return (f"{label} is a deferred-length character pointer; the adapter has no target to associate",) + if decision.codegen_action is not CodegenAction.CALL_LOCAL_INPUT: + return ( + f"{label} is a mutable deferred-length character argument; the native procedure may " + "reallocate it to a length the caller buffer cannot hold", + ) + return () + + def _character_length(semantic_type: models.SemanticType) -> int | None: """Return a positive fixed Fortran character length, normalizing accepted metadata spellings.""" value = semantic_type.metadata.get("fortran_character_length") diff --git a/prik/policy/models.py b/prik/policy/models.py index 5676b95f1..8b8f17037 100644 --- a/prik/policy/models.py +++ b/prik/policy/models.py @@ -1153,6 +1153,7 @@ class ArgumentPolicy: python_visible: bool result_position: int | None character_length: int | None + deferred_character_length: bool = False array: ArrayHandoffPolicy | None = None native_array_actual: NativeArrayActualPolicy | None = None native_array_handle: NativeArrayHandleWrapperPolicy | None = None diff --git a/tests/fortran/strings/codegen/test_string_input_lowering.py b/tests/fortran/strings/codegen/test_string_input_lowering.py index 3e3ffe3ac..2eaf0bf7a 100644 --- a/tests/fortran/strings/codegen/test_string_input_lowering.py +++ b/tests/fortran/strings/codegen/test_string_input_lowering.py @@ -101,3 +101,69 @@ def test_string_handoff_plan_edits_fail_before_backend_lowering(edit: str, diagn with pytest.raises(ValueError, match=diagnostic): WrapperGenerator().generate(plan) + + +DEFERRED_INPUT_SOURCE = """ +module deferred_input + implicit none +contains + subroutine measure(value, length) + character(len=:), allocatable, intent(in) :: value + integer(4), intent(out) :: length + length = len(value) + end subroutine measure +end module deferred_input +""" + + +def _deferred_input_plan(tmp_path): + 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.preprocessing import PreprocessingConfig + from prik.semantics.fortran2ir import fortran_project_to_semantic_modules + + source = tmp_path / "deferred_input.f90" + source.write_text(DEFERRED_INPUT_SOURCE, encoding="utf-8") + parsed = parse_fortran_project({str(source): _fortran_source_for_pipeline(source, PreprocessingConfig())}) + modules = fortran_project_to_semantic_modules(parsed) + _apply_source_python_exports(modules) + module = _merge_wrapper_modules(modules, name="deferred_input") + complete_semantic_policies(module) + return WrapperPlanner().build(module) + + +def test_deferred_length_string_input_plans_an_allocatable_adapter_local(tmp_path): + """The bridge facet carries the deferred fact; the shared entrypoint does not. + + A deferred-length dummy cannot appear in a ``bind(C)`` interface, so the + adapter local is adapter-local conversion rather than part of the C ABI. + """ + plan = _deferred_input_plan(tmp_path) + function = next( + function + for namespace in plan.namespaces + for function in namespace.functions + if function.binding.python_name == "measure" + ) + argument = function.arguments[0] + + assert argument.bridge.deferred_character_length is True + assert argument.entrypoint.handoff_mode is ArgumentHandoffMode.CHARACTER_BUFFER + assert argument.bridge.data_action is BridgeDataAction.COPY_REPRESENTATION + + +def test_deferred_length_string_input_lowers_to_allocatable_local_without_changing_the_binding(tmp_path): + """The adapter allocates on assignment; the C binding keeps the byte buffer.""" + artifacts = WrapperGenerator().generate(_deferred_input_plan(tmp_path)) + bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") + c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") + + assert "character(kind=c_char, len=:), allocatable :: value" in bridge_source + assert "transfer(value_bytes, repeat(' ', value_length))" in bridge_source + assert "character(kind=c_char, len=value_length)" not in bridge_source + # The shared C ABI is unchanged: the binding still hands over bytes plus a length. + assert "bind_c_measure" in c_source diff --git a/tests/fortran/strings/policy/test_string_wrapper_policy.py b/tests/fortran/strings/policy/test_string_wrapper_policy.py index 135d3b6cc..995b09f1e 100644 --- a/tests/fortran/strings/policy/test_string_wrapper_policy.py +++ b/tests/fortran/strings/policy/test_string_wrapper_policy.py @@ -1,5 +1,6 @@ from pathlib import Path +import pytest from tests.fortran._support.ownership_policy import parse_pyi_text from tests.fortran._support.wrapper_build import wrapper_source @@ -122,3 +123,107 @@ def discard_name(name: String[8]) -> None: ... assert identity.arguments[0].codegen_action is CodegenAction.CALL_LOCAL_INPUT assert identity.arguments[0].projects_result is False assert identity.writeback_actions == () + + +def _semantic_module_from_text(source_text: str, tmp_path: Path, *, module_name: str): + """Complete policy for one inline Fortran source without a shared fixture.""" + source = tmp_path / f"{module_name}.f90" + source.write_text(source_text, encoding="utf-8") + parsed = parse_fortran_project({str(source): _fortran_source_for_pipeline(source, PreprocessingConfig())}) + modules = fortran_project_to_semantic_modules(parsed) + _apply_source_python_exports(modules) + module = _merge_wrapper_modules(modules, name=module_name) + complete_semantic_policies(module) + return module + + +def test_read_only_deferred_length_string_argument_completes_deferred_policy(tmp_path: Path): + """A ``character(len=:)`` input records the fact the adapter needs. + + No ``bind(C)`` interface can declare a deferred-length dummy, so the + generated Fortran adapter must build the allocatable local itself. Policy + owns that fact; the bridge only implements it. + """ + module = _semantic_module_from_text( + """ +module deferred_input + implicit none +contains + subroutine measure(value, length) + character(len=:), allocatable, intent(in) :: value + integer(4), intent(out) :: length + length = len(value) + end subroutine measure +end module deferred_input +""", + tmp_path, + module_name="deferred_input", + ) + policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + + assert policy.supported is True + argument = policy.arguments[0] + assert argument.deferred_character_length is True + assert argument.character_length is None + assert argument.handoff_mode is ArgumentHandoffMode.CHARACTER_BUFFER + + +def test_fixed_and_assumed_length_string_arguments_stay_fixed_length(): + """Only a deferred length selects the allocatable adapter local. + + ``character(len=8)`` and ``character(len=*)`` both keep the fixed-length + local, so this guards the narrow scope of the deferred flag. + """ + module = parse_pyi_text( + """ +def fixed(text: String[8]) -> Int32: ... +def assumed(text: String) -> Int32: ... +""", + module_name="non_deferred_strings", + ) + complete_semantic_policies(module) + + for index in (0, 1): + policy = module.functions[index].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + assert policy.arguments[0].deferred_character_length is False + + +@pytest.mark.parametrize( + ("attribute", "intent", "expected"), + [ + ("allocatable", "inout", "mutable deferred-length character argument"), + ("pointer", "in", "deferred-length character pointer"), + ], +) +def test_unsupported_deferred_length_character_arguments_are_blocked( + attribute: str, + intent: str, + expected: str, + tmp_path: Path, +): + """Only the read-only allocatable deferred lane is wrapped. + + A mutable dummy may be reallocated to a length the caller buffer cannot + hold, and a pointer dummy needs a pointer actual the adapter has no target + for. Both must stop at policy rather than emit an adapter that miscompiles + or silently returns the pre-call value. + """ + module = _semantic_module_from_text( + f""" +module deferred_unsupported + implicit none +contains + subroutine consume(value, length) + character(len=:), {attribute}, intent({intent}) :: value + integer(4), intent(out) :: length + length = len(value) + end subroutine consume +end module deferred_unsupported +""", + tmp_path, + module_name="deferred_unsupported", + ) + policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + + assert policy.supported is False + assert any(expected in blocker for blocker in policy.blockers) From 8c492164ccf2adb21f422a3bfe8fd36556b11b4c Mon Sep 17 00:00:00 2001 From: said Date: Tue, 18 Aug 2026 19:03:31 +0100 Subject: [PATCH 02/26] Record the deferred-length character update design Documents the selected approach for mutable character(len=:) arguments and the two rejected alternatives, so the remaining work can start from a clean session without re-deriving the boundary. Co-Authored-By: Claude Sonnet 5 --- .../native-entrypoint-adoption-checklist.md | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md index dbba76416..dc41b2ede 100644 --- a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md +++ b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md @@ -798,6 +798,66 @@ Selective direct Fortran routing is ready to claim only when: Goal 2 completion does not claim that PRIK accepts native C inputs. +## Deferred-Length Character Update Lane + +Independent of Goal 3. Read-only `character(len=:), allocatable, intent(in)` +arguments and `intent(out)` results are implemented. This section records the +completed design for the remaining mutable case so it can be built from a clean +start. + +### Current State (2026-08-18) + +| Form | Behavior | +| --- | --- | +| `allocatable, intent(in)` | Supported. The adapter builds the allocatable local from the binding byte buffer. | +| `allocatable, intent(out)` | Supported. Projected descriptor result with `c_malloc` storage and a length readback. | +| `allocatable, intent(inout)` | Blocked in policy by `_deferred_character_blockers`. | +| `character(len=:), pointer` | Blocked in policy; the adapter has no target to associate. | + +The bridge fact is `ArgumentPolicy.deferred_character_length`, set by +`_uses_deferred_character_local` and projected onto `BridgeArgumentPlan`. The C +ABI is unchanged for the read-only lane: the binding still passes a byte buffer +and a length. + +### Selected Design For `intent(inout)` + +The dummy is a Python-visible **input argument** that also projects a +**descriptor-backed result**. Output transport belongs to the result facet and +to the bidirectional entrypoint, not to argument presence. + +- [ ] Complete one policy action for a deferred-length allocatable string + update: the argument keeps a plain character-buffer input + (`CALL_LOCAL_INPUT`, not `COPY_IN_OUT`), and a `ResultPolicy` carries the + existing `ScalarDescriptorResultPolicy` unchanged. +- [ ] Relax the `python_visible=False` gate in `_hidden_result_policies` for + that completed action only. A deferred string update is the first shape that + is caller-supplied *and* returns freshly allocated storage; hidden outputs and + fixed-length replacements keep their current selection. +- [ ] Let the entrypoint carry the descriptor output parameters it already + produces for `intent(out)`. Do not encode output transport as an + `OptionalMode`: that enum describes argument presence, and reusing it for + transport mixes two facets. +- [ ] Do not relax the `descriptor_boundary` equivalence with descriptor + optional modes in `pipeline/wrapper.py`. That invariant is what catches real + inconsistencies; the design above keeps it exact because the argument stays a + non-descriptor input. +- [ ] Reuse the existing binding result path that builds a Python string from + the returned pointer and length and releases the C storage. +- [ ] Prove the round trip end to end: a native procedure that reallocates its + dummy to a longer value must return the new value, and an unallocated dummy + must return `None`. + +### Rejected Alternatives + +Both were attempted and reverted; the notes prevent re-deriving them. + +- **Relaxing `descriptor_boundary ⟺ descriptor optional mode.** Makes the + invariant conditional and removes its ability to catch inconsistencies. +- **A new `OptionalMode` for string updates.** `OptionalMode` describes argument + presence. Setting `REQUIRED_DESCRIPTOR` also routes the C binding into + `_lower_argument_required_descriptor`, which calls + `PrimitiveScalarTypeRegistry.type_for` and rejects `String`. + ## Goal 3 — Initial Direct-Only C Adoption Start Goal 3 only after Goal 2 is complete. Goal 3 adds C as a native input From 35e3d5d711c8eafb646f7644a2f73412ec021989 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 19 Aug 2026 13:20:04 +0100 Subject: [PATCH 03/26] Support allocatable and pointer scalar character values A scalar character dummy carrying `allocatable` or `pointer` needs an adapter local with the same attribute; the generated adapter always built a plain fixed-length temporary, which the Fortran compiler rejects. Most of these forms therefore stopped at a policy diagnostic, and the declared-length ones reached gfortran or plan validation and failed there. Policy now completes the adapter-local storage each dummy needs -- its attribute, its length, and who releases it -- as `CharacterLocalPolicy`, replacing the narrower `deferred_character_length` fact. Every direction is supported at deferred and declared length: `intent(in)`, `intent(out)`, `intent(inout)`, and function results. The C ABI is unchanged; a scalar character argument still crosses as a byte buffer and a length. A `pointer` local is storage the adapter allocated, so its release is a completed decision. A read-only dummy cannot reassociate, so the adapter always frees it. A mutable dummy may be reassociated or deallocated by the native procedure, so the adapter frees its allocation only while the dummy still identifies it -- freeing the seed unconditionally double-frees the ordinary deallocate-then-reallocate idiom, so a reassociating procedure orphans the call-local allocation instead. An `allocatable` character function result is moved out through an allocatable dummy rather than assigned, which makes allocation a testable fact, so an unallocated result becomes `None`. Separately, pointer array handles now expose `deallocate()` without a `PointerPolicy` annotation, matching what allocatable handles already offered. Release stays manual and caller-driven -- prik never frees a native target on its own -- so this is the same responsibility a Fortran caller takes writing `deallocate`. Previously a procedure returning freshly allocated pointer storage leaked with no way to reclaim it from Python. Stages changed: policy (ownership, completion, construction), planning (models, planner), codegen (Fortran bridge), pipeline validation, and docs. Verified with the full Fortran suite (2213 passed), docs/c/tools/workflows (1188 passed), and the static-analysis gate. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 54 +++- README.md | 31 +- .../native-entrypoint-adoption-checklist.md | 103 +++++-- docs/user/guide/strings.md | 107 ++++++- docs/user/language-support/feature-matrix.md | 4 +- docs/user/reference/fortran-wrapper.md | 13 +- docs/user/reference/semantic-pyi-format.md | 85 +++++- prik/codegen/fortran/bridge.py | 289 ++++++++++++++++-- prik/pipeline/wrapper.py | 101 +++++- prik/planning/models.py | 40 ++- prik/planning/planner.py | 59 +++- prik/policy/completion.py | 58 +++- prik/policy/construction.py | 164 +++++++--- prik/policy/models.py | 56 +++- prik/policy/ownership.py | 102 ++++++- prik/printers/pyi.py | 24 +- prik/semantics/models.py | 1 + prik/semantics/pyi2ir.py | 62 ++-- .../pointers/codegen/test_pointer_lowering.py | 6 +- .../end_to_end/test_pointer_handles.py | 68 +++++ .../policy/test_pointer_ownership_policy.py | 14 +- .../test_calls_and_policy_metadata.py | 2 +- .../codegen/test_string_input_lowering.py | 200 +++++++++++- .../fstring_descriptors_f90/__init__.pyi | 1 + .../fstring_descriptors_f90.pyi | 126 ++++++++ .../contracts/fstrings_f90/fstrings_f90.pyi | 2 +- .../fixtures/fstring_descriptors_f90.f90 | 207 +++++++++++++ .../test_scalar_string_descriptors.py | 172 +++++++++++ .../test_generated_string_contracts.py | 1 + .../policy/test_string_wrapper_policy.py | 173 +++++++++-- .../semantics/test_string_pyi_semantics.py | 63 +++- 31 files changed, 2143 insertions(+), 245 deletions(-) create mode 100644 tests/fortran/strings/end_to_end/fixtures/contracts/fstring_descriptors_f90/__init__.pyi create mode 100644 tests/fortran/strings/end_to_end/fixtures/contracts/fstring_descriptors_f90/fstring_descriptors_f90.pyi create mode 100644 tests/fortran/strings/end_to_end/fixtures/fstring_descriptors_f90.f90 create mode 100644 tests/fortran/strings/end_to_end/test_scalar_string_descriptors.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 71220433a..010c114ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,53 @@ release tags add a leading `v` to the package version. ### Added -- Added wrapper support for read-only deferred-length scalar character - arguments (`character(len=:), allocatable, intent(in)`). The generated - Fortran adapter now builds the allocatable local the native dummy requires - instead of a fixed-length temporary the compiler rejected. The C ABI is - unchanged: the binding still passes a byte buffer and a length. Mutable - `intent(inout)` and `pointer` deferred-length arguments now stop at policy - with a diagnostic instead of failing in the Fortran compiler. +- Pointer array handles now expose `deallocate()` without a `PointerPolicy` + annotation, matching what allocatable handles already offered. Release stays + manual and caller-driven — prik never frees a native target on its own, on + garbage collection or otherwise — so this is the same responsibility a + Fortran caller takes when writing `deallocate` for the same pointer. + Previously a wrapped procedure that returned freshly allocated pointer + storage leaked with no way to reclaim it from Python. `allocate` and `resize` + still require `PointerPolicy`, because they establish a new target rather + than releasing the one the handle already names. +- Added wrapper support for `allocatable` and `pointer` scalar `character` + values in every direction: `intent(in)`, `intent(out)`, and `intent(inout)` + arguments, and function results, at both deferred (`len=:`) and declared + (`len=n`) length. Policy now completes the adapter-local storage each dummy + needs — its attribute, its length, and who releases it — instead of always + building a plain fixed-length temporary. The C ABI is unchanged: a scalar + character argument still crosses as a byte buffer and a length whatever the + dummy declares. Previously most of these forms either stopped at a policy + diagnostic or reached the Fortran compiler and failed there with + "Actual argument for 'x' must be ALLOCATABLE"; declared-length allocatable + and pointer forms additionally failed plan validation. +- Added a character-length subscription to semantic `.pyi` contracts. The first + subscription after `String` is always the length — `String[...]` assumed, + `String[8]` or `String[n]` explicit, `String[:]` deferred — and an array adds + its shape as a second subscription. Deferred-length scalars therefore have a + contract spelling for the first time, so those procedures rebuild from their + generated contract; the one-subscription array spellings the printer used to + emit (`String[::]`, and `String[n]` for an extent) are replaced by + `String[...][::]` and `String[...][n]`, which the parser had rejected or read + as a scalar length. +- Added wrapper support for mutable scalar character descriptor arguments + (`allocatable` or `pointer`, `intent(inout)`). The dummy stays a `str` + argument and additionally returns the value the native procedure left behind, + or `None` when it leaves the dummy unallocated or unassociated. Policy + completes two decisions for the one dummy — a call-local character-buffer + input and a nullable descriptor result — so the adapter copies back the local + the native procedure may have replaced rather than the caller's buffer. A + pointer dummy additionally records who releases the target the adapter + allocated: the adapter frees it only while the dummy still identifies it, so + storage the native procedure deallocated or replaced is left alone. The dummy + spells as `Allocatable(Arg(i))` or `Pointer(Arg(i))` with `String[:]` or + `String[n]` in a semantic `.pyi` contract, so these procedures also rebuild + from their generated contract. +- Added wrapper support for `allocatable` scalar `character` function results. + The adapter moves the result out through an allocatable dummy rather than + assigning it, which makes allocation a testable fact, so an unallocated + result becomes `None`. Other allocatable scalar function results remain + blocked, because they have no such completed move. - Added a native-entrypoint adoption roadmap for selective direct Fortran `bind(C)` calls and the initial direct-only C wrapper backend, including conservative starter-contract defaults for ambiguous C pointers. diff --git a/README.md b/README.md index 196252872..c455cd428 100644 --- a/README.md +++ b/README.md @@ -217,35 +217,28 @@ code generation with a diagnostic naming the boundary and the reason. - arrays of derived types, and assumed-type `type(*)` arrays; - character arrays that cannot be represented as a fixed-width NumPy bytes - dtype, and mutable or pointer deferred-length scalar character arguments - (`character(len=:)` with `intent(inout)` or `pointer`); read-only - `allocatable, intent(in)` arguments and `allocatable, intent(out)` results - are supported; + dtype, `allocatable` and `pointer` character *fields*, and character + *module variables* other than `allocatable` or `pointer` arrays. - quad precision — `real(16)` and `complex(16)` — which has no portable NumPy - dtype. Everything narrower is supported, including all `logical` kinds. + dtype. Everything narrower is supported. **Procedures and polymorphism** -- procedure pointers, including procedure-pointer module variables, and +- procedure-pointer module variables, and callbacks retained after the wrapped call returns; -- polymorphic outputs, mutable polymorphic arguments, polymorphic arrays, +- polymorphic outputs, mutable polymorphic arguments, unlimited polymorphism (`class(*)`), abstract types, and deferred bindings; - constructor overload sets whose candidates are ambiguous or incomplete. **Storage and ownership** -- pointer target deallocation and writable reassociation, which stay gated - behind explicit completed policy. - -Scalar allocatable and pointer *arguments* are supported — they cross the -boundary as values (`Float64 | None`) rather than as array handles, so there is -no rank-zero handle form such as `Allocatable[Float64]()`. - -**Builds** - -- dependency-graph discovery, prebuilt module-path resolution, and external - library discovery. Pass sources, objects, and libraries in the order you - want them built and linked. +- establishing a *new* pointer target — `allocate` and `resize` — which stays + gated behind an explicit `PointerPolicy`. Operations on the target a handle + already names (`deallocate`, `associate`, `nullify`) need no annotation, and + carry the same responsibility as writing them in Fortran. prik never frees a + native target on your behalf, so a wrapped procedure returning freshly + allocated storage leaks until you call `deallocate()`. Allocatable handles + additionally get `resize` without an annotation. The [language feature matrix](https://pynumlab.github.io/prik/user/language-support/feature-matrix/) records the full support status of every feature with its evidence. diff --git a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md index dc41b2ede..19521f8a6 100644 --- a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md +++ b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md @@ -798,26 +798,49 @@ Selective direct Fortran routing is ready to claim only when: Goal 2 completion does not claim that PRIK accepts native C inputs. -## Deferred-Length Character Update Lane +## Scalar Character Descriptor Lanes -Independent of Goal 3. Read-only `character(len=:), allocatable, intent(in)` -arguments and `intent(out)` results are implemented. This section records the -completed design for the remaining mutable case so it can be built from a clean -start. +Independent of Goal 3. Every `allocatable` and `pointer` scalar `character` +form is implemented. This section records the completed design. -### Current State (2026-08-18) +### Current State (2026-08-19, updated after implementation) + +The attribute, not the length, decides the lane. A dummy carrying `allocatable` +or `pointer` will not accept a plain temporary as its actual argument, so policy +completes the adapter local — attribute, length, and release — for each one. | Form | Behavior | | --- | --- | -| `allocatable, intent(in)` | Supported. The adapter builds the allocatable local from the binding byte buffer. | -| `allocatable, intent(out)` | Supported. Projected descriptor result with `c_malloc` storage and a length readback. | -| `allocatable, intent(inout)` | Blocked in policy by `_deferred_character_blockers`. | -| `character(len=:), pointer` | Blocked in policy; the adapter has no target to associate. | - -The bridge fact is `ArgumentPolicy.deferred_character_length`, set by -`_uses_deferred_character_local` and projected onto `BridgeArgumentPlan`. The C -ABI is unchanged for the read-only lane: the binding still passes a byte buffer -and a length. +| `allocatable`/`pointer`, `intent(in)` | Supported. The adapter builds the matching local from the binding byte buffer. | +| `allocatable`/`pointer`, `intent(out)` | Supported. Projected descriptor result with `c_malloc` storage and a length readback. | +| `allocatable`/`pointer`, `intent(inout)` | Supported. Call-local character-buffer input plus a projected descriptor result. | +| `allocatable` function result | Supported. Moved out through an allocatable dummy, so an unallocated result is `None` rather than a read of storage that was never established. | +| `pointer` function result | Supported. Copied out of the associated target. | + +Declared length (`len=n`) and deferred length (`len=:`) both work in each row. +A descriptor local spells the declared length rather than the runtime one, +because neither side is deferred there and the standard requires them to agree. + +A `pointer` local is storage the adapter allocated, so its release is a +completed decision: an `intent(in)` dummy cannot reassociate, so the adapter +always frees it; a mutable dummy is freed only while it still identifies that +allocation. A native procedure that reassociates or nullifies a mutable pointer +dummy therefore orphans the adapter's allocation — the alternative, freeing the +seed unconditionally, double-frees the ordinary "deallocate then reallocate" +idiom, so the leak is the deliberate choice. + +The contract vocabulary now spells every character length in the first +subscription after `String`: `String[...]` assumed, `String[8]` explicit, and +`String[:]` deferred, with any array shape in a second subscription. That closed +a round-trip gap affecting every deferred-length *scalar*, including the +read-only lane that shipped first, whose generated contract previously said +plain `String` (assumed length) and failed to rebuild. It also replaced the +one-subscription array spellings (`String[::]`, `String[n]`), which the printer +emitted but the parser rejected or silently read as a scalar length. + +The bridge fact is `ArgumentPolicy.character_local`, set by +`_character_local_policy` and projected onto `BridgeArgumentPlan`. The C ABI is +unchanged in every lane: the binding still passes a byte buffer and a length. ### Selected Design For `intent(inout)` @@ -825,27 +848,37 @@ The dummy is a Python-visible **input argument** that also projects a **descriptor-backed result**. Output transport belongs to the result facet and to the bidirectional entrypoint, not to argument presence. -- [ ] Complete one policy action for a deferred-length allocatable string +- [x] Complete one policy action for a deferred-length allocatable string update: the argument keeps a plain character-buffer input (`CALL_LOCAL_INPUT`, not `COPY_IN_OUT`), and a `ResultPolicy` carries the existing `ScalarDescriptorResultPolicy` unchanged. -- [ ] Relax the `python_visible=False` gate in `_hidden_result_policies` for - that completed action only. A deferred string update is the first shape that - is caller-supplied *and* returns freshly allocated storage; hidden outputs and +- [x] Let a Python-visible argument produce a `ResultPolicy`. The gate in + `_hidden_result_policies` stayed `python_visible=False`; instead the dummy + owns **two** completed decisions, following the getter/setter precedent. + `RESOLVED_UPDATE_RESULT_OWNERSHIP_POLICY_METADATA` holds the result facet, + resolved from the same native-output context an `intent(out)` dummy uses, so + every hidden-result validator keeps checking a real result contract instead of + being relaxed against the argument's input decision. Hidden outputs and fixed-length replacements keep their current selection. -- [ ] Let the entrypoint carry the descriptor output parameters it already - produces for `intent(out)`. Do not encode output transport as an - `OptionalMode`: that enum describes argument presence, and reusing it for - transport mixes two facets. -- [ ] Do not relax the `descriptor_boundary` equivalence with descriptor - optional modes in `pipeline/wrapper.py`. That invariant is what catches real - inconsistencies; the design above keeps it exact because the argument stays a - non-descriptor input. -- [ ] Reuse the existing binding result path that builds a Python string from - the returned pointer and length and releases the C storage. -- [ ] Prove the round trip end to end: a native procedure that reallocates its - dummy to a longer value must return the new value, and an unallocated dummy - must return `None`. +- [x] Let the entrypoint carry the descriptor output parameters it already + produces for `intent(out)`. `ResultPolicy.updates_argument` names the fact + through planning; the output group is named `_output` (the suffix the + existing required-descriptor copyout already uses) so it cannot collide with + the input's own name and length parameters. No new `OptionalMode`. +- [x] Do not relax the `descriptor_boundary` equivalence with descriptor + optional modes in `pipeline/wrapper.py`. The argument stays a non-descriptor + `REQUIRED` input, so the invariant held exactly and was not touched. +- [x] Reuse the existing binding result path that builds a Python string from + the returned pointer and length and releases the C storage. The C binding + needed no change at all. +- [x] Prove the round trip end to end. `tests/fortran/strings/end_to_end/` + compiles and imports the fixture: a reallocated dummy returns the new value, + a deallocated dummy returns `None`, an unallocated optional returns `None`, + and a zero-length value stays `''`. + +The one genuinely new emitted-code mechanism is in the adapter: the descriptor +readback reads the argument's call-local allocatable rather than a result-local +of its own, since the native procedure reallocates that local in place. ### Rejected Alternatives @@ -857,6 +890,12 @@ Both were attempted and reverted; the notes prevent re-deriving them. presence. Setting `REQUIRED_DESCRIPTOR` also routes the C binding into `_lower_argument_required_descriptor`, which calls `PrimitiveScalarTypeRegistry.type_for` and rejects `String`. +- **One ownership decision for both facets.** Reusing the argument's + `CALLER/CALL_LOCAL` input decision as the result's ownership forces + `_scalar_descriptor_result_blockers` and the plan's hidden-result checks to be + relaxed on owner, destruction, nullability, descriptor boundary, and Python + action at once — exactly the checks that would otherwise catch a wrapper + returning the pre-call value. The second decision keeps them enforcing. ## Goal 3 — Initial Direct-Only C Adoption diff --git a/docs/user/guide/strings.md b/docs/user/guide/strings.md index 402910074..d0c421ff6 100644 --- a/docs/user/guide/strings.md +++ b/docs/user/guide/strings.md @@ -248,15 +248,104 @@ b'Xlpha ' - `String[8][()]` and `String[8][count]` require dtype `S8`. - A dummy without `intent` uses the conservative `intent(inout)` behavior. -Deferred-length scalar storage (`character(len=:)`) is supported in two -places: a read-only `allocatable, intent(in)` argument, and an -`allocatable, intent(out)` result, which PRIK projects as a returned string. - -Two forms are blocked before code generation. A mutable -`allocatable, intent(inout)` argument is rejected because the native procedure -may reallocate it to a length the caller's buffer cannot hold. A -`character(len=:), pointer` argument is rejected because the adapter has no -target to associate. Use a fixed-width buffer for both. +## Allocatable And Pointer Scalar Strings + +A scalar `character` dummy may carry the `allocatable` or `pointer` attribute, +at a deferred length (`character(len=:)`) or a declared one +(`character(len=8)`). Every combination is supported, in every direction: + +| Fortran dummy | Python surface | +| --- | --- | +| `intent(in)` | A `str` argument. | +| `intent(out)` | A returned `str`, or `None` when the procedure leaves it unallocated or unassociated. | +| `intent(inout)` | A `str` argument that also returns the value the procedure left behind, or `None`. | +| function result | A returned `str`, or `None`. | + +The attribute never changes the Python surface, and it never changes how the +value crosses into native code — a scalar string is always a byte buffer and a +length. It changes only the storage PRIK builds inside the generated adapter, +because an `allocatable` or `pointer` dummy will not accept a plain temporary as +its actual argument. + +An update keeps its `str` argument and adds a return value, because the native +procedure chooses the new value during the call and the caller's string cannot +hold it: + +```fortran +subroutine grow(value) + character(len=:), allocatable, intent(inout) :: value + if (allocated(value)) value = value // '!!!' +end subroutine grow +``` + +```python +print(grow("ab")) # ab!!! +``` + +The Python string you pass is never modified; the reallocated value comes back +as the result. A procedure that deallocates the dummy returns `None`, which is +how you tell an unallocated result from an empty string: + +```python +print(drop("abc")) # None +print(repr(empty_out("abc"))) # '' +``` + +### Pointer Dummies And Native Storage + +A `pointer` dummy needs an associated actual argument, so PRIK allocates a +target for the call. What happens to that target afterwards is the native +procedure's decision, and PRIK follows it: + +| The native procedure… | Python receives | PRIK's target | +| --- | --- | --- | +| writes through the pointer | the edited value | freed after the call | +| leaves it alone | the value passed in | freed after the call | +| deallocates it | `None` | already freed; not freed again | +| nullifies it | `None` | orphaned by the procedure | +| reassociates it elsewhere | the new target's value | orphaned by the procedure | + +PRIK copies the value out of whatever the dummy ends up holding and never frees +native storage, because it cannot know whether that storage is a static target, +a fresh allocation, or something the library still owns. Two consequences are +worth planning for: a procedure that reassociates or nullifies the dummy +orphans the target PRIK allocated for that call, and a procedure that returns a +freshly allocated pointer each call leaks unless it also frees it. Prefer an +`allocatable` dummy, whose release is unambiguous, when you control the Fortran +side. + +### Spelling Them In A Contract + +In a semantic `.pyi` contract, the attribute is a `native_call` projection and +the length is the first subscription after `String`: + +| Contract | Fortran | +| --- | --- | +| `String` | `character(len=*)` — the caller fixes the length | +| `String[8]` | `character(len=8)` — exactly eight encoded bytes | +| `String[:]` | `character(len=:)` — the length comes from allocation | + +So the procedure above generates: + +```python +@native_call([Allocatable(Arg(0))]) +def grow(value: String[:] | None) -> Returns["value", String[:]] | None: ... +``` + +`Allocatable(...)` and `Pointer(...)` carry the attribute, and they wrap the +argument, the projected output, or the result: + +```python +@native_call([Pointer(Arg(0))]) +def edit(value: String[4] | None) -> Returns["value", String[4]] | None: ... + +@native_call([], result=Allocatable(Return(0))) +def build() -> String[:] | None: ... +``` + +Arrays keep the length in that same first slot and add their shape second, as in +`String[8][:]` or `Allocatable[String[:][:]]`. See the +[semantic `.pyi` format](../reference/semantic-pyi-format.md) for the full table. ## Next diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index 35e69044f..ef326db00 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -70,7 +70,7 @@ limitation for each feature. | Generic constructor interfaces and overloaded runtime initialization | Supported | [Constructors](../guide/wrapping-derived-types.md#custom-constructor) | [Class policy and lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Edited class surface tests](../../../tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py), [class policy tests](../../../tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates require distinguishable completed Python signatures; incomplete or ambiguous sets are blocked before emission. | | Module variables, constants, saved state, and common-block procedure state | Supported | [Wrapping modules](../guide/wrapping-modules.md) | [Module state route](../../developer/feature-to-code-map.md#feature-routes) | [Module state tests](../../../tests/fortran/modules/end_to_end/test_module_variables_and_state.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py), [common-block tests](../../../tests/fortran/modules/end_to_end/test_common_blocks.py) | Common-block storage is not exported as Python variables. Rank-zero derived module objects use direct, scoped, allocation-transaction, or pointer-transaction handoff selected before lowering. | | Fortran enum constants | Supported | [Enumerations](../guide/enumerations.md) | [Semantic constants route](../../developer/codebase-map.md#cross-stage-hotspots) | [Enum runtime tests](../../../tests/fortran/enumerations/end_to_end/test_enum_runtime.py), [enum semantic tests](../../../tests/fortran/enumerations/semantics/test_enum_semantics.py), [enum diagnostics](../../../tests/fortran/enumerations/parsing/test_enum_diagnostics.py) | No Python `Enum` or `IntEnum` classes are generated. | -| Scalar character arguments, results, and fields | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character argument tests](../../../tests/fortran/strings/end_to_end/test_character_boundaries.py), [edge-case tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Deferred-length `character(len=:)` scalars are supported as read-only `allocatable, intent(in)` arguments and as `allocatable, intent(out)` results; mutable `intent(inout)` and pointer deferred length are blocked before generation. | +| Scalar character arguments, results, and fields | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character argument tests](../../../tests/fortran/strings/end_to_end/test_character_boundaries.py), [edge-case tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Scalar `character` `allocatable` and `pointer` values are supported for `intent(in)`, `intent(out)`, `intent(inout)`, and function results, at deferred (`len=:`) and declared (`len=n`) length; a mutable dummy returns the value the procedure left behind, or `None`. prik copies out of native pointer storage and never frees it, so a procedure that allocates a fresh target per call leaks unless it frees its own. | | Scalar kind coverage | Supported | [Data types](../guide/data-types.md) | [Fortran type probe](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py) | Quad precision (`real(16)`, `complex(16)`) is blocked because it has no portable NumPy dtype. All `logical` kinds are supported and adapt to one-byte NumPy Booleans at the boundary. | | Caller-ordered multi-source builds, Makefiles, verbose mode, and output placement | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Multi-source tests](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py), [compiler verbose tests](../../../tests/fortran/building_shared_library/compiling/test_compiler_verbose.py) | prik does not discover, reorder, or resolve all external source dependencies. | | Visibility, naming, keyword escaping, and collision policy | Supported | [Visibility and naming](../reference/fortran-wrapper.md#visibility-naming-and-the-python-surface) | [Naming policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Visibility/naming tests](../../../tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_naming.py) | Strict mode rejects names that default mode can normalize. | @@ -113,7 +113,7 @@ memory, or outlive its native storage. | Blocked array forms | Unsupported | [Arrays](../guide/arrays.md) | [Array policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Array semantic tests](../../../tests/fortran/arrays/semantics/test_array_semantics.py), [diagnostics](../reference/diagnostic-codes.md) | Assumed type `type(*)`, arrays of derived types, and character arrays not representable as fixed-width bytes need missing runtime contracts. | | Unsupported polymorphic forms | Unsupported | [Inheritance limits](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/codegen/test_class_surfaces.py) | Results, mutable dummies, arrays, polymorphic allocatable/pointer scalars, and `class(*)` are blocked. | | Ambiguous or incomplete constructor overload sets | Unsupported | [Constructor limitations](../reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | [Constructor route](../../developer/codebase-map.md#cross-stage-hotspots) | [Constructor semantic tests](../../../tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py), [class-plan validation tests](../../../tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates must have distinguishable exact runtime signatures and compatible native-owner lifecycles. | -| Character arrays and caller-supplied deferred-length character storage | Partially supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character edge tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Fixed and allocatable deferred element length maps to dtype itemsize; Unicode/object arrays are unsupported. Deferred-length `character(len=:)` scalars work as read-only `allocatable` arguments and `allocatable, intent(out)` results; mutable `intent(inout)` and pointer deferred length are blocked. | +| Character arrays and caller-supplied deferred-length character storage | Partially supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character edge tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Fixed and allocatable deferred element length maps to dtype itemsize; Unicode/object arrays are unsupported. Scalar `character` `allocatable` and `pointer` values work for every intent and as function results. A mutable `pointer` dummy that the native procedure reassociates without deallocating orphans the target the adapter allocated for that call. | | Quad-precision real and complex storage | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | `real(16)` and `complex(16)` have no portable NumPy dtype, so prik blocks them rather than silently narrowing to 64-bit. Narrower real, complex, integer, and all logical kinds are supported. | Character arrays use fixed-width NumPy bytes dtypes such as `S5`; the dtype itemsize is the Fortran element length. Deferred-length allocatable character arrays carry that length at runtime and return a fresh fixed-width bytes array. -Python Unicode arrays, object arrays, mutable scalar deferred-length character -storage, deferred-length character fields, and mutable character-buffer fields -remain blocked until an explicit field and encoding policy exists. +Python Unicode arrays, object arrays, `allocatable` and `pointer` character +fields at any length, mutable character-buffer fields, and scalar or +fixed-shape character module variables remain blocked until an explicit field +and encoding policy exists. Plain fixed-length character fields, and +`allocatable` or `pointer` character module arrays, are supported. Scalar +`allocatable` and `pointer` character dummies and results are supported in +every direction; see +[Strings](../guide/strings.md#allocatable-and-pointer-scalar-strings). ## Scalar Types And Kind Coverage @@ -2348,7 +2353,7 @@ wrappers: | Pointers | Scalar-derived pointer results without stable typed holder storage, expired-target results, and unproved reassociation or ownership-changing operations | Stable target lifetime, descriptor identity, typed holder storage, or explicit operation policy. | | Polymorphism | Results, mutable dummies, arrays, allocatable/pointer scalars, `class(*)` | Dynamic type, allocation, replacement, and ownership. | | Constructors | Incomplete or indistinguishable constructor overload sets | Every candidate needs a complete exact runtime signature and compatible owner lifecycle. | -| Characters | Mutable scalar allocatable character dummies and deferred-length mutable fields | Allocation, encoding, replacement, and destruction. | +| Characters | Deferred-length mutable character fields | Allocation, encoding, replacement, and destruction. | | Kinds | Real wider than 64 bits, complex wider than 128 bits, wider explicit logical storage | Portable NumPy round-trip without silent precision loss. | | Callbacks | Stored, optional, cross-thread, or procedure-pointer callbacks | Persistent ownership, thread, exception, nullability, and teardown. | diff --git a/docs/user/reference/semantic-pyi-format.md b/docs/user/reference/semantic-pyi-format.md index 84e5c761a..f19c77fdc 100644 --- a/docs/user/reference/semantic-pyi-format.md +++ b/docs/user/reference/semantic-pyi-format.md @@ -940,22 +940,80 @@ PRIK_C_DOCS_END --> +## Character Length And Shape + +A `String` annotation carries two independent facts. The first subscription is +the character length; the second, when present, is the scalar-storage or array +shape. + +| Contract | Character length | Python/storage shape | +| --- | --- | --- | +| `String` | assumed | scalar | +| `String[...]` | assumed | scalar | +| `String[8]` | explicit `8` | scalar | +| `String[n]` | explicit `n` | scalar | +| `String[:]` | deferred | scalar | +| `String[8][()]` | explicit `8` | rank-0 storage | +| `String[8][:]` | explicit `8` | contiguous rank-1 | +| `String[8][::]` | explicit `8` | stride-aware rank-1 | +| `String[8][n]` | explicit `8` | extent `n` | +| `String[...][:]` | assumed | contiguous rank-1 | +| `String[...][::]` | assumed | stride-aware rank-1 | +| `String[...][n]` | assumed | extent `n` | +| `String[:][:]` | deferred | contiguous rank-1 | +| `String[:][::]` | deferred | stride-aware rank-1 | + +Bare `String` is the scalar shorthand for `String[...]`. Because an array always +spells its length first, a single subscription is never a shape: `String[::]` is +rejected with a diagnostic naming the second-subscription form. + +The three lengths mean different things at the native boundary: + +- `String[...]` is `character(len=*)`: the actual argument fixes the length for + the call, and native code cannot change it. +- `String[8]` is `character(len=8)`: the length is part of the contract, and the + wrapper requires exactly that many encoded bytes. +- `String[:]` is `character(len=:)`: the length is established by allocation and + may change during the call, so the dummy also needs `allocatable` or + `pointer` storage. A `String[:]` output is `None` when it is unallocated. + +The length is independent of the descriptor attribute. `Allocatable(Arg(i))` +and `Pointer(Arg(i))` name the attribute of the native dummy, and either one +combines with `String[n]` or `String[:]`: + +```python +@native_call([Allocatable(Arg(0))]) +def grow(value: String[:] | None) -> Returns["value", String[:]] | None: ... + +@native_call([Pointer(Arg(0))]) +def relabel(value: String[4] | None) -> Returns["value", String[4]] | None: ... + +@native_call([], result=Allocatable(Return(0))) +def build() -> String[:] | None: ... +``` + +A scalar character dummy with either attribute is a `str` argument that also +projects a result, because the native procedure may replace the storage rather +than write through it. The projected result is `None` when the procedure leaves +the dummy unallocated or unassociated. + ## Python And Native Boundaries Semantic `.pyi` annotations describe two related but separate boundaries: @@ -978,6 +1036,7 @@ arguments, or scalar by-address projection differs from the default lowering. | `Float64[()]` | rank-zero NumPy array with dtype `np.float64` | storage address | | `Float64[n]`, `Float64[:]`, `Float64[:, :]` | NumPy array storage | data address | | `String[n]` | Python `str` whose encoded length is exactly `n` | address of prik's call-local fixed-width character storage | +| `String[:]` | Python `str`; `None` when an output is unallocated | deferred-length character local built by the generated adapter, carrying the attribute `Allocatable(...)` or `Pointer(...)` names | | `String[n][:]`, `String[:][:]` | NumPy bytes array storage | character array descriptor/data contract | | `String[n][()]` | rank-zero NumPy bytes array with dtype `S` | fixed-width character storage copied back into the NumPy array when native code mutates it | | `Addr(Float64)`, `Addr(Float64[n])`, `Addr(String[n])` | integer raw address such as `array.ctypes.data` or a `ctypes` buffer address | that raw address | @@ -1293,7 +1352,7 @@ Loaded compatibility metadata: | --- | --- | | `Contiguous` | source provenance says the array is contiguous | | `ArrayCategory("...")` | source array category provenance | -| `FortranAllocatable` | older scalar character allocatable metadata; generated contracts use `Allocatable[String]` | +| `FortranAllocatable` | older scalar character allocatable metadata; generated contracts use `Allocatable[String[:]]` | ```text +/* Python callable 'ping'. */ +/* Calls the native entrypoint 'bind_c_ping'. */ static PyObject * wrap_ping(PyObject * self, PyObject * args, PyObject * kwargs) { static char * kwlist[] = {NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwlist)) return NULL; @@ -241,6 +243,8 @@ static PyObject * wrap_double_value(PyObject * self, PyObject * args, PyObject * #endif /* BINDING_DEMO_WRAPPER_H */ Rendered C binding wrapper: +/* Python callable 'double_value'. */ +/* Calls the native entrypoint 'bind_c_double_value'. */ static PyObject * wrap_double_value(PyObject * self, PyObject * args, PyObject * kwargs) { static char * kwlist[] = {"value", NULL}; PyObject * bound_value_obj; diff --git a/docs/developer/packages/codegen/fortran-bridge.md b/docs/developer/packages/codegen/fortran-bridge.md index a131e058b..edf3a0f30 100644 --- a/docs/developer/packages/codegen/fortran-bridge.md +++ b/docs/developer/packages/codegen/fortran-bridge.md @@ -194,6 +194,8 @@ print(FortranSourcePrinter().doprint(bridge_module.procedures[0])) ``` ```text +! Adapter for native procedure 'PING'. +! Exported to the binding as the C symbol 'bind_c_ping'. subroutine bind_c_ping() bind(c, name="bind_c_ping") external :: PING call PING() @@ -245,6 +247,9 @@ module bind_c_bridge_demo_wrapper use bridge_demo, only: native_double_value => DOUBLE_VALUE implicit none contains + + ! Adapter for native procedure 'DOUBLE_VALUE'. + ! Exported to the binding as the C symbol 'bind_c_double_value'. function bind_c_double_value(value) result(result) bind(c, name="bind_c_double_value") real(c_double), value :: value real(c_double) :: result diff --git a/docs/developer/packages/printers.md b/docs/developer/packages/printers.md index a5b71e9ae..0db27473e 100644 --- a/docs/developer/packages/printers.md +++ b/docs/developer/packages/printers.md @@ -125,6 +125,7 @@ module bind_c_printer_demo_wrapper 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 diff --git a/prik/codegen/c/binding.py b/prik/codegen/c/binding.py index 21eb0b225..366fa6457 100644 --- a/prik/codegen/c/binding.py +++ b/prik/codegen/c/binding.py @@ -161,6 +161,24 @@ class _COverloadDispatch: public: bool +_BINDING_GETTER_SUMMARIES = { + ModuleGetterAction.CONSTANT_VALUE: "The value is a constant placed in the module dictionary at import.", + ModuleGetterAction.NATIVE_CONSTANT_VALUE: "Builds a Python object from the compiler-evaluated constant.", + ModuleGetterAction.NATIVE_CONSTANT_ARRAY_VALUE: "Copies the parameter array into one read-only NumPy array.", + ModuleGetterAction.DIRECT_VALUE: "Builds a Python scalar from the current native value.", + ModuleGetterAction.CHARACTER_VALUE: "Decodes the fixed-width native characters into a Python str.", + ModuleGetterAction.NULLABLE_SNAPSHOT: "Returns a detached copy, or None when the native value holds nothing.", + ModuleGetterAction.BORROWED_ARRAY_VIEW: "Wraps the native storage in a live NumPy array without copying.", + ModuleGetterAction.DERIVED_OBJECT: "Returns the generated wrapper object for the native value.", +} + +_BINDING_SETTER_SUMMARIES = { + SetterAction.WRITE_THROUGH: "Validates the incoming object and writes it into native storage.", + SetterAction.REJECT_REPLACEMENT: "Replacement is rejected; the attribute is read-only.", + SetterAction.OMIT: "No setter is exposed.", +} + + class CBindingGenerator(ClassVisitor): """Build the CPython C half of a wrapper from validated binding-plan views. @@ -5362,11 +5380,28 @@ def _native_array_capsule_release_name(plan: ArgumentTransferPlan | ResultPlan) owner = re.sub(r"\W", "_", plan.owner_path).casefold() return f"prik_release_native_handle_{owner}" + @staticmethod + def _documented(functions: tuple[CFunction, ...], *doc: str) -> tuple[CFunction, ...]: + """Attach explanatory prose to generated functions that carry none.""" + return tuple(function if function.doc else replace(function, doc=doc) for function in functions) + def _visit_ModuleVariablePlan(self, plan: ModuleVariablePlan) -> tuple[CFunction, ...]: """Lower binding-owned getter and setter actions into C functions.""" + # The binding facet names the Python attribute and the C symbols it + # calls; the native Fortran variable belongs to the bridge facet and is + # deliberately not read here. + name = plan.binding.python_names[0] return ( - *self._lower_module_getter(plan), - *self._lower_module_setter(plan), + *self._documented( + self._lower_module_getter(plan), + f"Read module attribute '{name}'.", + _BINDING_GETTER_SUMMARIES.get(plan.binding.getter_action, ""), + ), + *self._documented( + self._lower_module_setter(plan), + f"Assign module attribute '{name}'.", + _BINDING_SETTER_SUMMARIES.get(plan.binding.setter_action, ""), + ), ) def _lower_module_getter(self, plan: ModuleVariablePlan) -> tuple[CFunction, ...]: @@ -5991,6 +6026,7 @@ def _visit_FunctionPlan(self, plan: FunctionPlan) -> CFunction: output_nodes = self._output_nodes(plan, context) return CFunction( name=self._binding_function_name(plan), + doc=self._binding_function_doc(plan), return_type="PyObject *", parameters=self._binding_parameters(), storage="static", @@ -6067,6 +6103,20 @@ def _binding_conversion_order(self, plan: FunctionPlan) -> tuple[ArgumentTransfe except KeyError as error: raise ValueError(f"Unknown binding argument conversion owner {error.args[0]!r}") from None + def _binding_function_doc(self, plan: FunctionPlan) -> tuple[str, ...]: + """Describe one CPython wrapper: its Python name and the symbol it calls. + + A reader opening the generated binding sees the Python entry point and + the native symbol it reaches without cross-referencing the plan. + """ + lines = [ + f"Python callable '{plan.binding.python_name}'.", + f"Calls the native entrypoint '{plan.entrypoint.symbol_name}'.", + ] + if plan.binding.release_gil: + lines.append("Releases the GIL around the native call.") + return tuple(lines) + def _visit_ArgumentTransferPlan( self, plan: ArgumentTransferPlan, diff --git a/prik/codegen/fortran/bridge.py b/prik/codegen/fortran/bridge.py index 55f7a45af..2560b068b 100644 --- a/prik/codegen/fortran/bridge.py +++ b/prik/codegen/fortran/bridge.py @@ -108,6 +108,28 @@ from prik.codegen.visitor import ClassVisitor +_MODULE_GETTER_SUMMARIES = { + ModuleGetterAction.CONSTANT_VALUE: "The value is a compile-time constant materialized by the binding.", + ModuleGetterAction.NATIVE_CONSTANT_VALUE: "Returns the compiler-evaluated constant by value.", + ModuleGetterAction.NATIVE_CONSTANT_ARRAY_VALUE: ( + "Copies the parameter array into persistent storage and reports its width and extents." + ), + ModuleGetterAction.DIRECT_VALUE: "Returns the variable's current value.", + ModuleGetterAction.CHARACTER_VALUE: "Copies the characters into a fixed-width byte buffer.", + ModuleGetterAction.NULLABLE_SNAPSHOT: ( + "Copies the value into C-owned storage, or reports a null pointer when it holds nothing." + ), + ModuleGetterAction.BORROWED_ARRAY_VIEW: "Returns the array's address plus its width and extents, without copying.", + ModuleGetterAction.DERIVED_OBJECT: "Returns the address of the derived object.", +} + +_MODULE_ASSIGNMENT_SUMMARIES = { + AssignmentMode.NONE: "No native assignment is generated.", + AssignmentMode.VALUE_COPY: "Copies the incoming value into the variable.", + AssignmentMode.ALIAS: "Points the variable at the incoming storage.", +} + + class FortranBridgeGenerator(ClassVisitor): """Build the Fortran half of a wrapper from validated bridge-plan views. @@ -504,6 +526,7 @@ def _visit_FunctionPlan( ) return FortranFunction( name=entrypoint_name, + doc=self._entrypoint_doc(plan, entrypoint_name), parameters=parameters, result_name=result_name, result_type=result_type, @@ -1982,12 +2005,34 @@ def _owned_native_array_result_operation_name( def _visit_ModuleVariablePlan(self, plan: ModuleVariablePlan) -> tuple[FortranFunction, ...]: """Lower bridge-owned getter and setter actions into procedures.""" if plan.bridge.native_getter_action is ModuleGetterAction.NATIVE_ARRAY_HANDLE: - return self._lower_module_native_array_operations(plan) + return self._documented( + self._lower_module_native_array_operations(plan), + f"Runtime handle operations for native module variable '{plan.bridge.native_name}'.", + "Each is one operation the generated Python handle calls.", + ) return ( - *self._lower_module_getter(plan), - *self._lower_module_setter(plan), + *self._documented( + self._lower_module_getter(plan), + f"Read native module variable '{plan.bridge.native_name}'.", + _MODULE_GETTER_SUMMARIES.get(plan.bridge.native_getter_action, ""), + ), + *self._documented( + self._lower_module_setter(plan), + f"Write native module variable '{plan.bridge.native_name}'.", + _MODULE_ASSIGNMENT_SUMMARIES.get(plan.bridge.native_assignment, ""), + ), ) + @staticmethod + def _documented(procedures: tuple[FortranFunction, ...], *doc: str) -> tuple[FortranFunction, ...]: + """Attach explanatory prose to generated procedures that carry none. + + The text is emitted as leading comments so a reader opening the + generated module can tell what each procedure is for without + reconstructing it from the wrapper plan. + """ + return tuple(procedure if procedure.doc else replace(procedure, doc=doc) for procedure in procedures) + def _lower_module_getter(self, plan: ModuleVariablePlan) -> tuple[FortranFunction, ...]: """Dispatch one completed bridge getter action explicitly.""" action = plan.bridge.native_getter_action @@ -3026,6 +3071,44 @@ def _lower_module_setter_value_copy(self, plan: ModuleVariablePlan) -> tuple[For ), ) + def _entrypoint_doc(self, plan: FunctionPlan, entrypoint_name: str) -> tuple[str, ...]: + """Describe one adapter: who calls it, what it calls, and what it converts. + + The adapter exists because the original procedure is not callable + across the C ABI as declared, so the summary names the conversions that + difference forces rather than restating the signature. + """ + # Only bridge and entrypoint facts are read here: the Python-visible + # name belongs to the binding facet, which this generator never reads. + lines = [ + f"Adapter for native procedure '{plan.bridge.native_name}'.", + f"Exported to the binding as the C symbol '{entrypoint_name}'.", + ] + work = self._entrypoint_doc_conversions(plan) + if work: + lines.append(f"Converts: {'; '.join(work)}.") + return tuple(lines) + + def _entrypoint_doc_conversions(self, plan: FunctionPlan) -> tuple[str, ...]: + """Summarize the conversions this adapter performs, in argument order.""" + notes: list[str] = [] + for argument in plan.arguments: + name = argument.entrypoint.parameter_name + if argument.entrypoint.handoff_mode is ArgumentHandoffMode.CHARACTER_BUFFER: + local = argument.bridge.character_local if argument.bridge is not None else None + attribute = local.descriptor_kind.value if local and local.descriptor_kind else "fixed-length" + article = "an" if attribute[0] in "aeiou" else "a" + notes.append(f"'{name}' byte buffer into {article} {attribute} character local") + elif argument.entrypoint.handoff_mode is ArgumentHandoffMode.ARRAY_BUFFER: + notes.append(f"'{name}' buffer into a Fortran array actual") + elif argument.entrypoint.handoff_mode is ArgumentHandoffMode.NATIVE_DESCRIPTOR: + notes.append(f"'{name}' native descriptor") + for result in plan.results: + if result.scalar_descriptor is not None: + role = "updated value" if result.updates_argument else "descriptor result" + notes.append(f"copies out the {role} for '{result.owner_path.rsplit('.', 1)[-1]}'") + return tuple(notes) + def _visit_ArgumentTransferPlan(self, plan: ArgumentTransferPlan) -> tuple[FortranParameter, ...]: """Lower one argument through the completed optional-mode action.""" return self._lower_argument(plan) diff --git a/prik/codegen/nodes.py b/prik/codegen/nodes.py index 168b530d3..f1bcb1173 100644 --- a/prik/codegen/nodes.py +++ b/prik/codegen/nodes.py @@ -63,6 +63,13 @@ class CComment(StageRecord): text: str +@dataclass +class FortranComment(StageRecord): + """One generated Fortran line comment.""" + + text: str + + @dataclass class CParameter(StageRecord): """C function parameter.""" @@ -240,6 +247,7 @@ class CFunction(StageRecord): ..., ] = () storage: str | None = None + doc: tuple[str, ...] = () @dataclass @@ -450,6 +458,7 @@ class FortranFunction(StageRecord): ] = () is_subroutine: bool = False internal_procedures: tuple[FortranFunction, ...] = () + doc: tuple[str, ...] = () @dataclass diff --git a/prik/printers/c.py b/prik/printers/c.py index cce308070..a3e940c4f 100644 --- a/prik/printers/c.py +++ b/prik/printers/c.py @@ -8,6 +8,8 @@ from __future__ import annotations +import textwrap + from prik.codegen.nodes import ( CAllowThreadsBegin, CAllowThreadsEnd, @@ -96,7 +98,8 @@ 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}}" + doc = "".join(f"/* {chunk} */\n" for line in node.doc for chunk in (textwrap.wrap(line, width=96) or [""])) + return f"{doc}{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.""" diff --git a/prik/printers/fortran.py b/prik/printers/fortran.py index 0c1114be5..562ce2c86 100644 --- a/prik/printers/fortran.py +++ b/prik/printers/fortran.py @@ -9,10 +9,13 @@ import re +import textwrap + from prik.codegen.nodes import ( FortranAllocate, FortranAssignment, FortranCall, + FortranComment, FortranDeallocate, FortranDeclaration, FortranFunction, @@ -224,11 +227,37 @@ def _visit_FortranModule(self, node: FortranModule) -> str: lines.extend(self._indented(self.visit(declaration)) for declaration in node.declarations) lines.extend(self._indented(self.visit(interface)) for interface in node.interfaces if not interface.abstract) lines.append("contains") - lines.extend(self._indented(self.visit(procedure)) for procedure in node.procedures) + for procedure in node.procedures: + # One blank line before each procedure keeps a long generated module + # scannable; without it every procedure abuts the previous `end`. + lines.append("") + lines.append(self._indented(self.visit(procedure))) lines.append(f"end module {node.name}") - lines.extend(self.visit(procedure) for procedure in node.standalone_procedures) + for procedure in node.standalone_procedures: + lines.append("") + lines.append(self.visit(procedure)) return "\n".join(lines) + @staticmethod + def _doc_comment_lines(doc: tuple[str, ...]) -> list[str]: + """Render one procedure's explanatory prose as wrapped Fortran line comments. + + Free-form Fortran caps a line at 132 columns, and a generated procedure + is indented inside its module, so prose is wrapped well short of that + rather than emitted as one long line. + """ + lines: list[str] = [] + for entry in doc: + if not entry: + lines.append("!") + continue + lines.extend(f"! {chunk}" for chunk in textwrap.wrap(entry, width=96) or [""]) + return lines + + def _visit_FortranComment(self, node: FortranComment) -> str: + """Render one generated Fortran line comment.""" + return f"! {node.text}" if node.text else "!" + def _visit_FortranUse(self, node: FortranUse) -> str: """Render one Fortran use statement and wrap a long ONLY list.""" if node.only: @@ -249,7 +278,7 @@ def _visit_FortranFunction(self, node: FortranFunction) -> str: optional internal procedures in Fortran's required source order. """ signature = self._function_signature(node) - lines = [signature, *self._fortran_function_specification(node)] + lines = [*self._doc_comment_lines(node.doc), signature, *self._fortran_function_specification(node)] lines.extend(self._indented(self.visit(statement)) for statement in node.body) if node.internal_procedures: lines.append("contains") diff --git a/tests/fortran/infrastructure/codegen/test_ordinary_fortran_codegen_baseline.py b/tests/fortran/infrastructure/codegen/test_ordinary_fortran_codegen_baseline.py index c1a00a47d..31e5bcd21 100644 --- a/tests/fortran/infrastructure/codegen/test_ordinary_fortran_codegen_baseline.py +++ b/tests/fortran/infrastructure/codegen/test_ordinary_fortran_codegen_baseline.py @@ -21,12 +21,12 @@ def test_ordinary_fortran_wrapper_preserves_exact_generated_bytes(): expected = { "bind_c_ordinary_entrypoint_baseline_wrapper.f90": ( - 740, - "cdda3f054ab348a128cfc31bb338fe0ec12277d41c607b209c8b401cc2a29004", + 843, + "01c092ac9eaa0d90b58f0289a49ba0c71c967510e60a384602fe2e6e1e9b035f", ), "ordinary_entrypoint_baseline_wrapper.c": ( - 1860, - "0401eb6eae8b2b3682a6f04986b8da1553fe77cf070fe4b981e642c7ed13c6d2", + 1941, + "9b944e6ebb8f5b1eef87407e046117b5d2b350286cc32917bb2f8182ab3bbb30", ), "ordinary_entrypoint_baseline_wrapper.h": ( 248, From 9084fc7e6ff1558385a3518994d4dfed9b10605e Mon Sep 17 00:00:00 2001 From: said Date: Wed, 19 Aug 2026 19:30:47 +0100 Subject: [PATCH 10/26] Keep the character work inside its owning stages An audit of this branch against main found six issues, all in code added here. None changed behavior; the full suite passes unchanged at 2222. Two were stage violations. Selecting the move collector for an allocatable character function result re-derived, in code generation, the fact that its storage may be absent -- the equivalent array result reads that as completed policy (`result_allocation is MAYBE_UNALLOCATED`). Policy now states it as `ScalarDescriptorResultPolicy.may_be_unallocated` and both collectors read a completed fact. Separately, both backends chose the module *setter* lowering by reading the *getter* action; the bridge now dispatches on `AssignmentMode.CHARACTER_COPY` and the binding on `setter_converts_characters`, each stating the mechanism on its own facet. The plan validator rejected the new assignment mode until it was taught it, which is that stage working as intended. The rest were minimality and duplication. `FortranComment` was added with a printer visitor but never constructed, since procedure prose travels on the function node's `doc` field; both are removed. `_module_array_element_type` was left as a one-line wrapper with a single caller once its character branch moved, and is inlined. Character-length normalization existed twice, in ownership and construction, with the same accepted spellings maintained separately; construction now uses the one parser that lives beside the other character metadata readers it already imports. Facet separation is intact in both directions: the bridge reads no binding fact or Python name, and the binding reads no bridge or adapter fact. Co-Authored-By: Claude Opus 5 --- prik/codegen/c/binding.py | 2 +- prik/codegen/fortran/bridge.py | 21 ++++++++++++--------- prik/codegen/nodes.py | 7 ------- prik/pipeline/wrapper.py | 4 +++- prik/planning/models.py | 2 ++ prik/planning/planner.py | 4 +++- prik/policy/construction.py | 29 +++++++++++++++++++---------- prik/policy/models.py | 8 +++++++- prik/policy/ownership.py | 30 +++++++++++++++++++++--------- prik/printers/fortran.py | 5 ----- 10 files changed, 68 insertions(+), 44 deletions(-) diff --git a/prik/codegen/c/binding.py b/prik/codegen/c/binding.py index 366fa6457..b9600f7b6 100644 --- a/prik/codegen/c/binding.py +++ b/prik/codegen/c/binding.py @@ -5969,7 +5969,7 @@ def _lower_module_setter(self, plan: ModuleVariablePlan) -> tuple[CFunction, ... def _lower_module_setter_write_through(self, plan: ModuleVariablePlan) -> tuple[CFunction, ...]: """Return a Python-to-native scalar write-through helper.""" - if plan.binding.getter_action is ModuleGetterAction.CHARACTER_VALUE: + if plan.binding.setter_converts_characters: return self._lower_module_setter_character_value(plan) scalar_type = PrimitiveScalarTypeRegistry.type_for(plan.semantic_type_name) return ( diff --git a/prik/codegen/fortran/bridge.py b/prik/codegen/fortran/bridge.py index 2560b068b..827eb552b 100644 --- a/prik/codegen/fortran/bridge.py +++ b/prik/codegen/fortran/bridge.py @@ -2828,7 +2828,9 @@ def _lower_module_getter_constant_array_value(self, plan: ModuleVariablePlan) -> # declaration spells `len=*` and takes it from an initializer prik does # not evaluate, so the element length is read from the parameter itself. element_type = ( - f"character(kind=c_char, len=len({native}))" if character else self._module_array_element_type(plan) + f"character(kind=c_char, len=len({native}))" + if character + else PrimitiveScalarTypeRegistry.type_for(plan.semantic_type_name).fortran_spelling ) width = ("itemsize",) if character else () return ( @@ -2885,10 +2887,6 @@ def _lower_module_getter_constant_array_value(self, plan: ModuleVariablePlan) -> ), ) - def _module_array_element_type(self, plan: ModuleVariablePlan) -> str: - """Return the Fortran scalar element spelling one module array declares.""" - return PrimitiveScalarTypeRegistry.type_for(plan.semantic_type_name).fortran_spelling - def _lower_module_getter_borrowed_array_view( self, plan: ModuleVariablePlan, @@ -3049,6 +3047,8 @@ def _lower_module_setter(self, plan: ModuleVariablePlan) -> tuple[FortranFunctio return self._lower_module_setter_none(plan) case AssignmentMode.VALUE_COPY: return self._lower_module_setter_value_copy(plan) + case AssignmentMode.CHARACTER_COPY: + return self._lower_module_setter_character_value(plan) raise ValueError(f"Unsupported Fortran module setter assignment for {plan.owner_path!r}: {action!r}") def _lower_module_setter_none(self, _plan: ModuleVariablePlan) -> tuple[FortranFunction, ...]: @@ -3057,8 +3057,6 @@ def _lower_module_setter_none(self, _plan: ModuleVariablePlan) -> tuple[FortranF def _lower_module_setter_value_copy(self, plan: ModuleVariablePlan) -> tuple[FortranFunction, ...]: """Return one value-copy native module assignment.""" - if plan.bridge.native_getter_action is ModuleGetterAction.CHARACTER_VALUE: - return self._lower_module_setter_character_value(plan) scalar_type = PrimitiveScalarTypeRegistry.type_for(plan.semantic_type_name) name = self._module_bridge_setter_name(plan) return ( @@ -5498,13 +5496,18 @@ def _direct_result_internal_procedures(self, plan: FunctionPlan) -> tuple[Fortra @classmethod def _uses_allocatable_character_result_collector(cls, result: ResultPlan | None) -> bool: - """Return whether a direct character result travels through the move helper.""" + """Return whether a direct character result travels through the move helper. + + Whether the storage may be absent is a completed policy fact, exactly as + it is for an owned array result; this only selects the lowering it asks + for. + """ descriptor = result.scalar_descriptor if result is not None else None return bool( result is not None and descriptor is not None and result.object_kind is ObjectKind.STRING - and descriptor.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE + and descriptor.may_be_unallocated ) @staticmethod diff --git a/prik/codegen/nodes.py b/prik/codegen/nodes.py index f1bcb1173..ba964b219 100644 --- a/prik/codegen/nodes.py +++ b/prik/codegen/nodes.py @@ -63,13 +63,6 @@ class CComment(StageRecord): text: str -@dataclass -class FortranComment(StageRecord): - """One generated Fortran line comment.""" - - text: str - - @dataclass class CParameter(StageRecord): """C function parameter.""" diff --git a/prik/pipeline/wrapper.py b/prik/pipeline/wrapper.py index 1fd06ec47..17dc1e0f0 100644 --- a/prik/pipeline/wrapper.py +++ b/prik/pipeline/wrapper.py @@ -1355,7 +1355,9 @@ def _module_write_through_setter_diagnostics( ) -> tuple[WrapperPlanDiagnostic, ...]: """Validate one scalar module write-through setter.""" diagnostics = [] - if plan.bridge.native_assignment is not AssignmentMode.VALUE_COPY: + # A character write copies a byte buffer rather than a value, but it is + # the same write-through contract; every other mechanism is rejected. + if plan.bridge.native_assignment not in {AssignmentMode.VALUE_COPY, AssignmentMode.CHARACTER_COPY}: diagnostics.append( self._diagnostic(plan.owner_path, "invalid-module-native-assignment", plan.bridge.native_assignment) ) diff --git a/prik/planning/models.py b/prik/planning/models.py index a36fd3fb4..3db87be44 100644 --- a/prik/planning/models.py +++ b/prik/planning/models.py @@ -591,6 +591,7 @@ class ScalarDescriptorResultPlan(StageRecord): copy_reason: str release_owner: OwnershipOwner presence_role: str + may_be_unallocated: bool = False @dataclass @@ -701,6 +702,7 @@ class BindingModuleVariablePlan(StageRecord): setter_action: SetterAction initializer: Any constant_value: Any + setter_converts_characters: bool = False @dataclass diff --git a/prik/planning/planner.py b/prik/planning/planner.py index 27174d710..7eb73a200 100644 --- a/prik/planning/planner.py +++ b/prik/planning/planner.py @@ -69,7 +69,7 @@ completed_module_variable_policy, ) from prik.policy.exports import PythonExportPolicy -from prik.policy.ownership import NativeBarrierAction, SetterAction +from prik.policy.ownership import AssignmentMode, NativeBarrierAction, SetterAction from prik.planning.models import ( ArrayHandoffPlan, ArgumentTransferPlan, @@ -1100,6 +1100,7 @@ def _module_variable_plan( setter_action=policy.setter_action, initializer=policy.initializer, constant_value=policy.constant_value, + setter_converts_characters=policy.native_assignment is AssignmentMode.CHARACTER_COPY, ), entrypoint=NativeEntrypointModuleVariablePlan( descriptor_kind=policy.descriptor_kind, @@ -2101,6 +2102,7 @@ def _scalar_descriptor_result_plan( copy_reason=policy.copy_reason, release_owner=policy.release_owner, presence_role=f"{owner_path}:present", + may_be_unallocated=policy.may_be_unallocated, ) # Native-array-handle planning. diff --git a/prik/policy/construction.py b/prik/policy/construction.py index be227002a..5b3fefb2c 100644 --- a/prik/policy/construction.py +++ b/prik/policy/construction.py @@ -44,6 +44,7 @@ StorageMode, TransferMode, character_descriptor_kind, + declared_character_length, is_character_descriptor_update, uses_deferred_character_length, ) @@ -1174,7 +1175,7 @@ def _scalar_module_variable_policy( getter_action=getter_action, getter=getter, setter_action=setter.setter_action if setter is not None else SetterAction.OMIT, - native_assignment=_scalar_module_native_assignment(setter), + native_assignment=_scalar_module_native_assignment(setter, variable), setter=setter, descriptor_kind=descriptor_kind, initializer=( @@ -2653,7 +2654,11 @@ def _direct_result_policy(context: _FunctionPolicyContext) -> _ResultPolicyCandi function.metadata.get(models.RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA), result_path, ) - scalar_descriptor = _scalar_descriptor_result_policy(return_type, decision) + scalar_descriptor = _scalar_descriptor_result_policy( + return_type, + decision, + may_be_unallocated=_scalar_descriptor_kind(return_type) == "allocatable", + ) blockers = list(_result_blockers(return_type, decision)) if ( scalar_descriptor is not None @@ -5131,12 +5136,7 @@ def _character_descriptor_blockers( def _character_length(semantic_type: models.SemanticType) -> int | None: """Return a positive fixed Fortran character length, normalizing accepted metadata spellings.""" - value = semantic_type.metadata.get("fortran_character_length") - if isinstance(value, int) and not isinstance(value, bool) and value > 0: - return value - if isinstance(value, str) and value.strip().isdigit() and int(value.strip()) > 0: - return int(value.strip()) - return None + return declared_character_length(semantic_type.metadata) def _lifecycle_policies( @@ -5286,6 +5286,7 @@ def _scalar_descriptor_result_policy( decision: OwnershipDecision, *, descriptor_kind: str | None = None, + may_be_unallocated: bool = False, ) -> ScalarDescriptorResultPolicy | None: """Project one completed nullable rank-zero descriptor copy policy.""" if decision.kind is ObjectKind.DERIVED_TYPE: @@ -5301,6 +5302,7 @@ def _scalar_descriptor_result_policy( nullable=decision.nullable, copy_reason=SCALAR_DESCRIPTOR_RESULT_COPY_REASON, release_owner=OwnershipOwner.PYTHON, + may_be_unallocated=may_be_unallocated, ) @@ -5980,7 +5982,7 @@ def _scalar_module_setter_blockers( return ("scalar constant must omit native setter assignment",) return () if setter.setter_action is SetterAction.WRITE_THROUGH: - if setter.assignment_mode is not AssignmentMode.VALUE_COPY: + if setter.assignment_mode not in {AssignmentMode.VALUE_COPY, AssignmentMode.CHARACTER_COPY}: return ("write-through scalar setter requires value-copy native assignment",) expected_python_action = ( PythonBarrierAction.STRING_VALUE if setter.kind is ObjectKind.STRING else PythonBarrierAction.SCALAR_VALUE @@ -6039,10 +6041,17 @@ def _source_parameter_needs_native_getter(variable: models.SemanticVariable) -> def _scalar_module_native_assignment( setter: OwnershipDecision | None, + variable: models.SemanticVariable, ) -> AssignmentMode: - """Project the completed native setter action for bridge lowering.""" + """Project the completed native setter action for bridge lowering. + + A character value has no by-value C ABI, so its write is a distinct native + mechanism rather than the same value copy a numeric scalar uses. + """ if setter is None or setter.setter_action is not SetterAction.WRITE_THROUGH: return AssignmentMode.NONE + if setter.assignment_mode is AssignmentMode.VALUE_COPY and _is_fixed_length_character_scalar(variable): + return AssignmentMode.CHARACTER_COPY return setter.assignment_mode diff --git a/prik/policy/models.py b/prik/policy/models.py index 378166a4f..92c0b635b 100644 --- a/prik/policy/models.py +++ b/prik/policy/models.py @@ -1085,13 +1085,19 @@ class CharacterLocalPolicy: @dataclass(frozen=True) class ScalarDescriptorResultPolicy: - """Completed nullable rank-zero descriptor result copy contract.""" + """Completed nullable rank-zero descriptor result copy contract. + + ``may_be_unallocated`` marks a result whose storage the native procedure is + not obliged to establish, so reading it directly is not permitted and the + value has to be moved out through a dummy that can test allocation first. + """ descriptor_kind: NativeArrayDescriptorKind runtime_length: bool nullable: bool copy_reason: str release_owner: OwnershipOwner + may_be_unallocated: bool = False @dataclass(frozen=True) diff --git a/prik/policy/ownership.py b/prik/policy/ownership.py index 9c6b23fd6..6a9ebb253 100644 --- a/prik/policy/ownership.py +++ b/prik/policy/ownership.py @@ -264,12 +264,15 @@ class AssignmentMode(str, Enum): Values: ``NONE`` emits no native assignment. ``VALUE_COPY`` copies the incoming - value into existing native storage. ``ALIAS`` associates the + value into existing native storage. ``CHARACTER_COPY`` copies an + incoming fixed-width byte buffer into existing native character + storage, which has no by-value C ABI. ``ALIAS`` associates the destination with existing storage rather than copying it. """ NONE = "none" VALUE_COPY = "value_copy" + CHARACTER_COPY = "character_copy" ALIAS = "alias" @@ -631,16 +634,25 @@ def uses_deferred_character_length(metadata: Mapping[str, Any] | None) -> bool: return bool(metadata) and metadata.get("fortran_character_length") == ":" +def declared_character_length(metadata: Mapping[str, Any] | None) -> int | None: + """Return a positive fixed Fortran character length, normalizing accepted spellings. + + A deferred (``:``) or assumed (``*``) length is not a declared width and + returns ``None``, as does any spelling that is not a positive integer. + """ + value = (metadata or {}).get("fortran_character_length") + if isinstance(value, bool) or value is None: + return None + if isinstance(value, int): + return value if value > 0 else None + text = str(value).strip() + return int(text) if text.isdigit() and int(text) > 0 else None + + def _has_declared_character_length(variable: Any) -> bool: """Return whether one character variable declares a positive fixed width.""" - metadata = getattr(getattr(variable, "semantic_type", None), "metadata", None) or {} - length = metadata.get("fortran_character_length") - if isinstance(length, bool) or length is None: - return False - if isinstance(length, int): - return length > 0 - text = str(length).strip() - return text.isdigit() and int(text) > 0 + metadata = getattr(getattr(variable, "semantic_type", None), "metadata", None) + return declared_character_length(metadata) is not None def character_descriptor_kind(metadata: Mapping[str, Any] | None) -> str | None: diff --git a/prik/printers/fortran.py b/prik/printers/fortran.py index 562ce2c86..f6f5d7f2b 100644 --- a/prik/printers/fortran.py +++ b/prik/printers/fortran.py @@ -15,7 +15,6 @@ FortranAllocate, FortranAssignment, FortranCall, - FortranComment, FortranDeallocate, FortranDeclaration, FortranFunction, @@ -254,10 +253,6 @@ def _doc_comment_lines(doc: tuple[str, ...]) -> list[str]: lines.extend(f"! {chunk}" for chunk in textwrap.wrap(entry, width=96) or [""]) return lines - def _visit_FortranComment(self, node: FortranComment) -> str: - """Render one generated Fortran line comment.""" - return f"! {node.text}" if node.text else "!" - def _visit_FortranUse(self, node: FortranUse) -> str: """Render one Fortran use statement and wrap a long ONLY list.""" if node.only: From 93baee617efd1300ec97e854b665162281b1dcf1 Mon Sep 17 00:00:00 2001 From: said Date: Wed, 19 Aug 2026 23:54:46 +0100 Subject: [PATCH 11/26] add assume intent in flag and remove unimportant docs --- CHANGELOG.md | 39 ++++++++ README.md | 2 - .../documentation-content-checklist.md | 48 +--------- docs/user/examples/cfd-mini-example.md | 18 ---- docs/user/examples/index.md | 22 +---- docs/user/examples/mpi-example.md | 19 ---- docs/user/examples/object-oriented-fortran.md | 19 ---- docs/user/examples/ode-solver.md | 17 ---- docs/user/examples/openmp-example.md | 17 ---- docs/user/guide/wrapping-subroutines.md | 54 ++++++++++- docs/user/index.md | 4 +- docs/user/language-support/feature-matrix.md | 1 - docs/user/reference/cli-commands.md | 1 + docs/user/reference/diagnostic-codes.md | 2 +- docs/user/troubleshooting/build-issues.md | 18 ---- docs/user/troubleshooting/compiler-issues.md | 2 +- docs/user/troubleshooting/index.md | 25 ----- .../troubleshooting/installation-issues.md | 18 ---- .../platform-specific-issues.md | 19 ---- docs/user/troubleshooting/runtime-issues.md | 18 ---- docs/user/tutorials/index.md | 28 ------ docs/user/tutorials/large-fortran-codebase.md | 19 ---- docs/user/tutorials/modern-fortran-project.md | 19 ---- docs/user/tutorials/numerical-solver.md | 18 ---- docs/user/tutorials/packaging.md | 18 ---- docs/user/tutorials/scientific-library.md | 18 ---- mkdocs.yml | 17 ---- prik/cli.py | 50 +++++++++- prik/pipeline/build.py | 10 ++ prik/semantics/fortran2ir.py | 92 +++++++++++++++++-- tests/fortran/_support/wrapper_build.py | 22 +++-- .../pipeline/test_argument_contract.py | 36 ++++++++ .../pipeline/test_output_contract.py | 35 +++++++ .../fixtures/contracts/fstrings/__init__.pyi | 12 +-- .../fixtures/assumed_scalar_intent.f90 | 40 ++++++++ .../end_to_end/test_assumed_scalar_intent.py | 75 +++++++++++++++ .../test_subroutine_argument_projection.py | 58 ++++++++++++ 37 files changed, 511 insertions(+), 419 deletions(-) delete mode 100644 docs/user/examples/cfd-mini-example.md delete mode 100644 docs/user/examples/mpi-example.md delete mode 100644 docs/user/examples/object-oriented-fortran.md delete mode 100644 docs/user/examples/ode-solver.md delete mode 100644 docs/user/examples/openmp-example.md delete mode 100644 docs/user/troubleshooting/build-issues.md delete mode 100644 docs/user/troubleshooting/index.md delete mode 100644 docs/user/troubleshooting/installation-issues.md delete mode 100644 docs/user/troubleshooting/platform-specific-issues.md delete mode 100644 docs/user/troubleshooting/runtime-issues.md delete mode 100644 docs/user/tutorials/index.md delete mode 100644 docs/user/tutorials/large-fortran-codebase.md delete mode 100644 docs/user/tutorials/modern-fortran-project.md delete mode 100644 docs/user/tutorials/numerical-solver.md delete mode 100644 docs/user/tutorials/packaging.md delete mode 100644 docs/user/tutorials/scientific-library.md create mode 100644 tests/fortran/subroutines/end_to_end/fixtures/assumed_scalar_intent.f90 create mode 100644 tests/fortran/subroutines/end_to_end/test_assumed_scalar_intent.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c0c6bba08..2e89ce8bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,47 @@ release tags add a leading `v` to the package version. ## Unreleased +### Added + +- Added `--assume-intent-in-scalars`, which treats a primitive scalar dummy + that declares no `intent` as `intent(in)` instead of applying the + conservative `intent(inout)` default. Fortran permits an undeclared dummy to + be written, so prik returns its post-call value; for sources that predate the + `intent` attribute this fills the Python return with unmodified controls, and + reference BLAS `ddot` returns `(value, n, incx, incy)` rather than the value + alone. With the option, that call returns `32.0`. The choice is made once in + semantic conversion, where an absent `intent` is interpreted, so the build + and `generate --pyi` describe the same Python surface. It is deliberately + narrow: it covers the primitive and character scalars whose replacement is + otherwise returned, a declared `intent` always wins, and arrays, + derived-type objects, and allocatable or pointer scalars are unaffected. It is an + assertion about the source rather than a fact derived from it — prik does not + inspect the procedure body, so a procedure that does write such a dummy + loses that value, exactly as removing the result from the generated contract + by hand would. The option appears in the first `--help` screen because it + changes the default Python surface, and every command that produces semantic + IR accepts it — the build, `generate --pyi`, and `semantics`. + `--build-manifest` rejects it along with the other saved wrapper settings, + and a `.pyi` wrapper build rejects it because a contract already states its + own results. + ### Changed +- A scalar `character` dummy that declares no `intent` now uses the same + conservative `intent(inout)` default as every other scalar, so the value the + native procedure left behind is returned. It was silently assumed + `intent(in)`, which meant a procedure that wrote to such a dummy lost that + write with no diagnostic, while an `integer` dummy on the same call had its + write returned. The exception was undocumented and untested; the strings + guide already stated the uniform rule this change makes true. Wrapping + fixed-form sources, where `intent` cannot be declared, therefore returns + `(result, text)` where it previously returned `result` — + `--assume-intent-in-scalars` restores the shorter surface and now covers + character scalars along with primitive ones. An `allocatable` or `pointer` + character scalar with no `intent` likewise now matches its numeric + counterpart and returns a nullable snapshot; the option does not reach either + one, because a snapshot is not a replacement value the caller supplied. + - Generated wrapper source is now readable. Each generated Fortran adapter and each CPython binding function carries a short leading comment naming what it is for — the native procedure an adapter wraps and the C symbol it exports, diff --git a/README.md b/README.md index 404a581cb..d696bb3f0 100644 --- a/README.md +++ b/README.md @@ -407,9 +407,7 @@ notice when redistributed. - **[User Guide](https://pynumlab.github.io/prik/user/guide/)** — Data types, functions, modules, arrays, derived types, callbacks, ownership, and runtime behavior - **[Changelog](CHANGELOG.md)** — User-visible changes by release diff --git a/docs/developer/roadmap/documentation-content-checklist.md b/docs/developer/roadmap/documentation-content-checklist.md index ca9954333..2b8d4b723 100644 --- a/docs/developer/roadmap/documentation-content-checklist.md +++ b/docs/developer/roadmap/documentation-content-checklist.md @@ -57,22 +57,9 @@ more specialized pages. ### Troubleshooting, FAQ, And Releases -- [ ] `docs/user/troubleshooting/index.md`: route users by symptom: install, build, - compiler, runtime, platform, wrapper contract, and generated artifact issues. -- [ ] `docs/user/troubleshooting/installation-issues.md`: document missing Python - headers, NumPy, compiler packages, virtual environments, and platform package - names. -- [ ] `docs/user/troubleshooting/build-issues.md`: document compile/link failures, - missing native libraries, Makefile regeneration, output directories, and - verbose logs. - [ ] `docs/user/troubleshooting/compiler-issues.md`: document compiler detection, Fortran flags, preprocessing, ABI probes, GNU ABI assumptions, and kind support failures. -- [ ] `docs/user/troubleshooting/runtime-issues.md`: document import failures, - symbol lookup errors, dtype or shape errors, callback exceptions, finalization, - and cleanup symptoms. -- [ ] `docs/user/troubleshooting/platform-specific-issues.md`: document Linux, - macOS, Windows, compiler, linker, and shared-library path caveats. - [x] `CHANGELOG.md`: defines the changelog policy and release-note shape at the repository root, where package users and GitHub visitors can find it. @@ -106,46 +93,21 @@ The old TODO-only contributor pages, duplicate pipeline/codebase maps, completed wrapper-plan and native-array migration ledgers, and separate internal indexes were removed after their stable facts moved to these owners. -### Tutorials And Examples +### Examples + +The reserved tutorial, troubleshooting, and project-example pages were removed +rather than carried as empty placeholders. A page returns here only when its +runnable content is ready, so this queue tracks pages that exist. -- [ ] `docs/user/tutorials/numerical-solver.md`: add a fast checked solver fixture, - build command, Python call, expected numeric output, and validation notes. -- [ ] `docs/user/tutorials/scientific-library.md`: document a small multi-routine - library workflow, package shape, generated `.pyi` review, and regression - checks. -- [ ] `docs/user/tutorials/modern-fortran-project.md`: document modules, derived - types, arrays, constructors, and limitations using checked modern Fortran - examples. -- [ ] `docs/user/tutorials/large-fortran-codebase.md`: document source ordering, - dependency strategy, generated contract review, staged verification, and - current limits for automatic dependency discovery. -- [ ] `docs/user/tutorials/packaging.md`: document packaging a generated extension, - native artifacts, wheel limitations, and reproducible build notes. - [ ] `docs/user/examples/blas-wrapper.md`: add the minimal BLAS-style runtime example or document the external dependency, with build, import, and numerical assertions. - [ ] `docs/user/examples/lapack-wrapper.md`: document the LAPACK example as CI-owned by default, including why local runs are optional and what evidence CI supplies. -- [ ] `docs/user/examples/openmp-example.md`: document supported OpenMP path, - required compiler flags, runtime environment variables, and fallback behavior. -- [ ] `docs/user/examples/object-oriented-fortran.md`: document classes, - type-bound procedures, construction, finalization, and unsupported object - model features with checked output. -- [ ] `docs/user/examples/ode-solver.md`: add a compact checked ODE fixture, - expected result tolerance, and failure troubleshooting. -- [ ] `docs/user/examples/cfd-mini-example.md`: define a small enough fixture, - supported array contracts, build command, and runtime validation. -- [ ] `docs/user/examples/mpi-example.md`: keep this page explicitly - not-yet-implemented until MPI build, runtime, and distribution constraints have - real evidence. ### Project Entry And Site Shell -- [ ] `docs/user/tutorials/index.md`: explain which tutorials are maintained and which - 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. - [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, diff --git a/docs/user/examples/cfd-mini-example.md b/docs/user/examples/cfd-mini-example.md deleted file mode 100644 index b8d6bf871..000000000 --- a/docs/user/examples/cfd-mini-example.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: CFD Mini-Example -audience: advanced users -prerequisites: arrays, large Fortran codebase tutorial -related: ../tutorials/large-fortran-codebase.md, ../guide/arrays.md -status: planned-documentation -publication: draft ---- - -# CFD Mini-Example - -Reserved runnable example for a small CFD-oriented native project. - -## TODO - -- TODO: Define a compact fixture that is fast enough for documentation - verification. -- TODO: Document memory layout and performance limitations. diff --git a/docs/user/examples/index.md b/docs/user/examples/index.md index e01ec6400..702ec5d5c 100644 --- a/docs/user/examples/index.md +++ b/docs/user/examples/index.md @@ -2,7 +2,7 @@ title: Examples Gallery audience: users prerequisites: getting started -related: ../tutorials/index.md, ../guide/building-shared-library.md +related: ../guide/building-shared-library.md status: maintained publication: draft --- @@ -13,9 +13,9 @@ This section includes checked recipes and four complete real-library examples: BLAS, LAPACK, FFTPACK, and MINPACK. Each one provides build commands, Python usage, and numerical checks for its public routines. -The larger project examples below are placeholders for future complete runnable -projects. Each one must include source, build command, import command, runtime -check, limitations, and test evidence before it is marked maintained. +Every page here is runnable. An example earns a place once it has source, a +build command, an import command, a runtime check, its limitations, and test +evidence. ## Choose a page @@ -37,17 +37,3 @@ PRIK_C_DOCS_END --> | Build complete Reference LAPACK and validate 127 float64 routines | [LAPACK wrapper](lapack-wrapper.md) | | Wrap and validate all 31 FFTPACK procedures with NumPy and SciPy | [FFTPACK wrapper](fftpack-wrapper.md) | | Wrap all 22 MINPACK procedures and use Python callbacks | [MINPACK wrapper](minpack-wrapper.md) | - -## Planned Project Examples - -- [ODE solver](ode-solver.md) -- [CFD mini-example](cfd-mini-example.md) -- [Object-oriented Fortran example](object-oriented-fortran.md) -- [MPI example](mpi-example.md) -- [OpenMP example](openmp-example.md) - -## TODO - -- TODO: Add further runnable checked examples one at a time. -- TODO: Keep examples with unavailable runtime support marked not yet - implemented. diff --git a/docs/user/examples/mpi-example.md b/docs/user/examples/mpi-example.md deleted file mode 100644 index b4fc6eb5e..000000000 --- a/docs/user/examples/mpi-example.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: MPI Example -audience: advanced users -prerequisites: packaging, platform-specific troubleshooting -related: openmp-example.md, ../troubleshooting/platform-specific-issues.md -status: not-yet-implemented -publication: draft ---- - -# MPI Example - -Not yet implemented. This page reserves documentation for future MPI-related -wrapper examples and distribution constraints. - -## TODO - -- TODO: Define the supported MPI contract before adding examples. -- TODO: Add runnable CI or manual-verification evidence before changing this - status. diff --git a/docs/user/examples/object-oriented-fortran.md b/docs/user/examples/object-oriented-fortran.md deleted file mode 100644 index 01cace948..000000000 --- a/docs/user/examples/object-oriented-fortran.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Object-Oriented Fortran Example -audience: advanced users -prerequisites: wrapping derived types, memory management -related: ../guide/wrapping-derived-types.md, ../guide/memory-management.md -status: planned-documentation -publication: draft ---- - -# Object-Oriented Fortran Example - -Reserved runnable example for derived types, type-bound procedures, inheritance, -constructors, and finalizers. - -## TODO - -- TODO: Add runtime-backed examples for supported object-oriented features. -- TODO: Mark unsupported inheritance or polymorphic cases through language - support links. diff --git a/docs/user/examples/ode-solver.md b/docs/user/examples/ode-solver.md deleted file mode 100644 index 1947e2470..000000000 --- a/docs/user/examples/ode-solver.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: ODE Solver Example -audience: users, advanced users -prerequisites: callbacks, arrays -related: ../tutorials/numerical-solver.md, ../guide/callbacks.md -status: planned-documentation -publication: draft ---- - -# ODE Solver Example - -Reserved runnable example for an ODE solver workflow. - -## TODO - -- TODO: Add a solver example with runtime assertions. -- TODO: Document callback lifetime and error propagation if callbacks are used. diff --git a/docs/user/examples/openmp-example.md b/docs/user/examples/openmp-example.md deleted file mode 100644 index 75084bb49..000000000 --- a/docs/user/examples/openmp-example.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: OpenMP Example -audience: advanced users -prerequisites: runtime troubleshooting, platform-specific troubleshooting -related: mpi-example.md, ../guide/error-handling.md -status: planned-documentation -publication: draft ---- - -# OpenMP Example - -Reserved runnable example for OpenMP-enabled native code and runtime behavior. - -## TODO - -- TODO: Document current OpenMP runtime support with checked tests. -- TODO: Add compiler flag, runtime library, and concurrency limitations. diff --git a/docs/user/guide/wrapping-subroutines.md b/docs/user/guide/wrapping-subroutines.md index e9a67368a..c6b630569 100644 --- a/docs/user/guide/wrapping-subroutines.md +++ b/docs/user/guide/wrapping-subroutines.md @@ -28,12 +28,58 @@ change in place. | Derived `intent(out/inout)` | Visible generated object | Mutated in place; not returned | | `intent(out)` allocatable | Hidden (or optional) | `Allocatable[...]` handle | | No `intent` | Visible argument | Conservative `intent(inout)` rule | +| No `intent`, assumed input | Visible argument | Not returned (opt-in, see below) | Without `intent`, prik uses the conservative `intent(inout)` behavior. A -primitive scalar stays visible and its replacement value is returned. If the -dummy is known to be input-only, remove that projected result from the -generated contract. This is common in legacy sources, but the rule applies to -any dummy declaration without `intent`. +scalar stays visible and its replacement value is returned — `character` +scalars included, on the same terms as numeric ones. This is common in legacy +sources, but the rule applies to any dummy declaration without `intent`. + +Two ways to drop a result you know the native procedure never writes: + +- remove that projected result from the generated contract, one dummy at a + time; or +- pass `--assume-intent-in-scalars`, which applies the same choice to every + scalar in the build that declares no `intent`. + +### `--assume-intent-in-scalars` + +`intent` did not exist before Fortran 90, so a fixed-form source cannot declare +it and its absence carries no information about the procedure. This option lets +you say so: + +```bash +python3 -m prik ddot.f --out blas --assume-intent-in-scalars +``` + +```python +# default ddot(...) -> tuple[float64, int32, int32, int32] +# --assume-intent-in-scalars ddot(...) -> float64 +``` + +The option is an assertion you make about the source, not a fact prik derives +from it. prik does not inspect the procedure body, so a procedure that *does* +write such a dummy silently loses that value, exactly as it would if you +removed the result from the contract by hand. Use it on sources whose scalar +arguments are known controls; leave it off when you are not sure. + +It is deliberately narrow: + +| Declaration | Effect | +| --- | --- | +| Primitive scalar with no `intent` | Treated as `intent(in)`; not returned | +| `character` scalar with no `intent` | Treated as `intent(in)`; not returned | +| Any declared `intent` | Unchanged — a declared `intent` always wins | +| Array with no `intent` | Unchanged — still mutated in place, never returned | +| Derived-type object with no `intent` | Unchanged — still mutated in place | +| Allocatable or pointer scalar with no `intent` | Unchanged — its result is a nullable snapshot, not a replacement | + +Every command that produces semantic IR accepts the option — the build, +`generate --pyi`, and `semantics` — because it changes how a missing `intent` +is read rather than how the wrapper is emitted. A contract generated with the +option and a direct build with the option therefore describe the same Python +surface. A `.pyi` wrapper build rejects it: a contract already states its own +results, so edit the contract there instead. --- diff --git a/docs/user/index.md b/docs/user/index.md index 2d2f79a0c..b3376a795 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -33,6 +33,6 @@ f2py comparison. and `.pyi` contract surfaces. - [Examples](examples/index.md) — complete wrappers for BLAS, LAPACK, FFTPACK, and MINPACK. -- [Troubleshooting](troubleshooting/index.md) — installation, compiler, build, - and runtime problems. +- [Troubleshooting](troubleshooting/compiler-issues.md) — compiler detection, + selection, and toolchain problems. - [FAQ](faq/index.md) — short answers to common questions. diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index 05d0c645d..e37783b70 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -126,4 +126,3 @@ PRIK_C_DOCS_END --> | Feature | Status | User docs | Source owner | Evidence | Limitations | | --- | --- | --- | --- | --- | --- | | Full semantic `.pyi` parity across all wrapper scenarios | Planned | [Semantic `.pyi` format](../reference/semantic-pyi-format.md) | [`.pyi` route](../../developer/architecture.md#build-architecture) | [semantic `.pyi` feature tests](../../../tests/fortran/semantic_pyi_format/) | Only the documented implemented subset is supported. | -| MPI examples and distribution constraints | Not implemented | [MPI example](../examples/mpi-example.md) | [Planned examples](../examples/index.md) | [Documentation navigation checks](../../../tests/docs/test_navigation.py) | No support contract or runnable evidence exists yet. | diff --git a/docs/user/reference/cli-commands.md b/docs/user/reference/cli-commands.md index c3f8b2a59..804541365 100644 --- a/docs/user/reference/cli-commands.md +++ b/docs/user/reference/cli-commands.md @@ -90,6 +90,7 @@ least one explicit native input: `--native-fortran-sources`, `--native-objects`, | `--compiler COMPILER` | The input-language compiler used for the whole build: preprocessing, datatype measurement, native and bridge compilation, and linking. Default `gfortran`. | | `-I DIR`, `--include-dir DIR` | Build-wide include directory. Repeat to preserve search order. | | `--strict-wrapper-names` | Rejects Python names that would need escaping or a collision suffix. | +| `--assume-intent-in-scalars` | Treats a primitive scalar dummy that declares no `intent` as `intent(in)`, so its value is not returned. A declared `intent` always wins; arrays, derived-type objects, and `character` values are unaffected. Also accepted by `generate --pyi`, where it removes the same results from the generated contract, and by `semantics`. | | `--no-compile-input-sources` | Treats positional sources as semantic inputs only. Requires an explicit native input. | | `--native-fortran-sources PATH ...` | Compiles extra native sources without exposing them as public API. | | `--native-compile-flags FLAG ...` | Flags for native implementation compilation. | diff --git a/docs/user/reference/diagnostic-codes.md b/docs/user/reference/diagnostic-codes.md index c3b37d0a7..8e25a639c 100644 --- a/docs/user/reference/diagnostic-codes.md +++ b/docs/user/reference/diagnostic-codes.md @@ -2,7 +2,7 @@ title: Diagnostic Codes audience: users, developers prerequisites: error handling -related: index.md, ../troubleshooting/index.md +related: index.md, ../troubleshooting/compiler-issues.md status: maintained publication: draft --- diff --git a/docs/user/troubleshooting/build-issues.md b/docs/user/troubleshooting/build-issues.md deleted file mode 100644 index a7b5c6326..000000000 --- a/docs/user/troubleshooting/build-issues.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Build Issues -audience: users, contributors -prerequisites: compiler issues -related: compiler-issues.md, runtime-issues.md -status: planned-documentation -publication: draft ---- - -# Build Issues - -Reserved troubleshooting page for generated bridge compilation, object linking, -library paths, and build artifact problems. - -## TODO - -- TODO: Add build-stage failure categories and recovery steps. -- TODO: Include verbose-build guidance and artifact inspection paths. diff --git a/docs/user/troubleshooting/compiler-issues.md b/docs/user/troubleshooting/compiler-issues.md index c7102c61a..7ed9fb054 100644 --- a/docs/user/troubleshooting/compiler-issues.md +++ b/docs/user/troubleshooting/compiler-issues.md @@ -2,7 +2,7 @@ title: Compiler Issues audience: users, contributors prerequisites: verification -related: build-issues.md, platform-specific-issues.md +related: ../getting-started/installation.md, ../guide/building-shared-library.md status: maintained publication: reviewed --- diff --git a/docs/user/troubleshooting/index.md b/docs/user/troubleshooting/index.md deleted file mode 100644 index 50a85eb38..000000000 --- a/docs/user/troubleshooting/index.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Troubleshooting -audience: users, contributors -prerequisites: installation, verification -related: ../faq/index.md, ../reference/diagnostic-codes.md -status: planned-documentation -publication: draft ---- - -# Troubleshooting - -Troubleshooting pages are organized by failure mode. - -## Pages - -- [Installation issues](installation-issues.md) -- [Compiler issues](compiler-issues.md) -- [Runtime issues](runtime-issues.md) -- [Build issues](build-issues.md) -- [Platform-specific issues](platform-specific-issues.md) - -## TODO - -- TODO: Add symptom-first troubleshooting entries linked to diagnostics. -- TODO: Distinguish user environment failures from prik bugs. diff --git a/docs/user/troubleshooting/installation-issues.md b/docs/user/troubleshooting/installation-issues.md deleted file mode 100644 index 232adf2bc..000000000 --- a/docs/user/troubleshooting/installation-issues.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Installation Issues -audience: users -prerequisites: installation -related: compiler-issues.md, ../getting-started/installation.md -status: planned-documentation -publication: draft ---- - -# Installation Issues - -Reserved troubleshooting page for Python package installation, dependency, and -environment problems. - -## TODO - -- TODO: Add common installation failures and fixes. -- TODO: Link missing compiler or header failures to compiler troubleshooting. diff --git a/docs/user/troubleshooting/platform-specific-issues.md b/docs/user/troubleshooting/platform-specific-issues.md deleted file mode 100644 index 176e9d22b..000000000 --- a/docs/user/troubleshooting/platform-specific-issues.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Platform-Specific Issues -audience: users, packagers -prerequisites: installation, compiler issues -related: installation-issues.md, build-issues.md -status: planned-documentation -publication: draft ---- - -# Platform-Specific Issues - -Reserved troubleshooting page for Linux, macOS, Windows, compiler, linker, and -packaging differences. - -## TODO - -- TODO: Add platform-specific guidance only after it is tested or clearly - labeled as a limitation. -- TODO: Link platform support to release and distribution policy. diff --git a/docs/user/troubleshooting/runtime-issues.md b/docs/user/troubleshooting/runtime-issues.md deleted file mode 100644 index afa86536e..000000000 --- a/docs/user/troubleshooting/runtime-issues.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Runtime Issues -audience: users -prerequisites: first wrapped module -related: build-issues.md, ../guide/error-handling.md -status: planned-documentation -publication: draft ---- - -# Runtime Issues - -Reserved troubleshooting page for import failures, Python exceptions, wrong -dtype or shape errors, callback failures, and native runtime behavior. - -## TODO - -- TODO: Add runtime symptoms with exact exception messages where stable. -- TODO: Link error behavior to user-guide pages and diagnostic codes. diff --git a/docs/user/tutorials/index.md b/docs/user/tutorials/index.md deleted file mode 100644 index 1fa2b3566..000000000 --- a/docs/user/tutorials/index.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Tutorials -audience: users -prerequisites: getting started -related: ../getting-started/index.md, ../examples/index.md -status: planned-documentation -publication: draft ---- - -# Tutorials - -Getting Started covers the first wrapper workflow. These tutorials are for -larger projects and should be step-by-step, runnable, and backed by checked -fixtures or tests. - -## Tutorial Order - -1. [Scientific library tutorial](scientific-library.md) -2. [Numerical solver tutorial](numerical-solver.md) -3. [Modern Fortran project tutorial](modern-fortran-project.md) -4. [Large Fortran codebase tutorial](large-fortran-codebase.md) -5. [Packaging tutorial](packaging.md) - -## TODO - -- TODO: Convert verified examples into step-by-step tutorials after the - documentation architecture is stable. -- TODO: Keep advanced tutorials blocked on runnable example projects. diff --git a/docs/user/tutorials/large-fortran-codebase.md b/docs/user/tutorials/large-fortran-codebase.md deleted file mode 100644 index 141dbd54d..000000000 --- a/docs/user/tutorials/large-fortran-codebase.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Large Fortran Codebase Tutorial -audience: advanced users -prerequisites: modern Fortran project tutorial, packaging -related: modern-fortran-project.md, ../guide/building-shared-library.md -status: planned-documentation -publication: draft ---- - -# Large Fortran Codebase Tutorial - -Reserved tutorial for multi-source projects, dependency ordering, build -artifacts, and namespace planning. - -## TODO - -- TODO: Create a representative large-codebase fixture or external example - policy. -- TODO: Document build ordering, generated artifacts, and failure recovery. diff --git a/docs/user/tutorials/modern-fortran-project.md b/docs/user/tutorials/modern-fortran-project.md deleted file mode 100644 index b3a8c33e9..000000000 --- a/docs/user/tutorials/modern-fortran-project.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Modern Fortran Project Tutorial -audience: users, advanced users -prerequisites: basic wrapper tutorial, wrapping modules -related: large-fortran-codebase.md, ../guide/wrapping-derived-types.md -status: planned-documentation -publication: draft ---- - -# Modern Fortran Project Tutorial - -Reserved tutorial for modern modules, derived types, allocatables, generics, and -module state. - -## TODO - -- TODO: Use a fixture that covers modern Fortran features with proven runtime - behavior. -- TODO: Link partial or unsupported features to the language support matrix. diff --git a/docs/user/tutorials/numerical-solver.md b/docs/user/tutorials/numerical-solver.md deleted file mode 100644 index c6299616d..000000000 --- a/docs/user/tutorials/numerical-solver.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Numerical Solver Tutorial -audience: users, advanced users -prerequisites: basic wrapper tutorial, arrays -related: scientific-library.md, ../guide/arrays.md -status: planned-documentation -publication: draft ---- - -# Numerical Solver Tutorial - -Reserved tutorial for wrapping a solver API with arrays, work buffers, and -runtime validation. - -## TODO - -- TODO: Add a solver fixture that can be run quickly in documentation tests. -- TODO: Document array dtype, shape, and mutation behavior. diff --git a/docs/user/tutorials/packaging.md b/docs/user/tutorials/packaging.md deleted file mode 100644 index f669ebe89..000000000 --- a/docs/user/tutorials/packaging.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Packaging Tutorial -audience: users, packagers -prerequisites: basic wrapper tutorial -related: ../guide/building-shared-library.md -status: planned-documentation -publication: draft ---- - -# Packaging Tutorial - -Reserved tutorial for packaging an prik wrapper project for reuse. - -## TODO - -- TODO: Define the supported packaging workflow before writing this tutorial. -- TODO: Add wheel, source distribution, and native dependency limits after they - are implemented and tested. diff --git a/docs/user/tutorials/scientific-library.md b/docs/user/tutorials/scientific-library.md deleted file mode 100644 index 10050307a..000000000 --- a/docs/user/tutorials/scientific-library.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Scientific Library Tutorial -audience: users -prerequisites: basic wrapper tutorial -related: numerical-solver.md, ../examples/index.md -status: planned-documentation -publication: draft ---- - -# Scientific Library Tutorial - -Reserved tutorial for wrapping a small scientific library with several public -entrypoints and data contracts. - -## TODO - -- TODO: Choose or create a compact scientific-library fixture. -- TODO: Show build, import, numerical validation, and limitations. diff --git a/mkdocs.yml b/mkdocs.yml index 23f759880..14d7fea31 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,24 +67,12 @@ nav: - Error Handling & Diagnostics: user/guide/error-handling.md - Building the Shared Library: user/guide/building-shared-library.md - Performance: user/performance.md - - Tutorials: - - Overview: user/tutorials/index.md - - Large Fortran Codebase: user/tutorials/large-fortran-codebase.md - - Modern Fortran Project: user/tutorials/modern-fortran-project.md - - Numerical Solver: user/tutorials/numerical-solver.md - - Packaging: user/tutorials/packaging.md - - Scientific Library: user/tutorials/scientific-library.md - Examples: - Overview: user/examples/index.md - BLAS Wrapper: user/examples/blas-wrapper.md - LAPACK Wrapper: user/examples/lapack-wrapper.md - FFTPACK Wrapper: user/examples/fftpack-wrapper.md - MINPACK Wrapper: user/examples/minpack-wrapper.md - - CFD Mini Example: user/examples/cfd-mini-example.md - - MPI Example: user/examples/mpi-example.md - - Object-Oriented Fortran: user/examples/object-oriented-fortran.md - - ODE Solver: user/examples/ode-solver.md - - OpenMP Example: user/examples/openmp-example.md - Recipes: - Build and Import With the Python API: user/examples/recipes/build-and-import-python-api.md - Inspect a Fortran API: user/examples/recipes/inspect-fortran-api.md @@ -94,12 +82,7 @@ nav: - Use Python Inspection APIs: user/examples/recipes/use-python-inspection-apis.md - Use Compiler Preprocessing Options: user/examples/recipes/compiler-preprocessing.md - Troubleshooting: - - Overview: user/troubleshooting/index.md - - Installation Issues: user/troubleshooting/installation-issues.md - Compiler Issues: user/troubleshooting/compiler-issues.md - - Build Issues: user/troubleshooting/build-issues.md - - Runtime Issues: user/troubleshooting/runtime-issues.md - - Platform-Specific Issues: user/troubleshooting/platform-specific-issues.md - FAQ: user/faq/index.md - Reference: - Overview: user/reference/index.md diff --git a/prik/cli.py b/prik/cli.py index 7b71ff83d..928fd5abd 100644 --- a/prik/cli.py +++ b/prik/cli.py @@ -408,6 +408,7 @@ class _SemanticPipelineContext: fortran_type_probe_runner: list[str] | None = None fortran_type_probe_cache_dir: str | None = None refresh_fortran_type_probe: bool = False + assume_intent_in_scalars: bool = False @dataclass(frozen=True) @@ -441,6 +442,7 @@ def _converted_semantic_files( fortran_type_probe_runner: list[str] | None = None, fortran_type_probe_cache_dir: str | None = None, refresh_fortran_type_probe: bool = False, + assume_intent_in_scalars: bool = False, ) -> list[tuple[Path, list[object]]]: context = _SemanticPipelineContext( paths=paths, @@ -454,6 +456,7 @@ def _converted_semantic_files( fortran_type_probe_runner=fortran_type_probe_runner, fortran_type_probe_cache_dir=fortran_type_probe_cache_dir, refresh_fortran_type_probe=refresh_fortran_type_probe, + assume_intent_in_scalars=assume_intent_in_scalars, ) pipeline = _SOURCE_SEMANTIC_PIPELINES[language] parsed = pipeline.parser(context) @@ -470,6 +473,7 @@ def _semantic_report( fortran_type_probe_runner: list[str] | None = None, fortran_type_probe_cache_dir: str | None = None, refresh_fortran_type_probe: bool = False, + assume_intent_in_scalars: bool = False, ) -> dict[str, dict]: preprocessing = preprocessing or PreprocessingConfig() converted_files = _converted_semantic_files( @@ -481,6 +485,7 @@ def _semantic_report( fortran_type_probe_runner=fortran_type_probe_runner, fortran_type_probe_cache_dir=fortran_type_probe_cache_dir, refresh_fortran_type_probe=refresh_fortran_type_probe, + assume_intent_in_scalars=assume_intent_in_scalars, ) return _semantic_payload_for_converted_files(converted_files) @@ -567,6 +572,7 @@ def _convert_fortran_semantic_sources( standalone_module_name=p.stem, compile_time_values=compile_time_values, wrapped_derived_types=wrapped_derived_types, + assume_intent_in_scalars=context.assume_intent_in_scalars, **({"type_facts": type_facts} if type_facts is not None else {}), ) converted_files.append((p, modules)) @@ -901,6 +907,11 @@ def _validate_pyi_wrapper_options(args: argparse.Namespace, parser: argparse.Arg parser.error("A .pyi wrapper build accepts exactly one entry contract") if getattr(args, "no_compile_input_sources", False): parser.error("--no-compile-input-sources applies only to source-driven wrapper builds") + if getattr(args, "assume_intent_in_scalars", False): + parser.error( + "--assume-intent-in-scalars interprets a missing Fortran intent; a semantic .pyi contract " + "already states its own results, so edit the contract instead" + ) if not ( getattr(args, "native_fortran_sources", None) or getattr(args, "native_objects", None) @@ -934,7 +945,11 @@ def _validate_manifest_wrapper_options(args: argparse.Namespace, parser: argpars ) if _native_link_options_used(args): parser.error("--build-manifest replays saved native inputs; do not pass native build flags") - if getattr(args, "strict_wrapper_names", False) or _wrapper_compile_options_used(args): + if ( + getattr(args, "strict_wrapper_names", False) + or getattr(args, "assume_intent_in_scalars", False) + or _wrapper_compile_options_used(args) + ): parser.error("--build-manifest replays saved wrapper behavior and compiler flags") @@ -1048,6 +1063,8 @@ def _semantic_stage_options( options: dict[str, object] = {"language": args.language} if c_standard_type_report is not None: options["c_standard_type_report"] = c_standard_type_report + if getattr(args, "assume_intent_in_scalars", False): + options["assume_intent_in_scalars"] = True return options @@ -1277,6 +1294,7 @@ def record_total_build_time(elapsed: float) -> None: output_name=_wrapper_output_name(args), preprocessing=preprocessing, strict_wrapper_names=getattr(args, "strict_wrapper_names", False), + assume_intent_in_scalars=getattr(args, "assume_intent_in_scalars", False), compile_input_sources=not getattr(args, "no_compile_input_sources", False), native_fortran_sources=getattr(args, "native_fortran_sources", None), native_fortran_flags=_cli_native_compile_flags(getattr(args, "native_compile_flags", None)), @@ -1754,6 +1772,27 @@ def _add_include_exposure_options( ) +def _add_semantic_interpretation_options( + parser: argparse.ArgumentParser, + *, + group_title: str = "semantic interpretation options", +) -> None: + """Add options that change how source facts are read into semantic IR. + + These belong to every command that produces semantic IR, because they + change the IR itself rather than a later wrapper or build choice. + """ + group = parser.add_argument_group(group_title) + group.add_argument( + "--assume-intent-in-scalars", + action="store_true", + help=( + "Treat a primitive scalar dummy that declares no intent as intent(in) instead of the " + "conservative intent(inout) default, so its value is not returned; a declared intent always wins" + ), + ) + + def _add_wrapper_behavior_options( parser: argparse.ArgumentParser, *, @@ -1911,6 +1950,7 @@ def _add_diagnostic_controls(group: argparse._ArgumentGroup, *, allow_verbose: b "native_link_items": None, "native_library_dirs": None, "strict_wrapper_names": False, + "assume_intent_in_scalars": False, "wrapper_compiler_debug": False, "wrapper_fortran_flags": None, "wrapper_c_flags": None, @@ -1966,6 +2006,7 @@ def _add_build_arguments(parser: argparse.ArgumentParser) -> None: compiler_help="Compiler used throughout the extension build (default: gfortran)", include_help="Add a compiler include search directory; repeat as needed", ) + _add_semantic_interpretation_options(parser) _add_wrapper_behavior_options(parser, group_title="wrapper options") native_group = parser.add_argument_group("native options") _add_native_compilation_options(native_group) @@ -2042,6 +2083,11 @@ def _add_top_level_arguments(parser: argparse.ArgumentParser) -> None: metavar="NAME", help=("Link against NAME; for example, --native-library openblas passes -lopenblas to the linker"), ) + build_group.add_argument( + "--assume-intent-in-scalars", + action="store_true", + help="Treat a scalar dummy with no declared intent as intent(in), so its value is not returned", + ) build_group.add_argument( "--verbose", action="store_true", @@ -2158,6 +2204,7 @@ def _semantics_parser(argv: list[str]) -> argparse.ArgumentParser: include_help="Add a preprocessing include search directory; repeat as needed", ) _add_include_exposure_options(parser, group_title="C include options") + _add_semantic_interpretation_options(parser) output_group = parser.add_argument_group("output options") _add_output_options( output_group, @@ -2220,6 +2267,7 @@ def _generate_parser(argv: list[str]) -> argparse.ArgumentParser: include_help="Add an include search directory; repeat as needed", ) _add_include_exposure_options(parser, group_title="C include options") + _add_semantic_interpretation_options(parser) _add_wrapper_behavior_options(parser, group_title="wrapper options") native_group = parser.add_argument_group("native options") _add_native_compilation_options(native_group) diff --git a/prik/pipeline/build.py b/prik/pipeline/build.py index 716f814bb..0816b8471 100644 --- a/prik/pipeline/build.py +++ b/prik/pipeline/build.py @@ -2644,6 +2644,7 @@ def _fortran_wrapper_module( fortran_type_probe_runner: list[str] | None, fortran_type_probe_cache_dir: str | Path | None, refresh_fortran_type_probe: bool, + assume_intent_in_scalars: bool = False, ) -> tuple[object, SemanticModule]: """Parse Fortran sources, resolve type facts, and form one wrapper module.""" # Preprocess and parse the complete source project. @@ -2676,6 +2677,7 @@ def _fortran_wrapper_module( parsed, compile_time_values=compile_time_values, type_facts=type_facts, + assume_intent_in_scalars=assume_intent_in_scalars, ) _apply_source_python_exports(modules) module_name = _validated_wrapper_module_name(output_name, source_paths[0].stem) @@ -2727,6 +2729,7 @@ def build_fortran_extension( output_name: str | None = None, preprocessing: PreprocessingConfig | None = None, strict_wrapper_names: bool = False, + assume_intent_in_scalars: bool = False, fortran_type_report=None, fortran_type_probe_runner: list[str] | None = None, fortran_type_probe_cache_dir: str | Path | None = None, @@ -2780,6 +2783,12 @@ def build_fortran_extension( strict_wrapper_names Reject generated Python names that cannot be represented without a strict naming decision. + assume_intent_in_scalars + Treat a primitive scalar dummy that declares no ``intent`` as + ``intent(in)`` rather than applying the conservative ``intent(inout)`` + default, so its value is not projected as a Python result. A declared + ``intent`` is always honored, and arrays, derived-type objects, and + character values are unaffected. fortran_type_report, fortran_type_probe_runner, fortran_type_probe_cache_dir, refresh_fortran_type_probe Optional controls for compiler-probed Fortran type facts used while @@ -2858,6 +2867,7 @@ def build_fortran_extension( fortran_type_probe_runner=fortran_type_probe_runner, fortran_type_probe_cache_dir=fortran_type_probe_cache_dir, refresh_fortran_type_probe=refresh_fortran_type_probe, + assume_intent_in_scalars=assume_intent_in_scalars, ) # 3. Complete wrapper policy and generate the canonical wrapper. diff --git a/prik/semantics/fortran2ir.py b/prik/semantics/fortran2ir.py index 120a94084..9b93a195d 100644 --- a/prik/semantics/fortran2ir.py +++ b/prik/semantics/fortran2ir.py @@ -270,6 +270,7 @@ def __init__( compile_time_values: dict[str, int | str] | None = None, wrapped_derived_types: Iterable[tuple[str, str]] | None = None, type_facts: dict[tuple[str, str | None], dict[str, object]] | None = None, + assume_intent_in_scalars: bool = False, ): """Configure parser-fact conversion without performing any conversion. @@ -278,7 +279,14 @@ def __init__( ``wrapped_derived_types`` marks imported types with generated wrappers; and ``type_facts`` supplies compiler-measured storage facts. Inputs are normalized into lookup-friendly forms and retained for later visitors. + + ``assume_intent_in_scalars`` replaces the conservative ``intent(inout)`` + default with ``intent(in)`` for primitive scalar dummies that declare no + ``intent`` at all. It is a caller assertion about sources that predate + the attribute, not a fact derived from the source, so it stays off by + default and never applies to a declared ``intent``. """ + self.assume_intent_in_scalars = bool(assume_intent_in_scalars) self.type_map = FORTRAN_TYPE_MAP if type_map is None else type_map self.compile_time_values = _normalize_compile_time_values(compile_time_values) self.wrapped_derived_types = { @@ -528,7 +536,11 @@ def _visit_FortranArgument( derived_type_context=derived_type_context, declaration_arrays=declaration_arrays, ) - access = self._argument_access(arg, semantic_type) + access = self._argument_access( + arg, + semantic_type, + assume_intent_in_scalars=self.assume_intent_in_scalars, + ) self._complete_argument_storage(arg, semantic_type, access=access) self._apply_argument_ownership(semantic_type, writes_argument=access[1]) @@ -946,7 +958,11 @@ def _visit_FortranProcedureSignature( native_name=proc.name, arguments=arguments, return_type=return_type, - projection=self._procedure_projection(proc, arguments), + projection=self._procedure_projection( + proc, + arguments, + assume_intent_in_scalars=self.assume_intent_in_scalars, + ), metadata=metadata, visibility=visibility, origin=SemanticOrigin( @@ -1403,6 +1419,7 @@ def _with_additional_wrapped_types( compile_time_values=self.compile_time_values, wrapped_derived_types=merged, type_facts=self.type_facts, + assume_intent_in_scalars=self.assume_intent_in_scalars, ) converter._known_procedures = set(self._known_procedures) return converter @@ -1422,6 +1439,7 @@ def _with_additional_known_procedures( compile_time_values=self.compile_time_values, wrapped_derived_types=self.wrapped_derived_types, type_facts=self.type_facts, + assume_intent_in_scalars=self.assume_intent_in_scalars, ) converter._known_procedures = merged return converter @@ -2099,16 +2117,42 @@ def _apply_pointer_result_policy(semantic_type: SemanticType) -> None: def _argument_access( arg: FortranArgument | FortranVariable, semantic_type: SemanticType, + *, + assume_intent_in_scalars: bool = False, ) -> tuple[bool, bool]: - """Return parser-provided read/write facts or the established conservative default.""" + """Return parser-provided read/write facts or the established conservative default. + + A declared ``intent`` always wins; ``assume_intent_in_scalars`` only + chooses which default an undeclared ``intent`` receives, and only for + the scalars whose replacement value would otherwise be projected as a + Python result. + """ reads = getattr(arg, "reads_argument", None) writes = getattr(arg, "writes_argument", None) if reads is None or writes is None: - if semantic_type.name == "String" and semantic_type.rank == 0: + if assume_intent_in_scalars and FortranToIRConverter._assumed_input_scalar(semantic_type): return True, False return True, True return bool(reads), bool(writes) + @staticmethod + def _assumed_input_scalar(semantic_type: SemanticType | None) -> bool: + """Return whether an undeclared ``intent`` on this dummy may be assumed ``intent(in)``. + + This covers exactly the rank-zero values whose replacement would + otherwise be projected as a Python result: primitive scalars and + non-descriptor character scalars. Descriptor scalars keep the + conservative default because their result is a nullable snapshot + rather than a replacement value. + """ + return bool( + FortranToIRConverter._is_primitive_scalar_replacement(semantic_type) + or ( + FortranToIRConverter._is_scalar_character(semantic_type) + and not FortranToIRConverter._is_scalar_descriptor(semantic_type) + ) + ) + @staticmethod def _argument_has_writable_storage(argument: SemanticArgument) -> bool: """Return whether semantic ownership or storage marks an argument writable.""" @@ -2723,6 +2767,8 @@ def _is_hidden_output_argument( def _procedure_projection( proc: FortranProcedureSignature, arguments: list[SemanticArgument], + *, + assume_intent_in_scalars: bool = False, ) -> list[ProjectionMapping]: """Build native-to-Python argument and result mappings for one procedure. @@ -2737,7 +2783,11 @@ def _procedure_projection( result_position = 1 if proc.result is not None else 0 for native_position, native_arg in enumerate(proc.arguments): arg = by_name[native_arg.name] - reads_argument, writes_argument = FortranToIRConverter._argument_access(native_arg, arg.semantic_type) + reads_argument, writes_argument = FortranToIRConverter._argument_access( + native_arg, + arg.semantic_type, + assume_intent_in_scalars=assume_intent_in_scalars, + ) is_output = writes_argument and not reads_argument is_replacement = reads_argument and writes_argument is_allocatable_replacement = is_replacement and FortranToIRConverter._is_allocatable_array( @@ -3350,6 +3400,7 @@ def _converter_for( compile_time_values: dict[str, int | str] | None = None, wrapped_derived_types: Iterable[tuple[str, str]] | None = None, type_facts: dict[tuple[str, str | None], dict[str, object]] | None = None, + assume_intent_in_scalars: bool = False, ) -> FortranToIRConverter: """Return the shared default converter or an isolated configured converter. @@ -3357,12 +3408,18 @@ def _converter_for( conversion input creates a new instance so per-call compile-time values and facts never leak into unrelated conversions. """ - if compile_time_values is None and wrapped_derived_types is None and type_facts is None: + if ( + compile_time_values is None + and wrapped_derived_types is None + and type_facts is None + and not assume_intent_in_scalars + ): return _DEFAULT_CONVERTER return FortranToIRConverter( compile_time_values=compile_time_values, wrapped_derived_types=wrapped_derived_types, type_facts=type_facts, + assume_intent_in_scalars=assume_intent_in_scalars, ) @@ -3375,6 +3432,7 @@ def fortran_module_to_semantic_module( compile_time_values: dict[str, int | str] | None = None, wrapped_derived_types: Iterable[tuple[str, str]] | None = None, type_facts: dict[tuple[str, str | None], dict[str, object]] | None = None, + assume_intent_in_scalars: bool = False, ) -> SemanticModule: """Convert one parsed Fortran module into a :class:`SemanticModule`. @@ -3394,7 +3452,12 @@ def fortran_module_to_semantic_module( >>> fortran_module_to_semantic_module(parsed).functions[0].arguments[0].semantic_type.name 'Float64' """ - converter = _converter_for(compile_time_values, wrapped_derived_types, type_facts) + converter = _converter_for( + compile_time_values, + wrapped_derived_types, + type_facts, + assume_intent_in_scalars=assume_intent_in_scalars, + ) return converter.visit(converter.first_module(module)) @@ -3405,6 +3468,7 @@ def fortran_file_to_semantic_modules( compile_time_values: dict[str, int | str] | None = None, wrapped_derived_types: Iterable[tuple[str, str]] | None = None, type_facts: dict[tuple[str, str | None], dict[str, object]] | None = None, + assume_intent_in_scalars: bool = False, ) -> list[SemanticModule]: """Convert every module and standalone procedure group in one parsed file. @@ -3417,7 +3481,12 @@ def fortran_file_to_semantic_modules( >>> [module.name for module in fortran_file_to_semantic_modules(parsed)] ['standalone'] """ - return _converter_for(compile_time_values, wrapped_derived_types, type_facts).visit( + return _converter_for( + compile_time_values, + wrapped_derived_types, + type_facts, + assume_intent_in_scalars=assume_intent_in_scalars, + ).visit( parsed_file, standalone_module_name=standalone_module_name, ) @@ -3428,6 +3497,7 @@ def fortran_project_to_semantic_modules( *, compile_time_values: dict[str, int | str] | None = None, type_facts: dict[tuple[str, str | None], dict[str, object]] | None = None, + assume_intent_in_scalars: bool = False, ) -> list[SemanticModule]: """Convert an ordered parsed Fortran project with project-wide type context. @@ -3441,7 +3511,11 @@ def fortran_project_to_semantic_modules( >>> [module.name for module in fortran_project_to_semantic_modules(project)] ['math'] """ - return _converter_for(compile_time_values, type_facts=type_facts).visit(project) + return _converter_for( + compile_time_values, + type_facts=type_facts, + assume_intent_in_scalars=assume_intent_in_scalars, + ).visit(project) if __name__ == "__main__": diff --git a/tests/fortran/_support/wrapper_build.py b/tests/fortran/_support/wrapper_build.py index ee19155f2..de773a659 100644 --- a/tests/fortran/_support/wrapper_build.py +++ b/tests/fortran/_support/wrapper_build.py @@ -307,12 +307,18 @@ def _build_source_and_import( source_template: Path, workdir: Path, expected_generated_sources: set[str], + **build_options, ): - """Build one source entry through the canonical production generator.""" + """Build one source entry through the canonical production generator. + + ``build_options`` forwards public build arguments so a test can exercise an + optional wrapper behavior without duplicating the build and import steps. + """ result = build_fortran_extension( source_template, output_dir=workdir, preprocessing=PreprocessingConfig(mode="compiler", compiler=_compiler()), + **build_options, ) assert result.shared_library.exists() assert {path.name for path in result.generated_sources} == expected_generated_sources @@ -511,15 +517,19 @@ def _assert_array_rejects_strided_views(module, function_name): def _assert_legacy_string_examples(module): - assert module.char_code_default("A") == ord("A") - assert module.char_code_star1(np.str_("B")) == ord("B") - assert module.string_len_star8("short ") == 5 + # Fixed-form sources predate the `intent` attribute, so every character + # dummy here reaches the conservative `intent(inout)` default and its + # unchanged value follows the result. `--assume-intent-in-scalars` is the + # documented way to drop it; see the assumed scalar-intent tests. + assert module.char_code_default("A") == (ord("A"), "A") + assert module.char_code_star1(np.str_("B")) == (ord("B"), "B") + assert module.string_len_star8("short ") == (5, "short ") with pytest.raises(TypeError, match="exactly 8 bytes"): module.string_len_star8("short") with pytest.raises(TypeError, match="exactly 8 bytes"): module.string_len_star8("too-long-value") - assert module.string_len_assumed("variable length") == 15 - assert module.string_len_entity("python") == 6 + assert module.string_len_assumed("variable length") == (15, "variable length") + assert module.string_len_entity("python") == (6, "python") assert module.char_result_default() == "L" assert module.string_result_star8() == "LEGACY!!" assert module.string_result_padded() == "PAD " 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 e83b11de2..6cc17133d 100644 --- a/tests/fortran/command_line_interface/pipeline/test_argument_contract.py +++ b/tests/fortran/command_line_interface/pipeline/test_argument_contract.py @@ -819,6 +819,7 @@ def test_subcommand_help_exposes_every_supported_option(parser_factory): (["--preprocessor-adapter", "auto"], "replays its saved preprocessing recipe"), (["-D", "USE_FAST=1"], "replays its saved preprocessing recipe"), (["--strict-wrapper-names"], "replays saved wrapper behavior"), + (["--assume-intent-in-scalars"], "replays saved wrapper behavior"), (["--native-library", "openblas"], "replays saved native inputs"), ], ) @@ -958,3 +959,38 @@ def test_prik_main_rejects_invalid_macro_names(macro_flag: str, monkeypatch): monkeypatch.setattr(sys, "argv", ["prik", "parse", str(TEST_FILE), macro_flag, "=invalid"]) with pytest.raises(SystemExit): prik_cli.main() + + +def test_assume_intent_in_scalars_is_discoverable_from_the_first_help_screen(): + """The option changes the default Python surface, so it is not hidden behind --help-build.""" + top_help = prik_cli._top_level_parser(["--help"]).format_help() + build_help = prik_cli._build_parser(["input.f90", "--help"]).format_help() + generate_help = prik_cli._generate_parser(["--help"]).format_help() + + semantics_help = prik_cli._semantics_parser(["--help"]).format_help() + + assert "--assume-intent-in-scalars" in top_help + assert "--assume-intent-in-scalars" in build_help + assert "--assume-intent-in-scalars" in generate_help + assert "--assume-intent-in-scalars" in semantics_help + + +def test_pyi_wrapper_build_rejects_assume_intent_in_scalars(tmp_path: Path, capsys): + """A contract states its own results, so the option has no missing intent to interpret.""" + contract = tmp_path / "api.pyi" + contract.write_text("from prik.contracts import Float64\n", encoding="utf-8") + source = tmp_path / "api.f90" + source.write_text("subroutine noop()\nend subroutine noop\n", encoding="utf-8") + + with pytest.raises(SystemExit) as exc_info: + prik_cli.main( + [ + str(contract), + "--native-fortran-sources", + str(source), + "--assume-intent-in-scalars", + ] + ) + + assert exc_info.value.code == 2 + assert "already states its own results" in capsys.readouterr().err 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 26517d30c..7794cab92 100644 --- a/tests/fortran/command_line_interface/pipeline/test_output_contract.py +++ b/tests/fortran/command_line_interface/pipeline/test_output_contract.py @@ -940,3 +940,38 @@ def fail_parse(_paths, _preprocessing): monkeypatch.setattr(sys, "argv", ["prik", "parse", str(source), "--debug"]) with pytest.raises(ValueError, match="invalid generated interface"): prik_cli.main() + + +ASSUMED_INTENT_SOURCE = """module legacy_mod +contains + real(8) function weigh(count, factor) + integer(4) :: count + real(8) :: factor + weigh = real(count, 8) * factor + end function weigh +end module legacy_mod +""" + + +def _generated_legacy_contract(tmp_path: Path, *extra_options: str) -> str: + source = tmp_path / f"legacy{len(extra_options)}.f90" + source.write_text(ASSUMED_INTENT_SOURCE, encoding="utf-8") + out = tmp_path / f"contracts{len(extra_options)}" + + cmd = [sys.executable, "-m", "prik", "generate", "--pyi", str(source), "--out", str(out), *extra_options] + subprocess.run(cmd, capture_output=True, text=True, check=True) + return (out / "legacy_mod.pyi").read_text(encoding="utf-8") + + +def test_generated_contract_projects_undeclared_scalars_by_default(tmp_path: Path): + text = _generated_legacy_contract(tmp_path) + + assert 'Returns["count", Int32]' in text + assert 'Returns["factor", Float64]' in text + + +def test_assume_intent_in_scalars_removes_them_from_the_generated_contract(tmp_path: Path): + text = _generated_legacy_contract(tmp_path, "--assume-intent-in-scalars") + + assert "Returns" not in text + assert "-> Float64: ..." in text diff --git a/tests/fortran/strings/end_to_end/fixtures/contracts/fstrings/__init__.pyi b/tests/fortran/strings/end_to_end/fixtures/contracts/fstrings/__init__.pyi index 954d1a526..9248c87c6 100644 --- a/tests/fortran/strings/end_to_end/fixtures/contracts/fstrings/__init__.pyi +++ b/tests/fortran/strings/end_to_end/fixtures/contracts/fstrings/__init__.pyi @@ -1,34 +1,34 @@ -from prik.contracts import Int32, String, bind, standalone +from prik.contracts import Int32, Returns, String, bind, standalone @bind("CHAR_CODE_DEFAULT") @standalone def char_code_default( C: String[1] -) -> Int32: ... +) -> tuple[Int32, Returns["C", String[1]]]: ... @bind("CHAR_CODE_STAR1") @standalone def char_code_star1( C: String[1] -) -> Int32: ... +) -> tuple[Int32, Returns["C", String[1]]]: ... @bind("STRING_LEN_STAR8") @standalone def string_len_star8( TEXT: String[8] -) -> Int32: ... +) -> tuple[Int32, Returns["TEXT", String[8]]]: ... @bind("STRING_LEN_ASSUMED") @standalone def string_len_assumed( TEXT: String -) -> Int32: ... +) -> tuple[Int32, Returns["TEXT", String]]: ... @bind("STRING_LEN_ENTITY") @standalone def string_len_entity( TEXT: String[6] -) -> Int32: ... +) -> tuple[Int32, Returns["TEXT", String[6]]]: ... @bind("CHAR_RESULT_DEFAULT") @standalone diff --git a/tests/fortran/subroutines/end_to_end/fixtures/assumed_scalar_intent.f90 b/tests/fortran/subroutines/end_to_end/fixtures/assumed_scalar_intent.f90 new file mode 100644 index 000000000..4ee1c58de --- /dev/null +++ b/tests/fortran/subroutines/end_to_end/fixtures/assumed_scalar_intent.f90 @@ -0,0 +1,40 @@ +module assumed_scalar_intent + implicit none + + type :: sample + real(8) :: x = 0.0d0 + end type sample + +contains + + real(8) function weighted(count, values, factor) + integer(4) :: count + real(8) :: values(:) + real(8) :: factor + integer(4) :: index + weighted = 0.0d0 + do index = 1, count + weighted = weighted + values(index) * factor + end do + end function weighted + + subroutine touch(count, item, values) + integer(4) :: count + type(sample) :: item + real(8) :: values(:) + count = count + 1 + item%x = item%x + 1.0d0 + values = values * 2.0d0 + end subroutine touch + + integer(4) function label_width(label) + character(len=4) :: label + label_width = len(label) + end function label_width + + subroutine declared(value) + real(8), intent(inout) :: value + value = value + 1.0d0 + end subroutine declared + +end module assumed_scalar_intent diff --git a/tests/fortran/subroutines/end_to_end/test_assumed_scalar_intent.py b/tests/fortran/subroutines/end_to_end/test_assumed_scalar_intent.py new file mode 100644 index 000000000..4aea04529 --- /dev/null +++ b/tests/fortran/subroutines/end_to_end/test_assumed_scalar_intent.py @@ -0,0 +1,75 @@ +"""Built-extension behavior of the assumed scalar-intent build option.""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest + +from tests.fortran._support.wrapper_build import _build_source_and_import + +pytestmark = pytest.mark.fortran_end_to_end + +SOURCE = Path(__file__).parent / "fixtures" / "assumed_scalar_intent.f90" +GENERATED = { + "bind_c_assumed_scalar_intent_wrapper.f90", + "assumed_scalar_intent_wrapper.c", + "assumed_scalar_intent_wrapper.h", +} + + +def _module(workdir: Path, *, assume_intent_in_scalars: bool): + return _build_source_and_import( + SOURCE, + workdir, + GENERATED, + assume_intent_in_scalars=assume_intent_in_scalars, + ) + + +def test_conservative_default_returns_every_undeclared_scalar(tmp_path: Path): + module = _module(tmp_path, assume_intent_in_scalars=False) + values = np.array([1.0, 2.0, 3.0], dtype=np.float64) + + assert module.weighted(np.int32(3), values, np.float64(2.0)) == ( + np.float64(12.0), + np.int32(3), + np.float64(2.0), + ) + + +def test_assumed_scalar_intent_returns_only_the_function_result(tmp_path: Path): + module = _module(tmp_path, assume_intent_in_scalars=True) + values = np.array([1.0, 2.0, 3.0], dtype=np.float64) + + assert module.weighted(np.int32(3), values, np.float64(2.0)) == np.float64(12.0) + + +def test_assumed_scalar_intent_keeps_array_and_derived_writeback(tmp_path: Path): + module = _module(tmp_path, assume_intent_in_scalars=True) + item = module.sample(x=np.float64(1.0)) + values = np.array([1.0, 2.0, 3.0], dtype=np.float64) + + assert module.touch(np.int32(5), item, values) is None + assert item.x == np.float64(2.0) + np.testing.assert_array_equal(values, np.array([2.0, 4.0, 6.0])) + + +def test_undeclared_character_scalar_follows_the_same_conservative_default(tmp_path: Path): + """A character dummy with no intent is returned exactly like a primitive one.""" + module = _module(tmp_path, assume_intent_in_scalars=False) + + assert module.label_width("abcd") == (np.int32(4), "abcd") + + +def test_assumed_scalar_intent_also_drops_the_character_result(tmp_path: Path): + module = _module(tmp_path, assume_intent_in_scalars=True) + + assert module.label_width("abcd") == np.int32(4) + + +def test_assumed_scalar_intent_does_not_change_a_declared_intent(tmp_path: Path): + module = _module(tmp_path, assume_intent_in_scalars=True) + + assert module.declared(np.float64(4.0)) == np.float64(5.0) diff --git a/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py b/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py index da42c8fe0..8fdeea1a7 100644 --- a/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py +++ b/tests/fortran/subroutines/semantics/test_subroutine_argument_projection.py @@ -89,3 +89,61 @@ def test_scalar_derived_output_stays_visible_without_result_projection(): python_position=0, ) ] + + +ASSUMED_INTENT_SOURCE = """ +module legacy + type :: pt + real(8) :: x = 0.0d0 + end type pt +contains +subroutine touch(count, item, values, label, declared) + integer(4) :: count + type(pt) :: item + real(8) :: values(:) + character(len=4) :: label + integer(4), intent(inout) :: declared + count = count + 1 + item%x = item%x + 1.0d0 + values = values * 2.0d0 + label = "zzzz" + declared = declared + 1 +end subroutine touch +end module legacy +""" + + +def _touch_result_names(*, assume_intent_in_scalars): + smod = fortran_module_to_semantic_module( + parse_fortran_source(ASSUMED_INTENT_SOURCE), + assume_intent_in_scalars=assume_intent_in_scalars, + ) + touch = get_function(smod, "touch") + return [mapping.native_name for mapping in touch.projection if mapping.result_position is not None] + + +def test_undeclared_intent_scalar_projects_a_replacement_result_by_default(): + """Primitive and character scalars share one conservative default.""" + assert _touch_result_names(assume_intent_in_scalars=False) == ["count", "label", "declared"] + + +def test_assumed_scalar_intent_drops_only_the_undeclared_scalar_results(): + """The assumption reaches undeclared scalars, primitive and character alike. + + A declared ``intent(inout)`` scalar keeps its replacement result, and + arrays and derived-type objects were never projected as results, so their + in-place contract is unchanged either way. + """ + assert _touch_result_names(assume_intent_in_scalars=True) == ["declared"] + + +def test_assumed_scalar_intent_leaves_undeclared_non_scalars_writable(): + smod = fortran_module_to_semantic_module( + parse_fortran_source(ASSUMED_INTENT_SOURCE), + assume_intent_in_scalars=True, + ) + arguments = {argument.name: argument for argument in get_function(smod, "touch").arguments} + + assert arguments["count"].semantic_type.ownership.mutable is False + assert arguments["item"].semantic_type.ownership.mutable is True + assert arguments["values"].semantic_type.ownership.mutable is True From 063330aff8a05c7877b3d0bdd52bf7eb2dbcce0e Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 07:03:47 +0100 Subject: [PATCH 12/26] fix issue related to handling bspline-fortran and update/expand the goal3 checklist for handling C --- CHANGELOG.md | 42 +++ .../native-entrypoint-adoption-checklist.md | 301 +++++++++++++++--- docs/user/guide/wrapping-derived-types.md | 34 ++ prik/parsers/fortran/models.py | 2 + prik/parsers/fortran/parser.py | 68 +++- prik/preprocessing/probes/fortran_types.py | 54 +++- prik/semantics/fortran2ir.py | 27 +- .../probes/test_fortran_type_probes.py | 53 +++ .../fixtures/type_accessibility.f90 | 30 ++ .../end_to_end/test_type_accessibility.py | 39 +++ .../parsing/test_derived_procedure_syntax.py | 22 +- .../test_fortran_derived_semantics.py | 79 +++++ .../fixtures/general/derived_type.json | 20 +- .../general/derived_types_and_methods.json | 34 +- .../fixtures/general/modern_pyi_example.json | 24 +- .../scope_name_reuse_combinations.json | 8 +- .../test_declaration_and_scope_regressions.py | 5 +- .../test_derived_types_and_program_units.py | 90 ++++++ 18 files changed, 846 insertions(+), 86 deletions(-) create mode 100644 tests/fortran/derived_types/end_to_end/fixtures/type_accessibility.f90 create mode 100644 tests/fortran/derived_types/end_to_end/test_type_accessibility.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e89ce8bd..a9036258f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,48 @@ release tags add a leading `v` to the package version. ## Unreleased +### Fixed + +- A derived type's `private` and `public` statements are now honored. The + statement before `contains` sets the default accessibility of components and + the statement after it sets the default for type-bound procedures; a + declaration that states its own accessibility still keeps it. The statement + after `contains` previously failed to parse at all, and the one before it + parsed but was discarded — so a type with private components reached the + Fortran compiler as generated accessors that read them, failing with + "Component 'x' is a PRIVATE component of 'y'". Private components and + bindings now simply stay off the generated Python class. Parsed derived types + additionally record `component_visibility` and `binding_visibility`, and each + type-bound binding records the `visibility` it resolves to, so the parser's + serialized form states the accessibility it read. + +- A `type, public ::` declaration is no longer hidden by a module-level + `private` default. The type's own declared accessibility is the most specific + statement about it, so it wins over the module default and over the module's + accessibility lists. Previously such a type — and every one of its methods — + was dropped from the extension silently, with the build still reporting + success. + +- A deferred type-bound binding (`procedure(iface), deferred :: name`) now + parses, so the decision about whether it can be wrapped is reported by policy + as an unsupported derived-type diagnostic naming the binding, rather than by + the parser as a syntax error. Abstract types and deferred bindings remain + unsupported; only the stage that owns the refusal has changed. + +- A named `block` construct (`main: block ... end block main`) is recognized as + the start of a procedure's execution part. A construct name prefix is now + stripped before a statement is classified, so named `do`, `if`, `select`, + `associate`, and `block` constructs are all read as executable rather than as + an unknown declaration. + +- The compiler type probe no longer emits a program it cannot compile. The + probe is a standalone program that cannot `use` a module from the project + being analyzed, because that module has not been compiled yet; an expression + naming a kind parameter declared elsewhere in the project — `storage_size(1_ip, + kind=ip)`, for example — is now left for the requirement report instead of + being compiled into the probe. Previously one such expression failed the whole + probe and with it the entire build. + ### Added - Added `--assume-intent-in-scalars`, which treats a primitive scalar dummy diff --git a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md index 19521f8a6..d9305e163 100644 --- a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md +++ b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md @@ -904,6 +904,66 @@ language by reusing the completed binding-to-entrypoint path. It does not add a generated native C adapter: an operation is either directly supported or blocked by completed policy before planning and source generation. +### Initial Scope And Readiness Boundary + +Goal 3 is deliberately a primitive lane, not general C-wrapper support. Its +required positive scope is: + +- externally linkable, non-variadic C functions using the ordinary C calling + convention; +- modeled C arithmetic primitives passed by value and returned by value, + together with `void` results; +- one-level pointers to those same primitives when an authoritative contract + selects one supported scalar-reference, rank-zero storage, projected-output, + or primitive-array interpretation; and +- renamed symbols and route-neutral `@native_call(...)` projections composed + only from mechanisms already supported by the shared direct entrypoint. + +“Primitive” means the complete modeled arithmetic set, not an unspecified +sample: C `_Bool`; plain, signed, and unsigned character and integer types; +`short`, `int`, `long`, and `long long` in both signednesses; `float`, `double`, +and `long double`; the corresponding standard C complex types; and resolved +standard scalar typedefs such as fixed-width integers and `size_t`. Target ABI +facts may map multiple C spellings to one semantic storage identity, but policy +and lowering must either preserve an exact compatible C ABI or reject the +spelling. They must never narrow, change signedness, or choose a nearby dtype. + +Initial readiness does **not** include multi-level pointers, pointer-valued +results, strings or character buffers, nullable pointers, ownership transfer, +retained native pointers, structs or unions, global state, callbacks, variadic +functions, nonstandard calling conventions, `volatile` or atomic access, or +general C feature adoption. Those remain fail-closed follow-on work. A single +edited numeric `T *`-to-array path is required because it proves the contract +can resolve the central pointer ambiguity; it does not claim the complete C +array feature, returned arrays, `_Bool` array compatibility, or pointer +ownership support. + +### Current Goal 3 Gap Audit (2026-08-20) + +The C parser and source-to-semantic conversion are ahead of the wrapper path. +The remaining work is not just compilation wiring: + +- C semantic inputs cannot currently enter `build_pyi_extension` or + `prik/pipeline/build.py` with C-native sources and a C compiler. +- Entrypoint policy currently recognizes only Fortran operations carrying an + original `bind(C)` ABI fact. A C operation therefore misses the direct branch + and falls toward the generated-Fortran-adapter action, which Goal 3 must + replace with direct-or-diagnostic C policy. +- The semantic converter models more C arithmetic types than the shared + first-lane policy and scalar codegen registry lower. Unsigned integers, + target-sized `Int`/`SizeT`, `long double`, and extended complex mappings need + an explicit resolution or blocker; “every primitive” cannot be checked while + those sets disagree. +- The generated one-level-pointer default exists, but no focused fixture freezes + every starter-contract row and no compiled test proves either the default + scalar-reference path or the edited pointer-to-array path. +- A generated `CFunctionPointer` placeholder is accepted by PRIK's own parser + but is not a public importable contract type. Initial Goal 3 should reject it + with a documented diagnostic instead of expanding callback scope. +- There are no C-owned policy, codegen, compiling, or end-to-end evidence + directories yet, and the build/artifact assertions do not cover a C module + with no native adapter. + ### Stage 0 — C Language And Contract Inputs #### Current Stage 0 Status (2026-08-18) @@ -927,13 +987,13 @@ and `tests/c//policy/`, `codegen/`, and `end_to_end/` evidence owners. - [x] Add C source conversion preserving `source_language = "c"` on semantic modules, declarations, and arguments. -- [ ] Emit authoritative source-free C semantic contracts. Function-pointer - parameters currently serialize as the `CFunctionPointer` placeholder built by - `prik/semantics/c2ir.py`, which `prik.contracts` does not export and the - generated import line omits, so such a contract is not hand-editable. Either - promote the placeholder into the public contract vocabulary or block the - operation with a documented diagnostic. Do not leave a spelling that only - PRIK's own `.pyi` parser accepts. +- [ ] Emit authoritative source-free C semantic contracts for the initial + primitive lane. Function-pointer parameters currently serialize as the + `CFunctionPointer` placeholder built by `prik/semantics/c2ir.py`, which + `prik.contracts` does not export and the generated import line omits. Reject + that operation with a documented out-of-scope diagnostic before wrapper + planning; do not expand Goal 3 into callback adoption and do not leave a + spelling that only PRIK's own `.pyi` parser accepts. - [ ] Preserve `source_language = "c"` on native inputs and build records. `build_pyi_extension` accepts only `native_fortran_sources` with a Fortran `input_compiler`, and the CLI documents Fortran inputs only. @@ -945,15 +1005,24 @@ and `tests/c//policy/`, `codegen/`, and `end_to_end/` evidence owners. by completed policy. Do not infer ownership, nullability, or aggregate layout merely from pointer or typedef syntax. Function-pointer facts are retained as origin provenance behind the placeholder named above. +- [ ] Resolve each modeled arithmetic spelling to an exact target ABI fact and + a supported lowering identity before policy. Preserve signedness, width, + complex representation, original compatible declaration facts, and typedef + provenance. A semantic dtype mapping alone must not authorize a direct call. +- [ ] Classify linkability and callable ABI facts before policy: reject + translation-unit-local symbols, unresolved external names, variadic + functions, unsupported calling conventions, and unsupported `volatile` or + atomic access with named diagnostics. - [x] Add language-owned parsing, semantic-contract, and diagnostic tests under `tests/c/` without importing Fortran-specific fixture helpers. #### Conservative C Starter-Contract Defaults -A C declaration cannot prove what a one-level pointer denotes. `double *x` is -equally a scalar passed by reference and a pointer to the first element of an -array, and no amount of signature inspection distinguishes them. Only the -library's author knows, so the starter contract commits to the safest reading — +A one-level pointer declaration cannot prove what its pointee count denotes. +`double *x` is equally a scalar passed by reference and a pointer to the first +element of an array, and no amount of effective-signature inspection +distinguishes them. Only the library's author knows, so the starter contract +commits to the least-assumptive reading — **one scalar passed by reference** — and the user promotes it to an array by editing the semantic `.pyi`. That edit is the intended workflow, not a workaround: it is where the contract earns its place. @@ -966,30 +1035,74 @@ must not infer rank, shape, direction, nullability, ownership, or lifetime. | `T value` | `value: T` | Primitive scalar passed by value. | | `T *value` | `value: T` with `@native_call([Addr(Arg(i))])` | One scalar passed by reference. The user refines it to array storage in the contract. | | `const T *value` | `value: T` with `@native_call([Addr(Arg(i))])`, with `const` retained in origin and policy facts | Same handoff as `T *`; `const` is recorded as provenance and does not by itself change the public contract. | -| `T **value` | `value: Addr[2](T)` | Two native pointer levels; support may remain policy-blocked after serialization. | +| `T **value` | `value: Addr[2](T)` | Two native pointer levels preserved for a stable unsupported diagnostic; initial Goal 3 blocks the operation. | | return `T` | `-> T` | Direct primitive scalar result. | -| return `T *` | `-> Addr(T)` | Raw pointer result with no invented ownership, lifetime, NumPy storage, or destruction policy. | +| return `T *` | `-> Addr(T)` | Raw pointer result with no invented ownership, lifetime, NumPy storage, or destruction policy; initial Goal 3 blocks the operation. | An authoritative semantic `.pyi` supplies the API meaning the declaration could not. It may promote the by-reference scalar default to `T[n]` or `T[:]` for proved array storage, keep `T[()]` for caller-provided rank-zero storage, or restate `Addr(T)` deliberately as a raw address. `Addr(Arg(i))` requests the -address of call-local scalar storage, while a matching `Returns["name", T]` -requests mutation readback. Direction uses the explicit `In`, `Out`, or `InOut` -contract, and nullability uses an explicit `| None`; neither is inferred from -pointer syntax. - -The by-reference scalar default is the only reading conversion may assume. The -source default must still not infer an array from an adjacent extent parameter, -infer output behavior from a parameter name, interpret non-`const` as -input/output, or interpret `char *` as a string. C parameter array syntax still -decays to a pointer at the ABI; retain its dimensions as source provenance and -emit a shaped public contract only when they establish a real validation -constraint. Raw pointer contracts do not imply ownership transfer, native -retention safety, or automatic cleanup. Serialization alone does not make an -operation eligible: completed policy must block any pointer contract whose -ownership, lifetime, nullability, transfer, or result behavior remains unsafe -or unsupported. +address of call-local scalar storage. Mutation of that temporary is discarded +unless the contract instead exposes rank-zero mutable storage or projects an +output through `Returns["name", T]` and `Return(...)`. + +For ordinary wrapper functions, direction is expressed by the visible call +shape, mutable storage, projected results, and `@native_call(...)`; `In(T)`, +`Out(T)`, and `InOut(T)` are reserved for exact `@prototype` declarations and +must not be recommended for this edit. Nullability would use an explicit +`| None`, but nullable pointers are outside initial Goal 3. + +Promoting a pointer argument to an array is a coordinated contract edit, not +an annotation-only change. For a native operation whose effective arguments +are an element count followed by `double *values`, the conservative starter +contract is equivalent to: + +```python +from prik.contracts import Addr, Arg, Float64, Int32, native_call + +@native_call([Arg(0), Addr(Arg(1))]) +def scale(n: Int32, values: Float64) -> None: ... +``` + +If the author knows that `values` addresses `n` elements, an edited contract +can expose only the array and derive the native extent from its shape: + +```python +from prik.contracts import Arg, Float64, native_call + +@native_call([Arg(0).shape[0], Arg(0)]) +def scale(values: Float64[:]) -> None: ... +``` + +The edit changes `Float64` to shaped storage **and** replaces +`Addr(Arg(i))` with the array's ordinary `Arg(i)` data-pointer projection. It +also decides rank, shape, C-order validation, mutability, and whether an extent +remains visible or is derived. Keeping the scalar address projection after +changing the annotation must fail contract validation. + +The by-reference scalar default is the only reading conversion may assume for a +source spelling of `T *`. It is a conservative starter interpretation, not +proof that calling the native function with one element is safe. Conversion +must not infer an array from an adjacent extent parameter, infer output behavior +from a parameter name, interpret non-`const` as input/output, or interpret +`char *` as a string. Source-driven builds use that scalar interpretation only +when it is correct for the native operation; an array API requires the edited +semantic contract above. + +A parameter written with C array declarator syntax carries extra source +provenance even though its effective ABI type is still a pointer. Preserve that +syntax separately from the ABI. An ordinary bound such as `T values[10]` does +not by itself prove an exact ten-element runtime contract, while `static 10` +states a minimum rather than an exact shape. Stage 0 must therefore settle how +open arrays and minimum bounds are serialized without strengthening either into +an invented exact extent; until the semantic vocabulary can state the proven +constraint, require an author edit or fail closed. + +Raw pointer contracts do not imply ownership transfer, native retention safety, +or automatic cleanup. Serialization alone does not make an operation eligible: +completed policy must block any pointer contract whose ownership, lifetime, +nullability, transfer, or result behavior remains unsafe or unsupported. - [x] Settle the one-level pointer default (decided 2026-08-18). A C signature cannot distinguish a by-reference scalar from a pointer to a first array @@ -1001,24 +1114,44 @@ or unsupported. it accepts a contract that a user could not import, and its unknown-type guard matches only the literal `Unknown`. A pointer-default change must fail a focused test instead of silently rewriting every generated C contract. +- [ ] Add focused array-declarator evidence distinguishing effective pointer + ABI from written array provenance. Prove that `[]`, `[n]`, and `[static n]` + do not silently become the same exact-shape Python contract. - [ ] Prove the promotion path end to end once C builds exist: one fixture where a `T *` parameter stays a by-reference scalar, and one where an edited contract promotes the same native procedure to a NumPy array argument. This - pair is the user-facing demonstration that the contract, not the signature, - owns the Python API. + pair must assert the `Addr(Arg(i))`-to-`Arg(i)` projection edit, validation of + rank/shape/order, compiled mutation behavior, and generated direct prototype. + It is the user-facing demonstration that the contract, not the effective C + signature, owns the Python API. ### Stage 1 — Direct-Only C Policy - [ ] Reuse `NativeEntrypointAction.DIRECT_C_ABI` for supported C operations and complete eligibility before `WrapperPlanner` starts. Do not introduce a C-adapter action or fallback. +- [ ] Replace the present Fortran-only route test with language-aware completed + policy. An ineligible Fortran operation may select its generated Fortran + adapter; an ineligible C operation must instead become unsupported with a + named diagnostic. It must never inherit + `GENERATED_FORTRAN_ADAPTER` merely because it lacks a Fortran `bind(C)` fact. - [ ] Reuse the entrypoint passing conventions and route-neutral `@native_call` projections completed in Goal 2. A C operation that needs an unsupported conversion, ownership, lifetime, callback, aggregate, or result mechanism must fail with a documented policy diagnostic. +- [ ] Complete the selected meaning of every one-level primitive pointer before + planning: call-local scalar address, caller-provided rank-zero storage, + hidden output storage, or shaped primitive-array data. Record passing, + mutation visibility, writeback, result projection, rank/shape/order, and + lifetime from the semantic contract; do not rediscover the choice from + pointer depth or `const` in planning or binding generation. +- [ ] Preserve `const` on the exact native entrypoint prototype and forbid + output/writeback contracts that contradict it. A non-`const` pointer permits + native writes but does not by itself make them Python-visible. - [ ] Keep C pointer nullability distinct from Fortran optional presence. A nullable C pointer may receive `NULL`, but it does not imply a hidden - presence convention or omitted native argument. + presence convention or omitted native argument. Initial Goal 3 blocks this + form; the rule governs its later adoption. - [ ] Define C `_Bool` through the same public `Bool` contract: accept Python `bool` and `numpy.bool_`, return Python `bool`, and require an explicit safe mechanism before treating NumPy Boolean array storage as C `_Bool` array @@ -1032,6 +1165,11 @@ or unsupported. - [ ] Make supported C operations produce the same always-present entrypoint facet and no bridge facet. The C binding consumes only binding plus entrypoint and calls the user C symbol directly. +- [ ] Carry an exact C declaration plan for every direct parameter and result. + C binding generation must not reconstruct a user prototype from a + Fortran-oriented scalar spelling or width alone. It must use the completed C + ABI type, signedness, qualifiers, pointer depth, function-result transport, + symbol, and calling convention selected before planning. - [ ] Reuse Goal 2 binding-local extraction, validation, temporary storage, passing-convention lowering, writeback, cleanup, and Python-result paths whenever the completed plans are identical. Add a new lowering mechanism @@ -1042,14 +1180,44 @@ or unsupported. - [ ] Compile and link C inputs through language-aware native build records. Select the final link driver and runtime dependencies from all input and generated object languages rather than from adapter presence. +- [ ] Define one public build input for C implementation sources and one way to + mark a source-free semantic `.pyi` as C-native. Preserve that identity in + saved manifests and rebuilds; do not infer it from a filename, compiler + executable, absence of Fortran source, or `@native_abi("c")`. - [ ] Cover source-driven and source-free semantic-contract builds, saved generated artifacts, Makefiles, manifests, verbose output, and imports. ### Stage 3 — C Scalar Baseline -- [ ] Add C scalar fixtures and compiled end-to-end tests for every initially - supported integer, real, complex, and Boolean contract, including functions - returning values and functions returning `void` with input/output pointers. +The scalar baseline is complete only when every row below has one exact target +mapping and the same semantic identity is accepted by policy, planning, C +prototype generation, binding conversion, and compiled runtime tests. The +“current gap” column records why existing C semantic conversion is not yet a +wrapper-support claim. + +| C primitive family | Required semantic/lowering coverage | Current gap to close | +| --- | --- | --- | +| `_Bool` | `Bool`/measured Boolean storage; Python `bool` result | Direct C policy/build route is absent; `_Bool` arrays remain outside the baseline. | +| plain, signed, and unsigned `char` | Target-probed signedness and width; `Int8` or `UInt8` without guessing | Unsigned lowering is absent, and the generated C prototype must retain the compatible native character ABI. | +| signed `short`, `int`, `long`, `long long` | Exact measured `Int8`/`Int16`/`Int32`/`Int64` identity | C `int` deliberately retains public name `Int` while current first-lane policy accepts only fixed-width names; normalize the lowering identity without losing source spelling. | +| unsigned `short`, `int`, `long`, `long long` | Exact measured `UInt8`/`UInt16`/`UInt32`/`UInt64` identity | The semantic converter models these names, but shared primitive policy and binding lowering do not yet adopt them. | +| `float`, `double`, `long double` | Exact measured `Float32`/`Float64`/`Float128` identity | `Float32`/`Float64` have shared lowering; `long double` still needs an exact supported target mapping and backend path. | +| `float _Complex`, `double _Complex`, `long double _Complex` | Exact measured `Complex64`/`Complex128`/`Complex256` identity and C function-return ABI | The first two have shared scalar lowering; extended complex still lacks it, and all three need direct-C compiled evidence. | +| resolved standard scalar typedefs | Fixed-width integer aliases, `size_t`, and other probed arithmetic typedefs reuse the exact underlying ABI while retaining typedef provenance | `SizeT` has a backend spelling but is absent from current first-lane policy; unresolved or unsupported typedefs need pre-planning diagnostics. | +| `void` | Function result only, producing Python `None` | C semantic conversion preserves it, but no direct C build proves the result path. | + +- [ ] Close every row of the primitive matrix or narrow the documented goal by + an explicit user decision. “Initially supported” must not hide an accidental + intersection of converter and codegen registries. +- [ ] Add C scalar fixtures and compiled end-to-end tests for every adopted + arithmetic spelling: by-value inputs, direct value returns, `void` returns, + `const T *` call-local scalar inputs, mutable `T *` rank-zero storage, and + contract-projected scalar outputs. Source conversion must not infer the + output forms; authoritative edited contracts select and prove them. +- [ ] Check Python boundary behavior, not only native call success: accepted + Python and NumPy scalar inputs, overflow/range diagnostics, exact NumPy + numeric result dtype, Python `bool` Boolean results, complex values, and + mutation visibility for each pointer contract. - [ ] Cover renamed symbols and route-neutral projections, including reordered arguments, `Addr`, `Value`, hidden result storage, and typed literals where the C contract supports them. @@ -1058,21 +1226,47 @@ or unsupported. - [ ] Add at least one parseable C operation whose unsupported ABI or transfer mechanism produces the documented pre-planning diagnostic. -### Stage 4 — C Feature-Local Adoption - -Adopt one C feature row at a time. A row remains unchecked when any required -operation needs an unavailable adapter mechanism; do not weaken the feature -contract or silently generate a fallback merely to mark it complete. - -| Feature boundary | Initial C direct-only evidence | Special acceptance concerns | +### Stage 4 — Primitive Pointer Contracts And Array Promotion + +This stage completes the promised one-level-pointer equivalent of the scalar +lane. It does not infer pointee count from the C ABI and does not turn Goal 3 +into general pointer support. + +- [ ] For every adopted primitive, prove the generated `T *` default is a + Python-visible scalar plus `Addr(Arg(i))`, with one call-local native element. + Native mutation is not returned unless an edited contract requests it. +- [ ] For every adopted primitive, prove an authoritative contract can expose + caller-provided rank-zero storage with `T[()]` and can project a hidden scalar + output with `Returns[...]`/`Return(...)`, with exact mutation and tuple-result + behavior. +- [ ] Preserve `const T *` in the generated C prototype and reject a + contradictory mutable/output contract. Preserve `restrict` as provenance; + it must not invent ownership or an array shape. +- [ ] Prove one native `T *` operation through both contract meanings: the + conservative one-element scalar-reference form and an edited numeric NumPy + array form. The array form must replace `Addr(Arg(i))` with `Arg(i)`, define + rank/shape/C order and mutation, validate zero and nonzero extents, compile, + call the same user symbol directly, and generate no C adapter. +- [ ] Reject `T **`, returned `T *`, `T * | None`, retained pointers, raw owned + addresses, pointer reassociation, and `_Bool *` array promotion with stable + pre-planning diagnostics until their separate ownership, nullability, + lifetime, or storage mechanisms are adopted. + +### Post-Goal 3 C Feature Backlog + +The rows below are later adoption work and do not block the narrowly defined +initial readiness above. Move a row into an implementation goal only with its +complete policy, planning, lowering, build, documentation, and compiled +evidence. Do not weaken a feature contract or silently generate a C adapter to +mark it complete. + +| Feature boundary | Later C direct-only evidence | Special acceptance concerns | | --- | --- | --- | -| Numeric and Boolean scalars | [ ] | Exact NumPy numeric results; Python Boolean results; scalar C `_Bool` conversion. | -| Reference, input/output, and projected results | [ ] | Pointer direction, mutation, writeback ordering, tuple results, and direct function returns. | -| Numeric and Boolean arrays | [ ] | Dtype, rank, shape, order, alignment, mutability, copy/writeback, zero extents, and explicit C `_Bool` storage handling. | | Strings and character buffers | [ ] | Length source, terminators, encoding, embedded NUL, mutation, ownership, and returned-buffer lifetime. | | Enumerations and constants | [ ] | Underlying integer ABI, exported constants, and no invented Python enum layout. | | Nullable values | [ ] | Null-pointer policy, omitted Python arguments, and output projection without invented native optionality. | | Raw addresses and native pointers | [ ] | Pointee type, pointer depth, qualifiers, nullability, ownership, target lifetime, and reassociation or writeback. | +| Complete numeric and Boolean arrays | [ ] | All element types, dtype, rank, shape, order, alignment, mutability, copy/writeback, zero extents, and explicit C `_Bool` storage handling beyond the one Goal 3 promotion proof. | | Structs, fields, and methods | [ ] | By-value versus pointer ABI, opaque/accessor routes, construction, destruction, borrowing, and proven layout. | | Native global state | [ ] | Direct exported storage versus generated accessors, mutability, lifetime, and ownership. | | Overloads and generated dispatch | [ ] | Each selected C symbol owns an entrypoint action; dispatch owns no shared adapter route. | @@ -1093,16 +1287,29 @@ contract or silently generate a fallback merely to mark it complete. - Zero-adapter materialization, compilation, linker selection, Makefiles, manifests, progress output, and imports: the relevant pipeline and compiling owners extended with C-native inputs. +- The initial lane should use named `primitive_scalars` and + `primitive_pointers` feature owners. Semantic fixture parametrization covers + every C spelling; policy and codegen parametrization covers every resolved + lowering identity; compiled fixtures cover every ABI family and target-width + case. None of those layers substitutes for the others. ## Definition Of Initial C Readiness Initial direct-only C wrapper support is ready to claim only when: -- [ ] the scalar baseline passes through C source and authoritative source-free - C semantic contracts; +- [ ] every row in the Stage 3 primitive matrix has an exact supported ABI path + or the goal was explicitly narrowed before implementation; +- [ ] by-value scalars, value and `void` results, and the Stage 4 one-level + pointer forms pass through C source and authoritative source-free C semantic + contracts; +- [ ] the same `T *` native signature has compiled scalar-reference and edited + NumPy-array contract evidence, including the required projection change; - [ ] supported C operations call their user symbols without a native adapter; - [ ] unsupported adapter-required operations fail at completed policy with a documented diagnostic and no partial generated artifacts; +- [ ] every out-of-scope pointer, callback, aggregate, variadic, calling + convention, and unsupported scalar-ABI form named above fails before + planning, files, or compiler execution; - [ ] zero-adapter compilation, linking, manifests, Makefiles, verbose output, and imports have focused evidence; - [ ] Goal 2 Fortran direct and adapted routes remain green after shared-path diff --git a/docs/user/guide/wrapping-derived-types.md b/docs/user/guide/wrapping-derived-types.md index eb512884f..b6b93f745 100644 --- a/docs/user/guide/wrapping-derived-types.md +++ b/docs/user/guide/wrapping-derived-types.md @@ -318,6 +318,40 @@ item.move(np.float64(2.0), np.float64(3.0)) To expose only the method, import `private` and add `@private` to the module-level declaration. +## What The Source Already Hides + +prik reads the accessibility a type declares and does not publish what the type +keeps to itself, so a contract is not needed to hide internals: + +```fortran +module solver + implicit none + private ! module default + + type,public :: state ! exported despite the module default + private ! components default to private + real(8) :: work(8) = 0.0d0 ! internal, not a Python attribute + integer(4),public :: steps = 0 + contains + private ! bindings default to private + procedure :: advance_once ! internal, not a Python method + procedure,public :: run => advance_once + end type state +end module solver +``` + +The generated `state` class exposes `steps` and `run` only. Each rule is the +Fortran one: + +| Declaration | Effect on the Python class | +| --- | --- | +| `type, public ::` | Exported, even when the module defaults to `private` | +| `type, private ::` | Not exported, even when the module defaults to `public` | +| `private` before `contains` | Components default to hidden | +| `private` after `contains` | Type-bound procedures default to hidden | +| `integer, public ::` on a component | Published regardless of the type default | +| `procedure, public ::` on a binding | Published regardless of the type default | + The class docstring now lists `move(dx, dy) -> None` under `Methods`. `points.point.move.__doc__` contains its complete parameter and return details. diff --git a/prik/parsers/fortran/models.py b/prik/parsers/fortran/models.py index 067437f3b..3279e1141 100644 --- a/prik/parsers/fortran/models.py +++ b/prik/parsers/fortran/models.py @@ -355,6 +355,8 @@ class FortranDerivedType: attributes: list[str] = field(default_factory=list) procedure_bindings: list[dict] = field(default_factory=list) generic_bindings: list[dict] = field(default_factory=list) + component_visibility: str = "public" + binding_visibility: str = "public" @dataclass diff --git a/prik/parsers/fortran/parser.py b/prik/parsers/fortran/parser.py index 146bd3465..cf9024d22 100644 --- a/prik/parsers/fortran/parser.py +++ b/prik/parsers/fortran/parser.py @@ -114,6 +114,16 @@ rejected by the slicer validation. """ + +def _binding_visibility(attributes: list[str], default_visibility: str) -> str: + """Return a type-bound binding's accessibility from its attributes and the type default.""" + if "private" in attributes: + return "private" + if "public" in attributes: + return "public" + return default_visibility + + _REGEX: dict[str, re.Pattern[str]] = { "type": re.compile( r"^(integer|real|complex|logical|character|double\s+(?:precision|complex))\b\s*(\([^)]*\))?\s*(.*)$", @@ -144,10 +154,14 @@ re.IGNORECASE, ), "legacy_parameter": re.compile(r"^parameter\s*\(\s*(?P.*)\s*\)$", re.IGNORECASE), + "construct_name": re.compile(r"^[A-Za-z_]\w*\s*:(?!:)\s*(?P.+)$"), "derived_type": re.compile(r"^type\s*(?P(?:,\s*[^:]+)?)::\s*(?P\w+)(?:\s*\([^)]*\))?$", re.IGNORECASE), "type_field": re.compile(r"^type\s*\(\s*(?P\w+(?:\s*\([^)]*\))?)\s*\)\s*(?P.*)$", re.IGNORECASE), "class_field": re.compile(r"^class\s*\(\s*(?P\w+(?:\s*\([^)]*\))?)\s*\)\s*(?P.*)$", re.IGNORECASE), - "procedure_binding": re.compile(r"^procedure\s*(?:,\s*[^:]*)?::\s*(?P.*)$", re.IGNORECASE), + "procedure_binding": re.compile( + r"^procedure\s*(?:\(\s*(?P\w+)\s*\))?\s*(?:,\s*[^:]*)?::\s*(?P.*)$", + re.IGNORECASE, + ), "procedure_dummy": re.compile(r"^procedure\s*\(\s*(?P\w+)\s*\)\s*(?P.*)$", re.IGNORECASE), "module": re.compile(r"^module\s+(?P\w+)\s*$", re.IGNORECASE), "submodule": re.compile(r"^submodule\s*\(\s*(?P[^)]+?)\s*\)\s*(?P\w+)\s*$", re.IGNORECASE), @@ -1170,6 +1184,11 @@ def is_executable_statement_start(cls, line: str) -> bool: stripped = labeled.group("body").strip() if not stripped: return False + named_construct = _REGEX["construct_name"].match(stripped) + if named_construct: + stripped = named_construct.group("body").strip() + if not stripped: + return False lowered = stripped.lower() if cls.is_openmp_directive(stripped): return not cls.is_openmp_declarative_directive(stripped) @@ -3651,7 +3670,8 @@ def _parse_type_spec_line( if "sequence" not in dtype.attributes: dtype.attributes.append("sequence") return - if stripped.lower() == "private": + if stripped.lower() in {"private", "public"}: + dtype.component_visibility = stripped.lower() return if self._source_unit_scanner.is_openmp_declarative_directive(stripped): raise FortranParseError( @@ -3661,6 +3681,7 @@ def _parse_type_spec_line( source_line=source_line, code="PARSE_UNSUPPORTED_OPENMP_DIRECTIVE", ) + field_count = len(dtype.fields) parsed = self._helper_parse_declaration_line( stripped, scope, @@ -3671,6 +3692,7 @@ def _parse_type_spec_line( parse_character_star=False, ) if parsed: + self._apply_default_component_visibility(dtype, stripped, first_new_field=field_count) return if "::" not in stripped and not self._source_unit_scanner.looks_like_declaration_or_spec(stripped): _raise_invalid_fortran_syntax_line( @@ -3688,6 +3710,28 @@ def _parse_type_spec_line( code="PARSE_UNSUPPORTED_DECLARATION", ) + @staticmethod + def _apply_default_component_visibility( + dtype: FortranDerivedType, + declaration: str, + *, + first_new_field: int, + ) -> None: + """Apply a type's component-accessibility default to newly parsed components. + + A component keeps the accessibility written on its own declaration; the + `private` or `public` statement in the type's specification part only + supplies the default for components that do not state one. + """ + if dtype.component_visibility != "private": + return + attribute_text = declaration.split("::", 1)[0].lower() if "::" in declaration else "" + if re.search(r"\bpublic\b", attribute_text): + return + for component in dtype.fields[first_new_field:]: + if component.visibility == "public": + component.visibility = "private" + def _parse_derived_type_contains_line( self, line: str, @@ -3698,14 +3742,23 @@ def _parse_derived_type_contains_line( source_line: str | None = None, ) -> None: """Parse type-bound procedure and generic bindings after `contains`.""" + if line.strip().lower() in {"private", "public"}: + dtype.binding_visibility = line.strip().lower() + return + proc_binding = _REGEX["procedure_binding"].match(line) if proc_binding: binding_names = split_csv(proc_binding.group("names")) dtype.methods.extend(binding_names) left = line.split("::", 1)[0] attrs = [a.strip().lower() for a in split_csv(left.split(",", 1)[1] if "," in left else "")] + visibility = _binding_visibility(attrs, dtype.binding_visibility) + interface_name = proc_binding.group("iface") for name in binding_names: - dtype.procedure_bindings.append({"name": name, "attrs": attrs}) + binding = {"name": name, "attrs": attrs, "visibility": visibility} + if interface_name: + binding["interface"] = interface_name + dtype.procedure_bindings.append(binding) return if line.lower().startswith("generic") and "::" in line and "=>" in line: @@ -3714,7 +3767,14 @@ def _parse_derived_type_contains_line( attrs = [a.strip().lower() for a in split_csv(attr_txt)] if attr_txt else [] lhs, rhs_txt = [x.strip() for x in right.split("=>", 1)] rhs = [r.strip() for r in split_csv(rhs_txt)] - dtype.generic_bindings.append({"name": lhs, "targets": rhs, "attrs": attrs}) + dtype.generic_bindings.append( + { + "name": lhs, + "targets": rhs, + "attrs": attrs, + "visibility": _binding_visibility(attrs, dtype.binding_visibility), + } + ) return if re.match(r"^final\s*::\s*[A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*\s*$", line, re.IGNORECASE): diff --git a/prik/preprocessing/probes/fortran_types.py b/prik/preprocessing/probes/fortran_types.py index cbf35a65f..11f18280e 100644 --- a/prik/preprocessing/probes/fortran_types.py +++ b/prik/preprocessing/probes/fortran_types.py @@ -52,6 +52,44 @@ _SAFE_EXPRESSION_RE = re.compile(r"^[A-Za-z0-9_+\-*/().,= :]+$") _TOKEN_RE = re.compile(r"\b[A-Za-z_][A-Za-z0-9_]*\b") +_PROBE_INTRINSIC_NAMES = frozenset( + { + # Numeric inquiry and kind-selection intrinsics that may appear in a + # constant kind or size expression. + "bit_size", + "digits", + "epsilon", + "huge", + "kind", + "len", + "maxexponent", + "minexponent", + "precision", + "radix", + "range", + "selected_char_kind", + "selected_int_kind", + "selected_real_kind", + "size", + "storage_size", + "tiny", + # Conversion and reduction intrinsics used to combine the above. + "abs", + "ceiling", + "floor", + "int", + "max", + "min", + "mod", + "modulo", + "nint", + "real", + # Constant operands that may appear as intrinsic arguments. + "false", + "true", + } +) + _ISO_FORTRAN_ENV_NAMES = { "int8", "int16", @@ -180,7 +218,7 @@ def fortran_type_probe_expressions( seen: set[str] = set() for item in requirements: expression = str(item.get("expression") or "").strip() - if not expression: + if not expression or not probe_can_resolve_expression(expression): continue key = expression.lower() if key in seen: @@ -190,6 +228,20 @@ def fortran_type_probe_expressions( return expressions +def probe_can_resolve_expression(expression: str) -> bool: + """Return whether the standalone probe program can evaluate ``expression``. + + The probe is a self-contained program: it can import intrinsic modules but + cannot ``use`` a module from the project being analyzed, whose compiled + interface does not exist yet. An expression naming a symbol declared + elsewhere in the project — a `wp` or `ip` kind parameter, for example — is + therefore left for the requirement report rather than compiled into a + program that cannot resolve it. + """ + known = _PROBE_INTRINSIC_NAMES | _ISO_FORTRAN_ENV_NAMES | _ISO_C_BINDING_NAMES + return all(token.lower() in known for token in _TOKEN_RE.findall(expression)) + + def build_fortran_type_probe_source(expressions: Sequence[str]) -> str: """Build free-form Fortran source that prints integer expression results. diff --git a/prik/semantics/fortran2ir.py b/prik/semantics/fortran2ir.py index 9b93a195d..c1ef665f0 100644 --- a/prik/semantics/fortran2ir.py +++ b/prik/semantics/fortran2ir.py @@ -1121,8 +1121,8 @@ def _visit_FortranModule( ) for dtype in module.derived_types ] - for semantic_cls in semantic_classes: - semantic_cls.visibility = self._symbol_visibility(module, semantic_cls.name) + for semantic_cls, dtype in zip(semantic_classes, module.derived_types, strict=True): + semantic_cls.visibility = self._derived_type_visibility(module, dtype) self._record_class_declaration_callables( semantic_cls, self._declaration_callable_context( @@ -2187,11 +2187,15 @@ def _bound_methods( continue binding_attributes = tuple(binding.get("attrs", ())) attrs = set(binding_attributes) - visibility = proc.visibility - if "private" in attrs: + declared_visibility = binding.get("visibility") + if declared_visibility in {"private", "public"}: + visibility = str(declared_visibility) + elif "private" in attrs: visibility = "private" elif "public" in attrs: visibility = "public" + else: + visibility = proc.visibility is_static = "nopass" in attrs passed_object_name, passed_object_position = self._passed_object_argument(proc, binding_attributes) proc.metadata["fortran_type_bound_target"] = True @@ -2947,6 +2951,21 @@ def _standalone_module_name(parsed_file: FortranFile) -> str: return Path(parsed_file.filename).stem return "standalone" + @staticmethod + def _derived_type_visibility(module: FortranModule, dtype: FortranDerivedType) -> str: + """Resolve a derived type's accessibility, preferring its own declaration. + + ``type, public ::`` and ``type, private ::`` state the type's own + accessibility, so they win over a module-level ``public``/``private`` + default and over the module's accessibility lists. + """ + attributes = {str(attribute).lower() for attribute in getattr(dtype, "attributes", ())} + if "private" in attributes: + return "private" + if "public" in attributes: + return "public" + return FortranToIRConverter._symbol_visibility(module, dtype.name) + @staticmethod def _symbol_visibility(module: FortranModule, symbol_name: str) -> str: """Resolve explicit private/public lists before the module default visibility.""" 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 9920cea01..e6aac6856 100644 --- a/tests/fortran/data_types/probes/test_fortran_type_probes.py +++ b/tests/fortran/data_types/probes/test_fortran_type_probes.py @@ -580,3 +580,56 @@ def test_prik_semantics_cli_uses_compiler_dependent_default_fortran_kinds(tmp_pa assert semantic_types["legacy_value"]["name"] == "Complex128" assert semantic_types["scale"]["metadata"]["fortran_type_fact_source"] == "compiler_probe" assert semantic_types["legacy_value"]["metadata"]["fortran_type_fact_source"] == "legacy_star_storage" + + +def test_probe_skips_expressions_naming_project_symbols(): + """The probe program cannot `use` a module that has not been compiled yet. + + An expression naming a kind parameter declared elsewhere in the project is + left out of the probe rather than compiled into a program that cannot + resolve it. Expressions built only from intrinsic names are still probed. + """ + assert fortran_type_probe.probe_can_resolve_expression("selected_real_kind(15, 307)") + assert fortran_type_probe.probe_can_resolve_expression("storage_size(1_4, kind=int32)") + assert not fortran_type_probe.probe_can_resolve_expression("storage_size(1_ip, kind=ip)") + assert not fortran_type_probe.probe_can_resolve_expression("wp") + + requirements = [ + {"expression": "real64"}, + {"expression": "storage_size(1_ip, kind=ip)"}, + {"expression": "selected_int_kind(9)"}, + ] + assert fortran_type_probe_expressions(requirements) == ["real64", "selected_int_kind(9)"] + + +def test_probe_source_compiles_for_a_module_using_imported_kind_parameters(tmp_path): + """A parameter defined from an imported kind must not break the whole probe.""" + source = tmp_path / "imported_kinds.f90" + source.write_text( + """ +module imported_kinds_kinds + use,intrinsic :: iso_fortran_env + implicit none + private + integer,parameter,public :: ip = int32 +end module imported_kinds_kinds + +module imported_kinds + use imported_kinds_kinds, only: ip + implicit none + integer(ip),parameter :: int_size = storage_size(1_ip, kind=ip) +contains + integer(ip) function bits() + bits = int_size + end function bits +end module imported_kinds +""", + encoding="utf-8", + ) + + project = parse_fortran_project([str(source)]) + expressions = fortran_type_probe_expressions(collect_semantic_compile_time_requirements(project)) + + assert "storage_size(1_ip, kind=ip)" not in expressions + assert "int32" in expressions + build_fortran_type_probe_source(expressions) diff --git a/tests/fortran/derived_types/end_to_end/fixtures/type_accessibility.f90 b/tests/fortran/derived_types/end_to_end/fixtures/type_accessibility.f90 new file mode 100644 index 000000000..a2f0a8e8c --- /dev/null +++ b/tests/fortran/derived_types/end_to_end/fixtures/type_accessibility.f90 @@ -0,0 +1,30 @@ +module type_accessibility + implicit none + private + + public :: gated + + type,public :: gated + private + integer(4) :: hidden = 7 + integer(4),public :: shown = 3 + contains + private + procedure :: internal_step + procedure,public :: step => internal_step + procedure,public :: peek => gated_peek + end type gated + +contains + + subroutine internal_step(self) + class(gated),intent(inout) :: self + self%hidden = self%hidden + 1 + end subroutine internal_step + + integer(4) function gated_peek(self) + class(gated),intent(in) :: self + gated_peek = self%hidden + end function gated_peek + +end module type_accessibility diff --git a/tests/fortran/derived_types/end_to_end/test_type_accessibility.py b/tests/fortran/derived_types/end_to_end/test_type_accessibility.py new file mode 100644 index 000000000..7cfab3465 --- /dev/null +++ b/tests/fortran/derived_types/end_to_end/test_type_accessibility.py @@ -0,0 +1,39 @@ +"""Generated class surface for Fortran accessibility statements.""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest + +from tests.fortran._support.wrapper_build import _build_source_and_import + +pytestmark = pytest.mark.fortran_end_to_end + +SOURCE = Path(__file__).parent / "fixtures" / "type_accessibility.f90" +GENERATED = { + "bind_c_type_accessibility_wrapper.f90", + "type_accessibility_wrapper.c", + "type_accessibility_wrapper.h", +} + + +def test_accessibility_statements_shape_the_generated_class(tmp_path: Path): + """Only components and bindings the type publishes reach Python. + + A `type, public ::` declaration is exported even though the module defaults + to `private`, while the type's own `private` statements keep its internal + component and binding off the generated surface. + """ + module = _build_source_and_import(SOURCE, tmp_path, GENERATED) + + assert hasattr(module, "gated") + members = {name for name in dir(module.gated) if not name.startswith("_")} + assert members == {"shown", "step", "peek"} + + instance = module.gated(shown=np.int32(5)) + assert instance.shown == np.int32(5) + assert instance.peek() == np.int32(7) + instance.step() + assert instance.peek() == np.int32(8) 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 4c421aa18..e593fced1 100644 --- a/tests/fortran/derived_types/parsing/test_derived_procedure_syntax.py +++ b/tests/fortran/derived_types/parsing/test_derived_procedure_syntax.py @@ -39,7 +39,21 @@ def test_derived_type_procedure_and_generic_bindings(): end module m """ dt = parse_fortran_file(code).modules[0].derived_types[0] - assert {"name": "init => t_init", "attrs": ["pass(self)"]} in dt.procedure_bindings - assert {"name": "clear", "attrs": ["nopass"]} in dt.procedure_bindings - assert {"name": "assignment(=)", "targets": ["init"], "attrs": []} in dt.generic_bindings - assert {"name": "setup", "targets": ["init", "clear"], "attrs": ["public"]} in dt.generic_bindings + assert { + "name": "init => t_init", + "attrs": ["pass(self)"], + "visibility": "public", + } in dt.procedure_bindings + assert {"name": "clear", "attrs": ["nopass"], "visibility": "public"} in dt.procedure_bindings + assert { + "name": "assignment(=)", + "targets": ["init"], + "attrs": [], + "visibility": "public", + } in dt.generic_bindings + assert { + "name": "setup", + "targets": ["init", "clear"], + "attrs": ["public"], + "visibility": "public", + } in dt.generic_bindings 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 43768d33f..de7ddd5d1 100644 --- a/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py +++ b/tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py @@ -293,3 +293,82 @@ def test_class_declarations_preserve_polymorphic_source_fact(): assert module.functions[0].metadata["fortran_passed_object_name"] == "self" assert accept_value.origin.source_type == "class(base)" assert accept_value.metadata["fortran_polymorphic"] is True + + +def test_declared_type_accessibility_wins_over_the_module_default(): + """`type, public ::` states the type's own accessibility. + + A module-level `private` default sets accessibility for symbols that do not + state one; it must not hide a type whose declaration says `public`. + """ + module = fortran_module_to_semantic_module( + parse_fortran_source( + """ +module exports_mod + implicit none + private + type,public :: exported + integer :: n = 0 + end type exported + type :: defaulted + integer :: n = 0 + end type defaulted +end module exports_mod +""" + ) + ) + + visibility = {semantic_class.name: semantic_class.visibility for semantic_class in module.classes} + assert visibility == {"exported": "public", "defaulted": "private"} + + +def test_private_components_carry_their_hidden_accessibility(): + """The type's `private` statement is the default accessibility of its components.""" + module = fortran_module_to_semantic_module( + parse_fortran_source( + """ +module hidden_mod + implicit none + type,public :: partly + private + integer :: hidden = 0 + integer,public :: shown = 0 + end type partly +end module hidden_mod +""" + ) + ) + + partly = module.classes[0] + assert {field.name: field.visibility for field in partly.fields} == { + "hidden": "private", + "shown": "public", + } + + +def test_private_type_bound_procedures_stay_off_the_generated_class_surface(): + """A binding hidden by the `private` statement after `contains` is not a method.""" + module = fortran_module_to_semantic_module( + parse_fortran_source( + """ +module bindings_mod + implicit none + type,public :: gated + integer :: n = 0 + contains + private + procedure :: internal_step + procedure,public :: step => internal_step + end type gated +contains + subroutine internal_step(self) + class(gated),intent(inout) :: self + self%n = self%n + 1 + end subroutine internal_step +end module bindings_mod +""" + ) + ) + + gated = module.classes[0] + assert [method.name for method in gated.methods if method.visibility == "public"] == ["step"] diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/derived_type.json b/tests/fortran/source_parsing/parsing/fixtures/general/derived_type.json index aa21b048e..eeabfd7f2 100644 --- a/tests/fortran/source_parsing/parsing/fixtures/general/derived_type.json +++ b/tests/fortran/source_parsing/parsing/fixtures/general/derived_type.json @@ -110,14 +110,18 @@ "procedure_bindings": [ { "name": "move", - "attrs": [] + "attrs": [], + "visibility": "public" }, { "name": "reset", - "attrs": [] + "attrs": [], + "visibility": "public" } ], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [], @@ -244,14 +248,18 @@ "procedure_bindings": [ { "name": "move", - "attrs": [] + "attrs": [], + "visibility": "public" }, { "name": "reset", - "attrs": [] + "attrs": [], + "visibility": "public" } ], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [], diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.json b/tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.json index 4ff760d38..d886875fc 100644 --- a/tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.json +++ b/tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.json @@ -73,10 +73,13 @@ "procedure_bindings": [ { "name": "move", - "attrs": [] + "attrs": [], + "visibility": "public" } ], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" }, { "name": "mesh", @@ -141,14 +144,18 @@ "procedure_bindings": [ { "name": "init", - "attrs": [] + "attrs": [], + "visibility": "public" }, { "name": "clear", - "attrs": [] + "attrs": [], + "visibility": "public" } ], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [], @@ -238,10 +245,13 @@ "procedure_bindings": [ { "name": "move", - "attrs": [] + "attrs": [], + "visibility": "public" } ], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" }, { "name": "mesh", @@ -306,14 +316,18 @@ "procedure_bindings": [ { "name": "init", - "attrs": [] + "attrs": [], + "visibility": "public" }, { "name": "clear", - "attrs": [] + "attrs": [], + "visibility": "public" } ], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [], diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.json b/tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.json index 4b99cbcd5..72d349fbc 100644 --- a/tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.json +++ b/tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.json @@ -656,7 +656,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" }, { "name": "vector3", @@ -695,7 +697,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" }, { "name": "hidden_state", @@ -728,7 +732,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [], @@ -1411,7 +1417,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" }, { "name": "vector3", @@ -1450,7 +1458,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" }, { "name": "hidden_state", @@ -1483,7 +1493,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [], diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.json b/tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.json index 799728509..4b3cfcfeb 100644 --- a/tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.json +++ b/tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.json @@ -489,7 +489,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [ @@ -1007,7 +1009,9 @@ "extends": null, "attributes": [], "procedure_bindings": [], - "generic_bindings": [] + "generic_bindings": [], + "component_visibility": "public", + "binding_visibility": "public" } ], "interfaces": [ 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 d5d9bbb66..0c4873584 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 @@ -646,14 +646,15 @@ def test_scope_include_import_and_derived_type_binding_contracts(): assert dtype.methods == ["update", "reset"] assert dtype.procedure_bindings == [ - {"name": "update", "attrs": ["pass(self)", "public"]}, - {"name": "reset", "attrs": ["pass(self)", "public"]}, + {"name": "update", "attrs": ["pass(self)", "public"], "visibility": "public"}, + {"name": "reset", "attrs": ["pass(self)", "public"], "visibility": "public"}, ] assert dtype.generic_bindings == [ { "name": "assignment(=)", "targets": ["assign_child", "assign_other"], "attrs": ["public"], + "visibility": "public", } ] 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 c7844d0de..1c6a5bfe1 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 @@ -258,3 +258,93 @@ def test_singular_parse_entrypoint_rejects_ambiguous_sources(): end subroutine second """) assert len(parsed.procedures) == 2 + + +def test_type_accessibility_statements_set_component_and_binding_defaults(): + """A type's `private` statement is a default, not an unsupported declaration. + + The statement before `contains` sets component accessibility; the statement + after it sets type-bound accessibility. Each declaration that states its own + accessibility keeps it. + """ + module = parse_fortran_module( + """ +module access_mod + implicit none + type,public :: t + private + integer :: hidden = 0 + integer,public :: shown = 0 + contains + private + procedure :: internal_step + procedure,public :: step => internal_step + end type t +contains + subroutine internal_step(self) + class(t),intent(inout) :: self + end subroutine internal_step +end module access_mod +""" + ) + + dtype = module.derived_types[0] + assert dtype.component_visibility == "private" + assert dtype.binding_visibility == "private" + assert {field.name: field.visibility for field in dtype.fields} == { + "hidden": "private", + "shown": "public", + } + assert [(binding["name"], binding["visibility"]) for binding in dtype.procedure_bindings] == [ + ("internal_step", "private"), + ("step => internal_step", "public"), + ] + + +def test_deferred_type_bound_binding_records_its_declaring_interface(): + """A deferred binding parses; whether it can be wrapped belongs to policy.""" + module = parse_fortran_module( + """ +module deferred_mod + implicit none + type,public,abstract :: base + contains + procedure(size_func),deferred,public :: size_of + end type base + abstract interface + pure function size_func(self) result(s) + import :: base + class(base),intent(in) :: self + integer :: s + end function size_func + end interface +end module deferred_mod +""" + ) + + binding = module.derived_types[0].procedure_bindings[0] + assert binding["name"] == "size_of" + assert binding["interface"] == "size_func" + assert "deferred" in binding["attrs"] + + +def test_named_block_construct_starts_the_execution_part(): + """`name: block` is an executable construct, not a declaration.""" + module = parse_fortran_module( + """ +module block_mod + implicit none +contains + subroutine scale_value(x) + real(8),intent(inout) :: x + main: block + real(8) :: factor + factor = 2.0d0 + x = x * factor + end block main + end subroutine scale_value +end module block_mod +""" + ) + + assert [procedure.name for procedure in module.procedures] == ["scale_value"] From cae4fa4f2936bf69b5b3b8b69b4c05686cefdbd5 Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 13:04:06 +0100 Subject: [PATCH 13/26] codex: Wrap abstract types, generic constructors, and BSPLINE-FORTRAN Fortran 2008 derived-type support, taken far enough that BSPLINE-FORTRAN wraps unmodified. Abstract types and deferred bindings A `type, abstract ::` declaration becomes a Python class with no constructor; instantiating it raises TypeError naming the concrete extensions. Its extensions stay ordinary Python subclasses. A deferred binding is declared on the base and resolved by the object's own type through the polymorphic discriminator the bridge already generated, so no new emitted-code mechanism was needed. An abstract type publishes no component accessors of its own and is excluded from the polymorphic cases a caller can supply. Generic constructors `interface ` is that type's constructor: its specifics become one overloaded `__init__`. A specific that is private in its module is reached through the public type name. A constructor carries no `@bind` -- the class name states the generic that reaches it -- and `@private` on `__init__` is refused. Accessibility statements A derived type's `private`/`public` statements are honored for both components and type-bound procedures. The statement after `contains` previously failed to parse; the one before it parsed but was discarded, so private components reached the compiler as accessors that read them. Parser and probe Deferred bindings and named `block` constructs parse. A `type, public ::` declaration is no longer hidden by a module `private` default, which silently dropped the type and every method. The compiler type probe no longer emits expressions naming project symbols it cannot resolve. bind(C) A module whose only procedures are `bind(C)` now installs the native support its derived-type accessors call, fixing an undefined-symbol link failure. Contracts Every build writes its semantic `.pyi` beside the extension, under `contracts/` in the build directory. `@abstract` and `@abstractmethod` join the contract vocabulary; `@native_type(attributes=('public',))` is no longer emitted, since `public` is the default. Example examples/bspline wraps BSPLINE-FORTRAN 7.4.0 unmodified and validates both interfaces against analytic values and scipy.interpolate. It is the first example project in modern Fortran rather than FORTRAN 77. Co-Authored-By: Claude Opus 5 --- .github/workflows/real-libraries.yml | 7 + CHANGELOG.md | 66 + docs/user/examples/bspline-wrapper.md | 78 + docs/user/examples/index.md | 5 +- docs/user/guide/wrapping-derived-types.md | 93 + docs/user/language-support/feature-matrix.md | 6 +- examples/bspline/README.md | 109 + examples/bspline/__init__.py | 0 examples/bspline/build_all.sh | 3 + examples/bspline/build_prik.sh | 16 + examples/bspline/conftest.py | 17 + examples/bspline/native/LICENSE | 125 + .../bspline/native/bspline_kinds_module.F90 | 40 + examples/bspline/native/bspline_oo_module.f90 | 2823 ++++++++++ .../bspline/native/bspline_sub_module.f90 | 4733 +++++++++++++++++ examples/bspline/routine_inventory.py | 51 + examples/bspline/tests/__init__.py | 0 .../bspline/tests/test_object_oriented_api.py | 107 + examples/bspline/tests/test_procedural_api.py | 105 + mkdocs.yml | 1 + prik/codegen/c/binding.py | 19 +- prik/codegen/c/python_surface.py | 38 +- prik/codegen/fortran/bridge.py | 63 +- prik/contracts/__init__.py | 14 + prik/pipeline/build.py | 48 +- prik/planning/entrypoints.py | 5 + prik/planning/models.py | 2 + prik/planning/planner.py | 2 + prik/policy/completion.py | 36 +- prik/policy/construction.py | 54 +- prik/policy/models.py | 2 + prik/printers/pyi.py | 59 +- prik/semantics/fortran2ir.py | 101 +- prik/semantics/metadata.py | 2 + prik/semantics/pyi2ir.py | 69 +- .../fixtures/abstract_hierarchy.f90 | 93 + .../fixtures/generic_constructor.f90 | 41 + .../end_to_end/test_abstract_hierarchy.py | 116 + .../end_to_end/test_generic_constructor.py | 77 + .../policy/test_derived_accessor_policy.py | 22 +- .../test_fortran_generic_semantics.py | 17 +- 41 files changed, 9191 insertions(+), 74 deletions(-) create mode 100644 docs/user/examples/bspline-wrapper.md create mode 100644 examples/bspline/README.md create mode 100644 examples/bspline/__init__.py create mode 100644 examples/bspline/build_all.sh create mode 100644 examples/bspline/build_prik.sh create mode 100644 examples/bspline/conftest.py create mode 100644 examples/bspline/native/LICENSE create mode 100644 examples/bspline/native/bspline_kinds_module.F90 create mode 100644 examples/bspline/native/bspline_oo_module.f90 create mode 100644 examples/bspline/native/bspline_sub_module.f90 create mode 100644 examples/bspline/routine_inventory.py create mode 100644 examples/bspline/tests/__init__.py create mode 100644 examples/bspline/tests/test_object_oriented_api.py create mode 100644 examples/bspline/tests/test_procedural_api.py create mode 100644 tests/fortran/derived_types/end_to_end/fixtures/abstract_hierarchy.f90 create mode 100644 tests/fortran/derived_types/end_to_end/fixtures/generic_constructor.f90 create mode 100644 tests/fortran/derived_types/end_to_end/test_abstract_hierarchy.py create mode 100644 tests/fortran/derived_types/end_to_end/test_generic_constructor.py diff --git a/.github/workflows/real-libraries.yml b/.github/workflows/real-libraries.yml index e93a51154..814e202d0 100644 --- a/.github/workflows/real-libraries.yml +++ b/.github/workflows/real-libraries.yml @@ -111,3 +111,10 @@ jobs: run: | source examples/minpack/build_all.sh python -m pytest -q examples/minpack/tests + - name: Run BSPLINE-FORTRAN abstract-hierarchy and interpolation audit + env: + PYTHONPATH: . + HYPOTHESIS_PROFILE: ci + run: | + source examples/bspline/build_all.sh + python -m pytest -q examples/bspline/tests diff --git a/CHANGELOG.md b/CHANGELOG.md index a9036258f..e900ccb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,65 @@ release tags add a leading `v` to the package version. ## Unreleased +### Added + +- Every build now writes its semantic `.pyi` contract beside the extension, in a + `contracts/` package inside the build directory (`__prik__/contracts/` by + default). Reshaping the generated Python API no longer needs a separate + `generate --pyi` run: the contract describing the API a build just produced is + always there, and rebuilding from it works directly. It lives in its own + directory so its `__init__.pyi` cannot make the build directory look like a + Python package. + +- Generic constructors declared as `interface ` are now wrapped from + Fortran source. Such an interface is that type's constructor, so its specifics + become the accepted signatures of one overloaded `__init__` rather than a + module-level generic, and a call matching none of them is refused instead of + guessed at. A specific that is `private` in its module is reached through the + public type name, which resolves to the same procedure. Because the interface + supplies every accepted signature, it replaces the keyword-field constructor, + and the generated contract states only the signatures the class accepts. The + three sources of a constructor are now: no user constructor keeps the + keyword-field `__init__`, an `interface ` supplies the overload set, + and an edited `.pyi` declares exactly what it says. A constructor candidate + carries no `@bind`, because the class name already states the generic that + reaches it — the same reason an unrenamed method omits it — and `@private` is + refused on `__init__`, since a constructor is published or absent and the + accessibility of the specific it selects is that procedure's own fact. + +- Added the BSPLINE-FORTRAN example under `examples/bspline`. It wraps the + upstream sources unmodified and validates both public interfaces from Python: + the object-oriented classes over an abstract base with deferred bindings and + generic constructors, and the procedural interpolation routines. Numerical + checks use analytic values and `scipy.interpolate` as independent oracles. It + is the first example project written in modern Fortran rather than FORTRAN 77. + +- Abstract Fortran derived types are now wrapped. A `type, abstract ::` + declaration becomes a Python class with no constructor — instantiating it + raises `TypeError` naming the concrete extensions to use instead — while its + extensions remain ordinary Python subclasses that inherit its implemented + bindings. A deferred binding (`procedure(iface), deferred ::`) is declared on + the base and resolved by the object's own type: the generated adapter converts + the address to the caller's concrete type and lets Fortran select the + override, so no Python-side dispatch is involved. An abstract type publishes + no component accessors of its own, because each extension already generates + one for every component it inherits, and it is excluded from the polymorphic + cases a caller can supply, since no object can have it as a dynamic type. In + semantic `.pyi` contracts the class carries `@abstract` and each deferred + binding carries `@abstractmethod`, both re-exported from `prik.contracts`; + a deferred binding never carries `@bind`, because it has no native symbol. + ### Fixed +- A module whose only procedures are `bind(C)` now installs the bundled native + support its derived-type accessors need. Compiled wrapper builds for such a + module previously failed to link with `undefined symbol: + prik_float64_to_numpy`, because native support was requested only for module + variables, for ordinary procedure arguments and results, and for array + components — and a `bind(C)` procedure supplies none of those. Every published + component converts through those helpers, so a type with any component now + requests them. + - A derived type's `private` and `public` statements are now honored. The statement before `contains` sets the default accessibility of components and the statement after it sets the default for type-bound procedures; a @@ -75,6 +132,15 @@ release tags add a leading `v` to the package version. ### Changed +- Expanded the initial direct-only C adoption roadmap around one exact scope: + modeled primitive arithmetic scalars and their one-level pointer forms. It + now records the unresolved scalar-lowering matrix, requires C inputs to fail + direct-or-diagnostic before planning, and makes the ambiguous `T *` workflow + explicit: generated contracts default to one scalar address, while an array + API requires an authoritative `.pyi` edit of both the shaped annotation and + the `Addr(Arg(...))` projection. Broader C pointers, arrays, callbacks, + aggregates, ownership, and nullability remain follow-on work. + - A scalar `character` dummy that declares no `intent` now uses the same conservative `intent(inout)` default as every other scalar, so the value the native procedure left behind is returned. It was silently assumed diff --git a/docs/user/examples/bspline-wrapper.md b/docs/user/examples/bspline-wrapper.md new file mode 100644 index 000000000..df64a8e91 --- /dev/null +++ b/docs/user/examples/bspline-wrapper.md @@ -0,0 +1,78 @@ +--- +title: Build and Validate BSPLINE-FORTRAN with PRIK +audience: users, advanced users +prerequisites: derived types, arrays +related: minpack-wrapper.md, ../guide/wrapping-derived-types.md +status: maintained +publication: reviewed +--- + +# Build and Validate BSPLINE-FORTRAN with PRIK + +This example wraps [BSPLINE-FORTRAN](https://github.com/jacobwilliams/bspline-fortran) +and validates both of its public interfaces from Python. + +It is the modern-Fortran example. The BLAS, LAPACK, FFTPACK, and MINPACK +projects are FORTRAN 77; this library is Fortran 2008, and PRIK wraps it +**unmodified**: + +- an **abstract** derived type, `bspline_class`, with two **deferred** bindings; +- six concrete extensions that inherit from it; +- **generic constructors** declared as `interface bspline_1d`; +- **private components and bindings** kept off the Python surface; +- generic procedure interfaces with several specifics each. + +## Build and test + +```bash +source examples/bspline/build_all.sh +python3 -m pytest -q examples/bspline/tests -m real_library +``` + +The build passes the three interpolation sources to PRIK in dependency order. +No `.pyi` contract is written and no source is edited. + +## The generated API + +```python +import numpy as np +import prik_bspline.bspline_oo_module as bspline + +x = np.linspace(0.0, 2.0 * np.pi, 25) +spline = bspline.bspline_1d(x, np.sin(x), np.int32(4)) + +value, iflag = spline.evaluate(np.float64(1.234), np.int32(0)) +area, iflag = spline.integral(np.float64(0.0), np.float64(np.pi)) +``` + +`bspline_1d(x, fcn, kx)` is the Fortran `interface bspline_1d` constructor; +`bspline_1d()` is its empty overload. The abstract base is exported but cannot +be constructed: + +```python +bspline.bspline_class() +# TypeError: bspline_class is an abstract native type and cannot be +# instantiated; create one of its concrete extensions instead + +issubclass(bspline.bspline_1d, bspline.bspline_class) # True +``` + +## What is validated + +| Test file | Covers | +| --- | --- | +| `test_object_oriented_api.py` | Abstract base, inheritance, deferred bindings, generic constructors, 1D and 2D interpolation, derivatives, definite integrals | +| `test_procedural_api.py` | Public procedures, order constants, generic interfaces, exactness on a cubic, derivatives, integrals, SciPy comparison | + +Numerical checks use analytic values and `scipy.interpolate.make_interp_spline` +as independent oracles rather than trusting the wrapper as its own reference. + +## Scope and licence + +The upstream least-squares module and its BLAS bridge are outside this example; +the interpolation surface does not need them. +[`routine_inventory.py`](../../../examples/bspline/routine_inventory.py) records +the reviewed surface and that exclusion. + +BSPLINE-FORTRAN is by Jacob Williams under a BSD-3-Clause licence, included with +the vendored sources at version 7.4.0. diff --git a/docs/user/examples/index.md b/docs/user/examples/index.md index 702ec5d5c..777c6a071 100644 --- a/docs/user/examples/index.md +++ b/docs/user/examples/index.md @@ -9,8 +9,8 @@ publication: draft # Examples Gallery -This section includes checked recipes and four complete real-library examples: -BLAS, LAPACK, FFTPACK, and MINPACK. Each one provides build commands, Python +This section includes checked recipes and five complete real-library examples: +BLAS, LAPACK, FFTPACK, MINPACK, and BSPLINE-FORTRAN. Each one provides build commands, Python usage, and numerical checks for its public routines. Every page here is runnable. An example earns a place once it has source, a @@ -37,3 +37,4 @@ PRIK_C_DOCS_END --> | Build complete Reference LAPACK and validate 127 float64 routines | [LAPACK wrapper](lapack-wrapper.md) | | Wrap and validate all 31 FFTPACK procedures with NumPy and SciPy | [FFTPACK wrapper](fftpack-wrapper.md) | | Wrap all 22 MINPACK procedures and use Python callbacks | [MINPACK wrapper](minpack-wrapper.md) | +| Wrap modern Fortran classes over an abstract base | [BSPLINE-FORTRAN wrapper](bspline-wrapper.md) | diff --git a/docs/user/guide/wrapping-derived-types.md b/docs/user/guide/wrapping-derived-types.md index b6b93f745..a96cb16bb 100644 --- a/docs/user/guide/wrapping-derived-types.md +++ b/docs/user/guide/wrapping-derived-types.md @@ -224,6 +224,45 @@ print(points.point.__init__.__doc__) --- +## Which Constructor You Get + +The Fortran source decides which constructor the generated class publishes: + +| Source | Generated Python constructor | +| --- | --- | +| No user constructor | Keyword-field `__init__` over the public components | +| `interface ` present | Overloaded `__init__` from its specific functions | +| Edited `.pyi` | Exactly what the contract declares | + +An interface named for a derived type is that type's constructor, so its +specifics become the accepted signatures: + +```fortran +type, public :: box + integer(4) :: count = 0 + real(8) :: value = 0.0d0 +end type box + +interface box + module procedure box_empty, box_from_count, box_from_value +end interface box +``` + +```python +box() # box_empty +box(np.int32(7)) # box_from_count +box(np.float64(2.5)) # box_from_value +box("unsupported") # TypeError: no matching overload for __init__ +``` + +Each specific may be `private` in its module — the type name is public and +resolves to the same procedure, so the generated wrapper calls through it. + +When a constructor interface exists it replaces the keyword-field form, and the +generated contract states only the signatures the class actually accepts. + +--- + ## Custom Constructor The default constructor assigns public fields directly. If the native module @@ -432,6 +471,60 @@ and unlimited polymorphism (`class(*)`) are not supported. --- +## Abstract Types And Deferred Bindings + +A `type, abstract ::` declaration has no instances, so its Python class has no +constructor. Its extensions are ordinary Python subclasses, and a deferred +binding resolves through the object you actually hold. + +```fortran +type, public, abstract :: shape_base + private + integer(4) :: sides = 0 +contains + private + procedure(area_interface), deferred, public :: area + procedure, public, non_overridable :: side_count => shape_side_count +end type shape_base + +type, extends(shape_base), public :: circle + real(8) :: radius = 1.0d0 +contains + procedure, public :: area => circle_area +end type circle +``` + +```python +import numpy as np +import shapes.abstract_hierarchy as shapes + +shapes.shape_base() +# TypeError: shape_base is an abstract native type and cannot be instantiated; +# create one of its concrete extensions instead + +circle = shapes.circle(radius=np.float64(2.0)) +print(circle.area()) # 12.566370614 +print(circle.side_count()) # 0, from the abstract base +print(isinstance(circle, shapes.shape_base)) # True +``` + +The rules follow the Fortran declaration: + +| Fortran | Python | +| --- | --- | +| `type, abstract ::` | Class with no constructor; instantiating it raises `TypeError` | +| `type, extends(base) ::` | Subclass of the base's generated class | +| `procedure(iface), deferred ::` | Declared on the base, resolved by the object's own type | +| `procedure, non_overridable ::` | Ordinary inherited method | +| Component of an abstract type | Reached through the extension that inherits it | + +A deferred binding needs no Python-side dispatch: the generated adapter converts +the object's address to its own concrete type and lets Fortran resolve the +override. The same applies when a procedure takes `class(base)` — the boundary +is still limited to required scalar inputs, as above. + +--- + ## Type-Bound Generics A type-bound generic groups several concrete methods under one Python method. diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index e37783b70..95870ed12 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -90,7 +90,7 @@ PRIK_C_DOCS_END --> | --- | --- | --- | --- | --- | --- | | Fortran parse, semantic IR, and `.pyi` inspection | Supported | [Fortran inspection recipe](../examples/recipes/inspect-fortran-api.md), [semantic IR](../reference/semantic-ir.md) | [Fortran parser route](../../developer/codebase-map.md#cross-stage-hotspots) | [Fortran parser fixtures](../../../tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py), [Fortran semantic tests](../../../tests/fortran/semantic_ir/semantics/) | Inspection support does not by itself prove runtime wrapper support. | | Semantic `.pyi` wrapper builds from explicit native artifacts | Partially supported | [Semantic `.pyi` contracts](../examples/recipes/semantic-pyi-contracts.md), [`.pyi` format](../reference/semantic-pyi-format.md) | [`.pyi` build route](../../developer/architecture.md#build-architecture) | [format and authoritative-input tests](../../../tests/fortran/semantic_pyi_format/), [multi-source contract tests](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py), [native build plan tests](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py) | Current runtime parity is limited; source/generated/modified multi-source package parity is covered, and broader parity remains tracked in the checklist. | -| Scalar inheritance and polymorphic dispatch | Partially supported | [Inheritance and polymorphism](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class lowering route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py) | Polymorphic results, mutable dummies, arrays, allocatable/pointer scalars, and `class(*)` are blocked. | +| Scalar inheritance and polymorphic dispatch | Partially supported | [Inheritance and polymorphism](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class lowering route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py) | Abstract types wrap as non-instantiable Python base classes and deferred bindings resolve through the caller's concrete type. Polymorphic results, mutable dummies, arrays, allocatable/pointer scalars, and `class(*)` are blocked. | | Assumed-size, assumed-rank, and lower-bound array contracts | Partially supported | [Arrays](../guide/arrays.md) | [Array bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Assumed-rank tests](../../../tests/fortran/arrays/end_to_end/test_assumed_rank_arrays.py) | Assumed type and derived-type arrays remain blocked. Character arrays require fixed-width NumPy bytes dtype. | | Generated reference pages for modules, functions, and classes | Partially supported | [Reference index](../reference/index.md) | [Codebase map](../../developer/codebase-map.md) | [Documentation reference checks](../../../tests/docs/test_reference_and_codebase_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. | @@ -111,8 +111,8 @@ memory, or outlive its native storage. | Persistent callbacks and procedure pointers | Unsupported | [Callback limitations](../guide/callbacks.md#important-limitations) | [Callback route](../../developer/codebase-map.md#cross-stage-hotspots) | [Callback policy tests](../../../tests/fortran/callbacks/policy/test_callback_policy.py), [scalar callback tests](../../../tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py) | Callbacks are valid only during the wrapped call. | | Advanced multi-source dependency discovery and external-library integration | Unsupported | [Multiple source files](../guide/building-shared-library.md#multiple-source-files) | [Build orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Multi-source tests](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py) | prik does not infer dependency graphs, prebuilt module paths, or external library discovery. | | Blocked array forms | Unsupported | [Arrays](../guide/arrays.md) | [Array policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Array semantic tests](../../../tests/fortran/arrays/semantics/test_array_semantics.py), [diagnostics](../reference/diagnostic-codes.md) | Assumed type `type(*)`, arrays of derived types, and character arrays not representable as fixed-width bytes need missing runtime contracts. | -| Unsupported polymorphic forms | Unsupported | [Inheritance limits](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/codegen/test_class_surfaces.py) | Results, mutable dummies, arrays, polymorphic allocatable/pointer scalars, and `class(*)` are blocked. | -| Ambiguous or incomplete constructor overload sets | Unsupported | [Constructor limitations](../reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | [Constructor route](../../developer/codebase-map.md#cross-stage-hotspots) | [Constructor semantic tests](../../../tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py), [class-plan validation tests](../../../tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates must have distinguishable exact runtime signatures and compatible native-owner lifecycles. | +| Unsupported polymorphic forms | Unsupported | [Inheritance limits](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/codegen/test_class_surfaces.py) | Results, mutable dummies, arrays, polymorphic allocatable/pointer scalars, and `class(*)` are blocked. Abstract types and deferred bindings are supported. | +| Ambiguous or incomplete constructor overload sets | Unsupported | [Constructor limitations](../reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | [Constructor route](../../developer/codebase-map.md#cross-stage-hotspots) | [Constructor semantic tests](../../../tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py), [class-plan validation tests](../../../tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates must have distinguishable exact runtime signatures and compatible native-owner lifecycles. A Fortran `interface ` is wrapped as the type's overloaded constructor. | | Character arrays and caller-supplied deferred-length character storage | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character edge tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype, whose width each accessor reports from the Fortran declaration; Unicode/object arrays are unsupported. Scalar `character` `allocatable` and `pointer` values work for every intent and as function results. A mutable `pointer` dummy that the native procedure reassociates without deallocating orphans the target the adapter allocated for that call. A deferred-length `character(len=:), allocatable` module array does not build under GNU Fortran 11.4, which raises an internal compiler error on that declaration. | | Quad-precision real and complex storage | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | `real(16)` and `complex(16)` have no portable NumPy dtype, so prik blocks them rather than silently narrowing to 64-bit. Narrower real, complex, integer, and all logical kinds are supported. | diff --git a/examples/bspline/README.md b/examples/bspline/README.md new file mode 100644 index 000000000..7edd1ffb9 --- /dev/null +++ b/examples/bspline/README.md @@ -0,0 +1,109 @@ +# Wrap BSPLINE-FORTRAN with PRIK + +Build [BSPLINE-FORTRAN](https://github.com/jacobwilliams/bspline-fortran) with +PRIK and validate both of its public interfaces from Python: the +object-oriented classes and the procedural routines. + +This is the example that exercises PRIK's modern-Fortran surface. Unlike the +BLAS, LAPACK, FFTPACK, and MINPACK projects — which are FORTRAN 77 — this +library is written in Fortran 2008 and wraps **unmodified**: + +- an **abstract** derived type (`bspline_class`) with two **deferred** bindings; +- six concrete extensions that inherit from it; +- **generic constructors** declared as `interface bspline_1d`; +- **private components and private bindings** kept off the Python surface; +- generic procedure interfaces (`db1ink`, `db1val`) with several specifics. + +## Requirements + +Install GNU Fortran. On Ubuntu: + +```console +sudo apt-get update +sudo apt-get install --yes gfortran +``` + +Install the Python test tools. SciPy is optional; the comparison test skips +without it: + +```console +python3 -m pip install numpy pytest scipy +``` + +Run the remaining commands from the repository root. + +## Quick start + +```bash +source examples/bspline/build_all.sh +python3 -m pytest -q examples/bspline/tests -m real_library +``` + +Use `source` so the build paths exported by `build_all.sh` stay available to +the test process. + +## How the build works + +`build_prik.sh` passes the three interpolation sources to PRIK in dependency +order and builds one extension: + +```bash +python3 -m prik \ + examples/bspline/native/bspline_kinds_module.F90 \ + examples/bspline/native/bspline_sub_module.f90 \ + examples/bspline/native/bspline_oo_module.f90 \ + --out prik_bspline +``` + +No `.pyi` contract is written and no source is edited. The upstream files are +vendored byte-for-byte under `native/`. + +## The Python API + +```python +import numpy as np +import prik_bspline.bspline_oo_module as bspline + +x = np.linspace(0.0, 2.0 * np.pi, 25) +spline = bspline.bspline_1d(x, np.sin(x), np.int32(4)) # generic constructor + +value, iflag = spline.evaluate(np.float64(1.234), np.int32(0)) +print(value) # about 0.943811 + +area, iflag = spline.integral(np.float64(0.0), np.float64(np.pi)) +print(area) # about 2.0 +``` + +The abstract base is present but cannot be constructed: + +```python +bspline.bspline_class() +# TypeError: bspline_class is an abstract native type and cannot be +# instantiated; create one of its concrete extensions instead + +issubclass(bspline.bspline_1d, bspline.bspline_class) # True +``` + +## What is validated + +| Test file | Covers | +| --- | --- | +| `tests/test_object_oriented_api.py` | Abstract base, inheritance, deferred bindings, generic constructors, 1D/2D interpolation, derivatives, definite integrals | +| `tests/test_procedural_api.py` | Public procedures, order constants, generic interfaces, interpolation exactness on a cubic, derivatives, integrals, SciPy comparison | + +Numerical checks use independent oracles — analytic values, and +`scipy.interpolate.make_interp_spline` — rather than trusting the wrapper as +its own reference. + +## Scope + +The upstream `bspline_defc_module` (least-squares fitting) and its +`bspline_blas_module` bridge are not part of this example; the interpolation +surface does not need them. `routine_inventory.py` records the reviewed +surface and this exclusion. + +## Upstream + +BSPLINE-FORTRAN is by Jacob Williams and is distributed under a BSD-3-Clause +licence, included at `native/LICENSE`. The vendored sources are version 7.4.0 +(commit `047c7244`). diff --git a/examples/bspline/__init__.py b/examples/bspline/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/bspline/build_all.sh b/examples/bspline/build_all.sh new file mode 100644 index 000000000..59e783a5d --- /dev/null +++ b/examples/bspline/build_all.sh @@ -0,0 +1,3 @@ +source examples/bspline/build_prik.sh +cd "$EXAMPLE_WORKSPACE" +export PYTHONPATH="$BSPLINE_BUILD_ROOT/prik${PYTHONPATH:+:$PYTHONPATH}" diff --git a/examples/bspline/build_prik.sh b/examples/bspline/build_prik.sh new file mode 100644 index 000000000..47d75fb48 --- /dev/null +++ b/examples/bspline/build_prik.sh @@ -0,0 +1,16 @@ +export EXAMPLE_WORKSPACE="$PWD" +export BSPLINE_BUILD_ROOT="$(mktemp -d)" + +mkdir -p "$BSPLINE_BUILD_ROOT/prik/generated" +cd "$BSPLINE_BUILD_ROOT/prik" + +python3 -m prik \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_kinds_module.F90" \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_sub_module.f90" \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_oo_module.f90" \ + --out prik_bspline \ + --out-dir "$BSPLINE_BUILD_ROOT/prik/generated" \ + --compiler "$(command -v gfortran)" \ + --jobs 8 \ + --wrapper-fortran-flags="-O0 -g0" \ + --wrapper-c-flags="-O0 -g0" diff --git a/examples/bspline/conftest.py b/examples/bspline/conftest.py new file mode 100644 index 000000000..bf5c16cec --- /dev/null +++ b/examples/bspline/conftest.py @@ -0,0 +1,17 @@ +"""Import the BSPLINE-FORTRAN extension built by ``build_all.sh``.""" + +import importlib + +import pytest + + +@pytest.fixture(scope="session") +def bspline_oo(): + """Return the object-oriented B-spline namespace.""" + return importlib.import_module("prik_bspline").bspline_oo_module + + +@pytest.fixture(scope="session") +def bspline_sub(): + """Return the procedural B-spline namespace.""" + return importlib.import_module("prik_bspline").bspline_sub_module diff --git a/examples/bspline/native/LICENSE b/examples/bspline/native/LICENSE new file mode 100644 index 000000000..dc5bb75cd --- /dev/null +++ b/examples/bspline/native/LICENSE @@ -0,0 +1,125 @@ +BSPLINE-FORTRAN: Multidimensional B-Spline Interpolation of Data on a Regular Grid + +Copyright (c) 2015-2023, Jacob Williams +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +* The names of its contributors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +!----------------------------------------------------------------------------------------- +! +! BSPLINE-FORTRAN includes code from CMLIB, a public domain library +! from the National Institute of Standards and Technology (NIST) +! +! The CMLIB license is given below: +! +!----------------------------------------------------------------------------------------- + +The research software provided on this web site ("software") is provided by NIST as a +public service. You may use, copy and distribute copies of the software in any medium, +provided that you keep intact this entire notice. You may improve, modify and create +derivative works of the software or any portion of the software, and you may copy and +distribute such modifications or works. Modified works should carry a notice stating that +you changed the software and should note the date and nature of any such change. Please +explicitly acknowledge the National Institute of Standards and Technology as the source +of the software. + +The software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, +IMPLIED, IN FACT OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE +IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT +AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE +WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT +WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS +THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR +USEFULNESS OF THE SOFTWARE. + +You are solely responsible for determining the appropriateness of using and distributing +the software and you assume all risks associated with its use, including but not limited +to the risks and costs of program errors, compliance with applicable laws, damage to or +loss of data, programs or equipment, and the unavailability or interruption of operation. +This software is not intended to be used in any situation where a failure could cause risk +of injury or damage to property. The software was developed by NIST employees. NIST +employee contributions are not subject to copyright protection within the United States. + +!----------------------------------------------------------------------------------------- +! LAPACK License +!----------------------------------------------------------------------------------------- + +Copyright (c) 1992-2022 The University of Tennessee and The University + of Tennessee Research Foundation. All rights + reserved. +Copyright (c) 2000-2022 The University of California Berkeley. All + rights reserved. +Copyright (c) 2006-2022 The University of Colorado Denver. All rights + reserved. + +$COPYRIGHT$ + +Additional copyrights may follow + +$HEADER$ + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + +- Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +The copyright holders provide no reassurances that the source code +provided does not infringe any patent, copyright, or any other +intellectual property rights of third parties. The copyright holders +disclaim any liability to any recipient for claims brought against +recipient by any third party for infringement of that parties +intellectual property rights. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +!----------------------------------------------------------------------------------------- +! +! BSPLINE-FORTRAN includes code from the SLATEC Common Mathematical Library, +! A public domain work of the U.S. government. +! +! https://netlib.org/slatec/ +! +!----------------------------------------------------------------------------------------- diff --git a/examples/bspline/native/bspline_kinds_module.F90 b/examples/bspline/native/bspline_kinds_module.F90 new file mode 100644 index 000000000..9330acd19 --- /dev/null +++ b/examples/bspline/native/bspline_kinds_module.F90 @@ -0,0 +1,40 @@ +!***************************************************************************************** +!> author: Jacob Williams +! license: BSD +! +!### Description +! Numeric kind definitions for BSpline-Fortran. + + module bspline_kinds_module + + use,intrinsic :: iso_fortran_env + + implicit none + + private + +#ifdef REAL32 + integer,parameter,public :: wp = real32 !! Real working precision [4 bytes] +#elif REAL64 + integer,parameter,public :: wp = real64 !! Real working precision [8 bytes] +#elif REAL128 + integer,parameter,public :: wp = real128 !! Real working precision [16 bytes] +#else + integer,parameter,public :: wp = real64 !! Real working precision if not specified [8 bytes] +#endif + +#ifdef INT8 + integer,parameter,public :: ip = int8 !! Integer working precision [1 byte] +#elif INT16 + integer,parameter,public :: ip = int16 !! Integer working precision [2 bytes] +#elif INT32 + integer,parameter,public :: ip = int32 !! Integer working precision [4 bytes] +#elif INT64 + integer,parameter,public :: ip = int64 !! Integer working precision [8 bytes] +#else + integer,parameter,public :: ip = int32 !! Integer working precision if not specified [4 bytes] +#endif + +!***************************************************************************************** + end module bspline_kinds_module +!***************************************************************************************** diff --git a/examples/bspline/native/bspline_oo_module.f90 b/examples/bspline/native/bspline_oo_module.f90 new file mode 100644 index 000000000..0a7c57495 --- /dev/null +++ b/examples/bspline/native/bspline_oo_module.f90 @@ -0,0 +1,2823 @@ +!***************************************************************************************** +!> author: Jacob Williams +! license: BSD +! date: 12/6/2015 +! +! Object-oriented style wrappers to [[bspline_sub_module]]. +! This module provides classes ([[bspline_1d(type)]], [[bspline_2d(type)]], +! [[bspline_3d(type)]], [[bspline_4d(type)]], [[bspline_5d(type)]], and [[bspline_6d(type)]]) +! which can be used instead of the main subroutine interface. + + module bspline_oo_module + + use bspline_kinds_module, only: wp, ip + use,intrinsic :: iso_fortran_env, only: error_unit + use bspline_sub_module + + implicit none + + private + + integer(ip),parameter :: int_size = storage_size(1_ip,kind=ip) !! size of a default integer [bits] + integer(ip),parameter :: logical_size = storage_size(.true.,kind=ip) !! size of a default logical [bits] + integer(ip),parameter :: real_size = storage_size(1.0_wp,kind=ip) !! size of a `real(wp)` [bits] + + type,public,abstract :: bspline_class + !! Base class for the b-spline types + private + integer(ip) :: inbvx = 1_ip !! internal variable used by [[dbvalu]] for efficient processing + integer(ip) :: iflag = 1_ip !! saved `iflag` from the list routine call. + logical :: initialized = .false. !! true if the class is initialized and ready to use + logical :: extrap = .false. !! if true, then extrapolation is allowed during evaluation + contains + private + procedure,non_overridable :: destroy_base !! destructor for the abstract type + procedure,non_overridable :: set_extrap_flag !! internal routine to set the `extrap` flag + procedure(destroy_func),deferred,public :: destroy !! destructor + procedure(size_func),deferred,public :: size_of !! size of the structure in bits + procedure,public,non_overridable :: status_ok !! returns true if the last `iflag` status code was `=0`. + procedure,public,non_overridable :: status_message => get_bspline_status_message !! retrieve the last + !! status message + procedure,public,non_overridable :: clear_flag => clear_bspline_flag !! to reset the `iflag` saved in the class. + end type bspline_class + + abstract interface + + pure subroutine destroy_func(me) + !! interface for bspline destructor routines + import :: bspline_class + implicit none + class(bspline_class),intent(inout) :: me + end subroutine destroy_func + + pure function size_func(me) result(s) + !! interface for size routines + import :: bspline_class,ip + implicit none + class(bspline_class),intent(in) :: me + integer(ip) :: s !! size of the structure in bits + end function size_func + + end interface + + type,extends(bspline_class),public :: bspline_1d + !! Class for 1d b-spline interpolation. + !! + !!@note The 1D class also contains two methods + !! for computing definite integrals. + private + integer(ip) :: nx = 0_ip !! Number of \(x\) abcissae + integer(ip) :: kx = 0_ip !! The order of spline pieces in \(x\) + real(wp),dimension(:),allocatable :: bcoef !! array of coefficients of the b-spline interpolant + real(wp),dimension(:),allocatable :: tx !! The knots in the \(x\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: work_val_1 !! [[db1val] work array of dimension `3*kx` + contains + private + generic,public :: initialize => initialize_1d_auto_knots,initialize_1d_specify_knots + procedure :: initialize_1d_auto_knots + procedure :: initialize_1d_specify_knots + procedure,public :: evaluate => evaluate_1d + procedure,public :: destroy => destroy_1d + procedure,public :: size_of => size_1d + procedure,public :: integral => integral_1d + procedure,public :: fintegral => fintegral_1d + final :: finalize_1d + end type bspline_1d + + type,extends(bspline_class),public :: bspline_2d + !! Class for 2d b-spline interpolation. + private + integer(ip) :: nx = 0_ip !! Number of \(x\) abcissae + integer(ip) :: ny = 0_ip !! Number of \(y\) abcissae + integer(ip) :: kx = 0_ip !! The order of spline pieces in \(x\) + integer(ip) :: ky = 0_ip !! The order of spline pieces in \(y\) + real(wp),dimension(:,:),allocatable :: bcoef !! array of coefficients of the b-spline interpolant + real(wp),dimension(:),allocatable :: tx !! The knots in the \(x\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: ty !! The knots in the \(y\) direction for the spline interpolant + integer(ip) :: inbvy = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloy = 1_ip !! internal variable used for efficient processing + real(wp),dimension(:),allocatable :: work_val_1 !! [[db2val] work array of dimension `ky` + real(wp),dimension(:),allocatable :: work_val_2 !! [[db2val] work array of dimension `3_ip*max(kx,ky)` + contains + private + generic,public :: initialize => initialize_2d_auto_knots,initialize_2d_specify_knots + procedure :: initialize_2d_auto_knots + procedure :: initialize_2d_specify_knots + procedure,public :: evaluate => evaluate_2d + procedure,public :: destroy => destroy_2d + procedure,public :: size_of => size_2d + final :: finalize_2d + end type bspline_2d + + type,extends(bspline_class),public :: bspline_3d + !! Class for 3d b-spline interpolation. + private + integer(ip) :: nx = 0_ip !! Number of \(x\) abcissae + integer(ip) :: ny = 0_ip !! Number of \(y\) abcissae + integer(ip) :: nz = 0_ip !! Number of \(z\) abcissae + integer(ip) :: kx = 0_ip !! The order of spline pieces in \(x\) + integer(ip) :: ky = 0_ip !! The order of spline pieces in \(y\) + integer(ip) :: kz = 0_ip !! The order of spline pieces in \(z\) + real(wp),dimension(:,:,:),allocatable :: bcoef !! array of coefficients of the b-spline interpolant + real(wp),dimension(:),allocatable :: tx !! The knots in the \(x\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: ty !! The knots in the \(y\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tz !! The knots in the \(z\) direction for the spline interpolant + integer(ip) :: inbvy = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvz = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloy = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloz = 1_ip !! internal variable used for efficient processing + real(wp),dimension(:,:),allocatable :: work_val_1 !! [[db3val] work array of dimension `ky,kz` + real(wp),dimension(:),allocatable :: work_val_2 !! [[db3val] work array of dimension `kz` + real(wp),dimension(:),allocatable :: work_val_3 !! [[db3val] work array of dimension `3_ip*max(kx,ky,kz)` + contains + private + generic,public :: initialize => initialize_3d_auto_knots,initialize_3d_specify_knots + procedure :: initialize_3d_auto_knots + procedure :: initialize_3d_specify_knots + procedure,public :: evaluate => evaluate_3d + procedure,public :: destroy => destroy_3d + procedure,public :: size_of => size_3d + final :: finalize_3d + end type bspline_3d + + type,extends(bspline_class),public :: bspline_4d + !! Class for 4d b-spline interpolation. + private + integer(ip) :: nx = 0_ip !! Number of \(x\) abcissae + integer(ip) :: ny = 0_ip !! Number of \(y\) abcissae + integer(ip) :: nz = 0_ip !! Number of \(z\) abcissae + integer(ip) :: nq = 0_ip !! Number of \(q\) abcissae + integer(ip) :: kx = 0_ip !! The order of spline pieces in \(x\) + integer(ip) :: ky = 0_ip !! The order of spline pieces in \(y\) + integer(ip) :: kz = 0_ip !! The order of spline pieces in \(z\) + integer(ip) :: kq = 0_ip !! The order of spline pieces in \(q\) + real(wp),dimension(:,:,:,:),allocatable :: bcoef !! array of coefficients of the b-spline interpolant + real(wp),dimension(:),allocatable :: tx !! The knots in the \(x\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: ty !! The knots in the \(y\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tz !! The knots in the \(z\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tq !! The knots in the \(q\) direction for the spline interpolant + integer(ip) :: inbvy = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvz = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvq = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloy = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloz = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloq = 1_ip !! internal variable used for efficient processing + real(wp),dimension(:,:,:),allocatable :: work_val_1 !! [[db4val]] work array of dimension `ky,kz,kq` + real(wp),dimension(:,:),allocatable :: work_val_2 !! [[db4val]] work array of dimension `kz,kq` + real(wp),dimension(:),allocatable :: work_val_3 !! [[db4val]] work array of dimension `kq` + real(wp),dimension(:),allocatable :: work_val_4 !! [[db4val]] work array of dimension `3_ip*max(kx,ky,kz,kq)` + contains + private + generic,public :: initialize => initialize_4d_auto_knots,initialize_4d_specify_knots + procedure :: initialize_4d_auto_knots + procedure :: initialize_4d_specify_knots + procedure,public :: evaluate => evaluate_4d + procedure,public :: destroy => destroy_4d + procedure,public :: size_of => size_4d + final :: finalize_4d + end type bspline_4d + + type,extends(bspline_class),public :: bspline_5d + !! Class for 5d b-spline interpolation. + private + integer(ip) :: nx = 0_ip !! Number of \(x\) abcissae + integer(ip) :: ny = 0_ip !! Number of \(y\) abcissae + integer(ip) :: nz = 0_ip !! Number of \(z\) abcissae + integer(ip) :: nq = 0_ip !! Number of \(q\) abcissae + integer(ip) :: nr = 0_ip !! Number of \(r\) abcissae + integer(ip) :: kx = 0_ip !! The order of spline pieces in \(x\) + integer(ip) :: ky = 0_ip !! The order of spline pieces in \(y\) + integer(ip) :: kz = 0_ip !! The order of spline pieces in \(z\) + integer(ip) :: kq = 0_ip !! The order of spline pieces in \(q\) + integer(ip) :: kr = 0_ip !! The order of spline pieces in \(r\) + real(wp),dimension(:,:,:,:,:),allocatable :: bcoef !! array of coefficients of the b-spline interpolant + real(wp),dimension(:),allocatable :: tx !! The knots in the \(x\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: ty !! The knots in the \(y\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tz !! The knots in the \(z\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tq !! The knots in the \(q\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tr !! The knots in the \(r\) direction for the spline interpolant + integer(ip) :: inbvy = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvz = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvq = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvr = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloy = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloz = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloq = 1_ip !! internal variable used for efficient processing + integer(ip) :: ilor = 1_ip !! internal variable used for efficient processing + real(wp),dimension(:,:,:,:),allocatable :: work_val_1 !! [[db5val]] work array of dimension `ky,kz,kq,kr` + real(wp),dimension(:,:,:),allocatable :: work_val_2 !! [[db5val]] work array of dimension `kz,kq,kr` + real(wp),dimension(:,:),allocatable :: work_val_3 !! [[db5val]] work array of dimension `kq,kr` + real(wp),dimension(:),allocatable :: work_val_4 !! [[db5val]] work array of dimension `kr` + real(wp),dimension(:),allocatable :: work_val_5 !! [[db5val]] work array of dimension `3_ip*max(kx,ky,kz,kq,kr)` + contains + private + generic,public :: initialize => initialize_5d_auto_knots,initialize_5d_specify_knots + procedure :: initialize_5d_auto_knots + procedure :: initialize_5d_specify_knots + procedure,public :: evaluate => evaluate_5d + procedure,public :: destroy => destroy_5d + procedure,public :: size_of => size_5d + final :: finalize_5d + end type bspline_5d + + type,extends(bspline_class),public :: bspline_6d + !! Class for 6d b-spline interpolation. + private + integer(ip) :: nx = 0_ip !! Number of \(x\) abcissae + integer(ip) :: ny = 0_ip !! Number of \(y\) abcissae + integer(ip) :: nz = 0_ip !! Number of \(z\) abcissae + integer(ip) :: nq = 0_ip !! Number of \(q\) abcissae + integer(ip) :: nr = 0_ip !! Number of \(r\) abcissae + integer(ip) :: ns = 0_ip !! Number of \(s\) abcissae + integer(ip) :: kx = 0_ip !! The order of spline pieces in \(x\) + integer(ip) :: ky = 0_ip !! The order of spline pieces in \(y\) + integer(ip) :: kz = 0_ip !! The order of spline pieces in \(z\) + integer(ip) :: kq = 0_ip !! The order of spline pieces in \(q\) + integer(ip) :: kr = 0_ip !! The order of spline pieces in \(r\) + integer(ip) :: ks = 0_ip !! The order of spline pieces in \(s\) + real(wp),dimension(:,:,:,:,:,:),allocatable :: bcoef !! array of coefficients of the b-spline interpolant + real(wp),dimension(:),allocatable :: tx !! The knots in the \(x\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: ty !! The knots in the \(y\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tz !! The knots in the \(z\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tq !! The knots in the \(q\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: tr !! The knots in the \(r\) direction for the spline interpolant + real(wp),dimension(:),allocatable :: ts !! The knots in the \(s\) direction for the spline interpolant + integer(ip) :: inbvy = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvz = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvq = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvr = 1_ip !! internal variable used for efficient processing + integer(ip) :: inbvs = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloy = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloz = 1_ip !! internal variable used for efficient processing + integer(ip) :: iloq = 1_ip !! internal variable used for efficient processing + integer(ip) :: ilor = 1_ip !! internal variable used for efficient processing + integer(ip) :: ilos = 1_ip !! internal variable used for efficient processing + real(wp),dimension(:,:,:,:,:),allocatable :: work_val_1 !! [[db6val]] work array of dimension `ky,kz,kq,kr,ks` + real(wp),dimension(:,:,:,:),allocatable :: work_val_2 !! [[db6val]] work array of dimension `kz,kq,kr,ks` + real(wp),dimension(:,:,:),allocatable :: work_val_3 !! [[db6val]] work array of dimension `kq,kr,ks` + real(wp),dimension(:,:),allocatable :: work_val_4 !! [[db6val]] work array of dimension `kr,ks` + real(wp),dimension(:),allocatable :: work_val_5 !! [[db6val]] work array of dimension `ks` + real(wp),dimension(:),allocatable :: work_val_6 !! [[db6val]] work array of dimension `3_ip*max(kx,ky,kz,kq,kr,ks)` + contains + private + generic,public :: initialize => initialize_6d_auto_knots,initialize_6d_specify_knots + procedure :: initialize_6d_auto_knots + procedure :: initialize_6d_specify_knots + procedure,public :: evaluate => evaluate_6d + procedure,public :: destroy => destroy_6d + procedure,public :: size_of => size_6d + final :: finalize_6d + end type bspline_6d + + interface bspline_1d + !! Constructor for [[bspline_1d(type)]] + procedure :: bspline_1d_constructor_empty,& + bspline_1d_constructor_auto_knots,& + bspline_1d_constructor_specify_knots + end interface + interface bspline_2d + !! Constructor for [[bspline_2d(type)]] + procedure :: bspline_2d_constructor_empty,& + bspline_2d_constructor_auto_knots,& + bspline_2d_constructor_specify_knots + end interface + interface bspline_3d + !! Constructor for [[bspline_3d(type)]] + procedure :: bspline_3d_constructor_empty,& + bspline_3d_constructor_auto_knots,& + bspline_3d_constructor_specify_knots + end interface + interface bspline_4d + !! Constructor for [[bspline_4d(type)]] + procedure :: bspline_4d_constructor_empty,& + bspline_4d_constructor_auto_knots,& + bspline_4d_constructor_specify_knots + end interface + interface bspline_5d + !! Constructor for [[bspline_5d(type)]] + procedure :: bspline_5d_constructor_empty,& + bspline_5d_constructor_auto_knots,& + bspline_5d_constructor_specify_knots + end interface + interface bspline_6d + !! Constructor for [[bspline_6d(type)]] + procedure :: bspline_6d_constructor_empty,& + bspline_6d_constructor_auto_knots,& + bspline_6d_constructor_specify_knots + end interface + + contains +!***************************************************************************************** + +!***************************************************************************************** +!> +! This routines returns true if the `iflag` code from the last +! routine called was `=0`. Maybe of the routines have output `iflag` +! variables, so they can be checked explicitly, or this routine +! can be used. +! +! If the class is initialized using a function constructor, then +! this is the only way to know if it was properly initialized, +! since those are pure functions with not output `iflag` arguments. +! +! If `status_ok=.false.`, then the error message can be +! obtained from the [[get_bspline_status_message]] routine. +! +! Note: after an error condition, the [[clear_bspline_flag]] routine +! can be called to reset the `iflag` to 0. + + elemental function status_ok(me) result(ok) + + implicit none + + class(bspline_class),intent(in) :: me + logical :: ok + + ok = ( me%iflag == 0_ip ) + + end function status_ok +!***************************************************************************************** + +!***************************************************************************************** +!> +! This sets the `iflag` variable in the class to `0` +! (which indicates that everything is OK). It can be used +! after an error is encountered. + + elemental subroutine clear_bspline_flag(me) + + implicit none + + class(bspline_class),intent(inout) :: me + + me%iflag = 0_ip + + end subroutine clear_bspline_flag +!***************************************************************************************** + +!***************************************************************************************** +!> +! Get the status message from a [[bspline_class]] routine call. +! +! If `iflag` is not included, then the one in the class is used (which +! corresponds to the last routine called.) +! Otherwise, it will convert the +! input `iflag` argument into the appropriate message. +! +! This is a wrapper for [[get_status_message]]. + + pure function get_bspline_status_message(me,iflag) result(msg) + + implicit none + + class(bspline_class),intent(in) :: me + character(len=:),allocatable :: msg !! status message associated with the flag + integer(ip),intent(in),optional :: iflag !! the corresponding status code + + if (present(iflag)) then + msg = get_status_message(iflag) + else + msg = get_status_message(me%iflag) + end if + + end function get_bspline_status_message +!***************************************************************************************** + +!***************************************************************************************** +!> +! Actual size of a [[bspline_1d]] structure in bits. + + pure function size_1d(me) result(s) + + implicit none + + class(bspline_1d),intent(in) :: me + integer(ip) :: s !! size of the structure in bits + + s = 2_ip*int_size + logical_size + 2_ip*int_size + + if (allocated(me%bcoef)) s = s + real_size*size(me%bcoef,kind=ip) + if (allocated(me%tx)) s = s + real_size*size(me%tx,kind=ip) + if (allocated(me%work_val_1)) s = s + real_size*size(me%work_val_1,kind=ip) + + end function size_1d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Actual size of a [[bspline_2d]] structure in bits. + + pure function size_2d(me) result(s) + + implicit none + + class(bspline_2d),intent(in) :: me + integer(ip) :: s !! size of the structure in bits + + s = 2_ip*int_size + logical_size + 6_ip*int_size + + if (allocated(me%bcoef)) s = s + real_size*size(me%bcoef,1_ip,kind=ip)*& + size(me%bcoef,2_ip,kind=ip) + if (allocated(me%tx)) s = s + real_size*size(me%tx,kind=ip) + if (allocated(me%ty)) s = s + real_size*size(me%ty,kind=ip) + if (allocated(me%work_val_1)) s = s + real_size*size(me%work_val_1,kind=ip) + if (allocated(me%work_val_2)) s = s + real_size*size(me%work_val_2,kind=ip) + + end function size_2d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Actual size of a [[bspline_3d]] structure in bits. + + pure function size_3d(me) result(s) + + implicit none + + class(bspline_3d),intent(in) :: me + integer(ip) :: s !! size of the structure in bits + + s = 2_ip*int_size + logical_size + 10_ip*int_size + + if (allocated(me%bcoef)) s = s + real_size*size(me%bcoef,1_ip,kind=ip)*& + size(me%bcoef,2_ip,kind=ip)*& + size(me%bcoef,3_ip,kind=ip) + if (allocated(me%tx)) s = s + real_size*size(me%tx,kind=ip) + if (allocated(me%ty)) s = s + real_size*size(me%ty,kind=ip) + if (allocated(me%tz)) s = s + real_size*size(me%tz,kind=ip) + if (allocated(me%work_val_1)) s = s + real_size*size(me%work_val_1,1_ip,kind=ip)*& + size(me%work_val_1,2_ip,kind=ip) + if (allocated(me%work_val_2)) s = s + real_size*size(me%work_val_2,kind=ip) + if (allocated(me%work_val_3)) s = s + real_size*size(me%work_val_3,kind=ip) + + end function size_3d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Actual size of a [[bspline_4d]] structure in bits. + + pure function size_4d(me) result(s) + + implicit none + + class(bspline_4d),intent(in) :: me + integer(ip) :: s !! size of the structure in bits + + s = 2_ip*int_size + logical_size + 14_ip*int_size + + if (allocated(me%bcoef)) s = s + real_size*size(me%bcoef,1_ip,kind=ip)*& + size(me%bcoef,2_ip,kind=ip)*& + size(me%bcoef,3_ip,kind=ip)*& + size(me%bcoef,4_ip,kind=ip) + if (allocated(me%tx)) s = s + real_size*size(me%tx,kind=ip) + if (allocated(me%ty)) s = s + real_size*size(me%ty,kind=ip) + if (allocated(me%tz)) s = s + real_size*size(me%tz,kind=ip) + if (allocated(me%tq)) s = s + real_size*size(me%tq,kind=ip) + if (allocated(me%work_val_1)) s = s + real_size*size(me%work_val_1,1_ip,kind=ip)*& + size(me%work_val_1,2_ip,kind=ip)*& + size(me%work_val_1,3_ip,kind=ip) + if (allocated(me%work_val_2)) s = s + real_size*size(me%work_val_2,1_ip,kind=ip)*& + size(me%work_val_2,2_ip,kind=ip) + if (allocated(me%work_val_3)) s = s + real_size*size(me%work_val_3,kind=ip) + if (allocated(me%work_val_4)) s = s + real_size*size(me%work_val_4,kind=ip) + + end function size_4d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Actual size of a [[bspline_5d]] structure in bits. + + pure function size_5d(me) result(s) + + implicit none + + class(bspline_5d),intent(in) :: me + integer(ip) :: s !! size of the structure in bits + + s = 2_ip*int_size + logical_size + 18_ip*int_size + + if (allocated(me%bcoef)) s = s + real_size*size(me%bcoef,1_ip,kind=ip)*& + size(me%bcoef,2_ip,kind=ip)*& + size(me%bcoef,3_ip,kind=ip)*& + size(me%bcoef,4_ip,kind=ip)*& + size(me%bcoef,5_ip,kind=ip) + if (allocated(me%tx)) s = s + real_size*size(me%tx,kind=ip) + if (allocated(me%ty)) s = s + real_size*size(me%ty,kind=ip) + if (allocated(me%tz)) s = s + real_size*size(me%tz,kind=ip) + if (allocated(me%tq)) s = s + real_size*size(me%tq,kind=ip) + if (allocated(me%tr)) s = s + real_size*size(me%tr,kind=ip) + if (allocated(me%work_val_1)) s = s + real_size*size(me%work_val_1,1_ip,kind=ip)*& + size(me%work_val_1,2_ip,kind=ip)*& + size(me%work_val_1,3_ip,kind=ip)*& + size(me%work_val_1,4_ip,kind=ip) + if (allocated(me%work_val_2)) s = s + real_size*size(me%work_val_2,1_ip,kind=ip)*& + size(me%work_val_2,2_ip,kind=ip)*& + size(me%work_val_2,3_ip,kind=ip) + if (allocated(me%work_val_3)) s = s + real_size*size(me%work_val_3,1_ip,kind=ip)*& + size(me%work_val_3,2_ip,kind=ip) + if (allocated(me%work_val_4)) s = s + real_size*size(me%work_val_4,kind=ip) + if (allocated(me%work_val_5)) s = s + real_size*size(me%work_val_5,kind=ip) + + end function size_5d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Actual size of a [[bspline_6d]] structure in bits. + + pure function size_6d(me) result(s) + + implicit none + + class(bspline_6d),intent(in) :: me + integer(ip) :: s !! size of the structure in bits + + s = 2_ip*int_size + logical_size + 22_ip*int_size + + if (allocated(me%bcoef)) s = s + real_size*size(me%bcoef,1_ip,kind=ip)*& + size(me%bcoef,2_ip,kind=ip)*& + size(me%bcoef,3_ip,kind=ip)*& + size(me%bcoef,4_ip,kind=ip)*& + size(me%bcoef,5_ip,kind=ip)*& + size(me%bcoef,6,kind=ip) + if (allocated(me%tx)) s = s + real_size*size(me%tx,kind=ip) + if (allocated(me%ty)) s = s + real_size*size(me%ty,kind=ip) + if (allocated(me%tz)) s = s + real_size*size(me%tz,kind=ip) + if (allocated(me%tq)) s = s + real_size*size(me%tq,kind=ip) + if (allocated(me%tr)) s = s + real_size*size(me%tr,kind=ip) + if (allocated(me%ts)) s = s + real_size*size(me%ts,kind=ip) + if (allocated(me%work_val_1)) s = s + real_size*size(me%work_val_1,1_ip,kind=ip)*& + size(me%work_val_1,2_ip,kind=ip)*& + size(me%work_val_1,3_ip,kind=ip)*& + size(me%work_val_1,4_ip,kind=ip)*& + size(me%work_val_1,5_ip,kind=ip) + if (allocated(me%work_val_2)) s = s + real_size*size(me%work_val_2,1_ip,kind=ip)*& + size(me%work_val_2,2_ip,kind=ip)*& + size(me%work_val_2,3_ip,kind=ip)*& + size(me%work_val_2,4_ip,kind=ip) + if (allocated(me%work_val_3)) s = s + real_size*size(me%work_val_3,1_ip,kind=ip)*& + size(me%work_val_3,2_ip,kind=ip)*& + size(me%work_val_3,3_ip,kind=ip) + if (allocated(me%work_val_4)) s = s + real_size*size(me%work_val_4,1_ip,kind=ip)*& + size(me%work_val_4,2_ip,kind=ip) + if (allocated(me%work_val_5)) s = s + real_size*size(me%work_val_5,kind=ip) + if (allocated(me%work_val_6)) s = s + real_size*size(me%work_val_6,kind=ip) + + end function size_6d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Destructor for contents of the base [[bspline_class]] class. +! (this routine is called by the extended classes). + + pure subroutine destroy_base(me) + + implicit none + + class(bspline_class),intent(inout) :: me + + me%inbvx = 1_ip + me%iflag = 1_ip + me%initialized = .false. + me%extrap = .false. + + end subroutine destroy_base +!***************************************************************************************** + +!***************************************************************************************** +!> +! Destructor for [[bspline_1d]] class. + + pure subroutine destroy_1d(me) + + implicit none + + class(bspline_1d),intent(inout) :: me + + call me%destroy_base() + + me%nx = 0_ip + me%kx = 0_ip + if (allocated(me%bcoef)) deallocate(me%bcoef) + if (allocated(me%tx)) deallocate(me%tx) + if (allocated(me%work_val_1)) deallocate(me%work_val_1) + + end subroutine destroy_1d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Destructor for [[bspline_2d]] class. + + pure subroutine destroy_2d(me) + + implicit none + + class(bspline_2d),intent(inout) :: me + + call me%destroy_base() + + me%nx = 0_ip + me%ny = 0_ip + me%kx = 0_ip + me%ky = 0_ip + me%inbvy = 1_ip + me%iloy = 1_ip + if (allocated(me%bcoef)) deallocate(me%bcoef) + if (allocated(me%tx)) deallocate(me%tx) + if (allocated(me%ty)) deallocate(me%ty) + if (allocated(me%work_val_1)) deallocate(me%work_val_1) + if (allocated(me%work_val_2)) deallocate(me%work_val_2) + + end subroutine destroy_2d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Destructor for [[bspline_3d]] class. + + pure subroutine destroy_3d(me) + + implicit none + + class(bspline_3d),intent(inout) :: me + + call me%destroy_base() + + me%nx = 0_ip + me%ny = 0_ip + me%nz = 0_ip + me%kx = 0_ip + me%ky = 0_ip + me%kz = 0_ip + me%inbvy = 1_ip + me%inbvz = 1_ip + me%iloy = 1_ip + me%iloz = 1_ip + if (allocated(me%bcoef)) deallocate(me%bcoef) + if (allocated(me%tx)) deallocate(me%tx) + if (allocated(me%ty)) deallocate(me%ty) + if (allocated(me%tz)) deallocate(me%tz) + if (allocated(me%work_val_1)) deallocate(me%work_val_1) + if (allocated(me%work_val_2)) deallocate(me%work_val_2) + if (allocated(me%work_val_3)) deallocate(me%work_val_3) + + end subroutine destroy_3d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Destructor for [[bspline_4d]] class. + + pure subroutine destroy_4d(me) + + implicit none + + class(bspline_4d),intent(inout) :: me + + me%nx = 0_ip + me%ny = 0_ip + me%nz = 0_ip + me%nq = 0_ip + me%kx = 0_ip + me%ky = 0_ip + me%kz = 0_ip + me%kq = 0_ip + me%inbvy = 1_ip + me%inbvz = 1_ip + me%inbvq = 1_ip + me%iloy = 1_ip + me%iloz = 1_ip + me%iloq = 1_ip + if (allocated(me%bcoef)) deallocate(me%bcoef) + if (allocated(me%tx)) deallocate(me%tx) + if (allocated(me%ty)) deallocate(me%ty) + if (allocated(me%tz)) deallocate(me%tz) + if (allocated(me%tq)) deallocate(me%tq) + if (allocated(me%work_val_1)) deallocate(me%work_val_1) + if (allocated(me%work_val_2)) deallocate(me%work_val_2) + if (allocated(me%work_val_3)) deallocate(me%work_val_3) + if (allocated(me%work_val_4)) deallocate(me%work_val_4) + + end subroutine destroy_4d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Destructor for [[bspline_5d]] class. + + pure subroutine destroy_5d(me) + + implicit none + + class(bspline_5d),intent(inout) :: me + + me%nx = 0_ip + me%ny = 0_ip + me%nz = 0_ip + me%nq = 0_ip + me%nr = 0_ip + me%kx = 0_ip + me%ky = 0_ip + me%kz = 0_ip + me%kq = 0_ip + me%kr = 0_ip + me%inbvy = 1_ip + me%inbvz = 1_ip + me%inbvq = 1_ip + me%inbvr = 1_ip + me%iloy = 1_ip + me%iloz = 1_ip + me%iloq = 1_ip + me%ilor = 1_ip + if (allocated(me%bcoef)) deallocate(me%bcoef) + if (allocated(me%tx)) deallocate(me%tx) + if (allocated(me%ty)) deallocate(me%ty) + if (allocated(me%tz)) deallocate(me%tz) + if (allocated(me%tq)) deallocate(me%tq) + if (allocated(me%tr)) deallocate(me%tr) + if (allocated(me%work_val_1)) deallocate(me%work_val_1) + if (allocated(me%work_val_2)) deallocate(me%work_val_2) + if (allocated(me%work_val_3)) deallocate(me%work_val_3) + if (allocated(me%work_val_4)) deallocate(me%work_val_4) + if (allocated(me%work_val_5)) deallocate(me%work_val_5) + + end subroutine destroy_5d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Destructor for [[bspline_6d]] class. + + pure subroutine destroy_6d(me) + + implicit none + + class(bspline_6d),intent(inout) :: me + + me%nx = 0_ip + me%ny = 0_ip + me%nz = 0_ip + me%nq = 0_ip + me%nr = 0_ip + me%ns = 0_ip + me%kx = 0_ip + me%ky = 0_ip + me%kz = 0_ip + me%kq = 0_ip + me%kr = 0_ip + me%ks = 0_ip + me%inbvy = 1_ip + me%inbvz = 1_ip + me%inbvq = 1_ip + me%inbvr = 1_ip + me%inbvs = 1_ip + me%iloy = 1_ip + me%iloz = 1_ip + me%iloq = 1_ip + me%ilor = 1_ip + me%ilos = 1_ip + if (allocated(me%bcoef)) deallocate(me%bcoef) + if (allocated(me%tx)) deallocate(me%tx) + if (allocated(me%ty)) deallocate(me%ty) + if (allocated(me%tz)) deallocate(me%tz) + if (allocated(me%tq)) deallocate(me%tq) + if (allocated(me%tr)) deallocate(me%tr) + if (allocated(me%ts)) deallocate(me%ts) + if (allocated(me%work_val_1)) deallocate(me%work_val_1) + if (allocated(me%work_val_2)) deallocate(me%work_val_2) + if (allocated(me%work_val_3)) deallocate(me%work_val_3) + if (allocated(me%work_val_4)) deallocate(me%work_val_4) + if (allocated(me%work_val_5)) deallocate(me%work_val_5) + if (allocated(me%work_val_6)) deallocate(me%work_val_6) + + end subroutine destroy_6d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Finalizer for [[bspline_1d]] class. Just a wrapper for [[destroy_1d]]. + pure elemental subroutine finalize_1d(me) + type(bspline_1d),intent(inout) :: me; call me%destroy() + end subroutine finalize_1d +!***************************************************************************************** +!***************************************************************************************** +!> +! Finalizer for [[bspline_2d]] class. Just a wrapper for [[destroy_2d]]. + pure elemental subroutine finalize_2d(me) + type(bspline_2d),intent(inout) :: me; call me%destroy() + end subroutine finalize_2d +!***************************************************************************************** +!***************************************************************************************** +!> +! Finalizer for [[bspline_3d]] class. Just a wrapper for [[destroy_3d]]. + pure elemental subroutine finalize_3d(me) + type(bspline_3d),intent(inout) :: me; call me%destroy() + end subroutine finalize_3d +!***************************************************************************************** +!***************************************************************************************** +!> +! Finalizer for [[bspline_4d]] class. Just a wrapper for [[destroy_4d]]. + pure elemental subroutine finalize_4d(me) + type(bspline_4d),intent(inout) :: me; call me%destroy() + end subroutine finalize_4d +!***************************************************************************************** +!***************************************************************************************** +!> +! Finalizer for [[bspline_5d]] class. Just a wrapper for [[destroy_5d]]. + pure elemental subroutine finalize_5d(me) + type(bspline_5d),intent(inout) :: me; call me%destroy() + end subroutine finalize_5d +!***************************************************************************************** +!***************************************************************************************** +!> +! Finalizer for [[bspline_6d]] class. Just a wrapper for [[destroy_6d]]. + pure elemental subroutine finalize_6d(me) + type(bspline_6d),intent(inout) :: me; call me%destroy() + end subroutine finalize_6d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Sets the `extrap` flag in the class. + + pure subroutine set_extrap_flag(me,extrap) + + implicit none + + class(bspline_class),intent(inout) :: me + logical,intent(in),optional :: extrap !! if not present, then False is used + + if (present(extrap)) then + me%extrap = extrap + else + me%extrap = .false. + end if + + end subroutine set_extrap_flag +!***************************************************************************************** + +!***************************************************************************************** +!> +! It returns an empty [[bspline_1d]] type. Note that INITIALIZE still +! needs to be called before it can be used. +! Not really that useful except perhaps in some OpenMP applications. + + pure elemental function bspline_1d_constructor_empty() result(me) + + implicit none + + type(bspline_1d) :: me + + end function bspline_1d_constructor_empty +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_1d]] type (auto knots). +! This is a wrapper for [[initialize_1d_auto_knots]]. + + pure function bspline_1d_constructor_auto_knots(x,fcn,kx,extrap) result(me) + + implicit none + + type(bspline_1d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: fcn !! `(nx)` array of function values to interpolate. `fcn(i)` should + !! contain the function value at the point `x(i)` + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_1d_auto_knots(me,x,fcn,kx,me%iflag,extrap) + + end function bspline_1d_constructor_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_1d]] type (user-specified knots). +! This is a wrapper for [[initialize_1d_specify_knots]]. + + pure function bspline_1d_constructor_specify_knots(x,fcn,kx,tx,extrap) result(me) + + implicit none + + type(bspline_1d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: fcn !! `(nx)` array of function values to interpolate. `fcn(i)` should + !! contain the function value at the point `x(i)` + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_1d_specify_knots(me,x,fcn,kx,tx,me%iflag,extrap) + + end function bspline_1d_constructor_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_1d]] type (with automatically-computed knots). +! This is a wrapper for [[db1ink]]. + + pure subroutine initialize_1d_auto_knots(me,x,fcn,kx,iflag,extrap) + + implicit none + + class(bspline_1d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: fcn !! `(nx)` array of function values to interpolate. `fcn(i)` should + !! contain the function value at the point `x(i)` + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(out) :: iflag !! status flag (see [[db1ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: iknot + integer(ip) :: nx + + call me%destroy() + + nx = size(x,kind=ip) + + me%nx = nx + me%kx = kx + + allocate(me%tx(nx+kx)) + allocate(me%bcoef(nx)) + allocate(me%work_val_1(3_ip*kx)) + + iknot = 0_ip !knot sequence chosen by db1ink + + call db1ink(x,nx,fcn,kx,iknot,me%tx,me%bcoef,iflag) + + if (iflag==0_ip) then + call me%set_extrap_flag(extrap) + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_1d_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_1d]] type (with user-specified knots). +! This is a wrapper for [[db1ink]]. + + pure subroutine initialize_1d_specify_knots(me,x,fcn,kx,tx,iflag,extrap) + + implicit none + + class(bspline_1d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: fcn !! `(nx)` array of function values to interpolate. `fcn(i)` should + !! contain the function value at the point `x(i)` + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + integer(ip),intent(out) :: iflag !! status flag (see [[db1ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: nx + + call me%destroy() + + nx = size(x,kind=ip) + + call check_knot_vectors_sizes(nx=nx,kx=kx,tx=tx,iflag=iflag) + + if (iflag == 0_ip) then + + me%nx = nx + me%kx = kx + + allocate(me%tx(nx+kx)) + allocate(me%bcoef(nx)) + allocate(me%work_val_1(3_ip*kx)) + + me%tx = tx + + call db1ink(x,nx,fcn,kx,1_ip,me%tx,me%bcoef,iflag) + + call me%set_extrap_flag(extrap) + + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_1d_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_1d]] interpolate. This is a wrapper for [[db1val]]. + + pure subroutine evaluate_1d(me,xval,idx,f,iflag) + + implicit none + + class(bspline_1d),intent(inout) :: me + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag (see [[db1val]]) + + if (me%initialized) then + call db1val(xval,idx,me%tx,me%nx,me%kx,me%bcoef,f,iflag,& + me%inbvx,me%work_val_1,extrap=me%extrap) + else + iflag = 1_ip + end if + me%iflag = iflag + + end subroutine evaluate_1d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_1d]] definite integral. This is a wrapper for [[db1sqad]]. + + pure subroutine integral_1d(me,x1,x2,f,iflag) + + implicit none + + class(bspline_1d),intent(inout) :: me + real(wp),intent(in) :: x1 !! left point of interval + real(wp),intent(in) :: x2 !! right point of interval + real(wp),intent(out) :: f !! integral of the b-spline over \( [x_1, x_2] \) + integer(ip),intent(out) :: iflag !! status flag (see [[db1sqad]]) + + if (me%initialized) then + call db1sqad(me%tx,me%bcoef,me%nx,me%kx,x1,x2,f,iflag,me%work_val_1) + else + iflag = 1_ip + end if + me%iflag = iflag + + end subroutine integral_1d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_1d]] definite integral. This is a wrapper for [[db1fqad]]. + + subroutine fintegral_1d(me,fun,idx,x1,x2,tol,f,iflag) + + implicit none + + class(bspline_1d),intent(inout) :: me + procedure(b1fqad_func) :: fun !! external function of one argument for the + !! integrand `bf(x)=fun(x)*dbvalu(tx,bcoef,nx,kx,idx,x,inbv)` + integer(ip),intent(in) :: idx !! order of the spline derivative, `0 <= idx <= k-1` + !! `idx=0` gives the spline function + real(wp),intent(in) :: x1 !! left point of interval + real(wp),intent(in) :: x2 !! right point of interval + real(wp),intent(in) :: tol !! desired accuracy for the quadrature + real(wp),intent(out) :: f !! integral of `bf(x)` over \( [x_1, x_2] \) + integer(ip),intent(out) :: iflag !! status flag (see [[db1sqad]]) + + if (me%initialized) then + call db1fqad(fun,me%tx,me%bcoef,me%nx,me%kx,idx,x1,x2,tol,f,iflag,me%work_val_1) + else + iflag = 1_ip + end if + me%iflag = iflag + + end subroutine fintegral_1d +!***************************************************************************************** + +!***************************************************************************************** +!> +! It returns an empty [[bspline_2d]] type. Note that INITIALIZE still +! needs to be called before it can be used. +! Not really that useful except perhaps in some OpenMP applications. + + elemental function bspline_2d_constructor_empty() result(me) + + implicit none + + type(bspline_2d) :: me + + end function bspline_2d_constructor_empty +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_2d]] type (auto knots). +! This is a wrapper for [[initialize_2d_auto_knots]]. + + pure function bspline_2d_constructor_auto_knots(x,y,fcn,kx,ky,extrap) result(me) + + implicit none + + type(bspline_2d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:),intent(in) :: fcn !! `(nx,ny)` matrix of function values to interpolate. + !! `fcn(i,j)` should contain the function value at the + !! point (`x(i)`,`y(j)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_2d_auto_knots(me,x,y,fcn,kx,ky,me%iflag,extrap) + + end function bspline_2d_constructor_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_2d]] type (user-specified knots). +! This is a wrapper for [[initialize_2d_specify_knots]]. + + pure function bspline_2d_constructor_specify_knots(x,y,fcn,kx,ky,tx,ty,extrap) result(me) + + implicit none + + type(bspline_2d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:),intent(in) :: fcn !! `(nx,ny)` matrix of function values to interpolate. + !! `fcn(i,j)` should contain the function value at the + !! point (`x(i)`,`y(j)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_2d_specify_knots(me,x,y,fcn,kx,ky,tx,ty,me%iflag,extrap) + + end function bspline_2d_constructor_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_2d]] type (with automatically-computed knots). +! This is a wrapper for [[db2ink]]. + + pure subroutine initialize_2d_auto_knots(me,x,y,fcn,kx,ky,iflag,extrap) + + implicit none + + class(bspline_2d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:),intent(in) :: fcn !! `(nx,ny)` matrix of function values to interpolate. + !! `fcn(i,j)` should contain the function value at the + !! point (`x(i)`,`y(j)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(out) :: iflag !! status flag (see [[db2ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: iknot + integer(ip) :: nx,ny + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + + me%nx = nx + me%ny = ny + + me%kx = kx + me%ky = ky + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%bcoef(nx,ny)) + allocate(me%work_val_1(ky)) + allocate(me%work_val_2(3_ip*max(kx,ky))) + + iknot = 0_ip !knot sequence chosen by db2ink + + call db2ink(x,nx,y,ny,fcn,kx,ky,iknot,me%tx,me%ty,me%bcoef,iflag) + + if (iflag==0_ip) then + call me%set_extrap_flag(extrap) + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_2d_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_2d]] type (with user-specified knots). +! This is a wrapper for [[db2ink]]. + + pure subroutine initialize_2d_specify_knots(me,x,y,fcn,kx,ky,tx,ty,iflag,extrap) + + implicit none + + class(bspline_2d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:),intent(in) :: fcn !! `(nx,ny)` matrix of function values to interpolate. + !! `fcn(i,j)` should contain the function value at the + !! point (`x(i)`,`y(j)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + integer(ip),intent(out) :: iflag !! status flag (see [[db2ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: nx,ny + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + + call check_knot_vectors_sizes(nx=nx,kx=kx,tx=tx,& + ny=ny,ky=ky,ty=ty,& + iflag=iflag) + + if (iflag == 0_ip) then + + me%nx = nx + me%ny = ny + + me%kx = kx + me%ky = ky + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%bcoef(nx,ny)) + allocate(me%work_val_1(ky)) + allocate(me%work_val_2(3_ip*max(kx,ky))) + + me%tx = tx + me%ty = ty + + call db2ink(x,nx,y,ny,fcn,kx,ky,1_ip,me%tx,me%ty,me%bcoef,iflag) + + call me%set_extrap_flag(extrap) + + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_2d_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_2d]] interpolate. This is a wrapper for [[db2val]]. + + pure subroutine evaluate_2d(me,xval,yval,idx,idy,f,iflag) + + implicit none + + class(bspline_2d),intent(inout) :: me + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag (see [[db2val]]) + + if (me%initialized) then + call db2val(xval,yval,& + idx,idy,& + me%tx,me%ty,& + me%nx,me%ny,& + me%kx,me%ky,& + me%bcoef,f,iflag,& + me%inbvx,me%inbvy,me%iloy,& + me%work_val_1,me%work_val_2,& + extrap=me%extrap) + else + iflag = 1_ip + end if + + me%iflag = iflag + + end subroutine evaluate_2d +!***************************************************************************************** + +!***************************************************************************************** +!> +! It returns an empty [[bspline_3d]] type. Note that INITIALIZE still +! needs to be called before it can be used. +! Not really that useful except perhaps in some OpenMP applications. + + elemental function bspline_3d_constructor_empty() result(me) + + implicit none + + type(bspline_3d) :: me + + end function bspline_3d_constructor_empty +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_3d]] type (auto knots). +! This is a wrapper for [[initialize_3d_auto_knots]]. + + pure function bspline_3d_constructor_auto_knots(x,y,z,fcn,kx,ky,kz,extrap) result(me) + + implicit none + + type(bspline_3d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:),intent(in) :: fcn !! `(nx,ny,nz)` matrix of function values to interpolate. + !! `fcn(i,j,k)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_3d_auto_knots(me,x,y,z,fcn,kx,ky,kz,me%iflag,extrap) + + end function bspline_3d_constructor_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_3d]] type (user-specified knots). +! This is a wrapper for [[initialize_3d_specify_knots]]. + + pure function bspline_3d_constructor_specify_knots(x,y,z,fcn,kx,ky,kz,tx,ty,tz,extrap) result(me) + + implicit none + + type(bspline_3d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:),intent(in) :: fcn !! `(nx,ny,nz)` matrix of function values to interpolate. + !! `fcn(i,j,k)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_3d_specify_knots(me,x,y,z,fcn,kx,ky,kz,tx,ty,tz,me%iflag,extrap) + + end function bspline_3d_constructor_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_3d]] type (with automatically-computed knots). +! This is a wrapper for [[db3ink]]. + + pure subroutine initialize_3d_auto_knots(me,x,y,z,fcn,kx,ky,kz,iflag,extrap) + + implicit none + + class(bspline_3d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:),intent(in) :: fcn !! `(nx,ny,nz)` matrix of function values to interpolate. + !! `fcn(i,j,k)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(out) :: iflag !! status flag (see [[db3ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: iknot + integer(ip) :: nx,ny,nz + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + + me%nx = nx + me%ny = ny + me%nz = nz + + me%kx = kx + me%ky = ky + me%kz = kz + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%bcoef(nx,ny,nz)) + allocate(me%work_val_1(ky,kz)) + allocate(me%work_val_2(kz)) + allocate(me%work_val_3(3_ip*max(kx,ky,kz))) + + iknot = 0_ip !knot sequence chosen by db3ink + + call db3ink(x,nx,y,ny,z,nz,& + fcn,& + kx,ky,kz,& + iknot,& + me%tx,me%ty,me%tz,& + me%bcoef,iflag) + + if (iflag==0_ip) then + call me%set_extrap_flag(extrap) + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_3d_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_3d]] type (with user-specified knots). +! This is a wrapper for [[db3ink]]. + + pure subroutine initialize_3d_specify_knots(me,x,y,z,fcn,kx,ky,kz,tx,ty,tz,iflag,extrap) + + implicit none + + class(bspline_3d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:),intent(in) :: fcn !! `(nx,ny,nz)` matrix of function values to interpolate. + !! `fcn(i,j,k)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + integer(ip),intent(out) :: iflag !! status flag (see [[db3ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: nx,ny,nz + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + + call check_knot_vectors_sizes(nx=nx,kx=kx,tx=tx,& + ny=ny,ky=ky,ty=ty,& + nz=nz,kz=kz,tz=tz,& + iflag=iflag) + + if (iflag == 0_ip) then + + me%nx = nx + me%ny = ny + me%nz = nz + + me%kx = kx + me%ky = ky + me%kz = kz + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%bcoef(nx,ny,nz)) + allocate(me%work_val_1(ky,kz)) + allocate(me%work_val_2(kz)) + allocate(me%work_val_3(3_ip*max(kx,ky,kz))) + + me%tx = tx + me%ty = ty + me%tz = tz + + call db3ink(x,nx,y,ny,z,nz,& + fcn,& + kx,ky,kz,& + 1_ip,& + me%tx,me%ty,me%tz,& + me%bcoef,iflag) + + call me%set_extrap_flag(extrap) + + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_3d_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_3d]] interpolate. This is a wrapper for [[db3val]]. + + pure subroutine evaluate_3d(me,xval,yval,zval,idx,idy,idz,f,iflag) + + implicit none + + class(bspline_3d),intent(inout) :: me + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag (see [[db3val]]) + + if (me%initialized) then + call db3val(xval,yval,zval,& + idx,idy,idz,& + me%tx,me%ty,me%tz,& + me%nx,me%ny,me%nz,& + me%kx,me%ky,me%kz,& + me%bcoef,f,iflag,& + me%inbvx,me%inbvy,me%inbvz,& + me%iloy,me%iloz,& + me%work_val_1,me%work_val_2,me%work_val_3,& + extrap=me%extrap) + else + iflag = 1_ip + end if + + me%iflag = iflag + + end subroutine evaluate_3d +!***************************************************************************************** + +!***************************************************************************************** +!> +! It returns an empty [[bspline_4d]] type. Note that INITIALIZE still +! needs to be called before it can be used. +! Not really that useful except perhaps in some OpenMP applications. + + elemental function bspline_4d_constructor_empty() result(me) + + implicit none + + type(bspline_4d) :: me + + end function bspline_4d_constructor_empty +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_4d]] type (auto knots). +! This is a wrapper for [[initialize_4d_auto_knots]]. + + pure function bspline_4d_constructor_auto_knots(x,y,z,q,fcn,kx,ky,kz,kq,extrap) result(me) + + implicit none + + type(bspline_4d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq)` matrix of function values to interpolate. + !! `fcn(i,j,k,l)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_4d_auto_knots(me,x,y,z,q,fcn,kx,ky,kz,kq,me%iflag,extrap) + + end function bspline_4d_constructor_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_4d]] type (user-specified knots). +! This is a wrapper for [[initialize_4d_specify_knots]]. + + pure function bspline_4d_constructor_specify_knots(x,y,z,q,fcn,kx,ky,kz,kq,& + tx,ty,tz,tq,extrap) result(me) + + implicit none + + type(bspline_4d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq)` matrix of function values to interpolate. + !! `fcn(i,j,k,l)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tq !! The `(nq+kq)` knots in the \(q\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_4d_specify_knots(me,x,y,z,q,fcn,kx,ky,kz,kq,tx,ty,tz,tq,me%iflag,extrap) + + end function bspline_4d_constructor_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_4d]] type (with automatically-computed knots). +! This is a wrapper for [[db4ink]]. + + pure subroutine initialize_4d_auto_knots(me,x,y,z,q,fcn,kx,ky,kz,kq,iflag,extrap) + + implicit none + + class(bspline_4d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq)` matrix of function values to interpolate. + !! `fcn(i,j,k,l)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(out) :: iflag !! status flag (see [[db4ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: iknot + integer(ip) :: nx,ny,nz,nq + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + nq = size(q,kind=ip) + + me%nx = nx + me%ny = ny + me%nz = nz + me%nq = nq + + me%kx = kx + me%ky = ky + me%kz = kz + me%kq = kq + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%tq(nq+kq)) + allocate(me%bcoef(nx,ny,nz,nq)) + allocate(me%work_val_1(ky,kz,kq)) + allocate(me%work_val_2(kz,kq)) + allocate(me%work_val_3(kq)) + allocate(me%work_val_4(3_ip*max(kx,ky,kz,kq))) + + iknot = 0_ip !knot sequence chosen by db4ink + + call db4ink(x,nx,y,ny,z,nz,q,nq,& + fcn,& + kx,ky,kz,kq,& + iknot,& + me%tx,me%ty,me%tz,me%tq,& + me%bcoef,iflag) + + if (iflag==0_ip) then + call me%set_extrap_flag(extrap) + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_4d_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_4d]] type (with user-specified knots). +! This is a wrapper for [[db4ink]]. + + pure subroutine initialize_4d_specify_knots(me,x,y,z,q,fcn,& + kx,ky,kz,kq,tx,ty,tz,tq,iflag,extrap) + + implicit none + + class(bspline_4d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq)` matrix of function values to interpolate. + !! `fcn(i,j,k,l)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tq !! The `(nq+kq)` knots in the \(q\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + integer(ip),intent(out) :: iflag !! status flag (see [[db4ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: nx,ny,nz,nq + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + nq = size(q,kind=ip) + + call check_knot_vectors_sizes(nx=nx,kx=kx,tx=tx,& + ny=ny,ky=ky,ty=ty,& + nz=nz,kz=kz,tz=tz,& + nq=nq,kq=kq,tq=tq,& + iflag=iflag) + + if (iflag == 0_ip) then + + me%nx = nx + me%ny = ny + me%nz = nz + me%nq = nq + + me%kx = kx + me%ky = ky + me%kz = kz + me%kq = kq + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%tq(nq+kq)) + allocate(me%bcoef(nx,ny,nz,nq)) + allocate(me%work_val_1(ky,kz,kq)) + allocate(me%work_val_2(kz,kq)) + allocate(me%work_val_3(kq)) + allocate(me%work_val_4(3_ip*max(kx,ky,kz,kq))) + + me%tx = tx + me%ty = ty + me%tz = tz + me%tq = tq + + call db4ink(x,nx,y,ny,z,nz,q,nq,& + fcn,& + kx,ky,kz,kq,& + 1_ip,& + me%tx,me%ty,me%tz,me%tq,& + me%bcoef,iflag) + + call me%set_extrap_flag(extrap) + + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_4d_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_4d]] interpolate. This is a wrapper for [[db4val]]. + + pure subroutine evaluate_4d(me,xval,yval,zval,qval,idx,idy,idz,idq,f,iflag) + + implicit none + + class(bspline_4d),intent(inout) :: me + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + real(wp),intent(in) :: qval !! \(q\) coordinate of evaluation point. + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idq !! \(q\) derivative of piecewise polynomial to evaluate. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag (see [[db4val]]) + + if (me%initialized) then + call db4val(xval,yval,zval,qval,& + idx,idy,idz,idq,& + me%tx,me%ty,me%tz,me%tq,& + me%nx,me%ny,me%nz,me%nq,& + me%kx,me%ky,me%kz,me%kq,& + me%bcoef,f,iflag,& + me%inbvx,me%inbvy,me%inbvz,me%inbvq,& + me%iloy,me%iloz,me%iloq,& + me%work_val_1,me%work_val_2,me%work_val_3,me%work_val_4,& + extrap=me%extrap) + else + iflag = 1_ip + end if + + me%iflag = iflag + + end subroutine evaluate_4d +!***************************************************************************************** + +!***************************************************************************************** +!> +! It returns an empty [[bspline_5d]] type. Note that INITIALIZE still +! needs to be called before it can be used. +! Not really that useful except perhaps in some OpenMP applications. + + elemental function bspline_5d_constructor_empty() result(me) + + implicit none + + type(bspline_5d) :: me + + end function bspline_5d_constructor_empty +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_5d]] type (auto knots). +! This is a wrapper for [[initialize_5d_auto_knots]]. + + pure function bspline_5d_constructor_auto_knots(x,y,z,q,r,fcn,kx,ky,kz,kq,kr,extrap) result(me) + + implicit none + + type(bspline_5d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_5d_auto_knots(me,x,y,z,q,r,fcn,kx,ky,kz,kq,kr,me%iflag,extrap) + + end function bspline_5d_constructor_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_5d]] type (user-specified knots). +! This is a wrapper for [[initialize_5d_specify_knots]]. + + pure function bspline_5d_constructor_specify_knots(x,y,z,q,r,fcn,& + kx,ky,kz,kq,kr,& + tx,ty,tz,tq,tr,extrap) result(me) + + implicit none + + type(bspline_5d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tq !! The `(nq+kq)` knots in the \(q\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tr !! The `(nr+kr)` knots in the \(r\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_5d_specify_knots(me,x,y,z,q,r,fcn,kx,ky,kz,kq,kr,tx,ty,tz,tq,tr,me%iflag,extrap) + + end function bspline_5d_constructor_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_5d]] type (with automatically-computed knots). +! This is a wrapper for [[db5ink]]. + + pure subroutine initialize_5d_auto_knots(me,x,y,z,q,r,fcn,kx,ky,kz,kq,kr,iflag,extrap) + + implicit none + + class(bspline_5d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(out) :: iflag !! status flag (see [[db5ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: iknot + integer(ip) :: nx,ny,nz,nq,nr + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + nq = size(q,kind=ip) + nr = size(r,kind=ip) + + me%nx = nx + me%ny = ny + me%nz = nz + me%nq = nq + me%nr = nr + + me%kx = kx + me%ky = ky + me%kz = kz + me%kq = kq + me%kr = kr + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%tq(nq+kq)) + allocate(me%tr(nr+kr)) + allocate(me%bcoef(nx,ny,nz,nq,nr)) + allocate(me%work_val_1(ky,kz,kq,kr)) + allocate(me%work_val_2(kz,kq,kr)) + allocate(me%work_val_3(kq,kr)) + allocate(me%work_val_4(kr)) + allocate(me%work_val_5(3_ip*max(kx,ky,kz,kq,kr))) + + iknot = 0_ip !knot sequence chosen by db5ink + + call db5ink(x,nx,y,ny,z,nz,q,nq,r,nr,& + fcn,& + kx,ky,kz,kq,kr,& + iknot,& + me%tx,me%ty,me%tz,me%tq,me%tr,& + me%bcoef,iflag) + + if (iflag==0_ip) then + call me%set_extrap_flag(extrap) + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_5d_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_5d]] type (with user-specified knots). +! This is a wrapper for [[db5ink]]. + + pure subroutine initialize_5d_specify_knots(me,x,y,z,q,r,fcn,& + kx,ky,kz,kq,kr,& + tx,ty,tz,tq,tr,iflag,extrap) + + implicit none + + class(bspline_5d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tq !! The `(nq+kq)` knots in the \(q\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tr !! The `(nr+kr)` knots in the \(r\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + integer(ip),intent(out) :: iflag !! status flag (see [[db5ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: nx,ny,nz,nq,nr + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + nq = size(q,kind=ip) + nr = size(r,kind=ip) + + call check_knot_vectors_sizes(nx=nx,kx=kx,tx=tx,& + ny=ny,ky=ky,ty=ty,& + nz=nz,kz=kz,tz=tz,& + nq=nq,kq=kq,tq=tq,& + nr=nr,kr=kr,tr=tr,& + iflag=iflag) + + if (iflag == 0_ip) then + + me%nx = nx + me%ny = ny + me%nz = nz + me%nq = nq + me%nr = nr + + me%kx = kx + me%ky = ky + me%kz = kz + me%kq = kq + me%kr = kr + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%tq(nq+kq)) + allocate(me%tr(nr+kr)) + allocate(me%bcoef(nx,ny,nz,nq,nr)) + allocate(me%work_val_1(ky,kz,kq,kr)) + allocate(me%work_val_2(kz,kq,kr)) + allocate(me%work_val_3(kq,kr)) + allocate(me%work_val_4(kr)) + allocate(me%work_val_5(3_ip*max(kx,ky,kz,kq,kr))) + + me%tx = tx + me%ty = ty + me%tz = tz + me%tq = tq + me%tr = tr + + call db5ink(x,nx,y,ny,z,nz,q,nq,r,nr,& + fcn,& + kx,ky,kz,kq,kr,& + 1_ip,& + me%tx,me%ty,me%tz,me%tq,me%tr,& + me%bcoef,iflag) + + call me%set_extrap_flag(extrap) + + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_5d_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_5d]] interpolate. This is a wrapper for [[db5val]]. + + pure subroutine evaluate_5d(me,xval,yval,zval,qval,rval,idx,idy,idz,idq,idr,f,iflag) + + implicit none + + class(bspline_5d),intent(inout) :: me + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + real(wp),intent(in) :: qval !! \(q\) coordinate of evaluation point. + real(wp),intent(in) :: rval !! \(r\) coordinate of evaluation point. + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idq !! \(q\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idr !! \(r\) derivative of piecewise polynomial to evaluate. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag (see [[db5val]]) + + if (me%initialized) then + call db5val(xval,yval,zval,qval,rval,& + idx,idy,idz,idq,idr,& + me%tx,me%ty,me%tz,me%tq,me%tr,& + me%nx,me%ny,me%nz,me%nq,me%nr,& + me%kx,me%ky,me%kz,me%kq,me%kr,& + me%bcoef,f,iflag,& + me%inbvx,me%inbvy,me%inbvz,me%inbvq,me%inbvr,& + me%iloy,me%iloz,me%iloq,me%ilor,& + me%work_val_1,me%work_val_2,me%work_val_3,me%work_val_4,me%work_val_5,& + extrap=me%extrap) + else + iflag = 1_ip + end if + + me%iflag = iflag + + end subroutine evaluate_5d +!***************************************************************************************** + +!***************************************************************************************** +!> +! It returns an empty [[bspline_6d]] type. Note that INITIALIZE still +! needs to be called before it can be used. +! Not really that useful except perhaps in some OpenMP applications. + + elemental function bspline_6d_constructor_empty() result(me) + + implicit none + + type(bspline_6d) :: me + + end function bspline_6d_constructor_empty +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_6d]] type (auto knots). +! This is a wrapper for [[initialize_6d_auto_knots]]. + + pure function bspline_6d_constructor_auto_knots(x,y,z,q,r,s,fcn,& + kx,ky,kz,kq,kr,ks,extrap) result(me) + + implicit none + + type(bspline_6d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: s !! `(ns)` array of \(s\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr,ns)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m,n)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`,`s(n)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ks !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_6d_auto_knots(me,x,y,z,q,r,s,fcn,kx,ky,kz,kq,kr,ks,me%iflag,extrap) + + end function bspline_6d_constructor_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Constructor for a [[bspline_6d]] type (user-specified knots). +! This is a wrapper for [[initialize_6d_specify_knots]]. + + pure function bspline_6d_constructor_specify_knots(x,y,z,q,r,s,fcn,& + kx,ky,kz,kq,kr,ks,& + tx,ty,tz,tq,tr,ts,extrap) result(me) + + implicit none + + type(bspline_6d) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: s !! `(ns)` array of \(s\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr,ns)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m,n)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`,`s(n)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ks !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tq !! The `(nq+kq)` knots in the \(q\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tr !! The `(nr+kr)` knots in the \(r\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ts !! The `(ns+ks)` knots in the \(s\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + call initialize_6d_specify_knots(me,x,y,z,q,r,s,fcn,& + kx,ky,kz,kq,kr,ks,& + tx,ty,tz,tq,tr,ts,me%iflag,extrap) + + end function bspline_6d_constructor_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_6d]] type (with automatically-computed knots). +! This is a wrapper for [[db6ink]]. + + pure subroutine initialize_6d_auto_knots(me,x,y,z,q,r,s,fcn,& + kx,ky,kz,kq,kr,ks,iflag,extrap) + + implicit none + + class(bspline_6d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: s !! `(ns)` array of \(s\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr,ns)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m,n)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`,`s(n)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ks !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(out) :: iflag !! status flag (see [[db6ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: iknot + integer(ip) :: nx,ny,nz,nq,nr,ns + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + nq = size(q,kind=ip) + nr = size(r,kind=ip) + ns = size(s,kind=ip) + + me%nx = nx + me%ny = ny + me%nz = nz + me%nq = nq + me%nr = nr + me%ns = ns + + me%kx = kx + me%ky = ky + me%kz = kz + me%kq = kq + me%kr = kr + me%ks = ks + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%tq(nq+kq)) + allocate(me%tr(nr+kr)) + allocate(me%ts(ns+ks)) + allocate(me%bcoef(nx,ny,nz,nq,nr,ns)) + allocate(me%work_val_1(ky,kz,kq,kr,ks)) + allocate(me%work_val_2(kz,kq,kr,ks)) + allocate(me%work_val_3(kq,kr,ks)) + allocate(me%work_val_4(kr,ks)) + allocate(me%work_val_5(ks)) + allocate(me%work_val_6(3_ip*max(kx,ky,kz,kq,kr,ks))) + + iknot = 0_ip !knot sequence chosen by db6ink + + call db6ink(x,nx,y,ny,z,nz,q,nq,r,nr,s,ns,& + fcn,& + kx,ky,kz,kq,kr,ks,& + iknot,& + me%tx,me%ty,me%tz,me%tq,me%tr,me%ts,& + me%bcoef,iflag) + + if (iflag==0_ip) then + call me%set_extrap_flag(extrap) + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_6d_auto_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Initialize a [[bspline_6d]] type (with user-specified knots). +! This is a wrapper for [[db6ink]]. + + pure subroutine initialize_6d_specify_knots(me,x,y,z,q,r,s,fcn,& + kx,ky,kz,kq,kr,ks,& + tx,ty,tz,tq,tr,ts,iflag,extrap) + + implicit none + + class(bspline_6d),intent(inout) :: me + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: s !! `(ns)` array of \(s\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr,ns)` matrix of function values to interpolate. + !! `fcn(i,j,k,l,m,n)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`,`s(n)`) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! The order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! The order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ks !! The order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ty !! The `(ny+ky)` knots in the \(y\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tz !! The `(nz+kz)` knots in the \(z\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tq !! The `(nq+kq)` knots in the \(q\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: tr !! The `(nr+kr)` knots in the \(r\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + real(wp),dimension(:),intent(in) :: ts !! The `(ns+ks)` knots in the \(s\) direction + !! for the spline interpolant. + !! Must be non-decreasing. + integer(ip),intent(out) :: iflag !! status flag (see [[db6ink]]) + logical,intent(in),optional :: extrap !! if true, then extrapolation is allowed + !! (default is false) + + integer(ip) :: nx,ny,nz,nq,nr,ns + + call me%destroy() + + nx = size(x,kind=ip) + ny = size(y,kind=ip) + nz = size(z,kind=ip) + nq = size(q,kind=ip) + nr = size(r,kind=ip) + ns = size(s,kind=ip) + + call check_knot_vectors_sizes(nx=nx,kx=kx,tx=tx,& + ny=ny,ky=ky,ty=ty,& + nz=nz,kz=kz,tz=tz,& + nq=nq,kq=kq,tq=tq,& + nr=nr,kr=kr,tr=tr,& + ns=ns,ks=ks,ts=ts,& + iflag=iflag) + + if (iflag == 0_ip) then + + me%nx = nx + me%ny = ny + me%nz = nz + me%nq = nq + me%nr = nr + me%ns = ns + + me%kx = kx + me%ky = ky + me%kz = kz + me%kq = kq + me%kr = kr + me%ks = ks + + allocate(me%tx(nx+kx)) + allocate(me%ty(ny+ky)) + allocate(me%tz(nz+kz)) + allocate(me%tq(nq+kq)) + allocate(me%tr(nr+kr)) + allocate(me%ts(ns+ks)) + allocate(me%bcoef(nx,ny,nz,nq,nr,ns)) + allocate(me%work_val_1(ky,kz,kq,kr,ks)) + allocate(me%work_val_2(kz,kq,kr,ks)) + allocate(me%work_val_3(kq,kr,ks)) + allocate(me%work_val_4(kr,ks)) + allocate(me%work_val_5(ks)) + allocate(me%work_val_6(3_ip*max(kx,ky,kz,kq,kr,ks))) + + me%tx = tx + me%ty = ty + me%tz = tz + me%tq = tq + me%tr = tr + me%ts = ts + + call db6ink(x,nx,y,ny,z,nz,q,nq,r,nr,s,ns,& + fcn,& + kx,ky,kz,kq,kr,ks,& + 1_ip,& + me%tx,me%ty,me%tz,me%tq,me%tr,me%ts,& + me%bcoef,iflag) + + call me%set_extrap_flag(extrap) + + end if + + me%initialized = iflag==0_ip + me%iflag = iflag + + end subroutine initialize_6d_specify_knots +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluate a [[bspline_6d]] interpolate. This is a wrapper for [[db6val]]. + + pure subroutine evaluate_6d(me,xval,yval,zval,qval,rval,sval,idx,idy,idz,idq,idr,ids,f,iflag) + + implicit none + + class(bspline_6d),intent(inout) :: me + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + real(wp),intent(in) :: qval !! \(q\) coordinate of evaluation point. + real(wp),intent(in) :: rval !! \(r\) coordinate of evaluation point. + real(wp),intent(in) :: sval !! \(s\) coordinate of evaluation point. + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idq !! \(q\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idr !! \(r\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: ids !! \(s\) derivative of piecewise polynomial to evaluate. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag (see [[db6val]]) + + if (me%initialized) then + call db6val(xval,yval,zval,qval,rval,sval,& + idx,idy,idz,idq,idr,ids,& + me%tx,me%ty,me%tz,me%tq,me%tr,me%ts,& + me%nx,me%ny,me%nz,me%nq,me%nr,me%ns,& + me%kx,me%ky,me%kz,me%kq,me%kr,me%ks,& + me%bcoef,f,iflag,& + me%inbvx,me%inbvy,me%inbvz,me%inbvq,me%inbvr,me%inbvs,& + me%iloy,me%iloz,me%iloq,me%ilor,me%ilos,& + me%work_val_1,me%work_val_2,me%work_val_3,me%work_val_4,me%work_val_5,me%work_val_6,& + extrap=me%extrap) + else + iflag = 1_ip + end if + + me%iflag = iflag + + end subroutine evaluate_6d +!***************************************************************************************** + +!***************************************************************************************** +!> +! Error checks for the user-specified knot vector sizes. +! +!@note If more than one is the wrong size, then the `iflag` error code will +! correspond to the one with the highest rank. + + pure subroutine check_knot_vectors_sizes(nx,ny,nz,nq,nr,ns,& + kx,ky,kz,kq,kr,ks,& + tx,ty,tz,tq,tr,ts,iflag) + + implicit none + + integer(ip),intent(in),optional :: nx + integer(ip),intent(in),optional :: ny + integer(ip),intent(in),optional :: nz + integer(ip),intent(in),optional :: nq + integer(ip),intent(in),optional :: nr + integer(ip),intent(in),optional :: ns + integer(ip),intent(in),optional :: kx + integer(ip),intent(in),optional :: ky + integer(ip),intent(in),optional :: kz + integer(ip),intent(in),optional :: kq + integer(ip),intent(in),optional :: kr + integer(ip),intent(in),optional :: ks + real(wp),dimension(:),intent(in),optional :: tx + real(wp),dimension(:),intent(in),optional :: ty + real(wp),dimension(:),intent(in),optional :: tz + real(wp),dimension(:),intent(in),optional :: tq + real(wp),dimension(:),intent(in),optional :: tr + real(wp),dimension(:),intent(in),optional :: ts + integer(ip),intent(out) :: iflag !! 0 if everything is OK + + iflag = 0_ip + + if (present(nx) .and. present(kx) .and. present(tx)) then + if (size(tx,kind=ip)/=(nx+kx)) then + iflag = 501_ip ! tx is not the correct size (nx+kx) + end if + end if + + if (present(ny) .and. present(ky) .and. present(ty)) then + if (size(ty,kind=ip)/=(ny+ky)) then + iflag = 502_ip ! ty is not the correct size (ny+ky) + end if + end if + + if (present(nz) .and. present(kz) .and. present(tz)) then + if (size(tz,kind=ip)/=(nz+kz)) then + iflag = 503_ip ! tz is not the correct size (nz+kz) + end if + end if + + if (present(nq) .and. present(kq) .and. present(tq)) then + if (size(tq,kind=ip)/=(nq+kq)) then + iflag = 504_ip ! tq is not the correct size (nq+kq) + end if + end if + + if (present(nr) .and. present(kr) .and. present(tr)) then + if (size(tr,kind=ip)/=(nr+kr)) then + iflag = 505_ip ! tr is not the correct size (nr+kr) + end if + end if + + if (present(ns) .and. present(ks) .and. present(ts)) then + if (size(ts,kind=ip)/=(ns+ks)) then + iflag = 506_ip ! ts is not the correct size (ns+ks) + end if + end if + + end subroutine check_knot_vectors_sizes +!***************************************************************************************** + +!***************************************************************************************** + end module bspline_oo_module +!***************************************************************************************** diff --git a/examples/bspline/native/bspline_sub_module.f90 b/examples/bspline/native/bspline_sub_module.f90 new file mode 100644 index 000000000..272af1878 --- /dev/null +++ b/examples/bspline/native/bspline_sub_module.f90 @@ -0,0 +1,4733 @@ +!***************************************************************************************** +!> author: Jacob Williams +! license: BSD +! +!### Description +! +! Multidimensional (1D-6D) B-spline interpolation of data on a regular grid. +! Basic pure subroutine interface. +! +!### Notes +! +! This module is based on the B-spline and spline routines from [1]. +! The original Fortran 77 routines were converted to free-form source. +! Some of them are relatively unchanged from the originals, but some have +! been extensively refactored. In addition, new routines for +! 1d, 4d, 5d, and 6d interpolation were also created (these are simply +! extensions of the same algorithm into higher dimensions). +! +!### See also +! * An object-oriented interface can be found in [[bspline_oo_module]]. +! +!### References +! +! 1. DBSPLIN and DTENSBS from the +! [NIST Core Math Library](http://www.nist.gov/itl/math/mcsd-software.cfm). +! Original code is public domain. +! 2. Carl de Boor, "A Practical Guide to Splines", +! Springer-Verlag, New York, 1978. +! 3. Carl de Boor, [Efficient Computer Manipulation of Tensor +! Products](http://dl.acm.org/citation.cfm?id=355831), +! ACM Transactions on Mathematical Software, +! Vol. 5 (1979), p. 173-182. +! 4. D.E. Amos, "Computation with Splines and B-Splines", +! SAND78-1968, Sandia Laboratories, March, 1979. +! 5. Carl de Boor, +! [Package for calculating with B-splines](http://epubs.siam.org/doi/abs/10.1137/0714026), +! SIAM Journal on Numerical Analysis 14, 3 (June 1977), p. 441-472. +! 6. D.E. Amos, "Quadrature subroutines for splines and B-splines", +! Report SAND79-1825, Sandia Laboratories, December 1979. + + module bspline_sub_module + + use bspline_kinds_module, only: wp, ip + use,intrinsic :: iso_fortran_env, only: error_unit + + implicit none + + private + + abstract interface + function b1fqad_func(x) result(f) + !! interface for the input function in [[dbfqad]] + import :: wp + implicit none + real(wp),intent(in) :: x + real(wp) :: f !! f(x) + end function b1fqad_func + end interface + public :: b1fqad_func + + integer(ip),parameter,public :: bspline_order_linear = 2_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + integer(ip),parameter,public :: bspline_order_quadratic = 3_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + integer(ip),parameter,public :: bspline_order_cubic = 4_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + integer(ip),parameter,public :: bspline_order_quartic = 5_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + integer(ip),parameter,public :: bspline_order_quintic = 6_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + integer(ip),parameter,public :: bspline_order_hexic = 7_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + integer(ip),parameter,public :: bspline_order_heptic = 8_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + integer(ip),parameter,public :: bspline_order_octic = 9_ip !! spline order `k` parameter + !! (for input to the `db*ink` routines) + !! [order = polynomial degree + 1] + + interface db1ink + !! 1D initialization routines. + module procedure :: db1ink_default, db1ink_alt, db1ink_alt_2 + end interface + interface db1val + !! 1D evaluation routines. + module procedure :: db1val_default, db1val_alt + end interface + + !main routines: + public :: db1ink, db1val, db1sqad, db1fqad + public :: db2ink, db2val + public :: db3ink, db3val + public :: db4ink, db4val + public :: db5ink, db5val + public :: db6ink, db6val + + public :: get_status_message + + contains +!***************************************************************************************** + +!***************************************************************************************** +!> +! Determines the parameters of a function that interpolates +! the one-dimensional gridded data +! $$ [x(i),\mathrm{fcn}(i)] ~\mathrm{for}~ i=1,..,n_x $$ +! The interpolating function and its derivatives may +! subsequently be evaluated by the function [[db1val]]. +! +!### History +! * Jacob Williams, 10/30/2015 : Created 1D routine. + + pure subroutine db1ink_default(x,nx,fcn,kx,iknot,tx,bcoef,iflag) + + implicit none + + integer(ip),intent(in) :: nx !! Number of \(x\) abcissae + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: fcn !! `(nx)` array of function values to interpolate. `fcn(i)` should + !! contain the function value at the point `x(i)` + integer(ip),intent(in) :: iknot !! knot sequence flag: + !! + !! * 0 = knot sequence chosen by [[db1ink]]. + !! * 1 = knot sequence chosen by user. + real(wp),dimension(:),intent(inout) :: tx !! The `(nx+kx)` knots in the \(x\) direction + !! for the spline interpolant: + !! + !! * If `iknot=0` these are chosen by [[db1ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(out) :: bcoef !! `(nx)` array of coefficients of the b-spline interpolant. + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0 = successful execution. + !! * 2 = `iknot` out of range. + !! * 3 = `nx` out of range. + !! * 4 = `kx` out of range. + !! * 5 = `x` not strictly increasing. + !! * 6 = `tx` not non-decreasing. + !! * 700 = `size(x)` \( \ne \) `size(fcn,1)`. + !! * 706 = `size(x)` \( \ne \) `nx`. + !! * 712 = `size(tx)` \( \ne \) `nx+kx`. + !! * 800 = `size(x)` \( \ne \) `size(bcoef,1)`. + + logical :: status_ok + real(wp),dimension(:),allocatable :: work !! work array of dimension `2*kx*(nx+1)` + + !check validity of inputs + + call check_inputs( iknot,& + iflag,& + nx=nx,& + kx=kx,& + x=x,& + f1=fcn,& + bcoef1=bcoef,& + tx=tx,& + status_ok=status_ok) + + if (status_ok) then + + !choose knots + if (iknot == 0_ip) then + call dbknot(x,nx,kx,tx) + end if + + allocate(work(2_ip*kx*(nx+1_ip))) + + !construct b-spline coefficients + call dbtpcf(x,nx,fcn,nx,1_ip,tx,kx,bcoef,work,iflag) + + deallocate(work) + + end if + + end subroutine db1ink_default +!***************************************************************************************** + +!***************************************************************************************** +!> +! Alternate version of [[db1ink_default]], where the boundary conditions can be specified. +! +!### History +! * Jacob Williams, 9/4/2018 : created this routine. +! +!### See also +! * [[dbint4]] -- the main routine that is called here. +! +!@note Currently, this only works for 3rd order (k=4). + + pure subroutine db1ink_alt(x,nx,fcn,kx,ibcl,ibcr,fbcl,fbcr,kntopt,tx,bcoef,iflag) + + implicit none + + real(wp),dimension(:),intent(in) :: x !! \(x\) vector of abscissae of length `nx`, distinct + !! and in increasing order + integer(ip),intent(in) :: nx !! number of data points, \( n_x \ge 2 \) + real(wp),dimension(:),intent(in) :: fcn !! \(y\) vector of ordinates of length `nx` + integer(ip),intent(in) :: kx !! spline order (Currently, this must be `4`) + integer(ip),intent(in) :: ibcl !! selection parameter for left boundary condition: + !! + !! * `ibcl = 1` constrain the first derivative at `x(1)` to `fbcl` + !! * `ibcl = 2` constrain the second derivative at `x(1)` to `fbcl` + integer(ip),intent(in) :: ibcr !! selection parameter for right boundary condition: + !! + !! * `ibcr = 1` constrain first derivative at `x(nx)` to `fbcr` + !! * `ibcr = 2` constrain second derivative at `x(nx)` to `fbcr` + real(wp),intent(in) :: fbcl !! left boundary values governed by `ibcl` + real(wp),intent(in) :: fbcr !! right boundary values governed by `ibcr` + integer(ip),intent(in) :: kntopt !! knot selection parameter: + !! + !! * `kntopt = 1` sets knot multiplicity at `t(4)` and + !! `t(nx+3)` to 4 + !! * `kntopt = 2` sets a symmetric placement of knots + !! about `t(4)` and `t(nx+3)` + real(wp),dimension(:),intent(out) :: tx !! knot array of length `nx+6` + real(wp),dimension(:),intent(out) :: bcoef !! b spline coefficient array of length `nx+2` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 806: [[dbint4]] can only be used when `k=4` + + real(wp),dimension(:,:),allocatable :: w !! work array of dimension `5,nx+2` + integer(ip) :: n !! number of coefficients (n=nx+2) + integer(ip) :: k !! order of spline (k=4) + logical :: status_ok !! status flag for error checking + + real(wp),dimension(3),parameter :: tleft = 0.0_wp !! not used for this case (see [[dbint4]]) + real(wp),dimension(3),parameter :: tright = 0.0_wp !! not used for this case (see [[dbint4]]) + + + if (kx /= 4_ip) then + iflag = 806_ip + else + + call check_inputs( 1_ip,& ! so it will check size of t + iflag,& + nx=nx,& + kx=kx,& + x=x,& + f1=fcn,& + bcoef1=bcoef,& + tx=tx,& + status_ok=status_ok,& + alt=.true.) + + if (status_ok) then + allocate(w(5_ip,nx+2_ip)) + call dbint4(x,fcn,nx,ibcl,ibcr,fbcl,fbcr,kntopt,tleft,tright,tx,bcoef,n,k,w,iflag) + deallocate(w) + end if + + end if + + end subroutine db1ink_alt +!***************************************************************************************** + +!***************************************************************************************** +!> +! Alternate version of [[db1ink_alt]], where the first and +! last 3 knots are specified by the user. +! +!### History +! * Jacob Williams, 9/4/2018 : created this routine. +! +!### See also +! * [[dbint4]] -- the main routine that is called here. +! +!@note Currently, this only works for 3rd order (k=4). + + pure subroutine db1ink_alt_2(x,nx,fcn,kx,ibcl,ibcr,fbcl,fbcr,tleft,tright,tx,bcoef,iflag) + + implicit none + + real(wp),dimension(:),intent(in) :: x !! \(x\) vector of abscissae of length `nx`, distinct + !! and in increasing order + integer(ip),intent(in) :: nx !! number of data points, \( n_x \ge 2 \) + real(wp),dimension(:),intent(in) :: fcn !! \(y\) vector of ordinates of length `nx` + integer(ip),intent(in) :: kx !! spline order (Currently, this must be `4`) + integer(ip),intent(in) :: ibcl !! selection parameter for left boundary condition: + !! + !! * `ibcl = 1` constrain the first derivative at `x(1)` to `fbcl` + !! * `ibcl = 2` constrain the second derivative at `x(1)` to `fbcl` + integer(ip),intent(in) :: ibcr !! selection parameter for right boundary condition: + !! + !! * `ibcr = 1` constrain first derivative at `x(nx)` to `fbcr` + !! * `ibcr = 2` constrain second derivative at `x(nx)` to `fbcr` + real(wp),intent(in) :: fbcl !! left boundary values governed by `ibcl` + real(wp),intent(in) :: fbcr !! right boundary values governed by `ibcr` + real(wp),dimension(3),intent(in) :: tleft !! `t(1:3)` in increasing order supplied by the user. + real(wp),dimension(3),intent(in) :: tright !! `t(nx+4:nx+6)` in increasing order supplied by the user. + real(wp),dimension(:),intent(out) :: tx !! knot array of length `nx+6` + real(wp),dimension(:),intent(out) :: bcoef !! b spline coefficient array of length `nx+2` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 806: [[dbint4]] can only be used when k=4 + + real(wp),dimension(:,:),allocatable :: w !! work array of dimension `5,nx+2` + integer(ip) :: n !! number of coefficients (`n=nx+2`) + integer(ip) :: k !! order of spline (`k=4`) + logical :: status_ok !! status flag for error checking + + integer(ip),parameter :: kntopt = 3 !! use `tleft` and `tright` in [[dbint4]] + + if (kx /= 4_ip) then + iflag = 806_ip + else + + call check_inputs( 1_ip,& ! so it will check size of t + iflag,& + nx=nx,& + kx=kx,& + x=x,& + f1=fcn,& + bcoef1=bcoef,& + tx=tx,& + status_ok=status_ok,& + alt=.true.) + + if (status_ok) then + allocate(w(5,nx+2)) + call dbint4(x,fcn,nx,ibcl,ibcr,fbcl,fbcr,kntopt,tleft,tright,tx,bcoef,n,k,w,iflag) + deallocate(w) + end if + + end if + + end subroutine db1ink_alt_2 +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluates the tensor product piecewise polynomial +! interpolant constructed by the routine [[db1ink]] or one of its +! derivatives at the point `xval`. +! +! To evaluate the interpolant itself, set `idx=0`, +! to evaluate the first partial with respect to `x`, set `idx=1`, and so on. +! +! [[db1val]] returns 0.0 if (`xval`,`yval`) is out of range. that is, if +!```fortran +! xval < tx(1) .or. xval > tx(nx+kx) +!``` +! if the knots `tx` were chosen by [[db1ink]], then this is equivalent to: +!```fortran +! xval < x(1) .or. xval > x(nx)+epsx +!``` +! where +!```fortran +! epsx = 0.1*(x(nx)-x(nx-1)) +!``` +! +! The input quantities `tx`, `nx`, `kx`, and `bcoef` should be +! unchanged since the last call of [[db1ink]]. +! +!### History +! * Jacob Williams, 10/30/2015 : Created 1D routine. + + pure subroutine db1val_default(xval,idx,tx,nx,kx,bcoef,f,iflag,inbvx,w0,extrap) + + implicit none + + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: nx !! the number of interpolation points in \(x\). + !! (same as in last call to [[db1ink]]) + integer(ip),intent(in) :: kx !! order of polynomial pieces in \(x\). + !! (same as in last call to [[db1ink]]) + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),dimension(nx+kx),intent(in) :: tx !! sequence of knots defining the piecewise polynomial + !! in the \(x\) direction. (same as in last call to [[db1ink]]) + real(wp),dimension(nx),intent(in) :: bcoef !! the b-spline coefficients computed by [[db1ink]]. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + integer(ip),intent(inout) :: inbvx !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + real(wp),dimension(3_ip*kx),intent(inout) :: w0 !! work array + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + f = 0.0_wp + + iflag = check_value(xval,tx,1_ip,extrap); if (iflag/=0_ip) return + + call dbvalu(tx,bcoef,nx,kx,idx,xval,inbvx,w0,iflag,f,extrap) + + end subroutine db1val_default +!***************************************************************************************** + +!***************************************************************************************** +!> +! Alternate version of [[db1val_default]] for use with [[db1ink_alt]] and [[db1ink_alt_2]]. + + pure subroutine db1val_alt(xval,idx,tx,nx,n,kx,bcoef,f,iflag,inbvx,w0,extrap) + + implicit none + + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: nx !! the number of interpolation points in \(x\). + integer(ip),intent(in) :: n !! length of `bcoef`: `nx+2` + integer(ip),intent(in) :: kx !! order of polynomial pieces in \(x\). + !! (same as in last call to [[db1ink]]) + real(wp),dimension(n+kx),intent(in) :: tx !! sequence of knots defining the piecewise polynomial + !! in the \(x\) direction. + real(wp),dimension(n),intent(in) :: bcoef !! the b-spline coefficients computed by [[db1ink]]. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + integer(ip),intent(inout) :: inbvx !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + real(wp),dimension(3_ip*kx),intent(inout) :: w0 !! work array + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + f = 0.0_wp + + iflag = check_value(xval,tx,1_ip,extrap); if (iflag/=0_ip) return + + call dbvalu(tx,bcoef,n,kx,idx,xval,inbvx,w0,iflag,f,extrap) + + end subroutine db1val_alt +!***************************************************************************************** + +!***************************************************************************************** +!> +! Computes the integral on `(x1,x2)` of a `kx`-th order b-spline. +! Orders `kx` as high as 20 are permitted by applying a 2, 6, or 10 +! point gauss formula on subintervals of `(x1,x2)` which are +! formed by included (distinct) knots. +! +!### See also +! * [[dbsqad]] -- the core routine. + + pure subroutine db1sqad(tx,bcoef,nx,kx,x1,x2,f,iflag,w0) + + implicit none + + integer(ip),intent(in) :: nx !! length of coefficient array + integer(ip),intent(in) :: kx !! order of b-spline, `1 <= k <= 20` + real(wp),dimension(nx+kx),intent(in) :: tx !! knot array + real(wp),dimension(nx),intent(in) :: bcoef !! b-spline coefficient array + real(wp),intent(in) :: x1 !! left point of quadrature interval in `t(kx) <= x <= t(nx+1)` + real(wp),intent(in) :: x2 !! right point of quadrature interval in `t(kx) <= x <= t(nx+1)` + real(wp),intent(out) :: f !! integral of the b-spline over (`x1`,`x2`) + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + real(wp),dimension(3*kx),intent(inout) :: w0 !! work array for [[dbsqad]] + + call dbsqad(tx,bcoef,nx,kx,x1,x2,f,w0,iflag) + + end subroutine db1sqad +!***************************************************************************************** + +!***************************************************************************************** +!> +! Computes the integral on `(x1,x2)` of a product of a +! function `fun` and the `idx`-th derivative of a `kx`-th order b-spline, +! using the b-representation `(tx,bcoef,nx,kx)`, with an adaptive +! 8-point Legendre-Gauss algorithm. +! `(x1,x2)` must be a subinterval of `t(kx) <= x <= t(nx+1)`. +! +!### See also +! * [[dbfqad]] -- the core routine. +! +!@note This one is not pure, because we are not enforcing +! that the user function `fun` be pure. + + subroutine db1fqad(fun,tx,bcoef,nx,kx,idx,x1,x2,tol,f,iflag,w0) + + implicit none + + procedure(b1fqad_func) :: fun !! external function of one argument for the + !! integrand `bf(x)=fun(x)*dbvalu(tx,bcoef,nx,kx,id,x,inbv,work)` + integer(ip),intent(in) :: nx !! length of coefficient array + integer(ip),intent(in) :: kx !! order of b-spline, `kx >= 1` + real(wp),dimension(nx+kx),intent(in):: tx !! knot array + real(wp),dimension(nx),intent(in) :: bcoef !! b-spline coefficient array + integer(ip),intent(in) :: idx !! order of the spline derivative, `0 <= idx <= k-1` + !! `idx=0` gives the spline function + real(wp),intent(in) :: x1 !! left point of quadrature interval in `t(k) <= x <= t(n+1)` + real(wp),intent(in) :: x2 !! right point of quadrature interval in `t(k) <= x <= t(n+1)` + real(wp),intent(in) :: tol !! desired accuracy for the quadrature, suggest + !! `10*dtol < tol <= 0.1` where `dtol` is the maximum + !! of `1.0e-300` and real(wp) unit roundoff for + !! the machine + real(wp),intent(out) :: f !! integral of `bf(x)` on `(x1,x2)` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + real(wp),dimension(3_ip*kx),intent(inout) :: w0 !! work array for [[dbfqad]] + + call dbfqad(fun,tx,bcoef,nx,kx,idx,x1,x2,tol,f,iflag,w0) + + end subroutine db1fqad +!***************************************************************************************** + +!***************************************************************************************** +!> +! Determines the parameters of a function that interpolates +! the two-dimensional gridded data +! $$ [x(i),y(j),\mathrm{fcn}(i,j)] ~\mathrm{for}~ i=1,..,n_x ~\mathrm{and}~ j=1,..,n_y $$ +! The interpolating function and its derivatives may +! subsequently be evaluated by the function [[db2val]]. +! +! The interpolating function is a piecewise polynomial function +! represented as a tensor product of one-dimensional b-splines. the +! form of this function is +! +! $$ s(x,y) = \sum_{i=1}^{n_x} \sum_{j=1}^{n_y} a_{ij} u_i(x) v_j(y) $$ +! +! where the functions \(u_i\) and \(v_j\) are one-dimensional b-spline +! basis functions. the coefficients \( a_{ij} \) are chosen so that +! +! $$ s(x(i),y(j)) = \mathrm{fcn}(i,j) ~\mathrm{for}~ i=1,..,n_x ~\mathrm{and}~ j=1,..,n_y $$ +! +! Note that for each fixed value of \(y\), \( s(x,y) \) is a piecewise +! polynomial function of \(x\) alone, and for each fixed value of \(x\), \( s(x,y) \) +! is a piecewise polynomial function of \(y\) alone. in one dimension +! a piecewise polynomial may be created by partitioning a given +! interval into subintervals and defining a distinct polynomial piece +! on each one. the points where adjacent subintervals meet are called +! knots. each of the functions \(u_i\) and \(v_j\) above is a piecewise +! polynomial. +! +! Users of [[db2ink]] choose the order (degree+1) of the polynomial +! pieces used to define the piecewise polynomial in each of the \(x\) and +! \(y\) directions (`kx` and `ky`). users also may define their own knot +! sequence in \(x\) and \(y\) separately (`tx` and `ty`). if `iflag=0`, however, +! [[db2ink]] will choose sequences of knots that result in a piecewise +! polynomial interpolant with `kx-2` continuous partial derivatives in +! \(x\) and `ky-2` continuous partial derivatives in \(y\). (`kx` knots are taken +! near each endpoint in the \(x\) direction, not-a-knot end conditions +! are used, and the remaining knots are placed at data points if `kx` +! is even or at midpoints between data points if `kx` is odd. the \(y\) +! direction is treated similarly.) +! +! After a call to [[db2ink]], all information necessary to define the +! interpolating function are contained in the parameters `nx`, `ny`, `kx`, +! `ky`, `tx`, `ty`, and `bcoef`. These quantities should not be altered until +! after the last call of the evaluation routine [[db2val]]. +! +!### History +! * Boisvert, Ronald, NBS : 25 may 1982 : Author of original routine. +! * JEC : 000330 modified array declarations. +! * Jacob Williams, 2/24/2015 : extensive refactoring of CMLIB routine. + + pure subroutine db2ink(x,nx,y,ny,fcn,kx,ky,iknot,tx,ty,bcoef,iflag) + + implicit none + + integer(ip),intent(in) :: nx !! Number of \(x\) abcissae + integer(ip),intent(in) :: ny !! Number of \(y\) abcissae + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. Must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. Must be strictly increasing. + real(wp),dimension(:,:),intent(in) :: fcn !! `(nx,ny)` matrix of function values to interpolate. + !! `fcn(i,j)` should contain the function value at the + !! point (`x(i)`,`y(j)`) + integer(ip),intent(in) :: iknot !! knot sequence flag: + !! + !! * 0 = knot sequence chosen by [[db1ink]]. + !! * 1 = knot sequence chosen by user. + real(wp),dimension(:),intent(inout) :: tx !! The `(nx+kx)` knots in the \(x\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db2ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: ty !! The `(ny+ky)` knots in the \(y\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db2ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:,:),intent(out) :: bcoef !! `(nx,ny)` matrix of coefficients of the b-spline interpolant. + integer(ip),intent(out) :: iflag !! * 0 = successful execution. + !! * 2 = `iknot` out of range. + !! * 3 = `nx` out of range. + !! * 4 = `kx` out of range. + !! * 5 = `x` not strictly increasing. + !! * 6 = `tx` not non-decreasing. + !! * 7 = `ny` out of range. + !! * 8 = `ky` out of range. + !! * 9 = `y` not strictly increasing. + !! * 10 = `ty` not non-decreasing. + !! * 700 = `size(x)` \( \ne \) `size(fcn,1)` + !! * 701 = `size(y)` \( \ne \) `size(fcn,2)` + !! * 706 = `size(x)` \( \ne \) `nx` + !! * 707 = `size(y)` \( \ne \) `ny` + !! * 712 = `size(tx)` \( \ne \) `nx+kx` + !! * 713 = `size(ty)` \( \ne \) `ny+ky` + !! * 800 = `size(x)` \( \ne \) `size(bcoef,1)` + !! * 801 = `size(y)` \( \ne \) `size(bcoef,2)` + + logical :: status_ok + real(wp),dimension(:),allocatable :: temp !! work array of length `nx*ny` + real(wp),dimension(:),allocatable :: work !! work array of length `max(2*kx*(nx+1),2*ky*(ny+1))` + + !check validity of inputs + + call check_inputs( iknot,& + iflag,& + nx=nx,ny=ny,& + kx=kx,ky=ky,& + x=x,y=y,& + tx=tx,ty=ty,& + f2=fcn,& + bcoef2=bcoef,& + status_ok=status_ok) + + if (status_ok) then + + !choose knots + if (iknot == 0_ip) then + call dbknot(x,nx,kx,tx) + call dbknot(y,ny,ky,ty) + end if + + allocate(temp(nx*ny)) + allocate(work(max(2_ip*kx*(nx+1_ip),2_ip*ky*(ny+1_ip)))) + + !construct b-spline coefficients + call dbtpcf(x,nx,fcn, nx,ny,tx,kx,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(y,ny,temp,ny,nx,ty,ky,bcoef,work,iflag) + + deallocate(temp) + deallocate(work) + + end if + + end subroutine db2ink +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluates the tensor product piecewise polynomial +! interpolant constructed by the routine [[db2ink]] or one of its +! derivatives at the point (`xval`,`yval`). +! +! To evaluate the interpolant +! itself, set `idx=idy=0`, to evaluate the first partial with respect +! to `x`, set `idx=1,idy=0`, and so on. +! +! [[db2val]] returns 0.0 if `(xval,yval)` is out of range. that is, if +!```fortran +! xval < tx(1) .or. xval > tx(nx+kx) .or. +! yval < ty(1) .or. yval > ty(ny+ky) +!``` +! if the knots tx and ty were chosen by [[db2ink]], then this is equivalent to: +!```fortran +! xval < x(1) .or. xval > x(nx)+epsx .or. +! yval < y(1) .or. yval > y(ny)+epsy +!``` +! where +!```fortran +! epsx = 0.1*(x(nx)-x(nx-1)) +! epsy = 0.1*(y(ny)-y(ny-1)) +!``` +! +! The input quantities `tx`, `ty`, `nx`, `ny`, `kx`, `ky`, and `bcoef` should be +! unchanged since the last call of [[db2ink]]. +! +!### History +! * Boisvert, Ronald, NBS : 25 may 1982 : Author of original routine. +! * JEC : 000330 modified array declarations. +! * Jacob Williams, 2/24/2015 : extensive refactoring of CMLIB routine. + + pure subroutine db2val(xval,yval,idx,idy,tx,ty,nx,ny,kx,ky,bcoef,f,iflag,inbvx,inbvy,iloy,w1,w0,extrap) + + implicit none + + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: nx !! the number of interpolation points in \(x\). + !! (same as in last call to [[db2ink]]) + integer(ip),intent(in) :: ny !! the number of interpolation points in \(y\). + !! (same as in last call to [[db2ink]]) + integer(ip),intent(in) :: kx !! order of polynomial pieces in \(x\). + !! (same as in last call to [[db2ink]]) + integer(ip),intent(in) :: ky !! order of polynomial pieces in \(y\). + !! (same as in last call to [[db2ink]]) + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),dimension(nx+kx),intent(in) :: tx !! sequence of knots defining the piecewise polynomial + !! in the \(x\) direction. + !! (same as in last call to [[db2ink]]) + real(wp),dimension(ny+ky),intent(in) :: ty !! sequence of knots defining the piecewise + !! polynomial in the \(y\) direction. + !! (same as in last call to [[db2ink]]) + real(wp),dimension(nx,ny),intent(in) :: bcoef !! the b-spline coefficients computed by [[db2ink]]. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + integer(ip),intent(inout) :: inbvx !! initialization parameter which must be set to 1 + !! the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvy !! initialization parameter which must be set to 1 + !! the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloy !! initialization parameter which must be set to 1 + !! the first time this routine is called, + !! and must not be changed by the user. + real(wp),dimension(ky),intent(inout) :: w1 !! work array + real(wp),dimension(3_ip*max(kx,ky)),intent(inout) :: w0 !! work array + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + integer(ip) :: k, lefty, kcol + + f = 0.0_wp + + iflag = check_value(xval,tx,1_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(yval,ty,2_ip,extrap); if (iflag/=0_ip) return + + call dintrv(ty,ny+ky,yval,iloy,lefty,iflag,extrap); if (iflag/=0_ip) return + + kcol = lefty - ky + do k=1_ip,ky + kcol = kcol + 1_ip + call dbvalu(tx,bcoef(:,kcol),nx,kx,idx,xval,inbvx,w0,iflag,w1(k),extrap) + if (iflag/=0_ip) return !error + end do + + kcol = lefty - ky + 1_ip + call dbvalu(ty(kcol:),w1,ky,ky,idy,yval,inbvy,w0,iflag,f,extrap) + + end subroutine db2val +!***************************************************************************************** + +!***************************************************************************************** +!> +! Determines the parameters of a function that interpolates +! the three-dimensional gridded data +! $$ [x(i),y(j),z(k),\mathrm{fcn}(i,j,k)] ~\mathrm{for}~ +! i=1,..,n_x ~\mathrm{and}~ j=1,..,n_y, ~\mathrm{and}~ k=1,..,n_z $$ +! The interpolating function and +! its derivatives may subsequently be evaluated by the function +! [[db3val]]. +! +! The interpolating function is a piecewise polynomial function +! represented as a tensor product of one-dimensional b-splines. the +! form of this function is +! $$ s(x,y,z) = \sum_{i=1}^{n_x} \sum_{j=1}^{n_y} \sum_{k=1}^{n_z} +! a_{ijk} u_i(x) v_j(y) w_k(z) $$ +! +! where the functions \(u_i\), \(v_j\), and \(w_k\) are one-dimensional b- +! spline basis functions. the coefficients \(a_{ijk}\) are chosen so that: +! +! $$ s(x(i),y(j),z(k)) = \mathrm{fcn}(i,j,k) +! ~\mathrm{for}~ i=1,..,n_x , j=1,..,n_y , k=1,..,n_z $$ +! +! Note that for fixed values of \(y\) and \(z\) \(s(x,y,z)\) is a piecewise +! polynomial function of \(x\) alone, for fixed values of \(x\) and \(z\) \(s(x,y,z)\) +! is a piecewise polynomial function of \(y\) alone, and for fixed +! values of \(x\) and \(y\) \(s(x,y,z)\) is a function of \(z\) alone. in one +! dimension a piecewise polynomial may be created by partitioning a +! given interval into subintervals and defining a distinct polynomial +! piece on each one. the points where adjacent subintervals meet are +! called knots. each of the functions \(u_i\), \(v_j\), and \(w_k\) above is a +! piecewise polynomial. +! +! Users of [[db3ink]] choose the order (degree+1) of the polynomial +! pieces used to define the piecewise polynomial in each of the \(x\), \(y\), +! and \(z\) directions (`kx`, `ky`, and `kz`). users also may define their own +! knot sequence in \(x\), \(y\), \(z\) separately (`tx`, `ty`, and `tz`). if `iflag=0`, +! however, [[db3ink]] will choose sequences of knots that result in a +! piecewise polynomial interpolant with `kx-2` continuous partial +! derivatives in \(x\), `ky-2` continuous partial derivatives in \(y\), and `kz-2` +! continuous partial derivatives in \(z\). (`kx` knots are taken near +! each endpoint in \(x\), not-a-knot end conditions are used, and the +! remaining knots are placed at data points if `kx` is even or at +! midpoints between data points if `kx` is odd. the \(y\) and \(z\) directions +! are treated similarly.) +! +! After a call to [[db3ink]], all information necessary to define the +! interpolating function are contained in the parameters `nx`, `ny`, `nz`, +! `kx`, `ky`, `kz`, `tx`, `ty`, `tz`, and `bcoef`. these quantities should not be +! altered until after the last call of the evaluation routine [[db3val]]. +! +!### History +! * Boisvert, Ronald, NBS : 25 may 1982 : Author of original routine. +! * JEC : 000330 modified array declarations. +! * Jacob Williams, 2/24/2015 : extensive refactoring of CMLIB routine. + + pure subroutine db3ink(x,nx,y,ny,z,nz,fcn,kx,ky,kz,iknot,tx,ty,tz,bcoef,iflag) + + implicit none + + integer(ip),intent(in) :: nx !! number of \(x\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: ny !! number of \(y\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nz !! number of \(z\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: kx !! The order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! The order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! the order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. must be strictly increasing. + real(wp),dimension(:,:,:),intent(in) :: fcn !! `(nx,ny,nz)` matrix of function values to interpolate. `fcn(i,j,k)` should + !! contain the function value at the point (`x(i)`,`y(j)`,`z(k)`) + integer(ip),intent(in) :: iknot !! knot sequence flag: + !! + !! * 0 = knot sequence chosen by [[db3ink]]. + !! * 1 = knot sequence chosen by user. + real(wp),dimension(:),intent(inout) :: tx !! The `(nx+kx)` knots in the \(x\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db3ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: ty !! The `(ny+ky)` knots in the \(y\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db3ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tz !! The `(nz+kz)` knots in the \(z\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db3ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:,:,:),intent(out) :: bcoef !! `(nx,ny,nz)` matrix of coefficients of the b-spline interpolant. + integer(ip),intent(out) :: iflag !! * 0 = successful execution. + !! * 2 = `iknot` out of range. + !! * 3 = `nx` out of range. + !! * 4 = `kx` out of range. + !! * 5 = `x` not strictly increasing. + !! * 6 = `tx` not non-decreasing. + !! * 7 = `ny` out of range. + !! * 8 = `ky` out of range. + !! * 9 = `y` not strictly increasing. + !! * 10 = `ty` not non-decreasing. + !! * 11 = `nz` out of range. + !! * 12 = `kz` out of range. + !! * 13 = `z` not strictly increasing. + !! * 14 = `ty` not non-decreasing. + !! * 700 = `size(x) ` \(\ne\) `size(fcn,1)` + !! * 701 = `size(y) ` \(\ne\) `size(fcn,2)` + !! * 702 = `size(z) ` \(\ne\) `size(fcn,3)` + !! * 706 = `size(x) ` \(\ne\) `nx` + !! * 707 = `size(y) ` \(\ne\) `ny` + !! * 708 = `size(z) ` \(\ne\) `nz` + !! * 712 = `size(tx)` \(\ne\) `nx+kx` + !! * 713 = `size(ty)` \(\ne\) `ny+ky` + !! * 714 = `size(tz)` \(\ne\) `nz+kz` + !! * 800 = `size(x) ` \(\ne\) `size(bcoef,1)` + !! * 801 = `size(y) ` \(\ne\) `size(bcoef,2)` + !! * 802 = `size(z) ` \(\ne\) `size(bcoef,3)` + + logical :: status_ok + real(wp),dimension(:),allocatable :: temp !! work array of length `nx*ny*nz` + real(wp),dimension(:),allocatable :: work !! work array of length `max(2*kx*(nx+1), + !! 2*ky*(ny+1),2*kz*(nz+1))` + integer(ip) :: i, j, k, ii !! counter + + ! check validity of input + + call check_inputs( iknot,& + iflag,& + nx=nx,ny=ny,nz=nz,& + kx=kx,ky=ky,kz=kz,& + x=x,y=y,z=z,& + tx=tx,ty=ty,tz=tz,& + f3=fcn,& + bcoef3=bcoef,& + status_ok=status_ok) + + if (status_ok) then + + ! choose knots + if (iknot == 0_ip) then + call dbknot(x,nx,kx,tx) + call dbknot(y,ny,ky,ty) + call dbknot(z,nz,kz,tz) + end if + + allocate(temp(nx*ny*nz)) + allocate(work(max(2_ip*kx*(nx+1_ip),2_ip*ky*(ny+1_ip),2_ip*kz*(nz+1_ip)))) + + ! copy fcn to work in packed for dbtpcf + !temp = reshape( fcn, [nx*ny*nz] ) + ! replaced with loops to avoid stack + ! overflow for large data set: + ii = 0_ip + do k = 1_ip, nz + do j = 1_ip, ny + do i = 1_ip, nx + ii = ii + 1_ip + temp(ii) = fcn(i,j,k) + end do + end do + end do + + ! construct b-spline coefficients + call dbtpcf(x,nx,temp, nx,ny*nz,tx,kx,bcoef,work,iflag) + if (iflag==0_ip) call dbtpcf(y,ny,bcoef,ny,nx*nz,ty,ky,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(z,nz,temp, nz,nx*ny,tz,kz,bcoef,work,iflag) + + deallocate(temp) + deallocate(work) + + end if + + end subroutine db3ink +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluates the tensor product piecewise polynomial +! interpolant constructed by the routine [[db3ink]] or one of its +! derivatives at the point (`xval`,`yval`,`zval`). +! +! To evaluate the +! interpolant itself, set `idx=idy=idz=0`, to evaluate the first +! partial with respect to `x`, set `idx=1`,`idy=idz=0`, and so on. +! +! [[db3val]] returns 0.0 if (`xval`,`yval`,`zval`) is out of range. that is, +!```fortran +! xvaltx(nx+kx) .or. +! yvalty(ny+ky) .or. +! zvaltz(nz+kz) +!``` +! if the knots `tx`, `ty`, and `tz` were chosen by [[db3ink]], then this is +! equivalent to +!```fortran +! xvalx(nx)+epsx .or. +! yvaly(ny)+epsy .or. +! zvalz(nz)+epsz +!``` +! where +!```fortran +! epsx = 0.1*(x(nx)-x(nx-1)) +! epsy = 0.1*(y(ny)-y(ny-1)) +! epsz = 0.1*(z(nz)-z(nz-1)) +!``` +! +! The input quantities `tx`, `ty`, `tz`, `nx`, `ny`, `nz`, `kx`, `ky`, `kz`, and `bcoef` +! should remain unchanged since the last call of [[db3ink]]. +! +!### History +! * Boisvert, Ronald, NBS : 25 may 1982 : Author of original routine. +! * JEC : 000330 modified array declarations. +! * Jacob Williams, 2/24/2015 : extensive refactoring of CMLIB routine. + + pure subroutine db3val(xval,yval,zval,idx,idy,idz,& + tx,ty,tz,& + nx,ny,nz,kx,ky,kz,bcoef,f,iflag,& + inbvx,inbvy,inbvz,iloy,iloz,w2,w1,w0,extrap) + + implicit none + + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: nx !! the number of interpolation points in \(x\). + !! (same as in last call to [[db3ink]]) + integer(ip),intent(in) :: ny !! the number of interpolation points in \(y\). + !! (same as in last call to [[db3ink]]) + integer(ip),intent(in) :: nz !! the number of interpolation points in \(z\). + !! (same as in last call to [[db3ink]]) + integer(ip),intent(in) :: kx !! order of polynomial pieces in \(z\). + !! (same as in last call to [[db3ink]]) + integer(ip),intent(in) :: ky !! order of polynomial pieces in \(y\). + !! (same as in last call to [[db3ink]]) + integer(ip),intent(in) :: kz !! order of polynomial pieces in \(z\). + !! (same as in last call to [[db3ink]]) + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + real(wp),dimension(nx+kx),intent(in) :: tx !! sequence of knots defining the piecewise polynomial + !! in the \(x\) direction. (same as in last call to [[db3ink]]) + real(wp),dimension(ny+ky),intent(in) :: ty !! sequence of knots defining the piecewise polynomial + !! in the \(y\) direction. (same as in last call to [[db3ink]]) + real(wp),dimension(nz+kz),intent(in) :: tz !! sequence of knots defining the piecewise polynomial + !! in the \(z\) direction. (same as in last call to [[db3ink]]) + real(wp),dimension(nx,ny,nz),intent(in) :: bcoef !! the b-spline coefficients computed by [[db3ink]]. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + integer(ip),intent(inout) :: inbvx !! initialization parameter which must be + !! set to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvy !! initialization parameter which must be + !! set to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvz !! initialization parameter which must be + !! set to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloy !! initialization parameter which must be + !! set to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloz !! initialization parameter which must be + !! set to 1 the first time this routine is called, + !! and must not be changed by the user. + real(wp),dimension(ky,kz),intent(inout) :: w2 !! work array + real(wp),dimension(kz),intent(inout) :: w1 !! work array + real(wp),dimension(3_ip*max(kx,ky,kz)),intent(inout) :: w0 !! work array + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + integer(ip) :: lefty, leftz, kcoly, kcolz, j, k + + f = 0.0_wp + + iflag = check_value(xval,tx,1_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(yval,ty,2_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(zval,tz,3_ip,extrap); if (iflag/=0_ip) return + + call dintrv(ty,ny+ky,yval,iloy,lefty,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tz,nz+kz,zval,iloz,leftz,iflag,extrap); if (iflag/=0_ip) return + + iflag = 0_ip + + kcolz = leftz - kz + do k=1_ip,kz + kcolz = kcolz + 1_ip + kcoly = lefty - ky + do j=1_ip,ky + kcoly = kcoly + 1_ip + call dbvalu(tx,bcoef(:,kcoly,kcolz),nx,kx,idx,xval,inbvx,w0,iflag,w2(j,k),extrap) + if (iflag/=0_ip) return + end do + end do + + kcoly = lefty - ky + 1_ip + do k=1_ip,kz + call dbvalu(ty(kcoly:),w2(:,k),ky,ky,idy,yval,inbvy,w0,iflag,w1(k),extrap) + if (iflag/=0_ip) return + end do + + kcolz = leftz - kz + 1_ip + call dbvalu(tz(kcolz:),w1,kz,kz,idz,zval,inbvz,w0,iflag,f,extrap) + + end subroutine db3val +!***************************************************************************************** + +!***************************************************************************************** +!> +! Determines the parameters of a function that interpolates +! the four-dimensional gridded data +! $$ [x(i),y(j),z(k),q(l),\mathrm{fcn}(i,j,k,l)] ~\mathrm{for}~ +! i=1,..,n_x ~\mathrm{and}~ j=1,..,n_y, ~\mathrm{and}~ k=1,..,n_z, +! ~\mathrm{and}~ l=1,..,n_q $$ +! The interpolating function and its derivatives may +! subsequently be evaluated by the function [[db4val]]. +! +! See [[db3ink]] header for more details. +! +!### History +! * Jacob Williams, 2/24/2015 : Created this routine. + + pure subroutine db4ink(x,nx,y,ny,z,nz,q,nq,& + fcn,& + kx,ky,kz,kq,& + iknot,& + tx,ty,tz,tq,& + bcoef,iflag) + + implicit none + + integer(ip),intent(in) :: nx !! number of \(x\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: ny !! number of \(y\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nz !! number of \(z\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nq !! number of \(q\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: kx !! the order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ). + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! the order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ). + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! the order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ). + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! the order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ). + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. must be strictly increasing. + real(wp),dimension(:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq)` matrix of function values to interpolate. + !! `fcn(i,j,k,q)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`) + integer(ip),intent(in) :: iknot !! knot sequence flag: + !! + !! * 0 = knot sequence chosen by [[db4ink]]. + !! * 1 = knot sequence chosen by user. + real(wp),dimension(:),intent(inout) :: tx !! The `(nx+kx)` knots in the x direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db4ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: ty !! The `(ny+ky)` knots in the y direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db4ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tz !! The `(nz+kz)` knots in the z direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db4ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tq !! The `(nq+kq)` knots in the q direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db4ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:,:,:,:),intent(out) :: bcoef !! `(nx,ny,nz,nq)` matrix of coefficients of the b-spline + !! interpolant. + integer(ip),intent(out) :: iflag !! * 0 = successful execution. + !! * 2 = `iknot` out of range. + !! * 3 = `nx` out of range. + !! * 4 = `kx` out of range. + !! * 5 = `x` not strictly increasing. + !! * 6 = `tx` not non-decreasing. + !! * 7 = `ny` out of range. + !! * 8 = `ky` out of range. + !! * 9 = `y` not strictly increasing. + !! * 10 = `ty` not non-decreasing. + !! * 11 = `nz` out of range. + !! * 12 = `kz` out of range. + !! * 13 = `z` not strictly increasing. + !! * 14 = `tz` not non-decreasing. + !! * 15 = `nq` out of range. + !! * 16 = `kq` out of range. + !! * 17 = `q` not strictly increasing. + !! * 18 = `tq` not non-decreasing. + !! * 700 = `size(x)` \( \ne \) `size(fcn,1)` + !! * 701 = `size(y)` \( \ne \) `size(fcn,2)` + !! * 702 = `size(z)` \( \ne \) `size(fcn,3)` + !! * 703 = `size(q)` \( \ne \) `size(fcn,4)` + !! * 706 = `size(x)` \( \ne \) `nx` + !! * 707 = `size(y)` \( \ne \) `ny` + !! * 708 = `size(z)` \( \ne \) `nz` + !! * 709 = `size(q)` \( \ne \) `nq` + !! * 712 = `size(tx`) \( \ne \) `nx+kx` + !! * 713 = `size(ty`) \( \ne \) `ny+ky` + !! * 714 = `size(tz`) \( \ne \) `nz+kz` + !! * 715 = `size(tq`) \( \ne \) `nq+kq` + !! * 800 = `size(x)` \( \ne \) `size(bcoef,1)` + !! * 801 = `size(y)` \( \ne \) `size(bcoef,2)` + !! * 802 = `size(z)` \( \ne \) `size(bcoef,3)` + !! * 803 = `size(q)` \( \ne \) `size(bcoef,4)` + + logical :: status_ok + real(wp),dimension(:),allocatable :: temp !! work array of dimension `nx*ny*nz*nq` + real(wp),dimension(:),allocatable :: work !! work array of dimension `max(2*kx*(nx+1), + !! 2*ky*(ny+1),2*kz*(nz+1),2*kq*(nq+1))` + + ! check validity of input + + call check_inputs( iknot,& + iflag,& + nx=nx,ny=ny,nz=nz,nq=nq,& + kx=kx,ky=ky,kz=kz,kq=kq,& + x=x,y=y,z=z,q=q,& + tx=tx,ty=ty,tz=tz,tq=tq,& + f4=fcn,& + bcoef4=bcoef,& + status_ok=status_ok) + + if (status_ok) then + + ! choose knots + if (iknot == 0_ip) then + call dbknot(x,nx,kx,tx) + call dbknot(y,ny,ky,ty) + call dbknot(z,nz,kz,tz) + call dbknot(q,nq,kq,tq) + end if + + allocate(temp(nx*ny*nz*nq)) + allocate(work(max(2_ip*kx*(nx+1_ip),2_ip*ky*(ny+1_ip),2_ip*kz*(nz+1_ip),2_ip*kq*(nq+1_ip)))) + + ! construct b-spline coefficients + call dbtpcf(x,nx,fcn, nx,ny*nz*nq,tx,kx,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(y,ny,temp, ny,nx*nz*nq,ty,ky,bcoef,work,iflag) + if (iflag==0_ip) call dbtpcf(z,nz,bcoef,nz,nx*ny*nq,tz,kz,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(q,nq,temp, nq,nx*ny*nz,tq,kq,bcoef,work,iflag) + + deallocate(temp) + deallocate(work) + + end if + + end subroutine db4ink +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluates the tensor product piecewise polynomial +! interpolant constructed by the routine [[db4ink]] or one of its +! derivatives at the point (`xval`,`yval`,`zval`,`qval`). +! +! To evaluate the +! interpolant itself, set `idx=idy=idz=idq=0`, to evaluate the first +! partial with respect to `x`, set `idx=1,idy=idz=idq=0`, and so on. +! +! See [[db3val]] header for more information. +! +!### History +! * Jacob Williams, 2/24/2015 : Created this routine. + + pure subroutine db4val(xval,yval,zval,qval,& + idx,idy,idz,idq,& + tx,ty,tz,tq,& + nx,ny,nz,nq,& + kx,ky,kz,kq,& + bcoef,f,iflag,& + inbvx,inbvy,inbvz,inbvq,& + iloy,iloz,iloq,w3,w2,w1,w0,extrap) + + implicit none + + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idq !! \(q\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: nx !! the number of interpolation points in \(x\). + !! (same as in last call to [[db4ink]]) + integer(ip),intent(in) :: ny !! the number of interpolation points in \(y\). + !! (same as in last call to [[db4ink]]) + integer(ip),intent(in) :: nz !! the number of interpolation points in \(z\). + !! (same as in last call to [[db4ink]]) + integer(ip),intent(in) :: nq !! the number of interpolation points in \(q\). + !! (same as in last call to [[db4ink]]) + integer(ip),intent(in) :: kx !! order of polynomial pieces in \(x\). + !! (same as in last call to [[db4ink]]) + integer(ip),intent(in) :: ky !! order of polynomial pieces in \(y\). + !! (same as in last call to [[db4ink]]) + integer(ip),intent(in) :: kz !! order of polynomial pieces in \(z\). + !! (same as in last call to [[db4ink]]) + integer(ip),intent(in) :: kq !! order of polynomial pieces in \(q\). + !! (same as in last call to [[db4ink]]) + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + real(wp),intent(in) :: qval !! \(q\) coordinate of evaluation point. + real(wp),dimension(nx+kx),intent(in) :: tx !! sequence of knots defining the piecewise polynomial + !! in the \(x\) direction. (same as in last call to + !! [[db4ink]]) + real(wp),dimension(ny+ky),intent(in) :: ty !! sequence of knots defining the piecewise polynomial + !! in the \(y\) direction. (same as in last call to + !! [[db4ink]]) + real(wp),dimension(nz+kz),intent(in) :: tz !! sequence of knots defining the piecewise polynomial + !! in the \(z\) direction. (same as in last call to + !! [[db4ink]]) + real(wp),dimension(nq+kq),intent(in) :: tq !! sequence of knots defining the piecewise polynomial + !! in the \(q\) direction. (same as in last call to + !! [[db4ink]]) + real(wp),dimension(nx,ny,nz,nq),intent(in) :: bcoef !! the b-spline coefficients computed by [[db4ink]]. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + integer(ip),intent(inout) :: inbvx !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvy !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvz !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvq !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloy !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloz !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloq !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + real(wp),dimension(ky,kz,kq),intent(inout) :: w3 !! work array + real(wp),dimension(kz,kq),intent(inout) :: w2 !! work array + real(wp),dimension(kq),intent(inout) :: w1 !! work array + real(wp),dimension(3_ip*max(kx,ky,kz,kq)),intent(inout) :: w0 !! work array + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + integer(ip) :: lefty, leftz, leftq, & + kcoly, kcolz, kcolq, j, k, q + + f = 0.0_wp + + iflag = check_value(xval,tx,1_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(yval,ty,2_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(zval,tz,3_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(qval,tq,4_ip,extrap); if (iflag/=0_ip) return + + call dintrv(ty,ny+ky,yval,iloy,lefty,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tz,nz+kz,zval,iloz,leftz,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tq,nq+kq,qval,iloq,leftq,iflag,extrap); if (iflag/=0_ip) return + + iflag = 0_ip + + ! x -> y, z, q + kcolq = leftq - kq + do q=1_ip,kq + kcolq = kcolq + 1_ip + kcolz = leftz - kz + do k=1_ip,kz + kcolz = kcolz + 1_ip + kcoly = lefty - ky + do j=1_ip,ky + kcoly = kcoly + 1_ip + call dbvalu(tx,bcoef(:,kcoly,kcolz,kcolq),& + nx,kx,idx,xval,inbvx,w0,iflag,& + w3(j,k,q),extrap) + if (iflag/=0_ip) return + end do + end do + end do + + ! y -> z, q + kcoly = lefty - ky + 1_ip + do q=1_ip,kq + do k=1_ip,kz + call dbvalu(ty(kcoly:),w3(:,k,q),& + ky,ky,idy,yval,inbvy,w0,iflag,& + w2(k,q),extrap) + if (iflag/=0_ip) return + end do + end do + + ! z -> q + kcolz = leftz - kz + 1_ip + do q=1_ip,kq + call dbvalu(tz(kcolz:),w2(:,q),& + kz,kz,idz,zval,inbvz,w0,iflag,& + w1(q),extrap) + if (iflag/=0_ip) return + end do + + ! q + kcolq = leftq - kq + 1_ip + call dbvalu(tq(kcolq:),w1,kq,kq,idq,qval,inbvq,w0,iflag,f,extrap) + + end subroutine db4val +!***************************************************************************************** + +!***************************************************************************************** +!> +! Determines the parameters of a function that interpolates +! the five-dimensional gridded data: +! +! $$ [x(i),y(j),z(k),q(l),r(m),\mathrm{fcn}(i,j,k,l,m)] $$ +! +! for: +! +! $$ i=1,..,n_x ~\mathrm{and}~ j=1,..,n_y, ~\mathrm{and}~ k=1,..,n_z, +! ~\mathrm{and}~ l=1,..,n_q, ~\mathrm{and}~ m=1,..,n_r $$ +! +! The interpolating function and its derivatives may subsequently be evaluated +! by the function [[db5val]]. +! +! See [[db3ink]] header for more details. +! +!### History +! * Jacob Williams, 2/24/2015 : Created this routine. + + pure subroutine db5ink(x,nx,y,ny,z,nz,q,nq,r,nr,& + fcn,& + kx,ky,kz,kq,kr,& + iknot,& + tx,ty,tz,tq,tr,& + bcoef,iflag) + + implicit none + + integer(ip),intent(in) :: nx !! number of \(x\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: ny !! number of \(y\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nz !! number of \(z\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nq !! number of \(q\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nr !! number of \(r\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: kx !! the order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ). + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! the order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ). + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! the order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ). + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! the order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ). + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! the order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ). + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. must be strictly increasing. + real(wp),dimension(:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr)` matrix of function values to interpolate. + !! `fcn(i,j,k,q,r)` should contain the function value at the + !! point (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`) + integer(ip),intent(in) :: iknot !! knot sequence flag: + !! + !! * 0 = knot sequence chosen by [[db5ink]]. + !! * 1 = knot sequence chosen by user. + real(wp),dimension(:),intent(inout) :: tx !! The `(nx+kx)` knots in the \(x\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db5ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: ty !! The `(ny+ky)` knots in the \(y\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db5ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tz !! The `(nz+kz)` knots in the \(z\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db5ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tq !! The `(nq+kq)` knots in the \(q\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db5ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tr !! The `(nr+kr)` knots in the \(r\) direction for the spline + !! interpolant. + !! + !! * If `iknot=0` these are chosen by [[db5ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:,:,:,:,:),intent(out) :: bcoef !! `(nx,ny,nz,nq,nr)` matrix of coefficients of the b-spline + !! interpolant. + integer(ip),intent(out) :: iflag !! * 0 = successful execution. + !! * 2 = `iknot` out of range. + !! * 3 = `nx` out of range. + !! * 4 = `kx` out of range. + !! * 5 = `x` not strictly increasing. + !! * 6 = `tx` not non-decreasing. + !! * 7 = `ny` out of range. + !! * 8 = `ky` out of range. + !! * 9 = `y` not strictly increasing. + !! * 10 = `ty` not non-decreasing. + !! * 11 = `nz` out of range. + !! * 12 = `kz` out of range. + !! * 13 = `z` not strictly increasing. + !! * 14 = `tz` not non-decreasing. + !! * 15 = `nq` out of range. + !! * 16 = `kq` out of range. + !! * 17 = `q` not strictly increasing. + !! * 18 = `tq` not non-decreasing. + !! * 19 = `nr` out of range. + !! * 20 = `kr` out of range. + !! * 21 = `r` not strictly increasing. + !! * 22 = `tr` not non-decreasing. + !! * 700 = `size(x)` \( \ne \) `size(fcn,1)` + !! * 701 = `size(y)` \( \ne \) `size(fcn,2)` + !! * 702 = `size(z)` \( \ne \) `size(fcn,3)` + !! * 703 = `size(q)` \( \ne \) `size(fcn,4)` + !! * 704 = `size(r)` \( \ne \) `size(fcn,5)` + !! * 706 = `size(x)` \( \ne \) `nx` + !! * 707 = `size(y)` \( \ne \) `ny` + !! * 708 = `size(z)` \( \ne \) `nz` + !! * 709 = `size(q)` \( \ne \) `nq` + !! * 710 = `size(r)` \( \ne \) `nr` + !! * 712 = `size(tx)` \( \ne \) `nx+kx` + !! * 713 = `size(ty)` \( \ne \) `ny+ky` + !! * 714 = `size(tz)` \( \ne \) `nz+kz` + !! * 715 = `size(tq)` \( \ne \) `nq+kq` + !! * 716 = `size(tr)` \( \ne \) `nr+kr` + !! * 800 = `size(x)` \( \ne \) `size(bcoef,1)` + !! * 801 = `size(y)` \( \ne \) `size(bcoef,2)` + !! * 802 = `size(z)` \( \ne \) `size(bcoef,3)` + !! * 803 = `size(q)` \( \ne \) `size(bcoef,4)` + !! * 804 = `size(r)` \( \ne \) `size(bcoef,5)` + + logical :: status_ok + real(wp),dimension(:),allocatable :: temp !! work array of length `nx*ny*nz*nq*nr` + real(wp),dimension(:),allocatable :: work !! work array of length `max(2*kx*(nx+1), + !! 2*ky*(ny+1),2*kz*(nz+1),2*kq*(nq+1),2*kr*(nr+1))` + integer(ip) :: i, j, k, l, m, ii !! counter + + ! check validity of input + call check_inputs( iknot,& + iflag,& + nx=nx,ny=ny,nz=nz,nq=nq,nr=nr,& + kx=kx,ky=ky,kz=kz,kq=kq,kr=kr,& + x=x,y=y,z=z,q=q,r=r,& + tx=tx,ty=ty,tz=tz,tq=tq,tr=tr,& + f5=fcn,& + bcoef5=bcoef,& + status_ok=status_ok) + + if (status_ok) then + + ! choose knots + if (iknot == 0_ip) then + call dbknot(x,nx,kx,tx) + call dbknot(y,ny,ky,ty) + call dbknot(z,nz,kz,tz) + call dbknot(q,nq,kq,tq) + call dbknot(r,nr,kr,tr) + end if + + allocate(temp(nx*ny*nz*nq*nr)) + allocate(work(max(2_ip*kx*(nx+1_ip),2_ip*ky*(ny+1_ip),2_ip*kz*(nz+1_ip),2_ip*kq*(nq+1_ip),2_ip*kr*(nr+1_ip)))) + + ! copy fcn to work in packed for dbtpcf + !temp(1:nx*ny*nz*nq*nr) = reshape( fcn, [nx*ny*nz*nq*nr] ) + ! replaced with loops to avoid stack + ! overflow for large data set: + ii = 0_ip + do m = 1_ip, nr + do l = 1_ip, nq + do k = 1_ip, nz + do j = 1_ip, ny + do i = 1_ip, nx + ii = ii + 1_ip + temp(ii) = fcn(i,j,k,l,m) + end do + end do + end do + end do + end do + + ! construct b-spline coefficients + call dbtpcf(x,nx,temp, nx,ny*nz*nq*nr,tx,kx,bcoef,work,iflag) + if (iflag==0_ip) call dbtpcf(y,ny,bcoef, ny,nx*nz*nq*nr,ty,ky,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(z,nz,temp, nz,nx*ny*nq*nr,tz,kz,bcoef,work,iflag) + if (iflag==0_ip) call dbtpcf(q,nq,bcoef, nq,nx*ny*nz*nr,tq,kq,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(r,nr,temp, nr,nx*ny*nz*nq,tr,kr,bcoef,work,iflag) + + deallocate(temp) + deallocate(work) + + end if + + end subroutine db5ink +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluates the tensor product piecewise polynomial +! interpolant constructed by the routine [[db5ink]] or one of its +! derivatives at the point (`xval`,`yval`,`zval`,`qval`,`rval`). +! +! To evaluate the +! interpolant itself, set `idx=idy=idz=idq=idr=0`, to evaluate the first +! partial with respect to `x`, set `idx=1,idy=idz=idq=idr=0,` and so on. +! +! See [[db3val]] header for more information. +! +!### History +! * Jacob Williams, 2/24/2015 : Created this routine. + + pure subroutine db5val(xval,yval,zval,qval,rval,& + idx,idy,idz,idq,idr,& + tx,ty,tz,tq,tr,& + nx,ny,nz,nq,nr,& + kx,ky,kz,kq,kr,& + bcoef,f,iflag,& + inbvx,inbvy,inbvz,inbvq,inbvr,& + iloy,iloz,iloq,ilor,& + w4,w3,w2,w1,w0,extrap) + + implicit none + + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idq !! \(q\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idr !! \(r\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: nx !! the number of interpolation points in \(x\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: ny !! the number of interpolation points in \(y\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: nz !! the number of interpolation points in \(z\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: nq !! the number of interpolation points in \(q\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: nr !! the number of interpolation points in \(r\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: kx !! order of polynomial pieces in \(x\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: ky !! order of polynomial pieces in \(y\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: kz !! order of polynomial pieces in \(z\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: kq !! order of polynomial pieces in \(q\). + !! (same as in last call to [[db5ink]]) + integer(ip),intent(in) :: kr !! order of polynomial pieces in \(r\). + !! (same as in last call to [[db5ink]]) + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + real(wp),intent(in) :: qval !! \(q\) coordinate of evaluation point. + real(wp),intent(in) :: rval !! \(r\) coordinate of evaluation point. + real(wp),dimension(nx+kx),intent(in) :: tx !! sequence of knots defining the piecewise polynomial + !! in the \(x\) direction. + !! (same as in last call to [[db5ink]]) + real(wp),dimension(ny+ky),intent(in) :: ty !! sequence of knots defining the piecewise polynomial + !! in the \(y\) direction. + !! (same as in last call to [[db5ink]]) + real(wp),dimension(nz+kz),intent(in) :: tz !! sequence of knots defining the piecewise polynomial + !! in the \(z\) direction. + !! (same as in last call to [[db5ink]]) + real(wp),dimension(nq+kq),intent(in) :: tq !! sequence of knots defining the piecewise polynomial + !! in the \(q\) direction. + !! (same as in last call to [[db5ink]]) + real(wp),dimension(nr+kr),intent(in) :: tr !! sequence of knots defining the piecewise polynomial + !! in the \(r\) direction. + !! (same as in last call to [[db5ink]]) + real(wp),dimension(nx,ny,nz,nq,nr),intent(in) :: bcoef !! the b-spline coefficients computed by [[db5ink]]. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + integer(ip),intent(inout) :: inbvx !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvy !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvz !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvq !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvr !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloy !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloz !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloq !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: ilor !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + real(wp),dimension(ky,kz,kq,kr),intent(inout) :: w4 !! work array + real(wp),dimension(kz,kq,kr),intent(inout) :: w3 !! work array + real(wp),dimension(kq,kr),intent(inout) :: w2 !! work array + real(wp),dimension(kr),intent(inout) :: w1 !! work array + real(wp),dimension(3_ip*max(kx,ky,kz,kq,kr)),intent(inout) :: w0 !! work array + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + integer(ip) :: lefty, leftz, leftq, leftr, & + kcoly, kcolz, kcolq, kcolr, j, k, q, r + + f = 0.0_wp + + iflag = check_value(xval,tx,1_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(yval,ty,2_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(zval,tz,3_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(qval,tq,4_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(rval,tr,5_ip,extrap); if (iflag/=0_ip) return + + call dintrv(ty,ny+ky,yval,iloy,lefty,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tz,nz+kz,zval,iloz,leftz,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tq,nq+kq,qval,iloq,leftq,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tr,nr+kr,rval,ilor,leftr,iflag,extrap); if (iflag/=0_ip) return + + iflag = 0_ip + + ! x -> y, z, q, r + kcolr = leftr - kr + do r=1_ip,kr + kcolr = kcolr + 1_ip + kcolq = leftq - kq + do q=1_ip,kq + kcolq = kcolq + 1_ip + kcolz = leftz - kz + do k=1_ip,kz + kcolz = kcolz + 1_ip + kcoly = lefty - ky + do j=1_ip,ky + kcoly = kcoly + 1_ip + call dbvalu(tx,bcoef(:,kcoly,kcolz,kcolq,kcolr),& + nx,kx,idx,xval,inbvx,w0,iflag,w4(j,k,q,r),& + extrap) + if (iflag/=0_ip) return + end do + end do + end do + end do + + ! y -> z, q, r + kcoly = lefty - ky + 1_ip + do r=1_ip,kr + do q=1_ip,kq + do k=1_ip,kz + call dbvalu(ty(kcoly:),w4(:,k,q,r),ky,ky,idy,yval,inbvy,& + w0,iflag,w3(k,q,r),extrap) + if (iflag/=0_ip) return + end do + end do + end do + + ! z -> q, r + kcolz = leftz - kz + 1_ip + do r=1_ip,kr + do q=1_ip,kq + call dbvalu(tz(kcolz:),w3(:,q,r),kz,kz,idz,zval,inbvz,& + w0,iflag,w2(q,r),extrap) + if (iflag/=0_ip) return + end do + end do + + ! q -> r + kcolq = leftq - kq + 1_ip + do r=1_ip,kr + call dbvalu(tq(kcolq:),w2(:,r),kq,kq,idq,qval,inbvq,& + w0,iflag,w1(r),extrap) + if (iflag/=0_ip) return + end do + + ! r + kcolr = leftr - kr + 1_ip + call dbvalu(tr(kcolr:),w1,kr,kr,idr,rval,inbvr,w0,iflag,f,extrap) + + end subroutine db5val +!***************************************************************************************** + +!***************************************************************************************** +!> +! Determines the parameters of a function that interpolates +! the six-dimensional gridded data: +! +! $$ [x(i),y(j),z(k),q(l),r(m),s(n),\mathrm{fcn}(i,j,k,l,m,n)] $$ +! +! for: +! +! $$ i=1,..,n_x, j=1,..,n_y, k=1,..,n_z, l=1,..,n_q, m=1,..,n_r, n=1,..,n_s $$ +! +! the interpolating function and its derivatives may subsequently be evaluated +! by the function [[db6val]]. +! +! See [[db3ink]] header for more details. +! +!### History +! * Jacob Williams, 2/24/2015 : Created this routine. + + pure subroutine db6ink(x,nx,y,ny,z,nz,q,nq,r,nr,s,ns,& + fcn,& + kx,ky,kz,kq,kr,ks,& + iknot,& + tx,ty,tz,tq,tr,ts,& + bcoef,iflag) + + implicit none + + integer(ip),intent(in) :: nx !! number of \(x\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: ny !! number of \(y\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nz !! number of \(z\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nq !! number of \(q\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: nr !! number of \(r\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: ns !! number of \(s\) abcissae ( \( \ge 3 \) ) + integer(ip),intent(in) :: kx !! the order of spline pieces in \(x\) + !! ( \( 2 \le k_x < n_x \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ky !! the order of spline pieces in \(y\) + !! ( \( 2 \le k_y < n_y \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kz !! the order of spline pieces in \(z\) + !! ( \( 2 \le k_z < n_z \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kq !! the order of spline pieces in \(q\) + !! ( \( 2 \le k_q < n_q \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: kr !! the order of spline pieces in \(r\) + !! ( \( 2 \le k_r < n_r \) ) + !! (order = polynomial degree + 1) + integer(ip),intent(in) :: ks !! the order of spline pieces in \(s\) + !! ( \( 2 \le k_s < n_s \) ) + !! (order = polynomial degree + 1) + real(wp),dimension(:),intent(in) :: x !! `(nx)` array of \(x\) abcissae. + !! must be strictly increasing. + real(wp),dimension(:),intent(in) :: y !! `(ny)` array of \(y\) abcissae. + !! must be strictly increasing. + real(wp),dimension(:),intent(in) :: z !! `(nz)` array of \(z\) abcissae. + !! must be strictly increasing. + real(wp),dimension(:),intent(in) :: q !! `(nq)` array of \(q\) abcissae. + !! must be strictly increasing. + real(wp),dimension(:),intent(in) :: r !! `(nr)` array of \(r\) abcissae. + !! must be strictly increasing. + real(wp),dimension(:),intent(in) :: s !! `(ns)` array of \(s\) abcissae. + !! must be strictly increasing. + real(wp),dimension(:,:,:,:,:,:),intent(in) :: fcn !! `(nx,ny,nz,nq,nr,ns)` matrix of function values to + !! interpolate. `fcn(i,j,k,q,r,s)` should contain the + !! function value at the point + !! (`x(i)`,`y(j)`,`z(k)`,`q(l)`,`r(m)`,`s(n)`) + integer(ip),intent(in) :: iknot !! knot sequence flag: + !! + !! * 0 = knot sequence chosen by [[db6ink]]. + !! * 1 = knot sequence chosen by user. + real(wp),dimension(:),intent(inout) :: tx !! The `(nx+kx)` knots in the \(x\) direction for the + !! spline interpolant. + !! + !! * f `iknot=0` these are chosen by [[db6ink]]. + !! * f `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: ty !! The `(ny+ky)` knots in the \(y\) direction for the + !! spline interpolant. + !! + !! * If `iknot=0` these are chosen by [[db6ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tz !! The `(nz+kz)` knots in the \(z\) direction for the + !! spline interpolant. + !! + !! * If `iknot=0` these are chosen by [[db6ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tq !! The `(nq+kq)` knots in the \(q\) direction for the + !! spline interpolant. + !! + !! * If `iknot=0` these are chosen by [[db6ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: tr !! The `(nr+kr)` knots in the \(r\) direction for the + !! spline interpolant. + !! + !! * If `iknot=0` these are chosen by [[db6ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:),intent(inout) :: ts !! The `(ns+ks)` knots in the \(s\) direction for the + !! spline interpolant. + !! + !! * If `iknot=0` these are chosen by [[db6ink]]. + !! * If `iknot=1` these are specified by the user. + !! + !! Must be non-decreasing. + real(wp),dimension(:,:,:,:,:,:),intent(out) :: bcoef !! `(nx,ny,nz,nq,nr,ns)` matrix of coefficients of the + !! b-spline interpolant. + integer(ip),intent(out) :: iflag !! * 0 = successful execution. + !! * 2 = `iknot` out of range. + !! * 3 = `nx` out of range. + !! * 4 = `kx` out of range. + !! * 5 = `x` not strictly increasing. + !! * 6 = `tx` not non-decreasing. + !! * 7 = `ny` out of range. + !! * 8 = `ky` out of range. + !! * 9 = `y` not strictly increasing. + !! * 10 = `ty` not non-decreasing. + !! * 11 = `nz` out of range. + !! * 12 = `kz` out of range. + !! * 13 = `z` not strictly increasing. + !! * 14 = `tz` not non-decreasing. + !! * 15 = `nq` out of range. + !! * 16 = `kq` out of range. + !! * 17 = `q` not strictly increasing. + !! * 18 = `tq` not non-decreasing. + !! * 19 = `nr` out of range. + !! * 20 = `kr` out of range. + !! * 21 = `r` not strictly increasing. + !! * 22 = `tr` not non-decreasing. + !! * 23 = `ns` out of range. + !! * 24 = `ks` out of range. + !! * 25 = `s` not strictly increasing. + !! * 26 = `ts` not non-decreasing. + !! * 700 = `size(x) ` \( \ne \) `size(fcn,1)` + !! * 701 = `size(y) ` \( \ne \) `size(fcn,2)` + !! * 702 = `size(z) ` \( \ne \) `size(fcn,3)` + !! * 703 = `size(q) ` \( \ne \) `size(fcn,4)` + !! * 704 = `size(r) ` \( \ne \) `size(fcn,5)` + !! * 705 = `size(s) ` \( \ne \) `size(fcn,6)` + !! * 706 = `size(x) ` \( \ne \) `nx` + !! * 707 = `size(y) ` \( \ne \) `ny` + !! * 708 = `size(z) ` \( \ne \) `nz` + !! * 709 = `size(q) ` \( \ne \) `nq` + !! * 710 = `size(r) ` \( \ne \) `nr` + !! * 711 = `size(s) ` \( \ne \) `ns` + !! * 712 = `size(tx)` \( \ne \) `nx+kx` + !! * 713 = `size(ty)` \( \ne \) `ny+ky` + !! * 714 = `size(tz)` \( \ne \) `nz+kz` + !! * 715 = `size(tq)` \( \ne \) `nq+kq` + !! * 716 = `size(tr)` \( \ne \) `nr+kr` + !! * 717 = `size(ts)` \( \ne \) `ns+ks` + !! * 800 = `size(x) ` \( \ne \) `size(bcoef,1)` + !! * 801 = `size(y) ` \( \ne \) `size(bcoef,2)` + !! * 802 = `size(z) ` \( \ne \) `size(bcoef,3)` + !! * 803 = `size(q) ` \( \ne \) `size(bcoef,4)` + !! * 804 = `size(r) ` \( \ne \) `size(bcoef,5)` + !! * 805 = `size(s) ` \( \ne \) `size(bcoef,6)` + + logical :: status_ok + real(wp),dimension(:),allocatable :: temp !! work array of size `nx*ny*nz*nq*nr*ns` + real(wp),dimension(:),allocatable :: work !! work array of size `max(2*kx*(nx+1), + !! 2*ky*(ny+1),2*kz*(nz+1),2*kq*(nq+1), + !! 2*kr*(nr+1),2*ks*(ns+1))` + + ! check validity of input + call check_inputs( iknot,& + iflag,& + nx=nx,ny=ny,nz=nz,nq=nq,nr=nr,ns=ns,& + kx=kx,ky=ky,kz=kz,kq=kq,kr=kr,ks=ks,& + x=x,y=y,z=z,q=q,r=r,s=s,& + tx=tx,ty=ty,tz=tz,tq=tq,tr=tr,ts=ts,& + f6=fcn,& + bcoef6=bcoef,& + status_ok=status_ok) + + if (status_ok) then + + ! choose knots + if (iknot == 0_ip) then + call dbknot(x,nx,kx,tx) + call dbknot(y,ny,ky,ty) + call dbknot(z,nz,kz,tz) + call dbknot(q,nq,kq,tq) + call dbknot(r,nr,kr,tr) + call dbknot(s,ns,ks,ts) + end if + + allocate(temp(nx*ny*nz*nq*nr*ns)) + allocate(work(max(2_ip*kx*(nx+1_ip),2_ip*ky*(ny+1_ip),& + 2_ip*kz*(nz+1_ip),2_ip*kq*(nq+1_ip),& + 2_ip*kr*(nr+1_ip),2_ip*ks*(ns+1_ip)))) + + ! construct b-spline coefficients + call dbtpcf(x,nx,fcn, nx,ny*nz*nq*nr*ns,tx,kx,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(y,ny,temp, ny,nx*nz*nq*nr*ns,ty,ky,bcoef,work,iflag) + if (iflag==0_ip) call dbtpcf(z,nz,bcoef,nz,nx*ny*nq*nr*ns,tz,kz,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(q,nq,temp, nq,nx*ny*nz*nr*ns,tq,kq,bcoef,work,iflag) + if (iflag==0_ip) call dbtpcf(r,nr,bcoef,nr,nx*ny*nz*nq*ns,tr,kr,temp, work,iflag) + if (iflag==0_ip) call dbtpcf(s,ns,temp, ns,nx*ny*nz*nq*nr,ts,ks,bcoef,work,iflag) + + deallocate(temp) + deallocate(work) + + end if + + end subroutine db6ink +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluates the tensor product piecewise polynomial +! interpolant constructed by the routine [[db6ink]] or one of its +! derivatives at the point (`xval`,`yval`,`zval`,`qval`,`rval`,`sval`). +! +! To evaluate the +! interpolant itself, set `idx=idy=idz=idq=idr=ids=0`, to evaluate the first +! partial with respect to `x`, set `idx=1,idy=idz=idq=idr=ids=0`, and so on. +! +! See [[db3val]] header for more information. +! +!### History +! * Jacob Williams, 2/24/2015 : Created this routine. + + pure subroutine db6val(xval,yval,zval,qval,rval,sval,& + idx,idy,idz,idq,idr,ids,& + tx,ty,tz,tq,tr,ts,& + nx,ny,nz,nq,nr,ns,& + kx,ky,kz,kq,kr,ks,& + bcoef,f,iflag,& + inbvx,inbvy,inbvz,inbvq,inbvr,inbvs,& + iloy,iloz,iloq,ilor,ilos,& + w5,w4,w3,w2,w1,w0,extrap) + + implicit none + + integer(ip),intent(in) :: idx !! \(x\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idy !! \(y\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idz !! \(z\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idq !! \(q\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: idr !! \(r\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: ids !! \(s\) derivative of piecewise polynomial to evaluate. + integer(ip),intent(in) :: nx !! the number of interpolation points in \(x\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: ny !! the number of interpolation points in \(y\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: nz !! the number of interpolation points in \(z\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: nq !! the number of interpolation points in \(q\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: nr !! the number of interpolation points in \(r\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: ns !! the number of interpolation points in \(s\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: kx !! order of polynomial pieces in \(x\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: ky !! order of polynomial pieces in \(y\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: kz !! order of polynomial pieces in \(z\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: kq !! order of polynomial pieces in \(q\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: kr !! order of polynomial pieces in \(r\). + !! (same as in last call to [[db6ink]]) + integer(ip),intent(in) :: ks !! order of polynomial pieces in \(s\). + !! (same as in last call to [[db6ink]]) + real(wp),intent(in) :: xval !! \(x\) coordinate of evaluation point. + real(wp),intent(in) :: yval !! \(y\) coordinate of evaluation point. + real(wp),intent(in) :: zval !! \(z\) coordinate of evaluation point. + real(wp),intent(in) :: qval !! \(q\) coordinate of evaluation point. + real(wp),intent(in) :: rval !! \(r\) coordinate of evaluation point. + real(wp),intent(in) :: sval !! \(s\) coordinate of evaluation point. + real(wp),dimension(nx+kx),intent(in) :: tx !! sequence of knots defining the piecewise polynomial + !! in the \(x\) direction. + !! (same as in last call to [[db6ink]]) + real(wp),dimension(ny+ky),intent(in) :: ty !! sequence of knots defining the piecewise polynomial + !! in the \(y\) direction. + !! (same as in last call to [[db6ink]]) + real(wp),dimension(nz+kz),intent(in) :: tz !! sequence of knots defining the piecewise polynomial + !! in the \(z\) direction. + !! (same as in last call to [[db6ink]]) + real(wp),dimension(nq+kq),intent(in) :: tq !! sequence of knots defining the piecewise polynomial + !! in the \(q\) direction. + !! (same as in last call to [[db6ink]]) + real(wp),dimension(nr+kr),intent(in) :: tr !! sequence of knots defining the piecewise polynomial + !! in the \(r\) direction. + !! (same as in last call to [[db6ink]]) + real(wp),dimension(ns+ks),intent(in) :: ts !! sequence of knots defining the piecewise polynomial + !! in the \(s\) direction. + !! (same as in last call to [[db6ink]]) + real(wp),dimension(nx,ny,nz,nq,nr,ns),intent(in) :: bcoef !! the b-spline coefficients computed by [[db6ink]]. + real(wp),intent(out) :: f !! interpolated value + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * \( = 0 \) : no errors + !! * \( \ne 0 \) : error + integer(ip),intent(inout) :: inbvx !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvy !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvz !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvq !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvr !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: inbvs !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloy !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloz !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: iloq !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: ilor !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + integer(ip),intent(inout) :: ilos !! initialization parameter which must be set + !! to 1 the first time this routine is called, + !! and must not be changed by the user. + real(wp),dimension(ky,kz,kq,kr,ks),intent(inout) :: w5 !! work array + real(wp),dimension(kz,kq,kr,ks),intent(inout) :: w4 !! work array + real(wp),dimension(kq,kr,ks),intent(inout) :: w3 !! work array + real(wp),dimension(kr,ks),intent(inout) :: w2 !! work array + real(wp),dimension(ks),intent(inout) :: w1 !! work array + real(wp),dimension(3_ip*max(kx,ky,kz,kq,kr,ks)),intent(inout) :: w0 !! work array + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + integer(ip) :: lefty,leftz,leftq,leftr,lefts,& + kcoly,kcolz,kcolq,kcolr,kcols,& + j,k,q,r,s + + f = 0.0_wp + + iflag = check_value(xval,tx,1_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(yval,ty,2_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(zval,tz,3_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(qval,tq,4_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(rval,tr,5_ip,extrap); if (iflag/=0_ip) return + iflag = check_value(sval,ts,6_ip,extrap); if (iflag/=0_ip) return + + call dintrv(ty,ny+ky,yval,iloy,lefty,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tz,nz+kz,zval,iloz,leftz,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tq,nq+kq,qval,iloq,leftq,iflag,extrap); if (iflag/=0_ip) return + call dintrv(tr,nr+kr,rval,ilor,leftr,iflag,extrap); if (iflag/=0_ip) return + call dintrv(ts,ns+ks,sval,ilos,lefts,iflag,extrap); if (iflag/=0_ip) return + + iflag = 0_ip + + ! x -> y, z, q, r, s + kcols = lefts - ks + do s=1_ip,ks + kcols = kcols + 1_ip + kcolr = leftr - kr + do r=1_ip,kr + kcolr = kcolr + 1_ip + kcolq = leftq - kq + do q=1_ip,kq + kcolq = kcolq + 1_ip + kcolz = leftz - kz + do k=1_ip,kz + kcolz = kcolz + 1_ip + kcoly = lefty - ky + do j=1_ip,ky + kcoly = kcoly + 1_ip + call dbvalu(tx,bcoef(:,kcoly,kcolz,kcolq,kcolr,kcols),& + nx,kx,idx,xval,inbvx,w0,iflag,& + w5(j,k,q,r,s),extrap) + if (iflag/=0_ip) return + end do + end do + end do + end do + end do + + ! y -> z, q, r, s + kcoly = lefty - ky + 1_ip + do s=1_ip,ks + do r=1_ip,kr + do q=1_ip,kq + do k=1_ip,kz + call dbvalu(ty(kcoly:),w5(:,k,q,r,s),& + ky,ky,idy,yval,inbvy,w0,iflag,& + w4(k,q,r,s),extrap) + if (iflag/=0_ip) return + end do + end do + end do + end do + + ! z -> q, r, s + kcolz = leftz - kz + 1_ip + do s=1_ip,ks + do r=1_ip,kr + do q=1_ip,kq + call dbvalu(tz(kcolz:),w4(:,q,r,s),& + kz,kz,idz,zval,inbvz,w0,iflag,& + w3(q,r,s),extrap) + if (iflag/=0_ip) return + end do + end do + end do + + ! q -> r, s + kcolq = leftq - kq + 1_ip + do s=1_ip,ks + do r=1_ip,kr + call dbvalu(tq(kcolq:),w3(:,r,s),& + kq,kq,idq,qval,inbvq,w0,iflag,& + w2(r,s),extrap) + if (iflag/=0_ip) return + end do + end do + + ! r -> s + kcolr = leftr - kr + 1_ip + do s=1_ip,ks + call dbvalu(tr(kcolr:),w2(:,s),& + kr,kr,idr,rval,inbvr,w0,iflag,& + w1(s),extrap) + if (iflag/=0_ip) return + end do + + ! s + kcols = lefts - ks + 1_ip + call dbvalu(ts(kcols:),w1,ks,ks,ids,sval,inbvs,w0,iflag,f,extrap) + + end subroutine db6val +!***************************************************************************************** + +!***************************************************************************************** +!> +! Checks if the value is withing the range of the knot vectors. +! This is called by the various `db*val` routines. + + pure function check_value(x,t,i,extrap) result(iflag) + + implicit none + + integer(ip) :: iflag !! returns 0 if value is OK, otherwise returns `600+i` + real(wp),intent(in) :: x !! the value to check + integer(ip),intent(in) :: i !! 1=x, 2=y, 3=z, 4=q, 5=r, 6=s + real(wp),dimension(:),intent(in) :: t !! the knot vector + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + logical :: allow_extrapolation !! if extrapolation is allowed + + if (present(extrap)) then + allow_extrapolation = extrap + else + allow_extrapolation = .false. + end if + + if (allow_extrapolation) then + ! in this case all values are OK + iflag = 0_ip + else + if (xt(size(t,kind=ip))) then + iflag = 600_ip + i ! value out of bounds (601, 602, etc.) + else + iflag = 0_ip + end if + end if + + end function check_value +!***************************************************************************************** + +!***************************************************************************************** +!> +! Check the validity of the inputs to the `db*ink` routines. +! Prints warning message if there is an error, +! and also sets iflag and status_ok. +! +! Supports up to 6D: `x`,`y`,`z`,`q`,`r`,`s` +! +!### Notes +! +! The code is new, but the logic is based on the original +! logic in the CMLIB routines `db2ink` and `db3ink`. +! +!### History +! * Jacob Williams, 2/24/2015 : Created this routine. + + pure subroutine check_inputs(iknot,& + iflag,& + nx,ny,nz,nq,nr,ns,& + kx,ky,kz,kq,kr,ks,& + x,y,z,q,r,s,& + tx,ty,tz,tq,tr,ts,& + f1,f2,f3,f4,f5,f6,& + bcoef1,bcoef2,bcoef3,bcoef4,bcoef5,bcoef6,& + alt,& + status_ok) + + implicit none + + integer(ip),intent(in) :: iknot !! = 0 if the `INK` routine is computing the knots. + integer(ip),intent(out) :: iflag + integer(ip),intent(in),optional :: nx,ny,nz,nq,nr,ns + integer(ip),intent(in),optional :: kx,ky,kz,kq,kr,ks + real(wp),dimension(:),intent(in),optional :: x,y,z,q,r,s + real(wp),dimension(:),intent(in),optional :: tx,ty,tz,tq,tr,ts + real(wp),dimension(:),intent(in),optional :: f1,bcoef1 + real(wp),dimension(:,:),intent(in),optional :: f2,bcoef2 + real(wp),dimension(:,:,:),intent(in),optional :: f3,bcoef3 + real(wp),dimension(:,:,:,:),intent(in),optional :: f4,bcoef4 + real(wp),dimension(:,:,:,:,:),intent(in),optional :: f5,bcoef5 + real(wp),dimension(:,:,:,:,:,:),intent(in),optional :: f6,bcoef6 + logical,intent(in),optional :: alt !! using the alt routine where 1st or + !! 2nd deriv is fixed at endpoints + !! [default is False] + logical,intent(out) :: status_ok + + logical :: error + integer :: iex !! extra points for the alt case (in `t` and `bcoef`) + !! [currently, only allowed for the 1D case & `k=4`] + + status_ok = .false. + + iex = 0_ip ! default + if (present(alt)) then + if (alt) iex = 2_ip ! for "alt" mode + end if + + if ((iknot < 0_ip) .or. (iknot > 1_ip)) then + + iflag = 2_ip ! iknot is out of range + + else + + call check('x',nx,kx,x,tx,[3_ip, 4_ip, 5_ip, 6_ip,706_ip,712_ip],iflag,error,iex); if (error) return + call check('y',ny,ky,y,ty,[7_ip, 8_ip, 9_ip,10_ip,707_ip,713_ip],iflag,error,iex); if (error) return + call check('z',nz,kz,z,tz,[11_ip,12_ip,13_ip,14_ip,708_ip,714_ip],iflag,error,iex); if (error) return + call check('q',nq,kq,q,tq,[15_ip,16_ip,17_ip,18_ip,709_ip,715_ip],iflag,error,iex); if (error) return + call check('r',nr,kr,r,tr,[19_ip,20_ip,21_ip,22_ip,710_ip,716_ip],iflag,error,iex); if (error) return + call check('s',ns,ks,s,ts,[23_ip,24_ip,25_ip,26_ip,711_ip,717_ip],iflag,error,iex); if (error) return + + if (present(x) .and. present(f1) .and. present(bcoef1)) then + if (size(x,kind=ip)/=size(f1,1_ip,kind=ip)) then; iflag = 700_ip; return; end if + if (size(x,kind=ip)+iex/=size(bcoef1,1_ip,kind=ip)) then; iflag = 800_ip; return; end if + end if + if (present(x) .and. present(y) .and. present(f2) .and. present(bcoef2)) then + if (size(x,kind=ip)/=size(f2,1_ip,kind=ip)) then; iflag = 700_ip; return; end if + if (size(y,kind=ip)/=size(f2,2_ip,kind=ip)) then; iflag = 701_ip; return; end if + if (size(x,kind=ip)+iex/=size(bcoef2,1_ip,kind=ip)) then; iflag = 800_ip; return; end if + if (size(y,kind=ip)+iex/=size(bcoef2,2_ip,kind=ip)) then; iflag = 801_ip; return; end if + end if + if (present(x) .and. present(y) .and. present(z) .and. present(f3) .and. & + present(bcoef3)) then + if (size(x,kind=ip)/=size(f3,1_ip,kind=ip)) then; iflag = 700_ip; return; end if + if (size(y,kind=ip)/=size(f3,2_ip,kind=ip)) then; iflag = 701_ip; return; end if + if (size(z,kind=ip)/=size(f3,3_ip,kind=ip)) then; iflag = 702_ip; return; end if + if (size(x,kind=ip)+iex/=size(bcoef3,1_ip,kind=ip)) then; iflag = 800_ip; return; end if + if (size(y,kind=ip)+iex/=size(bcoef3,2_ip,kind=ip)) then; iflag = 801_ip; return; end if + if (size(z,kind=ip)+iex/=size(bcoef3,3_ip,kind=ip)) then; iflag = 802_ip; return; end if + end if + if (present(x) .and. present(y) .and. present(z) .and. present(q) .and. & + present(f4) .and. present(bcoef4)) then + if (size(x,kind=ip)/=size(f4,1_ip,kind=ip)) then; iflag = 700_ip; return; end if + if (size(y,kind=ip)/=size(f4,2_ip,kind=ip)) then; iflag = 701_ip; return; end if + if (size(z,kind=ip)/=size(f4,3_ip,kind=ip)) then; iflag = 702_ip; return; end if + if (size(q,kind=ip)/=size(f4,4_ip,kind=ip)) then; iflag = 703_ip; return; end if + if (size(x,kind=ip)+iex/=size(bcoef4,1_ip,kind=ip)) then; iflag = 800_ip; return; end if + if (size(y,kind=ip)+iex/=size(bcoef4,2_ip,kind=ip)) then; iflag = 801_ip; return; end if + if (size(z,kind=ip)+iex/=size(bcoef4,3_ip,kind=ip)) then; iflag = 802_ip; return; end if + if (size(q,kind=ip)+iex/=size(bcoef4,4_ip,kind=ip)) then; iflag = 803_ip; return; end if + end if + if (present(x) .and. present(y) .and. present(z) .and. present(q) .and. & + present(r) .and. present(f5) .and. present(bcoef5)) then + if (size(x,kind=ip)/=size(f5,1_ip,kind=ip)) then; iflag = 700_ip; return; end if + if (size(y,kind=ip)/=size(f5,2_ip,kind=ip)) then; iflag = 701_ip; return; end if + if (size(z,kind=ip)/=size(f5,3_ip,kind=ip)) then; iflag = 702_ip; return; end if + if (size(q,kind=ip)/=size(f5,4_ip,kind=ip)) then; iflag = 703_ip; return; end if + if (size(r,kind=ip)/=size(f5,5_ip,kind=ip)) then; iflag = 704_ip; return; end if + if (size(x,kind=ip)+iex/=size(bcoef5,1_ip,kind=ip)) then; iflag = 800_ip; return; end if + if (size(y,kind=ip)+iex/=size(bcoef5,2_ip,kind=ip)) then; iflag = 801_ip; return; end if + if (size(z,kind=ip)+iex/=size(bcoef5,3_ip,kind=ip)) then; iflag = 802_ip; return; end if + if (size(q,kind=ip)+iex/=size(bcoef5,4_ip,kind=ip)) then; iflag = 803_ip; return; end if + if (size(r,kind=ip)+iex/=size(bcoef5,5_ip,kind=ip)) then; iflag = 804_ip; return; end if + end if + if (present(x) .and. present(y) .and. present(z) .and. present(q) .and. & + present(r) .and. present(s) .and. present(f6) .and. present(bcoef6)) then + if (size(x,kind=ip)/=size(f6,1_ip,kind=ip)) then; iflag = 700_ip; return; end if + if (size(y,kind=ip)/=size(f6,2_ip,kind=ip)) then; iflag = 701_ip; return; end if + if (size(z,kind=ip)/=size(f6,3_ip,kind=ip)) then; iflag = 702_ip; return; end if + if (size(q,kind=ip)/=size(f6,4_ip,kind=ip)) then; iflag = 703_ip; return; end if + if (size(r,kind=ip)/=size(f6,5_ip,kind=ip)) then; iflag = 704_ip; return; end if + if (size(s,kind=ip)/=size(f6,6_ip,kind=ip)) then; iflag = 705_ip; return; end if + if (size(x,kind=ip)+iex/=size(bcoef6,1_ip,kind=ip)) then; iflag = 800_ip; return; end if + if (size(y,kind=ip)+iex/=size(bcoef6,2_ip,kind=ip)) then; iflag = 801_ip; return; end if + if (size(z,kind=ip)+iex/=size(bcoef6,3_ip,kind=ip)) then; iflag = 802_ip; return; end if + if (size(q,kind=ip)+iex/=size(bcoef6,4_ip,kind=ip)) then; iflag = 803_ip; return; end if + if (size(r,kind=ip)+iex/=size(bcoef6,5_ip,kind=ip)) then; iflag = 804_ip; return; end if + if (size(s,kind=ip)+iex/=size(bcoef6,6_ip,kind=ip)) then; iflag = 805_ip; return; end if + + end if + + status_ok = .true. + iflag = 0_ip + + end if + + contains + + pure subroutine check(s,n,k,x,t,ierrs,iflag,error,ik) !! check `t`,`x`,`n`,`k` for validity + + implicit none + + character(len=1),intent(in) :: s !! coordinate string: 'x','y','z','q','r','s' + integer(ip),intent(in),optional :: n !! size of `x` + integer(ip),intent(in),optional :: k !! order + real(wp),dimension(:),intent(in),optional :: x !! abcissae vector + real(wp),dimension(:),intent(in),optional :: t !! knot vector `size(n+k)` + integer(ip),dimension(:),intent(in) :: ierrs !! int error codes for `n`,`k`,`x`,`t`, + !! `size(x)`,`size(t)` checks + integer(ip),intent(out) :: iflag !! status return code + logical,intent(out) :: error !! true if there was an error + integer,intent(in) :: ik !! add this value to k + + integer(ip),dimension(2) :: itmp !! temp integer array + + if (present(n) .and. present(k) .and. present(x) .and. present(t)) then + itmp = [ierrs(1_ip),ierrs(5)] + call check_n('n'//s,n,x,itmp,iflag,error); if (error) return + call check_k('k'//s,k+ik,n,ierrs(2),iflag,error); if (error) return + call check_x(s,n,x,ierrs(3),iflag,error); if (error) return + if (iknot /= 0_ip) then + itmp = [ierrs(4),ierrs(6)] + call check_t('t'//s,n,k+ik,t,itmp,iflag,error); if (error) return + end if + end if + + end subroutine check + + pure subroutine check_n(s,n,x,ierr,iflag,error) + + implicit none + + character(len=*),intent(in) :: s + integer(ip),intent(in) :: n + real(wp),dimension(:),intent(in) :: x !! abcissae vector + integer(ip),dimension(2),intent(in) :: ierr !! [n<3 check, size(x)==n check] + integer(ip),intent(out) :: iflag !! status return code + logical,intent(out) :: error + + if (n < 3_ip) then + iflag = ierr(1_ip) + error = .true. + else + if (size(x)/=n) then + iflag = ierr(2) + error = .true. + else + error = .false. + end if + end if + + end subroutine check_n + + pure subroutine check_k(s,k,n,ierr,iflag,error) + + implicit none + + character(len=*),intent(in) :: s + integer(ip),intent(in) :: k + integer(ip),intent(in) :: n + integer(ip),intent(in) :: ierr + integer(ip),intent(out) :: iflag !! status return code + logical,intent(out) :: error + + if ((k < 2_ip) .or. (k >= n)) then + iflag = ierr + error = .true. + else + error = .false. + end if + + end subroutine check_k + + pure subroutine check_x(s,n,x,ierr,iflag,error) + + implicit none + + character(len=*),intent(in) :: s + integer(ip),intent(in) :: n + real(wp),dimension(:),intent(in) :: x + integer(ip),intent(in) :: ierr + integer(ip),intent(out) :: iflag !! status return code + logical,intent(out) :: error + + integer(ip) :: i + + error = .true. + do i=2_ip,n + if (x(i) <= x(i-1_ip)) then + iflag = ierr + return + end if + end do + error = .false. + + end subroutine check_x + + pure subroutine check_t(s,n,k,t,ierr,iflag,error) + + implicit none + + character(len=*),intent(in) :: s + integer(ip),intent(in) :: n + integer(ip),intent(in) :: k + real(wp),dimension(:),intent(in) :: t + integer(ip),dimension(2),intent(in) :: ierr !! [non-decreasing check, size check] + integer(ip),intent(out) :: iflag !! status return code + logical,intent(out) :: error + + integer(ip) :: i + + error = .true. + + if (size(t)/=(n+k)) then + iflag = ierr(2) + return + end if + + if (iex==0_ip) then ! don't do this for "alt" mode since they haven't been computed yet + do i=2_ip,n + k + if (t(i) < t(i-1_ip)) then + iflag = ierr(1_ip) + return + end if + end do + end if + + error = .false. + + end subroutine check_t + + end subroutine check_inputs +!***************************************************************************************** + +!***************************************************************************************** +!> +! dbknot chooses a knot sequence for interpolation of order k at the +! data points x(i), i=1,..,n. the n+k knots are placed in the array +! t. k knots are placed at each endpoint and not-a-knot end +! conditions are used. the remaining knots are placed at data points +! if n is even and between data points if n is odd. the rightmost +! knot is shifted slightly to the right to insure proper interpolation +! at x(n) (see page 350 of the reference). +! +!### History +! * Jacob Williams, 2/24/2015 : Refactored this routine. + + pure subroutine dbknot(x,n,k,t) + + implicit none + + integer(ip),intent(in) :: n !! dimension of `x` + integer(ip),intent(in) :: k + real(wp),dimension(:),intent(in) :: x + real(wp),dimension(:),intent(out) :: t + + integer(ip) :: i, j, ipj, npj, ip1, jstrt + real(wp) :: rnot + + !put k knots at each endpoint + !(shift right endpoints slightly -- see pg 350 of reference) + rnot = x(n) + 0.1_wp*( x(n)-x(n-1_ip) ) + do j=1_ip,k + t(j) = x(1_ip) + npj = n + j + t(npj) = rnot + end do + + !distribute remaining knots + + if (mod(k,2_ip) == 1_ip) then + + !case of odd k -- knots between data points + + i = (k-1_ip)/2_ip - k + ip1 = i + 1_ip + jstrt = k + 1_ip + do j=jstrt,n + ipj = i + j + t(j) = 0.5_wp*( x(ipj) + x(ipj+1_ip) ) + end do + + else + + !case of even k -- knots at data points + + i = (k/2_ip) - k + jstrt = k+1_ip + do j=jstrt,n + ipj = i + j + t(j) = x(ipj) + end do + + end if + + end subroutine dbknot +!***************************************************************************************** + +!***************************************************************************************** +!> +! dbtpcf computes b-spline interpolation coefficients for nf sets +! of data stored in the columns of the array fcn. the b-spline +! coefficients are stored in the rows of bcoef however. +! each interpolation is based on the n abcissa stored in the +! array x, and the n+k knots stored in the array t. the order +! of each interpolation is k. +! +!### History +! * Jacob Williams, 2/24/2015 : Refactored this routine. + + pure subroutine dbtpcf(x,n,fcn,ldf,nf,t,k,bcoef,work,iflag) + + integer(ip),intent(in) :: n !! dimension of `x` + integer(ip),intent(in) :: nf + integer(ip),intent(in) :: ldf + integer(ip),intent(in) :: k + real(wp),dimension(:),intent(in) :: x + real(wp),dimension(ldf,nf),intent(in) :: fcn + real(wp),dimension(:),intent(in) :: t + real(wp),dimension(nf,n),intent(out) :: bcoef + real(wp),dimension(*),intent(out) :: work !! work array of size >= `2*k*(n+1)` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 301: n should be >0 + + integer(ip) :: i, j, m1, m2, iq, iw + + ! check for null input + + if (nf > 0_ip) then + + ! partition work array + m1 = k - 1_ip + m2 = m1 + k + iq = 1_ip + n + iw = iq + m2*n+1_ip + + ! compute b-spline coefficients + + ! first data set + + call dbintk(x,fcn,t,n,k,work,work(iq),work(iw),iflag) + if (iflag == 0_ip) then + do i=1_ip,n + bcoef(1_ip,i) = work(i) + end do + + ! all remaining data sets by back-substitution + + if (nf == 1_ip) return + do j=2_ip,nf + do i=1_ip,n + work(i) = fcn(i,j) + end do + call dbnslv(work(iq),m2,n,m1,m1,work) + do i=1_ip,n + bcoef(j,i) = work(i) + end do + end do + end if + + else + !write(error_unit,'(A)') 'dbtpcf - n should be >0' + iflag = 301_ip + end if + + end subroutine dbtpcf +!***************************************************************************************** + +!***************************************************************************************** +!> +! dbintk produces the b-spline coefficients, bcoef, of the +! b-spline of order k with knots t(i), i=1,...,n+k, which +! takes on the value y(i) at x(i), i=1,...,n. the spline or +! any of its derivatives can be evaluated by calls to [[dbvalu]]. +! +! the i-th equation of the linear system a*bcoef = b for the +! coefficients of the interpolant enforces interpolation at +! x(i), i=1,...,n. hence, b(i) = y(i), for all i, and a is +! a band matrix with 2k-1 bands if a is invertible. the matrix +! a is generated row by row and stored, diagonal by diagonal, +! in the rows of q, with the main diagonal going into row k. +! the banded system is then solved by a call to dbnfac (which +! constructs the triangular factorization for a and stores it +! again in q), followed by a call to dbnslv (which then +! obtains the solution bcoef by substitution). dbnfac does no +! pivoting, since the total positivity of the matrix a makes +! this unnecessary. the linear system to be solved is +! (theoretically) invertible if and only if +! t(i) < x(i) < t(i+k), for all i. +! equality is permitted on the left for i=1 and on the right +! for i=n when k knots are used at x(1) or x(n). otherwise, +! violation of this condition is certain to lead to an error. +! +!### Error conditions +! +! * improper input +! * singular system of equations +! +!### History +! * splint written by carl de boor [5] +! * dbintk author: amos, d. e., (snla) : date written 800901 +! * revision date 820801 +! * 000330 modified array declarations. (jec) +! * Jacob Williams, 5/10/2015 : converted to free-form Fortran. + + pure subroutine dbintk(x,y,t,n,k,bcoef,q,work,iflag) + + implicit none + + integer(ip),intent(in) :: n !! number of data points, n >= k + real(wp),dimension(n),intent(in) :: x !! vector of length n containing data point abscissa + !! in strictly increasing order. + real(wp),dimension(n),intent(in) :: y !! corresponding vector of length n containing data + !! point ordinates. + real(wp),dimension(*),intent(in) :: t !! knot vector of length n+k + !! since t(1),..,t(k) <= x(1) and t(n+1),..,t(n+k) + !! >= x(n), this leaves only n-k knots (not + !! necessarily x(i) values) interior to (x(1),x(n)) + integer(ip),intent(in) :: k !! order of the spline, k >= 1 + real(wp),dimension(n),intent(out) :: bcoef !! a vector of length n containing the b-spline coefficients + real(wp),dimension(*),intent(out) :: q !! a work vector of length (2*k-1)*n, containing + !! the triangular factorization of the coefficient + !! matrix of the linear system being solved. the + !! coefficients for the interpolant of an + !! additional data set (x(i),yy(i)), i=1,...,n + !! with the same abscissa can be obtained by loading + !! yy into bcoef and then executing + !! call dbnslv(q,2k-1,n,k-1,k-1,bcoef) + real(wp),dimension(*),intent(out) :: work !! work vector of length 2*k + integer(ip),intent(out) :: iflag !! * 0: no errors. + !! * 100: k does not satisfy k>=1. + !! * 101: n does not satisfy n>=k. + !! * 102: x(i) does not satisfy x(i)=1' + iflag = 100_ip + return + end if + + if (n=k' + iflag = 101_ip + return + end if + + jj = n - 1_ip + if (jj/=0_ip) then + do i=1_ip,jj + if (x(i)>=x(i+1_ip)) then + !write(error_unit,'(A)') 'dbintk - x(i) does not satisfy x(i)=ilp1mx) exit + end do + if (.not. found) then + left = left - 1_ip + if (xi>t(left+1_ip)) then + !write(error_unit,'(A)') 'dbintk - some abscissa was not in the support of the'//& + ! ' corresponding basis function and the system is singular' + iflag = 103_ip + return + end if + end if + ! the i-th equation enforces interpolation at xi, hence + ! a(i,j) = b(j,k,t)(xi), all j. only the k entries with j = + ! left-k+1,...,left actually might be nonzero. these k numbers + ! are returned, in bcoef (used for temp.storage here), by the + ! following + call dbspvn(t, k, k, 1_ip, xi, left, bcoef, work, iwork, iflag) + if (iflag/=0_ip) return + + ! we therefore want bcoef(j) = b(left-k+j)(xi) to go into + ! a(i,left-k+j), i.e., into q(i-(left+j)+2*k,(left+j)-k) since + ! a(i+j,j) is to go into q(i+k,j), all i,j, if we consider q + ! as a two-dim. array , with 2*k-1 rows (see comments in + ! dbnfac). in the present program, we treat q as an equivalent + ! one-dimensional array (because of fortran restrictions on + ! dimension statements) . we therefore want bcoef(j) to go into + ! entry + ! i -(left+j) + 2*k + ((left+j) - k-1)*(2*k-1) + ! = i-left+1 + (left -k)*(2*k-1) + (2*k-2)*j + ! of q. + jj = i - left + 1_ip + (left-k)*(k+km1) + do j=1_ip,k + jj = jj + kpkm2 + q(jj) = bcoef(j) + end do + + end do + + ! obtain factorization of a, stored again in q. + call dbnfac(q, k+km1, n, km1, km1, iflag) + + if (iflag==1) then !success + ! solve a*bcoef = y by backsubstitution + do i=1_ip,n + bcoef(i) = y(i) + end do + call dbnslv(q, k+km1, n, km1, km1, bcoef) + iflag = 0_ip + else !failure + !write(error_unit,'(A)') 'dbintk - the system of solver detects a singular system'//& + ! ' although the theoretical conditions for a solution were satisfied' + iflag = 104_ip + end if + + end subroutine dbintk +!***************************************************************************************** + +!***************************************************************************************** +!> +! Returns in w the LU-factorization (without pivoting) of the banded +! matrix a of order nrow with (nbandl + 1 + nbandu) bands or diagonals +! in the work array w . +! +! gauss elimination without pivoting is used. the routine is +! intended for use with matrices a which do not require row inter- +! changes during factorization, especially for the totally +! positive matrices which occur in spline calculations. +! the routine should not be used for an arbitrary banded matrix. +! +!### Work array +! +! **Input** +! +! w array of size (nroww,nrow) contains the interesting +! part of a banded matrix a , with the diagonals or bands of a +! stored in the rows of w , while columns of a correspond to +! columns of w . this is the storage mode used in linpack and +! results in efficient innermost loops. +! explicitly, a has nbandl bands below the diagonal +! + 1 (main) diagonal +! + nbandu bands above the diagonal +! and thus, with middle = nbandu + 1, +! a(i+j,j) is in w(i+middle,j) for i=-nbandu,...,nbandl +! j=1,...,nrow . +! for example, the interesting entries of a (1,2)-banded matrix +! of order 9 would appear in the first 1+1+2 = 4 rows of w +! as follows. +! 13 24 35 46 57 68 79 +! 12 23 34 45 56 67 78 89 +! 11 22 33 44 55 66 77 88 99 +! 21 32 43 54 65 76 87 98 +! +! all other entries of w not identified in this way with an en- +! try of a are never referenced . +! +! **Output** +! +! * if iflag = 1, then +! w contains the lu-factorization of a into a unit lower triangu- +! lar matrix l and an upper triangular matrix u (both banded) +! and stored in customary fashion over the corresponding entries +! of a . this makes it possible to solve any particular linear +! system a*x = b for x by a +! call dbnslv ( w, nroww, nrow, nbandl, nbandu, b ) +! with the solution x contained in b on return . +! * if iflag = 2, then +! one of nrow-1, nbandl,nbandu failed to be nonnegative, or else +! one of the potential pivots was found to be zero indicating +! that a does not have an lu-factorization. this implies that +! a is singular in case it is totally positive . +! +!### History +! * banfac written by carl de boor [5] +! * dbnfac from CMLIB [1] +! * Jacob Williams, 5/10/2015 : converted to free-form Fortran. + + pure subroutine dbnfac(w,nroww,nrow,nbandl,nbandu,iflag) + + integer(ip),intent(in) :: nroww !! row dimension of the work array w. must be >= nbandl + 1 + nbandu. + integer(ip),intent(in) :: nrow !! matrix order + integer(ip),intent(in) :: nbandl !! number of bands of a below the main diagonal + integer(ip),intent(in) :: nbandu !! number of bands of a above the main diagonal + integer(ip),intent(out) :: iflag !! indicating success(=1) or failure (=2) + real(wp),dimension(nroww,nrow),intent(inout) :: w !! work array. See header for details. + + integer(ip) :: i, ipk, j, jmax, k, kmax, middle, midmk, nrowm1 + real(wp) :: factor, pivot + + iflag = 1_ip + middle = nbandu + 1_ip ! w(middle,.) contains the main diagonal of a. + nrowm1 = nrow - 1_ip + + if (nrowm1 < 0_ip) then + iflag = 2_ip + return + else if (nrowm1 == 0_ip) then + if (w(middle,nrow)==0.0_wp) iflag = 2_ip + return + end if + + if (nbandl<=0_ip) then + ! a is upper triangular. check that diagonal is nonzero . + do i=1_ip,nrowm1 + if (w(middle,i)==0.0_wp) then + iflag = 2_ip + return + end if + end do + if (w(middle,nrow)==0.0_wp) iflag = 2_ip + return + end if + + if (nbandu<=0_ip) then + ! a is lower triangular. check that diagonal is nonzero and + ! divide each column by its diagonal. + do i=1_ip,nrowm1 + pivot = w(middle,i) + if (pivot==0.0_wp) then + iflag = 2_ip + return + end if + jmax = min(nbandl,nrow-i) + do j=1_ip,jmax + w(middle+j,i) = w(middle+j,i)/pivot + end do + end do + return + end if + + ! a is not just a triangular matrix. construct lu factorization + do i=1_ip,nrowm1 + ! w(middle,i) is pivot for i-th step . + pivot = w(middle,i) + if (pivot==0.0_wp) then + iflag = 2_ip + return + end if + ! jmax is the number of (nonzero) entries in column i + ! below the diagonal. + jmax = min(nbandl,nrow-i) + ! divide each entry in column i below diagonal by pivot. + do j=1_ip,jmax + w(middle+j,i) = w(middle+j,i)/pivot + end do + ! kmax is the number of (nonzero) entries in row i to + ! the right of the diagonal. + kmax = min(nbandu,nrow-i) + ! subtract a(i,i+k)*(i-th column) from (i+k)-th column + ! (below row i). + do k=1_ip,kmax + ipk = i + k + midmk = middle - k + factor = w(midmk,ipk) + do j=1_ip,jmax + w(midmk+j,ipk) = w(midmk+j,ipk) - w(middle+j,i)*factor + end do + end do + end do + + ! check the last diagonal entry. + if (w(middle,nrow)==0.0_wp) iflag = 2_ip + + end subroutine dbnfac +!***************************************************************************************** + +!***************************************************************************************** +!> +! Companion routine to [[dbnfac]]. it returns the solution x of the +! linear system a*x = b in place of b, given the lu-factorization +! for a in the work array w from dbnfac. +! +! (with \( a = l*u \), as stored in w), the unit lower triangular system +! \( l(u*x) = b \) is solved for \( y = u*x \), and y stored in b. then the +! upper triangular system \(u*x = y \) is solved for x. the calculations +! are so arranged that the innermost loops stay within columns. +! +!### History +! * banslv written by carl de boor [5] +! * dbnslv from SLATEC library [1] +! * Jacob Williams, 5/10/2015 : converted to free-form Fortran. + + pure subroutine dbnslv(w,nroww,nrow,nbandl,nbandu,b) + + integer(ip),intent(in) :: nroww !! describes the lu-factorization of a banded matrix a of order `nrow` + !! as constructed in [[dbnfac]]. + integer(ip),intent(in) :: nrow !! describes the lu-factorization of a banded matrix a of order `nrow` + !! as constructed in [[dbnfac]]. + integer(ip),intent(in) :: nbandl !! describes the lu-factorization of a banded matrix a of order `nrow` + !! as constructed in [[dbnfac]]. + integer(ip),intent(in) :: nbandu !! describes the lu-factorization of a banded matrix a of order `nrow` + !! as constructed in [[dbnfac]]. + real(wp),dimension(nroww,nrow),intent(in) :: w !! describes the lu-factorization of a banded matrix a of + !! order `nrow` as constructed in [[dbnfac]]. + real(wp),dimension(nrow),intent(inout) :: b !! * **in**: right side of the system to be solved + !! * **out**: the solution x, of order nrow + + integer(ip) :: i, j, jmax, middle, nrowm1 + + middle = nbandu + 1_ip + if (nrow/=1_ip) then + + nrowm1 = nrow - 1_ip + if (nbandl/=0_ip) then + + ! forward pass + ! for i=1,2,...,nrow-1, subtract right side(i)*(i-th column of l) + ! from right side (below i-th row). + do i=1_ip,nrowm1 + jmax = min(nbandl,nrow-i) + do j=1_ip,jmax + b(i+j) = b(i+j) - b(i)*w(middle+j,i) + end do + end do + + end if + + ! backward pass + ! for i=nrow,nrow-1,...,1, divide right side(i) by i-th diagonal + ! entry of u, then subtract right side(i)*(i-th column + ! of u) from right side (above i-th row). + if (nbandu<=0_ip) then + ! a is lower triangular. + do i=1_ip,nrow + b(i) = b(i)/w(1_ip,i) + end do + return + end if + + i = nrow + do + b(i) = b(i)/w(middle,i) + jmax = min(nbandu,i-1_ip) + do j=1_ip,jmax + b(i-j) = b(i-j) - b(i)*w(middle-j,i) + end do + i = i - 1_ip + if (i<=1_ip) exit + end do + + end if + + b(1_ip) = b(1_ip)/w(middle,1_ip) + + end subroutine dbnslv +!***************************************************************************************** + +!***************************************************************************************** +!> +! Calculates the value of all (possibly) nonzero basis +! functions at x of order max(jhigh,(j+1)*(index-1)), where t(k) +! <= x <= t(n+1) and j=iwork is set inside the routine on +! the first call when index=1. ileft is such that t(ileft) <= +! x < t(ileft+1). a call to dintrv(t,n+1,x,ilo,ileft,mflag) +! produces the proper ileft. dbspvn calculates using the basic +! algorithm needed in dbspvd. if only basis functions are +! desired, setting jhigh=k and index=1 can be faster than +! calling dbspvd, but extra coding is required for derivatives +! (index=2) and dbspvd is set up for this purpose. +! +! left limiting values are set up as described in dbspvd. +! +!### Error Conditions +! +! * improper input +! +!### History +! * bsplvn written by carl de boor [5] +! * dbspvn author: amos, d. e., (snla) : date written 800901 +! * revision date 820801 +! * 000330 modified array declarations. (jec) +! * Jacob Williams, 2/24/2015 : extensive refactoring of CMLIB routine. + + pure subroutine dbspvn(t,jhigh,k,index,x,ileft,vnikx,work,iwork,iflag) + + implicit none + + real(wp),dimension(*),intent(in) :: t !! knot vector of length `n+k`, where + !! `n` = number of b-spline basis functions + !! `n` = sum of knot multiplicities-`k` + !! dimension `t(ileft+jhigh)` + integer(ip),intent(in) :: jhigh !! order of b-spline, `1 <= jhigh <= k` + integer(ip),intent(in) :: k !! highest possible order + integer(ip),intent(in) :: index !! index = 1 gives basis functions of order `jhigh` + !! = 2 denotes previous entry with `work`, `iwork` + !! values saved for subsequent calls to + !! dbspvn. + real(wp),intent(in) :: x !! argument of basis functions, `t(k) <= x <= t(n+1)` + integer(ip),intent(in) :: ileft !! largest integer such that `t(ileft) <= x < t(ileft+1)` + real(wp),dimension(k),intent(out) :: vnikx !! vector of length `k` for spline values. + real(wp),dimension(*),intent(inout) :: work !! a work vector of length `2*k` + integer(ip),intent(inout) :: iwork !! a work parameter. both `work` and `iwork` contain + !! information necessary to continue for `index = 2`. + !! when `index = 1` exclusively, these are scratch + !! variables and can be used for other purposes. + integer(ip),intent(out) :: iflag !! * 0: no errors + !! * 201: `k` does not satisfy `k>=1` + !! * 202: `jhigh` does not satisfy `1<=jhigh<=k` + !! * 203: `index` is not 1 or 2 + !! * 204: `x` does not satisfy `t(ileft)<=x<=t(ileft+1)` + + integer(ip) :: imjp1, ipj, jp1, jp1ml, l + real(wp) :: vm, vmprev + + ! content of j, deltam, deltap is expected unchanged between calls. + ! work(i) = deltap(i), + ! work(k+i) = deltam(i), i = 1,k + + if (k<1_ip) then + !write(error_unit,'(A)') 'dbspvn - k does not satisfy k>=1' + iflag = 201_ip + return + end if + if (jhigh>k .or. jhigh<1_ip) then + !write(error_unit,'(A)') 'dbspvn - jhigh does not satisfy 1<=jhigh<=k' + iflag = 202_ip + return + end if + if (index<1_ip .or. index>2_ip) then + !write(error_unit,'(A)') 'dbspvn - index is not 1 or 2' + iflag = 203_ip + return + end if + if (xt(ileft+1_ip)) then + !write(error_unit,'(A)') 'dbspvn - x does not satisfy t(ileft)<=x<=t(ileft+1)' + iflag = 204_ip + return + end if + + iflag = 0_ip + + if (index==1_ip) then + iwork = 1_ip + vnikx(1_ip) = 1.0_wp + if (iwork>=jhigh) return + end if + + do + ipj = ileft + iwork + work(iwork) = t(ipj) - x + imjp1 = ileft - iwork + 1_ip + work(k+iwork) = x - t(imjp1) + vmprev = 0.0_wp + jp1 = iwork + 1_ip + do l=1_ip,iwork + jp1ml = jp1 - l + vm = vnikx(l)/(work(l)+work(k+jp1ml)) + vnikx(l) = vm*work(l) + vmprev + vmprev = vm*work(k+jp1ml) + end do + vnikx(jp1) = vmprev + iwork = jp1 + if (iwork>=jhigh) exit + end do + + end subroutine dbspvn +!***************************************************************************************** + +!***************************************************************************************** +!> +! Evaluates the b-representation (`t`,`a`,`n`,`k`) of a b-spline +! at `x` for the function value on `ideriv=0` or any of its +! derivatives on `ideriv=1,2,...,k-1`. right limiting values +! (right derivatives) are returned except at the right end +! point `x=t(n+1)` where left limiting values are computed. the +! spline is defined on `t(k)` \( \le \) `x` \( \le \) `t(n+1)`. +! dbvalu returns a fatal error message when `x` is outside of this +! interval. +! +! To compute left derivatives or left limiting values at a +! knot `t(i)`, replace `n` by `i-1` and set `x=t(i), i=k+1,n+1`. +! +!### Error Conditions +! +! * improper input +! +!### History +! * bvalue written by carl de boor [5] +! * dbvalu author: amos, d. e., (snla) : date written 800901 +! * revision date 820801 +! * 000330 modified array declarations. (jec) +! * Jacob Williams, 2/24/2015 : extensive refactoring of CMLIB routine. + + pure subroutine dbvalu(t,a,n,k,ideriv,x,inbv,work,iflag,val,extrap) + + implicit none + + real(wp),intent(out) :: val !! the interpolated value + integer(ip),intent(in) :: n !! number of b-spline coefficients. + !! (sum of knot multiplicities-`k`) + real(wp),dimension(:),intent(in) :: t !! knot vector of length `n+k` + real(wp),dimension(n),intent(in) :: a !! b-spline coefficient vector of length `n` + integer(ip),intent(in) :: k !! order of the b-spline, `k >= 1` + integer(ip),intent(in) :: ideriv !! order of the derivative, `0 <= ideriv <= k-1`. + !! `ideriv = 0` returns the b-spline value + real(wp),intent(in) :: x !! argument, `t(k) <= x <= t(n+1)` + integer(ip),intent(inout) :: inbv !! an initialization parameter which must be set + !! to 1 the first time [[dbvalu]] is called. + !! `inbv` contains information for efficient processing + !! after the initial call and `inbv` must not + !! be changed by the user. distinct splines require + !! distinct `inbv` parameters. + real(wp),dimension(:),intent(inout) :: work !! work vector of length at least `3*k` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 401: `k` does not satisfy `k` \( \ge \) 1 + !! * 402: `n` does not satisfy `n` \( \ge \) `k` + !! * 403: `ideriv` does not satisfy 0 \( \le \) `ideriv` \(<\) `k` + !! * 404: `x` is not greater than or equal to `t(k)` + !! * 405: `x` is not less than or equal to `t(n+1)` + !! * 406: a left limiting value cannot be obtained at `t(k)` + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + integer(ip) :: i,iderp1,ihi,ihmkmj,ilo,imk,imkpj,ipj,& + ip1,ip1mj,j,jj,j1,j2,kmider,kmj,km1,kpk,mflag + real(wp) :: fkmj + real(wp) :: xt + logical :: extrapolation_allowed !! if extrapolation is allowed + + val = 0.0_wp + + if (k<1_ip) then + iflag = 401_ip ! dbvalu - k does not satisfy k>=1 + return + end if + + if (n=k + return + end if + + if (ideriv<0_ip .or. ideriv>=k) then + iflag = 403_ip ! dbvalu - ideriv does not satisfy 0<=iderivt(n+1_ip)) then + xt = t(n+1_ip) + else + xt = x + end if + else + xt = x + end if + + kmider = k - ideriv + + ! find *i* in (k,n) such that t(i) <= x < t(i+1) + ! (or, <= t(i+1) if t(i) < t(i+1) = t(n+1)). + + km1 = k - 1_ip + call dintrv(t, n+1, xt, inbv, i, mflag) + if (xtt(i)) then + iflag = 405_ip ! dbvalu - x is not less than or equal to t(n+1) + return + end if + + do + if (i==k) then + iflag = 406_ip ! dbvalu - a left limiting value cannot be obtained at t(k) + return + end if + i = i - 1_ip + if (xt/=t(i)) exit + end do + + end if + + ! difference the coefficients *ideriv* times + ! work(i) = aj(i), work(k+i) = dp(i), work(k+k+i) = dm(i), i=1.k + + imk = i - k + do j=1_ip,k + imkpj = imk + j + work(j) = a(imkpj) + end do + + if (ideriv/=0_ip) then + do j=1_ip,ideriv + kmj = k - j + fkmj = real(kmj,wp) + do jj=1_ip,kmj + ihi = i + jj + ihmkmj = ihi - kmj + work(jj) = (work(jj+1_ip)-work(jj))/(t(ihi)-t(ihmkmj))*fkmj + end do + end do + end if + + ! compute value at *x* in (t(i),(t(i+1)) of ideriv-th derivative, + ! given its relevant b-spline coeff. in aj(1),...,aj(k-ideriv). + + if (ideriv/=km1) then + ip1 = i + 1_ip + kpk = k + k + j1 = k + 1_ip + j2 = kpk + 1_ip + do j=1_ip,kmider + ipj = i + j + work(j1) = t(ipj) - x + ip1mj = ip1 - j + work(j2) = x - t(ip1mj) + j1 = j1 + 1_ip + j2 = j2 + 1_ip + end do + iderp1 = ideriv + 1_ip + do j=iderp1,km1 + kmj = k - j + ilo = kmj + do jj=1_ip,kmj + work(jj) = (work(jj+1_ip)*work(kpk+ilo)+work(jj)*& + work(k+jj))/(work(kpk+ilo)+work(k+jj)) + ilo = ilo - 1 + end do + end do + end if + + iflag = 0_ip + val = work(1_ip) + + end subroutine dbvalu +!***************************************************************************************** + +!***************************************************************************************** +!> +! Computes the largest integer `ileft` in 1 \( \le \) `ileft` \( \le \) `lxt` +! such that `xt(ileft)` \( \le \) `x` where `xt(*)` is a subdivision of +! the `x` interval. +! precisely, +! +!```fortran +! if x < xt(1) then ileft=1, mflag=-1 +! if xt(i) <= x < xt(i+1) then ileft=i, mflag=0 +! if xt(lxt) <= x then ileft=lxt, mflag=-2 +!``` +! +! that is, when multiplicities are present in the break point +! to the left of `x`, the largest index is taken for `ileft`. +! +!### History +! * interv written by carl de boor [5] +! * dintrv author: amos, d. e., (snla) : date written 800901 +! * revision date 820801 +! * Jacob Williams, 2/24/2015 : updated to free-form Fortran. +! * Jacob Williams, 2/17/2016 : additional refactoring (eliminated GOTOs). +! * Jacob Williams, 3/4/2017 : added extrapolation option. + + pure subroutine dintrv(xt,lxt,xx,ilo,ileft,mflag,extrap) + + implicit none + + integer(ip),intent(in) :: lxt !! length of the `xt` vector + real(wp),dimension(:),intent(in) :: xt !! a knot or break point vector of length `lxt` + real(wp),intent(in) :: xx !! argument + integer(ip),intent(inout) :: ilo !! an initialization parameter which must be set + !! to 1 the first time the spline array `xt` is + !! processed by dintrv. `ilo` contains information for + !! efficient processing after the initial call and `ilo` + !! must not be changed by the user. distinct splines + !! require distinct `ilo` parameters. + integer(ip),intent(out) :: ileft !! largest integer satisfying `xt(ileft)` \( \le \) `x` + integer(ip),intent(out) :: mflag !! signals when `x` lies out of bounds + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + integer(ip) :: ihi, istep, middle + real(wp) :: x + + x = get_temp_x_for_extrap(xx,xt(1_ip),xt(lxt),extrap) + + ihi = ilo + 1_ip + if ( ihi>=lxt ) then + if ( x>=xt(lxt) ) then + mflag = -2_ip + ileft = lxt + return + end if + if ( lxt<=1 ) then + mflag = -1_ip + ileft = 1_ip + return + end if + ilo = lxt - 1_ip + ihi = lxt + end if + + if ( x>=xt(ihi) ) then + + ! now x >= xt(ilo). find upper bound + istep = 1_ip + do + ilo = ihi + ihi = ilo + istep + if ( ihi>=lxt ) then + if ( x>=xt(lxt) ) then + mflag = -2_ip + ileft = lxt + return + end if + ihi = lxt + else if ( x>=xt(ihi) ) then + istep = istep*2_ip + cycle + end if + exit + end do + + else + + if ( x>=xt(ilo) ) then + mflag = 0_ip + ileft = ilo + return + end if + ! now x <= xt(ihi). find lower bound + istep = 1_ip + do + ihi = ilo + ilo = ihi - istep + if ( ilo<=1_ip ) then + ilo = 1_ip + if ( x +! DBINT4 computes the B representation (`t`,`bcoef`,`n`,`k`) of a +! cubic spline (`k=4`) which interpolates data (`x(i)`,`y(i)`),`i=1,ndata`. +! +! Parameters `ibcl`, `ibcr`, `fbcl`, `fbcr` allow the specification of the spline +! first or second derivative at both `x(1)` and `x(ndata)`. When this data is not specified +! by the problem, it is common practice to use a natural spline by setting second +! derivatives at `x(1)` and `x(ndata)` to zero (`ibcl=ibcr=2`,`fbcl=fbcr=0.0`). +! +! The spline is defined on `t(4) <= x <= t(n+1)` with (ordered) interior knots at +! `x(i)` values where n=ndata+2. The knots `t(1)`,`t(2)`,`t(3)` lie to the left of +! `t(4)=x(1)` and the knots `t(n+2)`, `t(n+3)`, `t(n+4)` lie to the right of `t(n+1)=x(ndata)` +! in increasing order. +! +! * If no extrapolation outside (`x(1)`,`x(ndata)`) is anticipated, the +! knots `t(1)=t(2)=t(3)=t(4)=x(1)` and `t(n+2)=t(n+3)=t(n+4)=t(n+1)=x(ndata)` +! can be specified by `kntopt=1`. +! * `kntopt=2` selects a knot placement for `t(1)`, `t(2)`, `t(3)` to make the +! first 7 knots symmetric about `t(4)=x(1)` and similarly for +! `t(n+2)`, `t(n+3)`, `t(n+4)` about `t(n+1)=x(ndata)`. +! * `kntopt=3` allows the user to make his own selection, in increasing order, +! for `t(1)`, `t(2)`, `t(3)` to the left of `x(1)` and `t(n+2)`, `t(n+3)`, `t(n+4)` to +! the right of x(ndata). +! +! In any case, the interpolation on `t(4) <= x <= t(n+1)` +! by using function [[dbvalu]] is unique for given boundary +! conditions. +! +!### Error conditions +! * improper input +! * singular system of equations +! +!### See also +! * [[dbintk]] +! +!### History +! * Written by D. E. Amos (SNLA), August, 1979. +! * date written 800901 +! * revision date 820801 +! * 000330 Modified array declarations. (JEC) +! * Jacob Williams, 8/30/2018 : refactored to modern Fortran. + + pure subroutine dbint4(x,y,ndata,ibcl,ibcr,fbcl,fbcr,kntopt,tleft,tright,t,bcoef,n,k,w,iflag) + + implicit none + + real(wp),dimension(:),intent(in) :: x !! x vector of abscissae of length `ndata`, distinct + !! and in increasing order + real(wp),dimension(:),intent(in) :: y !! y vector of ordinates of length ndata + integer(ip),intent(in) :: ndata !! number of data points, `ndata >= 2` + integer(ip),intent(in) :: ibcl !! selection parameter for left boundary condition: + !! + !! * `ibcl = 1` constrain the first derivative at `x(1)` to `fbcl` + !! * `ibcl = 2` constrain the second derivative at `x(1)` to `fbcl` + integer(ip),intent(in) :: ibcr !! selection parameter for right boundary condition: + !! + !! * `ibcr = 1` constrain first derivative at `x(ndata)` to `fbcr` + !! * `ibcr = 2` constrain second derivative at `x(ndata)` to `fbcr` + real(wp),intent(in) :: fbcl !! left boundary values governed by `ibcl` + real(wp),intent(in) :: fbcr !! right boundary values governed by `ibcr` + integer(ip),intent(in) :: kntopt !! knot selection parameter: + !! + !! * `kntopt = 1` sets knot multiplicity at `t(4)` and + !! `t(n+1)` to 4 + !! * `kntopt = 2` sets a symmetric placement of knots + !! about `t(4)` and `t(n+1)` + !! * `kntopt = 3` sets `t(i)=tleft(i)` and + !! `t(n+1+i)=tright(i)`,`i=1,3` + real(wp),dimension(3),intent(in) :: tleft !! when `kntopt = 3`: `t(1:3)` in increasing + !! order to be supplied by the user. + real(wp),dimension(3),intent(in) :: tright !! when `kntopt = 3`: `t(n+2:n+4)` in increasing + !! order to be supplied by the user. + real(wp),dimension(:),intent(out) :: t !! knot array of length `n+4` + real(wp),dimension(:),intent(out) :: bcoef !! b spline coefficient array of length `n` + integer(ip),intent(out) :: n !! number of coefficients, `n=ndata+2` + integer(ip),intent(out) :: k !! order of spline, `k=4` + real(wp),dimension(5,ndata+2),intent(inout) :: w !! work array + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 2001: `ndata` is less than 2 + !! * 2002: `x` values are not distinct or not ordered + !! * 2003: `ibcl` is not 1 or 2 + !! * 2004: `ibcr` is not 1 or 2 + !! * 2005: `kntopt` is not 1, 2, or 3 + !! * 2006: knot input through `tleft`, `tright` is + !! not ordered properly + !! * 2007: the system of equations is singular + + integer(ip) :: i, ilb, ileft, it, iub, iw, iwp, j, jw, ndm, np, nwrow + real(wp) :: txn, tx1, xl + real(wp),dimension(4,4) :: vnikx + real(wp),dimension(15) :: work !! work array for [[dbspvd]] -- length `(k+1)*(k+2)/2` + + real(wp),parameter :: wdtol = epsilon(1.0_wp) !! d1mach(4) + real(wp),parameter :: tol = sqrt(wdtol) + + if (ndata<2_ip) then + iflag = 2001_ip ! ndata is less than 2 + return + end if + + ndm = ndata - 1_ip + do i=1_ip,ndm + if (x(i)>=x(i+1_ip)) then + iflag = 2002_ip ! x values are not distinct or not ordered + return + end if + end do + + if (ibcl<1_ip .or. ibcl>2_ip) then + iflag = 2003_ip ! ibcl is not 1 or 2 + return + end if + + if (ibcr<1_ip .or. ibcr>2_ip) then + iflag = 2004_ip ! ibcr is not 1 or 2 + return + end if + + if (kntopt<1_ip .or. kntopt>3_ip) then + iflag = 2005_ip ! kntopt is not 1, 2, or 3 + return + end if + + iflag = 0_ip + k = 4_ip + n = ndata + 2_ip + np = n + 1_ip + do i=1_ip,ndata + t(i+3) = x(i) + end do + + select case (kntopt) + case(1_ip) + ! set up knot array with multiplicity 4 at x(1) and x(ndata) + do i=1,3_ip + t(4-i) = x(1) + t(np+i) = x(ndata) + end do + case(2_ip) + !set up knot array with symmetric placement about end points + if (ndata>3) then + tx1 = x(1) + x(1) + txn = x(ndata) + x(ndata) + do i=1,3 + t(4-i) = tx1 - x(i+1) + t(np+i) = txn - x(ndata-i) + end do + else + xl = (x(ndata)-x(1))/3.0_wp + do i=1,3 + t(4-i) = t(5-i) - xl + t(np+i) = t(np+i-1) + xl + end do + end if + case(3_ip) + ! set up knot array less than x(1) and greater than x(ndata) to be + ! supplied by user in tleft & tright when kntopt=3 + t(1:3) = tleft + t(ndata+4:ndata+6) = tright + do i=1,3 + if ((t(4-i)>t(5-i)) .or. (t(np+i)=2) then + do i=2,ndm + ileft = ileft + 1_ip + call dbspvd(t, k, 1_ip, x(i), ileft, 4_ip, vnikx, work, iflag) + if (iflag/=0_ip) return ! error check + do j=1,3 + w(j+1,3+i-j) = vnikx(4-j,1) + end do + bcoef(i+1) = y(i) + end do + end if + + ! set up right interpolation point and right boundary condition for + ! left limits(ileft is associated with t(n)=x(ndata-1)) + it = ibcr + 1_ip + call dbspvd(t, k, it, x(ndata), ileft, 4_ip, vnikx, work, iflag) + if (iflag/=0_ip) return ! error check + jw = 0_ip + if (abs(vnikx(2,1)) +! DBSPVD calculates the value and all derivatives of order +! less than `nderiv` of all basis functions which do not +! (possibly) vanish at `x`. `ileft` is input such that +! `t(ileft) <= x < t(ileft+1)`. A call to [[dintrv]](`t`,`n+1`,`x`, +! `ilo`,`ileft`,`mflag`) will produce the proper `ileft`. The output of +! dbspvd is a matrix `vnikx(i,j)` of dimension at least `(k,nderiv)` +! whose columns contain the `k` nonzero basis functions and +! their `nderiv-1` right derivatives at `x`, `i=1,k, j=1,nderiv`. +! These basis functions have indices `ileft-k+i`, `i=1,k, +! k <= ileft <= n`. The nonzero part of the `i`-th basis +! function lies in `(t(i),t(i+k)), i=1,n)`. +! +! If `x=t(ileft+1)` then `vnikx` contains left limiting values +! (left derivatives) at `t(ileft+1)`. In particular, `ileft = n` +! produces left limiting values at the right end point +! `x=t(n+1)`. To obtain left limiting values at `t(i)`, `i=k+1,n+1`, +! set `x` = next lower distinct knot, call [[dintrv]] to get `ileft`, +! set `x=t(i)`, and then call dbspvd. +! +!### History +! * Written by Carl de Boor and modified by D. E. Amos +! * date written 800901 +! * revision date 820801 +! * 000330 Modified array declarations. (JEC) +! * Jacob Williams, 8/30/2018 : refactored to modern Fortran. +! +!@note `DBSPVD` is the `BSPLVD` routine of the reference. + + pure subroutine dbspvd(t,k,nderiv,x,ileft,ldvnik,vnikx,work,iflag) + + implicit none + + real(wp),dimension(:),intent(in) :: t !! knot vector of length `n+k`, where + !! `n` = number of b-spline basis functions + !! `n` = sum of knot multiplicities-k + integer(ip),intent(in) :: k !! order of the b-spline, `k >= 1` + integer(ip),intent(in) :: nderiv !! number of derivatives = `nderiv-1`, + !! `1 <= nderiv <= k` + real(wp),intent(in) :: x !! argument of basis functions, + !! `t(k) <= x <= t(n+1)` + integer(ip),intent(in) :: ileft !! largest integer such that + !! `t(ileft) <= x < t(ileft+1)` + integer(ip),intent(in) :: ldvnik !! leading dimension of matrix `vnikx` + real(wp),dimension(ldvnik,nderiv),intent(out) :: vnikx !! matrix of dimension at least `(k,nderiv)` + !! containing the nonzero basis functions + !! at `x` and their derivatives columnwise. + real(wp),dimension(*),intent(out) :: work !! a work vector of length `(k+1)*(k+2)/2` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 3001: `k` does not satisfy `k>=1` + !! * 3002: `nderiv` does not satisfy `1<=nderiv<=k` + !! * 3003: `ldvnik` does not satisfy `ldvnik>=k` + + integer(ip) :: i,ideriv,ipkmd,j,jj,jlow,jm,jp1mid,kmd,kp1,l,ldummy,m,mhigh,iwork + real(wp) :: factor, fkmd, v + + ! dimension t(ileft+k), work((k+1)*(k+2)/2) + ! a(i,j) = work(i+j*(j+1)/2), i=1,j+1 j=1,k-1 + ! a(i,k) = work(i+k*(k-1)/2) i=1.k + ! work(1) and work((k+1)*(k+2)/2) are not used. + + if (k<1) then + iflag = 3001_ip ! k does not satisfy k>=1 + return + end if + + if (nderiv<1 .or. nderiv>k) then + iflag = 3002_ip ! nderiv does not satisfy 1<=nderiv<=k + return + end if + + if (ldvnik=k + return + end if + + iflag = 0_ip + + ideriv = nderiv + kp1 = k + 1 + jj = kp1 - ideriv + call dbspvn(t, jj, k, 1_ip, x, ileft, vnikx, work, iwork, iflag) + if (iflag/=0 .or. ideriv==1) return + mhigh = ideriv + do m=2,mhigh + jp1mid = 1 + do j=ideriv,k + vnikx(j,ideriv) = vnikx(jp1mid,1) + jp1mid = jp1mid + 1 + end do + ideriv = ideriv - 1 + jj = kp1 - ideriv + call dbspvn(t, jj, k, 2_ip, x, ileft, vnikx, work, iwork, iflag) + if (iflag/=0) return + end do + + jm = kp1*(kp1+1)/2 + do l = 1,jm + work(l) = 0.0_wp + end do + ! a(i,i) = work(i*(i+3)/2) = 1.0 i = 1,k + l = 2 + j = 0 + do i = 1,k + j = j + l + work(j) = 1.0_wp + l = l + 1 + end do + kmd = k + do m=2,mhigh + kmd = kmd - 1 + fkmd = real(kmd,wp) + i = ileft + j = k + jj = j*(j+1)/2 + jm = jj - j + do ldummy=1,kmd + ipkmd = i + kmd + factor = fkmd/(t(ipkmd)-t(i)) + do l=1,j + work(l+jj) = (work(l+jj)-work(l+jm))*factor + end do + i = i - 1 + j = j - 1 + jj = jm + jm = jm - j + end do + + do i=1,k + v = 0.0_wp + jlow = max(i,m) + jj = jlow*(jlow+1)/2 + do j=jlow,k + v = work(i+jj)*vnikx(j,m) + v + jj = jj + j + 1 + end do + vnikx(i,m) = v + end do + end do + + end subroutine dbspvd +!***************************************************************************************** + +!***************************************************************************************** +!> +! DBSQAD computes the integral on `(x1,x2)` of a `k`-th order +! b-spline using the b-representation `(t,bcoef,n,k)`. orders +! `k` as high as 20 are permitted by applying a 2, 6, or 10 +! point gauss formula on subintervals of `(x1,x2)` which are +! formed by included (distinct) knots. +! +! If orders `k` greater than 20 are needed, use [[dbfqad]] with +! `f(x) = 1`. +! +!### Note +! * The maximum number of significant digits obtainable in +! DBSQAD is the smaller of ~300 and the number of digits +! carried in `real(wp)` arithmetic. +! +!### References +! * D. E. Amos, "Quadrature subroutines for splines and +! B-splines", Report SAND79-1825, Sandia Laboratories, +! December 1979. +! +!### History +! * Author: Amos, D. E., (SNLA) +! * 800901 DATE WRITTEN +! * 890531 Changed all specific intrinsics to generic. (WRB) +! * 890531 REVISION DATE from Version 3.2 +! * 891214 Prologue converted to Version 4.0 format. (BAB) +! * 900315 CALLs to XERROR changed to CALLs to XERMSG. (THJ) +! * 900326 Removed duplicate information from DESCRIPTION section. (WRB) +! * 920501 Reformatted the REFERENCES section. (WRB) +! * Jacob Williams, 9/6/2017 : refactored to modern Fortran. +! Added higher precision coefficients. +! +!@note Extrapolation is not enabled for this routine. + + pure subroutine dbsqad(t,bcoef,n,k,x1,x2,bquad,work,iflag) + + implicit none + + real(wp),dimension(:),intent(in) :: t !! knot array of length `n+k` + real(wp),dimension(:),intent(in) :: bcoef !! b-spline coefficient array of length `n` + integer(ip),intent(in) :: n !! length of coefficient array + integer(ip),intent(in) :: k !! order of b-spline, `1 <= k <= 20` + real(wp),intent(in) :: x1 !! end point of quadrature interval + !! in `t(k) <= x <= t(n+1)` + real(wp),intent(in) :: x2 !! end point of quadrature interval + !! in `t(k) <= x <= t(n+1)` + real(wp),intent(out) :: bquad !! integral of the b-spline over (`x1`,`x2`) + real(wp),dimension(:),intent(inout) :: work !! work vector of length `3*k` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 901: `k` does not satisfy `1<=k<=20` + !! * 902: `n` does not satisfy `n>=k` + !! * 903: `x1` or `x2` or both do + !! not satisfy `t(k)<=x<=t(n+1)` + + integer(ip) :: i,il1,il2,ilo,inbv,jf,left,m,mf,mflag,npk,np1 + real(wp) :: a,aa,b,bb,bma,bpa,c1,gx,q,ta,tb,y1,y2 + real(wp),dimension(5) :: s !! sum + + real(wp),dimension(9),parameter :: gpts = [ & + &0.577350269189625764509148780501957455647601751270126876018602326483977& + &67230293334569371539558574952522520871380513556767665664836499965082627& + &05518373647912161760310773007685273559916067003615583077550051041144223& + &01107628883557418222973945990409015710553455953862673016662179126619796& + &4892168_wp,& + &0.238619186083196908630501721680711935418610630140021350181395164574274& + &93427563984224922442725734913160907222309701068720295545303507720513526& + &28872175189982985139866216812636229030578298770859440976999298617585739& + &46921613621659222233462641640013936777894532787145324672151888999339900& + &0945406150514997832_wp,& + &0.661209386466264513661399595019905347006448564395170070814526705852183& + &49660714310094428640374646145642988837163927514667955734677222538043817& + &23198010093367423918538864300079016299442625145884902455718821970386303& + &22362011735232135702218793618906974301231555871064213101639896769013566& + &1651261150514997832_wp,& + &0.932469514203152027812301554493994609134765737712289824872549616526613& + &50084420019627628873992192598504786367972657283410658797137951163840419& + &21786180750210169211578452038930846310372961174632524612619760497437974& + &07422632089671621172178385230505104744277222209386367655366917903888025& + &2326771150514997832_wp,& + &0.148874338981631210884826001129719984617564859420691695707989253515903& + &61735566852137117762979946369123003116080525533882610289018186437654023& + &16761969968090913050737827720371059070942475859422743249837177174247346& + &21691485290294292900319346665908243383809435507599683357023000500383728& + &0634351_wp,& + &0.433395394129247190799265943165784162200071837656246496502701513143766& + &98907770350122510275795011772122368293504099893794727422475772324920512& + &67741032822086200952319270933462032011328320387691584063411149801129823& + &14148878744320432476641442157678880770848387945248811854979703928792696& + &4254222_wp,& + &0.679409568299024406234327365114873575769294711834809467664817188952558& + &57539507492461507857357048037949983390204739931506083674084257663009076& + &82741718202923543197852846977409718369143712013552962837733153108679126& + &93254495485472934132472721168027426848661712101171203022718105101071880& + &4444161_wp,& + &0.865063366688984510732096688423493048527543014965330452521959731845374& + &75513805556135679072894604577069440463108641176516867830016149345356373& + &92729396890950011571349689893051612072435760480900979725923317923795535& + &73929059587977695683242770223694276591148364371481692378170157259728913& + &9322313_wp,& + &0.973906528517171720077964012084452053428269946692382119231212066696595& + &20323463615962572356495626855625823304251877421121502216860143447777992& + &05409587259942436704413695764881258799146633143510758737119877875210567& + &06745243536871368303386090938831164665358170712568697066873725922944928& + &4383797_wp] + + real(wp),dimension(9),parameter :: gwts = [ & + &1.0_wp,& + &0.467913934572691047389870343989550994811655605769210535311625319963914& + &20162039812703111009258479198230476626878975479710092836255417350295459& + &35635592733866593364825926382559018030281273563502536241704619318259000& + &99756987095900533474080074634376824431808173206369174103416261765346292& + &7888917150514997832_wp,& + &0.360761573048138607569833513837716111661521892746745482289739240237140& + &03783726171832096220198881934794311720914037079858987989027836432107077& + &67872114085818922114502722525757771126000732368828591631602895111800517& + &40813685547074482472486101183259931449817216402425586777526768199930950& + &3106873150514997832_wp,& + &0.171324492379170345040296142172732893526822501484043982398635439798945& + &76054234015464792770542638866975211652206987440430919174716746217597462& + &96492293180314484520671351091683210843717994067668872126692485569940481& + &59429327357024984053433824182363244118374610391205239119044219703570297& + &7497812150514997832_wp,& + &0.295524224714752870173892994651338329421046717026853601354308029755995& + &93821715232927035659579375421672271716440125255838681849078955200582600& + &19363424941869666095627186488841680432313050615358674090830512706638652& + &87483901746874726597515954450775158914556548308329986393605934912382356& + &670244_wp,& + &0.269266719309996355091226921569469352859759938460883795800563276242153& + &43231917927676422663670925276075559581145036869830869292346938114524155& + &64658846634423711656014432259960141729044528030344411297902977067142537& + &53480628460839927657500691168674984281408628886853320804215041950888191& + &6391898_wp,& + &0.219086362515982043995534934228163192458771870522677089880956543635199& + &91065295128124268399317720219278659121687281288763476662690806694756883& + &09211843316656677105269915322077536772652826671027878246851010208832173& + &32006427348325475625066841588534942071161341022729156547776892831330068& + &8702802_wp,& + &0.149451349150580593145776339657697332402556639669427367835477268753238& + &65472663001094594726463473195191400575256104543633823445170674549760147& + &13716011937109528798134828865118770953566439639333773939909201690204649& + &08381561877915752257830034342778536175692764212879241228297015017259084& + &2897331_wp,& + &0.066671344308688137593568809893331792857864834320158145128694881613412& + &06408408710177678550968505887782109005471452041933148750712625440376213& + &93049873169940416344953637064001870112423155043935262424506298327181987& + &18647480566044117862086478449236378557180717569208295026105115288152794& + &421677_wp] + + iflag = 0_ip + bquad = 0.0_wp + + if ( k<1_ip .or. k>20_ip ) then + + iflag = 901_ip ! error return + + else if ( n=t(k) ) then + np1 = n + 1_ip + if ( bb<=t(np1) ) then + if ( aa==bb ) return + npk = n + k + ! selection of 2, 6, or 10 point gauss formula + jf = 0_ip + mf = 1_ip + if ( k>4_ip ) then + jf = 1_ip + mf = 3_ip + if ( k>12_ip ) then + jf = 4_ip + mf = 5_ip + end if + end if + do i = 1_ip , mf + s(i) = 0.0_wp + end do + ilo = 1_ip + inbv = 1_ip + call dintrv(t,npk,aa,ilo,il1,mflag) + call dintrv(t,npk,bb,ilo,il2,mflag) + if ( il2>=np1 ) il2 = n + do left = il1 , il2 + ta = t(left) + tb = t(left+1_ip) + if ( ta/=tb ) then + a = max(aa,ta) + b = min(bb,tb) + bma = 0.5_wp*(b-a) + bpa = 0.5_wp*(b+a) + do m = 1_ip , mf + c1 = bma*gpts(jf+m) + gx = -c1 + bpa + call dbvalu(t,bcoef,n,k,0_ip,gx,inbv,work,iflag,y2) + if (iflag/=0_ip) return + gx = c1 + bpa + call dbvalu(t,bcoef,n,k,0_ip,gx,inbv,work,iflag,y1) + if (iflag/=0_ip) return + s(m) = s(m) + (y1+y2)*bma + end do + end if + end do + q = 0.0_wp + do m = 1_ip , mf + q = q + gwts(jf+m)*s(m) + end do + if ( x1>x2 ) q = -q + bquad = q + return + end if + end if + + iflag = 903_ip ! error return + + end if + + end subroutine dbsqad +!***************************************************************************************** + +!***************************************************************************************** +!> +! dbfqad computes the integral on `(x1,x2)` of a product of a +! function `f` and the `id`-th derivative of a `k`-th order b-spline, +! using the b-representation `(t,bcoef,n,k)`. `(x1,x2)` must be a +! subinterval of `t(k) <= x <= t(n+1)`. an integration routine, +! [[dbsgq8]] (a modification of `gaus8`), integrates the product +! on subintervals of `(x1,x2)` formed by included (distinct) knots +! +!### Reference +! * D. E. Amos, "Quadrature subroutines for splines and +! B-splines", Report SAND79-1825, Sandia Laboratories, +! December 1979. +! +!### History +! * 800901 Amos, D. E., (SNLA) +! * 890531 Changed all specific intrinsics to generic. (WRB) +! * 890531 REVISION DATE from Version 3.2 +! * 891214 Prologue converted to Version 4.0 format. (BAB) +! * 900315 CALLs to XERROR changed to CALLs to XERMSG. (THJ) +! * 900326 Removed duplicate information from DESCRIPTION section. (WRB) +! * 920501 Reformatted the REFERENCES section. (WRB) +! * Jacob Williams, 9/6/2017 : refactored to modern Fortran. Some changes. +! +!@note the maximum number of significant digits obtainable in +! [[dbsqad]] is the smaller of ~300 and the number of digits +! carried in `real(wp)` arithmetic. +! +!@note Extrapolation is not enabled for this routine. + + subroutine dbfqad(f,t,bcoef,n,k,id,x1,x2,tol,quad,iflag,work) + + implicit none + + procedure(b1fqad_func) :: f !! external function of one argument for the + !! integrand `bf(x)=f(x)*dbvalu(t,bcoef,n,k,id,x,inbv,work)` + integer(ip),intent(in) :: n !! length of coefficient array + integer(ip),intent(in) :: k !! order of b-spline, `k >= 1` + real(wp),dimension(n+k),intent(in) :: t !! knot array + real(wp),dimension(n),intent(in) :: bcoef !! coefficient array + integer(ip),intent(in) :: id !! order of the spline derivative, `0 <= id <= k-1` + !! `id=0` gives the spline function + real(wp),intent(in) :: x1 !! left point of quadrature interval in `t(k) <= x <= t(n+1)` + real(wp),intent(in) :: x2 !! right point of quadrature interval in `t(k) <= x <= t(n+1)` + real(wp),intent(in) :: tol !! desired accuracy for the quadrature, suggest + !! `10*dtol < tol <= 0.1` where `dtol` is the maximum + !! of `1.0e-300` and real(wp) unit roundoff for + !! the machine + real(wp),intent(out) :: quad !! integral of `bf(x)` on `(x1,x2)` + real(wp),dimension(:),intent(inout) :: work !! work vector of length `3*k` + integer(ip),intent(out) :: iflag !! status flag: + !! + !! * 0: no errors + !! * 1001: `k` does not satisfy `k>=1` + !! * 1002: `n` does not satisfy `n>=k` + !! * 1003: `d` does not satisfy `0<=id=k ) then + iflag = 1003_ip ! error + else + if ( tol>=min_tol .and. tol<=0.1_wp ) then + aa = min(x1,x2) + bb = max(x1,x2) + if ( aa>=t(k) ) then + np1 = n + 1_ip + if ( bb<=t(np1) ) then + if ( aa==bb ) return + npk = n + k + ilo = 1_ip + call dintrv(t,npk,aa,ilo,il1,mflag) + call dintrv(t,npk,bb,ilo,il2,mflag) + if ( il2>=np1 ) il2 = n + inbv = 1_ip + q = 0.0_wp + do left = il1 , il2 + ta = t(left) + tb = t(left+1_ip) + if ( ta/=tb ) then + a = max(aa,ta) + b = min(bb,tb) + call dbsgq8(f,t,bcoef,n,k,id,a,b,inbv,err,ans,iflag,work) + if ( iflag/=0_ip .and. iflag/=1101_ip ) return + q = q + ans + end if + end do + if ( x1>x2 ) q = -q + quad = q + end if + else + iflag = 1004_ip ! error + end if + else + iflag = 1005_ip ! error + end if + end if + + end subroutine dbfqad +!***************************************************************************************** + +!***************************************************************************************** +!> +! DBSGQ8, a modification of [gaus8](http://netlib.sandia.gov/slatec/src/gaus8.f), +! integrates the product of `fun(x)` by the `id`-th derivative of a spline +! [[dbvalu]] between limits `a` and `b` using an adaptive 8-point Legendre-Gauss +! algorithm. +! +!### See also +! * [[dbfqad]] +! +!### History +! * 800901 Jones, R. E., (SNLA) +! * 890531 Changed all specific intrinsics to generic. (WRB) +! * 890911 Removed unnecessary intrinsics. (WRB) +! * 891214 Prologue converted to Version 4.0 format. (BAB) +! * 900315 CALLs to XERROR changed to CALLs to XERMSG. (THJ) +! * 900326 Removed duplicate information from DESCRIPTION section. (WRB) +! * 900328 Added TYPE section. (WRB) +! * 910408 Updated the AUTHOR section. (WRB) +! * Jacob Williams, 9/6/2017 : refactored to modern Fortran. Some changes. +! Added higher precision coefficients. + + subroutine dbsgq8(fun,xt,bc,n,kk,id,a,b,inbv,err,ans,iflag,work) + + implicit none + + procedure(b1fqad_func) :: fun !! name of external function of one + !! argument which multiplies [[dbvalu]]. + integer(ip),intent(in) :: n !! number of b-coefficients for [[dbvalu]] + integer(ip),intent(in) :: kk !! order of the spline, `kk>=1` + real(wp),dimension(:),intent(in) :: xt !! knot array for [[dbvalu]] + real(wp),dimension(n),intent(in) :: bc !! b-coefficient array for [[dbvalu]] + integer(ip),intent(in) :: id !! Order of the spline derivative, `0<=id<=kk-1` + real(wp),intent(in) :: a !! lower limit of integral + real(wp),intent(in) :: b !! upper limit of integral (may be less than `a`) + integer(ip),intent(inout) :: inbv !! initialization parameter for [[dbvalu]] + real(wp),intent(inout) :: err !! **IN:** is a requested pseudorelative error + !! tolerance. normally pick a value of + !! `abs(err)<1e-3`. `ans` will normally + !! have no more error than `abs(err)` times + !! the integral of the absolute value of + !! `fun(x)*[[dbvalu]]()`. + !! + !! **OUT:** will be an estimate of the absolute + !! error in ans if the input value of `err` + !! was negative. (`err` is unchanged if + !! the input value of `err` was nonnegative.) + !! the estimated error is solely for information + !! to the user and should not be used as a + !! correction to the computed integral. + real(wp),intent(out) :: ans !! computed value of integral + integer(ip),intent(out) :: iflag !! a status code: + !! + !! * 0: `ans` most likely meets requested + !! error tolerance, or `a=b`. + !! * 1101: `a` and `b` are too nearly equal + !! to allow normal integration. + !! `ans` is set to zero. + !! * 1102: `ans` probably does not meet + !! requested error tolerance. + real(wp),dimension(:),intent(inout) :: work !! work vector of length `3*k` for [[dbvalu]] + + integer(ip) :: k,l,lmn,lmx,mxl,nbits,nib,nlmx + real(wp) :: ae,anib,area,c,ce,ee,ef,eps,est,gl,glr,tol,vr,x + integer(ip),dimension(60) :: lr + real(wp),dimension(60) :: aa,hh,vl,gr + + integer(ip),parameter :: i1mach14 = digits(1.0_wp) !! i1mach(14) + real(wp),parameter :: d1mach5 = log10(real(radix(x),wp)) !! d1mach(5) + real(wp),parameter :: ln2 = log(2.0_wp) !! 0.69314718d0 + real(wp),parameter :: sq2 = sqrt(2.0_wp) + integer(ip),parameter :: nlmn = 1 + integer(ip),parameter :: kmx = 5000 + integer(ip),parameter :: kml = 6 + + ! initialize + inbv = 1_ip + iflag = 0_ip + k = i1mach14 + anib = d1mach5*k/0.30102000_wp + nbits = int(anib,ip) + nlmx = min((nbits*5_ip)/8_ip,60_ip) + ans = 0.0_wp + ce = 0.0_wp + + if ( a==b ) then + if ( err<0.0_wp ) err = ce + else + lmx = nlmx + lmn = nlmn + if ( b/=0.0_wp ) then + if ( sign(1.0_wp,b)*a>0.0_wp ) then + c = abs(1.0_wp-a/b) + if ( c<=0.1_wp ) then + if ( c<=0.0_wp ) then + if ( err<0.0_wp ) err = ce + return + else + anib = 0.5_wp - log(c)/ln2 + nib = int(anib,ip) + lmx = min(nlmx,nbits-nib-7_ip) + if ( lmx<1_ip ) then + ! a and b are too nearly equal + ! to allow normal integration + iflag = 1101_ip + if ( err<0.0_wp ) err = ce + return + else + lmn = min(lmn,lmx) + end if + end if + end if + end if + end if + tol = max(abs(err),2.0_wp**(5-nbits))/2.0_wp + if ( err==0.0_wp ) tol = sqrt(epsilon(1.0_wp)) + eps = tol + hh(1_ip) = (b-a)/4.0_wp + aa(1_ip) = a + lr(1_ip) = 1_ip + l = 1_ip + call g8(aa(l)+2.0_wp*hh(l),2.0_wp*hh(l),est,iflag) + if (iflag/=0_ip) return + k = 8_ip + area = abs(est) + ef = 0.5_wp + mxl = 0_ip + end if + + do + ! compute refined estimates, estimate the error, etc. + call g8(aa(l)+hh(l),hh(l),gl,iflag) + if (iflag/=0_ip) return + call g8(aa(l)+3.0_wp*hh(l),hh(l),gr(l),iflag) + if (iflag/=0_ip) return + k = k + 16_ip + area = area + (abs(gl)+abs(gr(l))-abs(est)) + glr = gl + gr(l) + ee = abs(est-glr)*ef + ae = max(eps*area,tol*abs(glr)) + if ( ee>ae ) then + ! consider the left half of this level + if ( k>kmx ) lmx = kml + if ( l>=lmx ) then + mxl = 1_ip + else + l = l + 1_ip + eps = eps*0.5_wp + ef = ef/sq2 + hh(l) = hh(l-1)*0.5_wp + lr(l) = -1_ip + aa(l) = aa(l-1_ip) + est = gl + cycle + end if + end if + ce = ce + (est-glr) + if ( lr(l)<=0_ip ) then + ! proceed to right half at this level + vl(l) = glr + else + ! return one level + vr = glr + do + if ( l<=1_ip ) then + ! exit + ans = vr + if ( (mxl/=0_ip) .and. (abs(ce)>2.0_wp*tol*area) ) then + iflag = 1102_ip + end if + if ( err<0.0_wp ) err = ce + return + else + l = l - 1_ip + eps = eps*2.0_wp + ef = ef*sq2 + if ( lr(l)<=0 ) then + vl(l) = vl(l+1_ip) + vr + exit + else + vr = vl(l+1_ip) + vr + end if + end if + end do + end if + est = gr(l-1_ip) + lr(l) = 1_ip + aa(l) = aa(l) + 4.0_wp*hh(l) + end do + + contains + + subroutine g8(x,h,res,iflag) + + !! 8-point formula. + !! + !!@note Replaced the original double precision abscissa and weight + !! coefficients with the higher precision versions from here: + !! http://pomax.github.io/bezierinfo/legendre-gauss.html + !! So, if `wp` is changed to say, `real128`, more precision + !! can be obtained. These coefficients have about 300 digits. + + implicit none + + real(wp),intent(in) :: x + real(wp),intent(in) :: h + real(wp),intent(out) :: res + integer(ip),intent(out) :: iflag + + real(wp),dimension(8) :: f + real(wp),dimension(8) :: v + + ! abscissa and weight coefficients: + real(wp),parameter :: x1 = & + &0.1834346424956498049394761423601839806667578129129737823171884736992044& + &742215421141160682237111233537452676587642867666089196012523876865683788& + &569995160663568104475551617138501966385810764205532370882654749492812314& + &961247764619363562770645716456613159405134052985058171969174306064445289& + &638150514997832_wp + real(wp),parameter :: x2 = & + &0.5255324099163289858177390491892463490419642431203928577508570992724548& + &207685612725239614001936319820619096829248252608507108793766638779939805& + &395303668253631119018273032402360060717470006127901479587576756241288895& + &336619643528330825624263470540184224603688817537938539658502113876953598& + &879150514997832_wp + real(wp),parameter :: x3 = & + &0.7966664774136267395915539364758304368371717316159648320701702950392173& + &056764730921471519272957259390191974534530973092653656494917010859602772& + &562074621689676153935016290342325645582634205301545856060095727342603557& + &415761265140428851957341933710803722783136113628137267630651413319993338& + &002150514997832_wp + real(wp),parameter :: x4 = & + &0.9602898564975362316835608685694729904282352343014520382716397773724248& + &977434192844394389592633122683104243928172941762102389581552171285479373& + &642204909699700433982618326637346808781263553346927867359663480870597542& + &547603929318533866568132868842613474896289232087639988952409772489387324& + &25615051499783203_wp + real(wp),parameter :: w1 = & + &0.3626837833783619829651504492771956121941460398943305405248230675666867& + &347239066773243660420848285095502587699262967065529258215569895173844995& + &576007862076842778350382862546305771007553373269714714894268328780431822& + &779077846722965535548199601402487767505928976560993309027632737537826127& + &502150514997832_wp + real(wp),parameter :: w2 = & + &0.3137066458778872873379622019866013132603289990027349376902639450749562& + &719421734969616980762339285560494275746410778086162472468322655616056890& + &624276469758994622503118776562559463287222021520431626467794721603822601& + &295276898652509723185157998353156062419751736972560423953923732838789657& + &919150514997832_wp + real(wp),parameter :: w3 = & + &0.2223810344533744705443559944262408844301308700512495647259092892936168& + &145704490408536531423771979278421592661012122181231114375798525722419381& + &826674532090577908613289536840402789398648876004385697202157482063253247& + &195590228631570651319965589733545440605952819880671616779621183704306688& + &233150514997832_wp + real(wp),parameter :: w4 = & + &0.1012285362903762591525313543099621901153940910516849570590036980647401& + &787634707848602827393040450065581543893314132667077154940308923487678731& + &973041136073584690533208824050731976306575729205467961435779467552492328& + &730055025992954089946676810510810729468366466585774650346143712142008566& + &866150514997832_wp + + res = 0.0_wp + + v(1_ip) = x-x1*h + v(2_ip) = x+x1*h + v(3_ip) = x-x2*h + v(4_ip) = x+x2*h + v(5_ip) = x-x3*h + v(6_ip) = x+x3*h + v(7_ip) = x-x4*h + v(8_ip) = x+x4*h + + call dbvalu(xt,bc,n,kk,id,v(1_ip),inbv,work,iflag,f(1_ip)); if (iflag/=0_ip) return + call dbvalu(xt,bc,n,kk,id,v(2_ip),inbv,work,iflag,f(2_ip)); if (iflag/=0_ip) return + call dbvalu(xt,bc,n,kk,id,v(3_ip),inbv,work,iflag,f(3_ip)); if (iflag/=0_ip) return + call dbvalu(xt,bc,n,kk,id,v(4_ip),inbv,work,iflag,f(4_ip)); if (iflag/=0_ip) return + call dbvalu(xt,bc,n,kk,id,v(5_ip),inbv,work,iflag,f(5_ip)); if (iflag/=0_ip) return + call dbvalu(xt,bc,n,kk,id,v(6_ip),inbv,work,iflag,f(6_ip)); if (iflag/=0_ip) return + call dbvalu(xt,bc,n,kk,id,v(7_ip),inbv,work,iflag,f(7_ip)); if (iflag/=0_ip) return + call dbvalu(xt,bc,n,kk,id,v(8_ip),inbv,work,iflag,f(8_ip)); if (iflag/=0_ip) return + + res = h*((w1*(fun(v(1_ip))*f(1_ip) + fun(v(2_ip))*f(2_ip)) + & + w2*(fun(v(3_ip))*f(3_ip) + fun(v(4_ip))*f(4_ip))) + & + (w3*(fun(v(5_ip))*f(5_ip) + fun(v(6_ip))*f(6_ip)) + & + w4*(fun(v(7_ip))*f(7_ip) + fun(v(8_ip))*f(8_ip)))) + + end subroutine g8 + + end subroutine dbsgq8 +!***************************************************************************************** + +!***************************************************************************************** +!> +! Returns the value of `x` to use for computing the interval +! in `t`, depending on if extrapolation is allowed or not. +! +! If extrapolation is allowed and x is < tmin or > tmax, then either +! `tmin` or `tmax - 2.0_wp*spacing(tmax)` is returned. +! Otherwise, `x` is returned. + + pure function get_temp_x_for_extrap(x,tmin,tmax,extrap) result(xt) + + implicit none + + real(wp),intent(in) :: x !! variable value + real(wp),intent(in) :: tmin !! first knot vector element for b-splines + real(wp),intent(in) :: tmax !! last knot vector element for b-splines + real(wp) :: xt !! The value returned (it will either + !! be `tmin`, `x`, or `tmax`) + logical,intent(in),optional :: extrap !! if extrapolation is allowed + !! (if not present, default is False) + + logical :: extrapolation_allowed !! if extrapolation is allowed + + if (present(extrap)) then + extrapolation_allowed = extrap + else + extrapolation_allowed = .false. + end if + + if (extrapolation_allowed) then + if (xtmax) then + ! Put it just inside the upper bound. + ! This is sort of a hack to get + ! extrapolation to work. + xt = tmax - 2.0_wp*spacing(tmax) + else + xt = x + end if + else + xt = x + end if + + end function get_temp_x_for_extrap +!***************************************************************************************** + +!***************************************************************************************** +!> +! Returns a message string associated with the status code. + + pure function get_status_message(iflag) result(msg) + + implicit none + + integer(ip),intent(in) :: iflag !! return code from one of the routines + character(len=:),allocatable :: msg !! status message associated with the flag + + character(len=10) :: istr !! for integer to string conversion + integer(ip) :: istat !! for write statement + + select case (iflag) + + case( 0_ip); msg='Successful execution' + + case( -1_ip); msg='Error in dintrv: x < xt(1_ip)' + case( -2_ip); msg='Error in dintrv: x >= xt(lxt)' + + case( 1_ip); msg='Error in evaluate_*d: class is not initialized' + + case( 2_ip); msg='Error in db*ink: iknot out of range' + case( 3_ip); msg='Error in db*ink: nx out of range' + case( 4_ip); msg='Error in db*ink: kx out of range' + case( 5_ip); msg='Error in db*ink: x not strictly increasing' + case( 6_ip); msg='Error in db*ink: tx not non-decreasing' + case( 7_ip); msg='Error in db*ink: ny out of range' + case( 8_ip); msg='Error in db*ink: ky out of range' + case( 9_ip); msg='Error in db*ink: y not strictly increasing' + case( 10_ip); msg='Error in db*ink: ty not non-decreasing' + case( 11_ip); msg='Error in db*ink: nz out of range' + case( 12_ip); msg='Error in db*ink: kz out of range' + case( 13_ip); msg='Error in db*ink: z not strictly increasing' + case( 14_ip); msg='Error in db*ink: tz not non-decreasing' + case( 15_ip); msg='Error in db*ink: nq out of range' + case( 16_ip); msg='Error in db*ink: kq out of range' + case( 17_ip); msg='Error in db*ink: q not strictly increasing' + case( 18_ip); msg='Error in db*ink: tq not non-decreasing' + case( 19_ip); msg='Error in db*ink: nr out of range' + case( 20_ip); msg='Error in db*ink: kr out of range' + case( 21_ip); msg='Error in db*ink: r not strictly increasing' + case( 22_ip); msg='Error in db*ink: tr not non-decreasing' + case( 23_ip); msg='Error in db*ink: ns out of range' + case( 24_ip); msg='Error in db*ink: ks out of range' + case( 25_ip); msg='Error in db*ink: s not strictly increasing' + case( 26_ip); msg='Error in db*ink: ts not non-decreasing' + case(700_ip); msg='Error in db*ink: size(x) /= size(fcn,1)' + case(701_ip); msg='Error in db*ink: size(y) /= size(fcn,2)' + case(702_ip); msg='Error in db*ink: size(z) /= size(fcn,3)' + case(703_ip); msg='Error in db*ink: size(q) /= size(fcn,4)' + case(704_ip); msg='Error in db*ink: size(r) /= size(fcn,5)' + case(705_ip); msg='Error in db*ink: size(s) /= size(fcn,6)' + case(706_ip); msg='Error in db*ink: size(x) /= nx' + case(707_ip); msg='Error in db*ink: size(y) /= ny' + case(708_ip); msg='Error in db*ink: size(z) /= nz' + case(709_ip); msg='Error in db*ink: size(q) /= nq' + case(710_ip); msg='Error in db*ink: size(r) /= nr' + case(711_ip); msg='Error in db*ink: size(s) /= ns' + case(712_ip); msg='Error in db*ink: size(tx) /= nx+kx' + case(713_ip); msg='Error in db*ink: size(ty) /= ny+ky' + case(714_ip); msg='Error in db*ink: size(tz) /= nz+kz' + case(715_ip); msg='Error in db*ink: size(tq) /= nq+kq' + case(716_ip); msg='Error in db*ink: size(tr) /= nr+kr' + case(717_ip); msg='Error in db*ink: size(ts) /= ns+ks' + case(800_ip); msg='Error in db*ink: size(x) /= size(bcoef,1)' + case(801_ip); msg='Error in db*ink: size(y) /= size(bcoef,2)' + case(802_ip); msg='Error in db*ink: size(z) /= size(bcoef,3)' + case(803_ip); msg='Error in db*ink: size(q) /= size(bcoef,4)' + case(804_ip); msg='Error in db*ink: size(r) /= size(bcoef,5)' + case(805_ip); msg='Error in db*ink: size(s) /= size(bcoef,6)' + + case(806_ip); msg='Error in dbint4: currently, only k=4 can be used' + + case(100_ip); msg='Error in dbintk: k does not satisfy k>=1' + case(101_ip); msg='Error in dbintk: n does not satisfy n>=k' + case(102_ip); msg='Error in dbintk: x(i) does not satisfy x(i) np.int32(0) + + spline.destroy() + assert spline.status_ok() is False diff --git a/examples/bspline/tests/test_procedural_api.py b/examples/bspline/tests/test_procedural_api.py new file mode 100644 index 000000000..2e1eed2a5 --- /dev/null +++ b/examples/bspline/tests/test_procedural_api.py @@ -0,0 +1,105 @@ +"""Procedural B-spline routines checked against SciPy and analytic values.""" + +from __future__ import annotations + +import numpy as np +import pytest + +from examples.bspline.routine_inventory import ALL_SUB_ROUTINES, ORDER_CONSTANTS + +pytestmark = [pytest.mark.fortran_end_to_end, pytest.mark.real_library] + +CUBIC = np.int32(4) +NOT_A_KNOT = np.int32(0) + + +def _interpolant(bspline_sub, x, fcn): + """Build one cubic interpolant through the procedural entry points.""" + nx = np.int32(x.size) + knots = np.zeros(x.size + int(CUBIC), dtype=np.float64) + bcoef = np.zeros(x.size, dtype=np.float64) + + iflag = bspline_sub.db1ink(x, nx, fcn, CUBIC, NOT_A_KNOT, knots, bcoef) + assert iflag == np.int32(0), bspline_sub.get_status_message(iflag) + return knots, bcoef, nx + + +def _evaluate(bspline_sub, knots, bcoef, nx, point, derivative=0): + work = np.zeros(3 * int(CUBIC), dtype=np.float64) + value, iflag, _inbvx = bspline_sub.db1val( + np.float64(point), + np.int32(derivative), + knots, + nx, + CUBIC, + bcoef, + np.int32(1), + work, + ) + assert iflag == np.int32(0), bspline_sub.get_status_message(iflag) + return value + + +def test_every_reviewed_procedure_is_exported(bspline_sub): + missing = [name for name in ALL_SUB_ROUTINES if not hasattr(bspline_sub, name)] + assert not missing, f"missing procedures: {missing}" + + +def test_spline_order_constants_reach_python(bspline_sub): + for name, expected in ORDER_CONSTANTS.items(): + assert getattr(bspline_sub, name) == np.int32(expected), name + + +def test_generic_interfaces_publish_every_specific_signature(bspline_sub): + """`db1ink` and `db1val` are Fortran generics, so each specific is accepted.""" + assert bspline_sub.db1ink.__doc__.count("db1ink(x:") == 3 + assert bspline_sub.db1val.__doc__.count("db1val(xval:") == 2 + + +def test_interpolant_reproduces_the_sampled_function(bspline_sub): + x = np.linspace(0.0, 2.0 * np.pi, 30) + knots, bcoef, nx = _interpolant(bspline_sub, x, np.sin(x)) + + for point in np.linspace(0.3, 5.9, 7): + assert _evaluate(bspline_sub, knots, bcoef, nx, point) == pytest.approx(np.sin(point), abs=1.0e-5) + + +def test_interpolant_is_exact_on_a_low_order_polynomial(bspline_sub): + """A cubic spline reproduces a cubic exactly, up to rounding.""" + x = np.linspace(0.0, 1.0, 25) + knots, bcoef, nx = _interpolant(bspline_sub, x, x**3) + + for point in (0.25, 0.5, 0.75): + assert _evaluate(bspline_sub, knots, bcoef, nx, point) == pytest.approx(point**3, abs=1.0e-9) + + +def test_first_derivative_matches_the_analytic_derivative(bspline_sub): + x = np.linspace(0.0, 2.0 * np.pi, 60) + knots, bcoef, nx = _interpolant(bspline_sub, x, np.sin(x)) + + for point in np.linspace(0.5, 5.5, 5): + value = _evaluate(bspline_sub, knots, bcoef, nx, point, derivative=1) + assert value == pytest.approx(np.cos(point), abs=1.0e-4) + + +def test_definite_integral_matches_the_analytic_integral(bspline_sub): + x = np.linspace(0.0, np.pi, 60) + knots, bcoef, nx = _interpolant(bspline_sub, x, np.sin(x)) + work = np.zeros(3 * int(CUBIC), dtype=np.float64) + + value, iflag = bspline_sub.db1sqad(knots, bcoef, nx, CUBIC, np.float64(0.0), np.float64(np.pi), work) + assert iflag == np.int32(0) + assert value == pytest.approx(2.0, abs=1.0e-6) + + +def test_scipy_agrees_with_the_wrapped_interpolant(bspline_sub): + """An independent oracle checks the wrapper rather than the wrapper alone.""" + scipy_interpolate = pytest.importorskip("scipy.interpolate") + + x = np.linspace(0.0, 3.0, 40) + fcn = np.exp(-x) * np.cos(3.0 * x) + knots, bcoef, nx = _interpolant(bspline_sub, x, fcn) + reference = scipy_interpolate.make_interp_spline(x, fcn, k=3) + + for point in np.linspace(0.2, 2.8, 9): + assert _evaluate(bspline_sub, knots, bcoef, nx, point) == pytest.approx(float(reference(point)), abs=1.0e-6) diff --git a/mkdocs.yml b/mkdocs.yml index 14d7fea31..158decdd9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -73,6 +73,7 @@ nav: - LAPACK Wrapper: user/examples/lapack-wrapper.md - FFTPACK Wrapper: user/examples/fftpack-wrapper.md - MINPACK Wrapper: user/examples/minpack-wrapper.md + - BSPLINE-FORTRAN Wrapper: user/examples/bspline-wrapper.md - Recipes: - Build and Import With the Python API: user/examples/recipes/build-and-import-python-api.md - Inspect a Fortran API: user/examples/recipes/inspect-fortran-api.md diff --git a/prik/codegen/c/binding.py b/prik/codegen/c/binding.py index b9600f7b6..b1112d4bd 100644 --- a/prik/codegen/c/binding.py +++ b/prik/codegen/c/binding.py @@ -667,11 +667,9 @@ def requires_native_support(self, plan: ModulePlan) -> bool: return ( bool(tuple(self._variables(plan))) or any(function.arguments or function.results for function in self._functions(plan)) - or any( - field.object_kind is ObjectKind.NUMPY_ARRAY - for derived in self._derived_types(plan) - for field in derived.fields - ) + # Every published component converts through the bundled helpers, so a + # type whose module exposes only `bind(C)` procedures still needs them. + or any(derived.fields for derived in self._derived_types(plan)) ) def _module_needs_allocator(self, plan: ModulePlan) -> bool: @@ -2377,6 +2375,7 @@ def _direct_field_bridge_prototype_entries(self, plan: ModulePlan) -> tuple[CFun return tuple( self._generated_support_procedure_entrypoint_prototype(operation) for derived in self._derived_types(plan) + if not derived.abstract for field in derived.fields for operation in self._generated_support_procedure_entrypoints_for( f"{derived.owner_path}.{field.name}", "field:direct:" @@ -2446,6 +2445,7 @@ def _direct_field_functions_for_plan(self, plan: ModulePlan) -> tuple[CFunction, return tuple( function for derived in self._derived_types(plan) + if not derived.abstract for field in derived.fields for function in self._direct_field_functions(derived, field) ) @@ -11030,7 +11030,13 @@ def _namespace_overload_dispatches(namespace: NamespacePlan) -> tuple[_COverload 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)) + # A constructor overload whose candidates are type-bound takes the + # receiver; one whose candidates are functions returning the type + # -- a Fortran `interface ` -- does not. + constructor_receiver = bool( + constructor.candidate_passed_objects and constructor.candidate_passed_objects[0] + ) + dispatches.append(_COverloadDispatch(constructor, receiver=constructor_receiver, public=False)) seen.add(id(constructor)) for overload in surface.overloads: if id(overload) in seen: @@ -11456,6 +11462,7 @@ def _direct_field_method_names(self, namespace: NamespacePlan) -> tuple[str, ... return tuple( self._derived_field_method_name(derived, field, action) for derived in namespace.derived_types + if not derived.abstract for field in derived.fields for action in self._field_method_actions(field) ) diff --git a/prik/codegen/c/python_surface.py b/prik/codegen/c/python_surface.py index 558d06c34..7be5ee62e 100644 --- a/prik/codegen/c/python_surface.py +++ b/prik/codegen/c/python_surface.py @@ -277,18 +277,33 @@ def _bound_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[st return tuple(lines) def _overloaded_constructor_python_lines(self, surface: ClassSurfacePlan) -> tuple[str, ...]: - """Dispatch one completed constructor overload after owner allocation.""" + """Dispatch one completed constructor overload. + + A type-bound candidate initializes an instance the wrapper allocates + first. A candidate that returns the type -- the specifics of a Fortran + `interface ` -- produces the instance itself, so the dispatch + happens in ``__new__`` and the returned object is the new value. + """ overload = surface.constructor.overload if overload is None: raise ValueError(f"Overloaded constructor {surface.owner_path!r} has no overload plan") + if overload.candidate_passed_objects and overload.candidate_passed_objects[0]: + 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, + ), + ) + dispatch = CBindingNames.overload_dispatch_method(overload) 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, - ), + f" {surface.constructor.docstring!r}", + f" return {dispatch}(*args, **kwargs)", + " def __init__(self, *args, **kwargs):", + " pass", ) def _class_method_python_lines(self, method: ClassMethodPlan) -> tuple[str, ...]: @@ -476,9 +491,14 @@ def _derived_property_python_lines(field: DerivedFieldPlan) -> tuple[str, ...]: return tuple(lines) def _direct_type_ops_literal(self, derived: DerivedTypePlan) -> str: - """Return the operation dictionary for directly owned native storage.""" + """Return the operation dictionary for directly owned native storage. + + An abstract type publishes no accessor of its own, so its dictionary is + empty; each concrete extension supplies one for every component it + inherits. + """ entries = [] - for field in derived.fields: + for field in () if derived.abstract else 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')}") diff --git a/prik/codegen/fortran/bridge.py b/prik/codegen/fortran/bridge.py index 827eb552b..5c574be68 100644 --- a/prik/codegen/fortran/bridge.py +++ b/prik/codegen/fortran/bridge.py @@ -218,6 +218,11 @@ def _visit_ModulePlan(self, plan: ModulePlan) -> FortranModule: self._derived_owner_paths = { derived.backend_symbol: derived.owner_path for derived in self._derived_types(plan) } + # An abstract native type has no instances of its own, so an adapter + # reaches one only through a concrete extension's address. + self._abstract_backend_symbols = frozenset( + derived.backend_symbol for derived in self._derived_types(plan) if derived.abstract + ) if plan.bridge is None: raise ValueError(f"Fortran lowering requires a bridge plan for {plan.owner_path!r}") self._bridge_allocatable_holder_owner_paths = frozenset(plan.bridge.allocatable_holder_type_owner_paths) @@ -1021,19 +1026,27 @@ def _derived_call_declarations(self, plan: FunctionPlan) -> tuple[FortranDeclara declarations = [FortranDeclaration("prik_derived_ready", "logical")] for argument in arguments: name = argument.entrypoint.parameter_name - native_type = f"type({self._derived_native_alias(argument.derived.backend_symbol)})" + abstract = argument.derived.backend_symbol in self._abstract_backend_symbols + declaration_kind = "class" if abstract else "type" + native_type = f"{declaration_kind}({self._derived_native_alias(argument.derived.backend_symbol)})" declarations.extend( ( FortranDeclaration(name, native_type, ("pointer",)), - FortranDeclaration( - f"{name}_allocatable_holder", - f"type({self._allocatable_holder_type_name(argument.derived.backend_symbol)})", - ("pointer",), - ), - FortranDeclaration( - f"{name}_pointer_holder", - f"type({self._pointer_holder_type_name(argument.derived.backend_symbol)})", - ("pointer",), + *( + () + if abstract + else ( + FortranDeclaration( + f"{name}_allocatable_holder", + f"type({self._allocatable_holder_type_name(argument.derived.backend_symbol)})", + ("pointer",), + ), + FortranDeclaration( + f"{name}_pointer_holder", + f"type({self._pointer_holder_type_name(argument.derived.backend_symbol)})", + ("pointer",), + ), + ) ), FortranDeclaration(f"{name}_call_pointer", native_type, ("pointer",)), FortranDeclaration(f"{name}_transaction_address", "type(c_ptr)"), @@ -1366,8 +1379,16 @@ def _derived_transaction_acquisition( acquisition = FortranSelectCase( CodeExpression(f"bound_{name}_access"), ( - FortranCase(5, self._one_derived_transaction_acquisition(argument, allocatable=True)), - FortranCase(6, self._one_derived_transaction_acquisition(argument, allocatable=False)), + *( + (FortranCase(5, self._one_derived_transaction_acquisition(argument, allocatable=True)),) + if self._uses_allocatable_holder(argument) + else () + ), + *( + (FortranCase(6, self._one_derived_transaction_acquisition(argument, allocatable=False)),) + if self._uses_pointer_holder(argument) + else () + ), FortranCase(None, ()), ), ) @@ -1595,18 +1616,20 @@ def _derived_argument_output_and_cleanup(self, argument: ArgumentTransferPlan) - if argument.entrypoint.descriptor_output_role is not None: nodes.append(self._derived_argument_output_finalizer(argument)) else: - nodes.extend( - ( + if self._uses_allocatable_holder(argument): + nodes.append( FortranIf( CodeExpression(f"{name}_created .and. bound_{name}_access == 3_c_int"), body=(FortranDeallocate(f"{name}_allocatable_holder"),), - ), + ) + ) + if self._uses_pointer_holder(argument): + nodes.append( FortranIf( CodeExpression(f"{name}_created .and. bound_{name}_access == 4_c_int"), body=(FortranDeallocate(f"{name}_pointer_holder"),), - ), + ) ) - ) return tuple(nodes) def _derived_argument_output_finalizer(self, argument: ArgumentTransferPlan) -> FortranIf: @@ -6310,6 +6333,11 @@ def _uses_allocatable_holder(argument: ArgumentTransferPlan) -> bool: """Return whether the module plan requires the allocatable holder for one native derived identity.""" return FortranBridgeGenerator._uses_holder(argument, DerivedActualAccess.ALLOCATABLE_HOLDER) + @staticmethod + def _uses_pointer_holder(argument: ArgumentTransferPlan) -> bool: + """Return whether the completed matrix keeps the pointer holder for one carrier.""" + return FortranBridgeGenerator._uses_holder(argument, DerivedActualAccess.POINTER_HOLDER) + @staticmethod def _uses_holder(argument: ArgumentTransferPlan, access: DerivedActualAccess) -> bool: """Return whether one completed derived matrix includes a holder row.""" @@ -6334,6 +6362,7 @@ def _direct_field_procedure_entries(self, plan: ModulePlan) -> tuple[FortranFunc return tuple( procedure for derived in self._derived_types(plan) + if not derived.abstract for field in derived.fields for procedure in self._planned_support_procedures( f"{derived.owner_path}.{field.name}", diff --git a/prik/contracts/__init__.py b/prik/contracts/__init__.py index f7f56674a..60b028af8 100644 --- a/prik/contracts/__init__.py +++ b/prik/contracts/__init__.py @@ -7,6 +7,7 @@ from __future__ import annotations +from abc import abstractmethod as abstractmethod from typing import Annotated as Annotated, Any as Any, Final as Final import numpy as np @@ -233,6 +234,17 @@ def apply(target): Value = _expression Work = _expression + +def abstract(target): + """Mark a contract class as an abstract native type. + + A class carrying this marker cannot be constructed: the native type is + declared ``abstract``, so only its concrete extensions have instances. It + is returned unchanged so the contract stays an ordinary Python stub. + """ + return target + + bind = _decorator nogil = _decorator native_abi = _decorator @@ -332,6 +344,8 @@ def apply(target): "Void", "Work", "WrappedType", + "abstract", + "abstractmethod", "bind", "nogil", "native_abi", diff --git a/prik/pipeline/build.py b/prik/pipeline/build.py index 0816b8471..085b9cfe7 100644 --- a/prik/pipeline/build.py +++ b/prik/pipeline/build.py @@ -39,6 +39,7 @@ resolve_fortran_logical_storage_types, ) from prik.preprocessing import PreprocessingConfig, preprocess_source +from prik.pipeline.pyi import emit_module_stubs from prik.pipeline.wrapper import GeneratedSource, GeneratedWrapper, WrapperGenerator from prik.semantics.fortran2ir import ( collect_fortran_type_storage_requirements, @@ -562,6 +563,46 @@ def _generated_source_output_path(output_dir: Path, path: Path) -> Path: return output_dir / path +BUILD_CONTRACT_DIRECTORY_NAME = "contracts" + + +def _write_build_contract_package( + source_modules: tuple[SemanticModule, ...], + output_dir: Path, + *, + verbose: bool | int = False, +) -> tuple[Path, ...]: + """Write the editable semantic contract for one build beside its artifacts. + + Every build leaves the contract that describes the API it just generated, so + reshaping the Python surface never needs a separate `generate --pyi` run. + The package lives in its own directory inside the build output so its + ``__init__.pyi`` cannot make the build directory look like a Python package. + """ + if not source_modules: + return () + try: + stubs = emit_module_stubs(source_modules) + except (ValueError, KeyError) as error: + # The extension is already built; a contract that cannot be rendered is + # reported rather than allowed to fail the build behind it. + _print_verbose_step(verbose, f"Skip contract package: {error}") + return () + package_dir = output_dir / BUILD_CONTRACT_DIRECTORY_NAME + package_dir.mkdir(parents=True, exist_ok=True) + written = [] + for module_name, text in stubs.items(): + path = package_dir / f"{module_name}.pyi" + path.write_text(f"{text}\n", encoding="utf-8") + _print_verbose_step(verbose, f"Write semantic contract: {path}") + written.append(path) + root = package_dir / "__init__.pyi" + root.write_text("".join(f"from . import {name}\n" for name in sorted(stubs)), encoding="utf-8") + _print_verbose_step(verbose, f"Write semantic contract package: {root}") + written.append(root) + return tuple(written) + + def _write_generated_wrapper_sources( rendered: GeneratedWrapper, output_dir: Path, @@ -2645,7 +2686,7 @@ def _fortran_wrapper_module( fortran_type_probe_cache_dir: str | Path | None, refresh_fortran_type_probe: bool, assume_intent_in_scalars: bool = False, -) -> tuple[object, SemanticModule]: +) -> tuple[object, SemanticModule, tuple[SemanticModule, ...]]: """Parse Fortran sources, resolve type facts, and form one wrapper module.""" # Preprocess and parse the complete source project. preprocessed_sources = { @@ -2681,7 +2722,7 @@ def _fortran_wrapper_module( ) _apply_source_python_exports(modules) module_name = _validated_wrapper_module_name(output_name, source_paths[0].stem) - return parsed, _merge_wrapper_modules(modules, name=module_name) + return parsed, _merge_wrapper_modules(modules, name=module_name), tuple(modules) def _complete_pyi_fortran_boolean_types( @@ -2858,7 +2899,7 @@ def build_fortran_extension( type_probe_preprocessing = _type_probe_preprocessing(preprocessing, native_inputs.source_flags) # 2. Parse source, resolve target facts, and assemble semantic IR. - parsed, module = _fortran_wrapper_module( + parsed, module, source_modules = _fortran_wrapper_module( source_paths, preprocessing=preprocessing, type_probe_preprocessing=type_probe_preprocessing, @@ -2912,6 +2953,7 @@ def build_fortran_extension( source_objects=native_source_objects, extra_dependencies=_link_item_paths(native_build_plan.link_items), ) + _write_build_contract_package(source_modules, output_path, verbose=verbose) _report_total_build_time( verbose, time.perf_counter() - build_started, diff --git a/prik/planning/entrypoints.py b/prik/planning/entrypoints.py index 6a9e8b784..1761f833f 100644 --- a/prik/planning/entrypoints.py +++ b/prik/planning/entrypoints.py @@ -570,6 +570,11 @@ def _derived_field_operations( ) -> tuple[GeneratedSupportProcedureEntrypointPlan, ...]: operations = [] for derived in self.derived_types: + # An abstract type has no instance to address, so it publishes no + # accessor of its own; each concrete extension already generates one + # for every component it inherits. + if derived.abstract: + continue for field in derived.fields: operations.extend(self._field_operations(derived, field, "direct")) for variable in self.variables: diff --git a/prik/planning/models.py b/prik/planning/models.py index 3db87be44..d2cc25cab 100644 --- a/prik/planning/models.py +++ b/prik/planning/models.py @@ -315,6 +315,8 @@ class DerivedTypePlan(StageRecord): finalizers: tuple[str, ...] bind_c: bool sequence: bool + abstract: bool = False + deferred_bindings: tuple[str, ...] = () @dataclass diff --git a/prik/planning/planner.py b/prik/planning/planner.py index 7eb73a200..9c55a4b96 100644 --- a/prik/planning/planner.py +++ b/prik/planning/planner.py @@ -584,6 +584,8 @@ def _derived_type_plan( finalizers=policy.finalizers, bind_c=policy.bind_c, sequence=policy.sequence, + abstract=policy.abstract, + deferred_bindings=policy.deferred_bindings, ) # Generated class surfaces compose Phase 8 types and ordinary function plans. diff --git a/prik/policy/completion.py b/prik/policy/completion.py index d25b7d309..bc3107a23 100644 --- a/prik/policy/completion.py +++ b/prik/policy/completion.py @@ -466,11 +466,15 @@ def _complete_class_method_policies( """ type_bound_targets = _type_bound_target_names(module_functions) module_targets = {str(function.native_name or function.name) for function in module_functions} + private_module_targets = { + str(function.native_name or function.name) for function in module_functions if function.visibility == "private" + } for semantic_class in class_nodes: _complete_one_class_method_policy( semantic_class, type_bound_targets, module_targets, + private_module_targets, derived_types, polymorphic_variants, ) @@ -489,6 +493,7 @@ def _complete_one_class_method_policy( semantic_class: models.SemanticClass, type_bound_targets: set[str], module_targets: set[str], + private_module_targets: set[str], derived_types: dict[tuple[str, str], DerivedTypePolicy], polymorphic_variants: dict[tuple[str, str], tuple[tuple[str, str], ...]], ) -> None: @@ -523,6 +528,7 @@ def _complete_one_class_method_policy( derived, type_bound_targets, module_targets, + private_module_targets, derived_types, polymorphic_variants, ) @@ -585,6 +591,7 @@ def _complete_class_overload_methods( derived: DerivedTypePolicy, type_bound_targets: set[str], module_targets: set[str], + private_module_targets: set[str], derived_types: dict[tuple[str, str], DerivedTypePolicy], polymorphic_variants: dict[tuple[str, str], tuple[tuple[str, str], ...]], ) -> None: @@ -604,6 +611,7 @@ def _complete_class_overload_methods( generic_bindings, type_bound_targets, module_targets, + private_module_targets, derived_types, polymorphic_variants, ) @@ -616,6 +624,7 @@ def _complete_one_class_overload_method( generic_bindings: dict[str, str], type_bound_targets: set[str], module_targets: set[str], + private_module_targets: set[str], derived_types: dict[tuple[str, str], DerivedTypePolicy], polymorphic_variants: dict[tuple[str, str], tuple[tuple[str, str], ...]], ) -> None: @@ -637,12 +646,20 @@ def _complete_one_class_overload_method( else None, ) overload_kind = str(procedure.metadata.get(models.OVERLOAD_KIND_METADATA, "generic")) + # An overload dispatches through a native generic only when its own name is + # one. `__init__` is a Python name with no native counterpart, so a + # constructor candidate falls back to the specific procedure it selects -- + # or, when that specific is private and therefore unreachable by name, to + # the constructor generic Fortran names for the type itself. + dispatches_through_overload_name = overload_kind != "generic" and overload.name != "__init__" + if not bind_target and overload.name == "__init__" and native_name in private_module_targets: + bind_target = derived.native_type_name native_dispatch_name = ( str(bind_target) if bind_target else ( str(procedure.metadata.get(models.FORTRAN_GENERIC_NAME_METADATA, overload.name)) - if overload_kind != "generic" + if dispatches_through_overload_name else None ) ) @@ -889,8 +906,23 @@ def extends(candidate: tuple[str, str], base: tuple[str, str]) -> bool: return candidate == base or any(extends(parent, base) for parent in bases.get(candidate, ())) identities = tuple(surface.type_identity for surface in surfaces) + # An abstract type has no instance, so it is never the dynamic type a caller + # can supply; it stays a dispatch base without becoming one of its own cases. + abstract_identities = { + surface.type_identity + for semantic_class, surface in zip(class_nodes, surfaces, strict=False) + if any( + str(attribute).casefold() == "abstract" + for attribute in semantic_class.metadata.get("fortran_type_attributes", ()) + ) + } return { - base: tuple(candidate for candidate in reversed(identities) if extends(candidate, base)) for base in identities + base: tuple( + candidate + for candidate in reversed(identities) + if extends(candidate, base) and candidate not in abstract_identities + ) + for base in identities } diff --git a/prik/policy/construction.py b/prik/policy/construction.py index 5b3fefb2c..374257836 100644 --- a/prik/policy/construction.py +++ b/prik/policy/construction.py @@ -383,18 +383,15 @@ def build_derived_type_policy( str(attribute).casefold() for attribute in semantic_class.metadata.get("fortran_type_attributes", ()) } deferred_bindings = tuple(semantic_class.metadata.get("fortran_deferred_bindings", ())) + abstract = "abstract" in type_attributes blockers = tuple( [*(f"field {name!r} is missing completed derived-field policy" for name in missing)] + [reason for field in fields for reason in field.blockers] + ( - ["abstract derived types need a non-instantiable Python class policy"] - if "abstract" in type_attributes + [f"deferred type-bound procedure {name!r} needs a declaring abstract type" for name in deferred_bindings] + if not abstract else [] ) - + [ - f"deferred type-bound procedure {name!r} needs an override and dispatch policy" - for name in deferred_bindings - ] ) exports = completed_python_exports(semantic_class, semantic_class.name) native_type_name = str(semantic_class.native_name or semantic_class.name) @@ -413,6 +410,8 @@ def build_derived_type_policy( sequence=bool(semantic_class.metadata.get("fortran_sequence")), supported=not blockers, blockers=blockers, + abstract=abstract, + deferred_bindings=deferred_bindings, ) @@ -565,7 +564,28 @@ def _class_constructor_policy( owner_path: str, derived: DerivedTypePolicy, ) -> tuple[ConstructorPolicy, tuple[str, ...]]: - """Select exactly one constructor surface from the semantic contract.""" + """Select exactly one constructor surface from the semantic contract. + + An abstract native type has no constructor at all: Fortran forbids an + instance of it, so the generated class exposes its inherited surface while + only a concrete extension can be created. + """ + if derived.abstract: + return ( + ConstructorPolicy( + kind=ClassConstructorKind.ABSENT, + fields=(), + target_owner_path=None, + overload_name=None, + call=None, + lifecycle=(), + rejection_message=( + f"{semantic_class.name} is an abstract native type and cannot be instantiated; " + "create one of its concrete extensions instead" + ), + ), + (), + ) bound = tuple( method for method in semantic_class.methods @@ -3648,6 +3668,15 @@ def _derived_object_storage( return DerivedObjectStorage.DIRECT +# An abstract type has no instances of its own. Every origin that would declare +# storage of that exact type -- a wrapper-owned holder, or a module variable -- +# has nothing to hold, so only a plain concrete object address stays reachable. +# The adapter converts that address to the extension's own type and passes it to +# the `class(...)` dummy through the polymorphic discriminator. +_ABSTRACT_REACHABLE_STORAGES = frozenset({DerivedObjectStorage.DIRECT}) +_ABSTRACT_INCOMPATIBLE_STORAGES = frozenset(DerivedObjectStorage) - _ABSTRACT_REACHABLE_STORAGES + + def _derived_call_policy( argument: models.SemanticArgument, decision: OwnershipDecision, @@ -3661,8 +3690,19 @@ def _derived_call_policy( argument.semantic_type, native_value=_native_by_value_argument(argument), ) + abstract_dummy = bool(argument.semantic_type.metadata.get("fortran_abstract_type")) cases = tuple( _derived_call_case(category, storage, projects_result=decision.projects_result) + if not (abstract_dummy and storage in _ABSTRACT_INCOMPATIBLE_STORAGES) + else _derived_incompatible_case( + storage, + "abstract-owner-storage", + ( + f"{argument.semantic_type.name} is an abstract type; a " + f"{storage.value.replace('_', ' ')} actual would declare storage of that exact " + "type, which has no instance. Pass a concrete extension instead." + ), + ) for storage in DerivedObjectStorage ) writeback = { diff --git a/prik/policy/models.py b/prik/policy/models.py index 92c0b635b..dc3b653fb 100644 --- a/prik/policy/models.py +++ b/prik/policy/models.py @@ -579,6 +579,8 @@ class DerivedTypePolicy: sequence: bool supported: bool blockers: tuple[str, ...] = () + abstract: bool = False + deferred_bindings: tuple[str, ...] = () @dataclass(frozen=True) diff --git a/prik/printers/pyi.py b/prik/printers/pyi.py index 4d95d6f95..4c9188358 100644 --- a/prik/printers/pyi.py +++ b/prik/printers/pyi.py @@ -30,6 +30,7 @@ ADDRESS_ROLE_PROJECTION, ADDRESS_ROLE_RAW, BIND_TARGET_METADATA, + DEFERRED_BINDING_METADATA, MAYBE_UNALLOCATED_METADATA, NATIVE_PROJECTION_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, @@ -77,6 +78,12 @@ _FLAT_DIMENSION_PRINT_SENTINEL = "@prik.Flat" +# Type attributes the contract states through its own vocabulary rather than +# through `native_type`: `public` is the default accessibility, `private` has a +# marker, and `abstract` has one too. +_IMPLIED_TYPE_ATTRIBUTES = frozenset({"public", "private", "abstract"}) + + @dataclass(frozen=True) class _PyiEmissionContext: """Own all state accumulated while rendering one semantic node tree.""" @@ -388,6 +395,11 @@ def _visit_ProcedureOverloadSet( indent = "" generic = self._overload_generic_argument(candidate, overload_set.name) if in_class else "" bind_target = candidate.metadata.get(BIND_TARGET_METADATA) + if self._constructor_binds_its_own_type(overload_set.name, bind_target, context): + # A constructor's native generic is named for its type, so the + # class already states the target the way an unrenamed method + # states its own. + bind_target = None if candidate.origin.native_abi == "c" and candidate.origin.native_symbol: bind_target = ( candidate.origin.native_symbol @@ -420,6 +432,8 @@ def _visit_SemanticClass( decorators = [] if self._is_private(cls): decorators.append(f"@{context.contract('private')}") + if self._is_abstract(cls): + decorators.append(f"@{context.contract('abstract')}") native_type = self._native_type_decorator(cls, context) if native_type: decorators.append(native_type) @@ -436,12 +450,23 @@ def _class_base_text(base: str, context: _PyiEmissionContext) -> str: """Return an imported contract base name or a user base name.""" return context.contract_type(base) + @staticmethod + def _is_abstract(cls: SemanticClass) -> bool: + """Return whether the native type is declared ``abstract``.""" + return any( + str(attribute).casefold() == "abstract" for attribute in cls.metadata.get("fortran_type_attributes", ()) + ) + @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 "" - attributes = tuple(str(item) for item in cls.metadata.get("fortran_type_attributes", ())) + attributes = tuple( + str(item) + for item in cls.metadata.get("fortran_type_attributes", ()) + if str(item).casefold() not in _IMPLIED_TYPE_ATTRIBUTES + ) finalizers = tuple(str(item) for item in cls.metadata.get("fortran_final_procedures", ())) parts = [] if attributes: @@ -1269,8 +1294,16 @@ def _class_constructor( cls: SemanticClass, context: _PyiEmissionContext, ) -> str: - """Handle class constructor for the current generation context.""" - if cls.origin.source_language != "fortran": + """Handle class constructor for the current generation context. + + An abstract native type has no constructor: the type cannot be + instantiated, so the contract states no ``__init__`` for it. + """ + if cls.origin.source_language != "fortran" or self._is_abstract(cls): + return "" + if any(overload.name == "__init__" for overload in cls.overload_sets): + # A generic constructor supplies every accepted signature, so the + # keyword-field form is not part of this class's surface. return "" arguments = [ self._constructor_argument(field, context) for field in cls.fields if self._constructor_accepts_field(field) @@ -2003,10 +2036,14 @@ def _identity_decorators( ) -> list[str]: """Emit visibility, method-kind, native-ABI, and link-name markers.""" decorators = [] - if self._is_private(func): + # A constructor is published or absent; the accessibility of the + # specific it selects is that procedure's own fact, not the class's. + if self._is_private(func) and emitted_name != "__init__": decorators.append(f"{indent}@{context.contract('private')}") if isinstance(func, SemanticMethod) and func.is_static: decorators.append(f"{indent}@staticmethod") + if func.metadata.get(DEFERRED_BINDING_METADATA): + decorators.append(f"{indent}@{context.contract('abstractmethod')}") is_native_c_abi = func.origin.source_language == "fortran" and func.origin.native_abi == "c" is_overload = bool(func.metadata.get(OVERLOAD_TARGET_METADATA)) if is_native_c_abi and not is_overload: @@ -2018,6 +2055,20 @@ def _identity_decorators( decorators.append(f"{indent}@{context.contract('bind')}({json.dumps(str(bind_target))})") return decorators + @staticmethod + def _constructor_binds_its_own_type( + overload_name: str, + bind_target: object | None, + context: _PyiEmissionContext, + ) -> bool: + """Return whether a constructor's link name simply repeats its class name.""" + return bool( + bind_target + and overload_name == "__init__" + and context.public_namespace + and str(bind_target).casefold() == str(context.public_namespace[-1]).casefold() + ) + @staticmethod def _bind_target( func: SemanticFunction, diff --git a/prik/semantics/fortran2ir.py b/prik/semantics/fortran2ir.py index c1ef665f0..54393b28d 100644 --- a/prik/semantics/fortran2ir.py +++ b/prik/semantics/fortran2ir.py @@ -47,6 +47,8 @@ ) from prik.semantics.ownership_metadata import set_ownership_metadata from prik.semantics.metadata import ( + CONSTRUCTOR_SPECIFIC_METADATA, + DEFERRED_BINDING_METADATA, BIND_TARGET_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, PROJECTED_OUTPUT_METADATA, @@ -287,6 +289,7 @@ def __init__( default and never applies to a declared ``intent``. """ self.assume_intent_in_scalars = bool(assume_intent_in_scalars) + self._abstract_type_names: set[str] = set() self.type_map = FORTRAN_TYPE_MAP if type_map is None else type_map self.compile_time_values = _normalize_compile_time_values(compile_time_values) self.wrapped_derived_types = { @@ -457,6 +460,8 @@ def _convert_variable_type( metadata["fortran_allocatable"] = True if getattr(var, "polymorphic", False): metadata["fortran_polymorphic"] = True + if semantic_name.casefold() in self._abstract_type_names: + metadata["fortran_abstract_type"] = True if getattr(var, "target", False): metadata["aliased"] = True metadata["fortran_target"] = True @@ -982,6 +987,7 @@ def _visit_FortranDerivedType( procedure_lookup: dict[str, SemanticFunction] | None = None, *, derived_type_context: _DerivedTypeContext | None = None, + prototype_lookup: dict[str, SemanticFunction] | None = None, ) -> SemanticClass: """Convert a Fortran derived type into fields, bound methods, and overload sets. @@ -990,11 +996,12 @@ def _visit_FortranDerivedType( declaration facts for later semantic and printing stages. """ lookup = procedure_lookup or {} + prototypes = prototype_lookup or {} context = derived_type_context or _DerivedTypeContext( module=dtype.module, local_types=frozenset({dtype.name.lower()}), ) - methods = self._bound_methods(dtype, lookup) + methods = self._bound_methods(dtype, lookup, prototypes) overload_sets = self._bound_overload_sets(dtype, methods) type_attributes = list(dict.fromkeys(str(attr).casefold() for attr in dtype.attributes)) metadata = { @@ -1074,6 +1081,11 @@ def _visit_FortranModule( later policy completion owns wrapper behavior decisions. """ context = self._module_derived_type_context(module) + self._abstract_type_names |= { + str(dtype.name).casefold() + for dtype in module.derived_types + if any(str(attribute).casefold() == "abstract" for attribute in dtype.attributes) + } callback_interfaces = { **(callback_interfaces or {}), **self._callback_interface_lookup(module), @@ -1118,6 +1130,7 @@ def _visit_FortranModule( dtype, procedure_lookup=procedure_lookup, derived_type_context=context, + prototype_lookup={prototype.name.casefold(): prototype for prototype in prototypes}, ) for dtype in module.derived_types ] @@ -2168,6 +2181,7 @@ def _bound_methods( self, dtype: FortranDerivedType, procedure_lookup: dict[str, SemanticFunction], + prototype_lookup: dict[str, SemanticFunction] | None = None, ) -> list[SemanticMethod]: """Project resolved type-bound procedure bindings into semantic methods. @@ -2184,6 +2198,9 @@ def _bound_methods( binding_name, target_name = self._procedure_binding_names(binding["name"]) proc = procedure_lookup.get(target_name.casefold()) if proc is None: + deferred = self._deferred_bound_method(binding, binding_name, prototype_lookup or {}) + if deferred is not None: + methods.append(deferred) continue binding_attributes = tuple(binding.get("attrs", ())) attrs = set(binding_attributes) @@ -2261,11 +2278,22 @@ def _module_overload_sets( overload_sets.append(ProcedureOverloadSet(interface.name)) continue if self._is_procedure_generic_name(interface.name): - if interface.name.casefold() in class_map: - raise ValueError( - f"Fortran semantic conversion cannot represent generic constructor " - f"{module.name}.{interface.name!s}; constructor projection is not implemented" - ) + constructor_class = class_map.get(interface.name.casefold()) + if constructor_class is not None: + # An interface named for a derived type is that type's + # constructor, so its specifics become the class's own + # `__init__` overload set rather than a module generic. + constructor_set = self._normal_overload_set("__init__", procedures) + target_lookup = procedure_lookup | inline_lookup + for target_name, candidate in zip(target_names, constructor_set.procedures, strict=True): + if target_lookup[target_name.casefold()].visibility == "private": + # A private specific is unreachable by name; the type + # name is public and resolves to the same procedure. + candidate.native_name = interface.name + candidate.metadata[BIND_TARGET_METADATA] = interface.name + self._merge_overload_sets(constructor_class.overload_sets, [constructor_set]) + self._mark_constructor_specifics(procedures, procedure_lookup, interface.name) + continue overload_set = self._normal_overload_set(interface.name, procedures) target_lookup = procedure_lookup | inline_lookup for target_name, candidate in zip(target_names, overload_set.procedures, strict=True): @@ -2356,6 +2384,23 @@ def _apply_assignment_projection_to_originals( if original is not None: original.projection = self._assignment_projection(original, 0) + @staticmethod + def _mark_constructor_specifics( + procedures: list[SemanticFunction], + procedure_lookup: dict[str, SemanticFunction], + type_name: str, + ) -> None: + """Hide the module functions a generic constructor selects between. + + Each specific stays reachable as the constructor's native target, but it + is no longer published as a separate module procedure: the type name is + the public spelling the source chose for it. + """ + for procedure in procedures: + original = procedure_lookup.get((procedure.native_name or procedure.name).casefold()) + if original is not None: + original.metadata[CONSTRUCTOR_SPECIFIC_METADATA] = type_name + @staticmethod def _merge_overload_sets( overload_sets: list[ProcedureOverloadSet], @@ -2710,6 +2755,50 @@ def _passed_object_argument( f"Type-bound procedure {proc.name!r} declares pass({pass_name}), but that dummy argument is not present" ) + @staticmethod + def _deferred_bound_method( + binding: dict, + binding_name: str, + prototype_lookup: dict[str, SemanticFunction], + ) -> SemanticMethod | None: + """Project a deferred type-bound binding from its declared interface. + + A deferred binding names an interface instead of an implementation, so + the method carries that signature and no native target. Every concrete + extension supplies the override that a caller actually reaches. + """ + interface_name = binding.get("interface") + if not interface_name: + return None + prototype = prototype_lookup.get(str(interface_name).casefold()) + if prototype is None: + return None + attributes = tuple(binding.get("attrs", ())) + passed_object_name, passed_object_position = FortranToIRConverter._passed_object_argument( + prototype, + attributes, + ) + # A prototype spells a subroutine's absent result as the "None" semantic + # type; a method states the same absence by carrying no result at all. + return_type = prototype.return_type + if return_type is not None and return_type.name == "None": + return_type = None + return SemanticMethod( + name=binding_name, + native_name="", + arguments=list(prototype.arguments), + return_type=return_type, + visibility=str(binding.get("visibility", "public")), + is_static="nopass" in set(attributes), + passed_object_name=passed_object_name, + passed_object_position=passed_object_position, + binding_attributes=attributes, + metadata={ + DEFERRED_BINDING_METADATA: True, + "fortran_deferred_interface": str(interface_name), + }, + ) + @staticmethod def _procedure_binding_names(name: str) -> tuple[str, str]: """Split a Fortran binding ``local => target`` spelling into both names.""" diff --git a/prik/semantics/metadata.py b/prik/semantics/metadata.py index ba8f633c0..25ee335c4 100644 --- a/prik/semantics/metadata.py +++ b/prik/semantics/metadata.py @@ -10,6 +10,8 @@ SCALAR_STORAGE_CATEGORY = "scalar_storage" SUPPRESS_DEFAULT_CONSTRUCTOR_METADATA = "suppress_default_constructor" USER_PRIVATE_METADATA = "user_private" +DEFERRED_BINDING_METADATA = "deferred_binding" +CONSTRUCTOR_SPECIFIC_METADATA = "constructor_specific" NATIVE_PROJECTION_METADATA = "native_projection" NATIVE_ARRAY_DESCRIPTOR_METADATA = "native_array_descriptor" NATIVE_ARRAY_HANDLE_POLICY_METADATA = "native_array_handle_policy" diff --git a/prik/semantics/pyi2ir.py b/prik/semantics/pyi2ir.py index 57c0fd08e..271e16eee 100644 --- a/prik/semantics/pyi2ir.py +++ b/prik/semantics/pyi2ir.py @@ -37,6 +37,7 @@ ADDRESS_ROLE_PROJECTION, ADDRESS_ROLE_RAW, BIND_TARGET_METADATA, + DEFERRED_BINDING_METADATA, MAYBE_UNALLOCATED_METADATA, NATIVE_PROJECTION_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, @@ -164,6 +165,8 @@ class _Decorators: error_status_policy: dict[str, object] | None = None prototype: bool = False pure: bool = False + abstract: bool = False + abstract_method: bool = False @dataclass @@ -431,6 +434,7 @@ def class_def( *, visibility: str, native_type: dict[str, object] | None = None, + abstract: bool = False, ) -> SemanticClass: """Convert one class AST node, its body, and supported native metadata. @@ -445,15 +449,19 @@ def class_def( raise ValueError("Direct constructor bindings replace the generated field constructor; remove one __init__") base_classes = [self.base_class_name(base) for base in node.bases] origin = self._origin( - source_language="fortran" if body.constructor_from_fields or native_type is not None else None, + source_language=( + "fortran" if body.constructor_from_fields or native_type is not None or abstract else None + ), user_private=visibility == "private", ) if not body.constructor_from_fields: origin.metadata[SUPPRESS_DEFAULT_CONSTRUCTOR_METADATA] = True metadata = self._class_metadata(base_classes) + if abstract: + metadata["fortran_type_attributes"] = [*metadata.get("fortran_type_attributes", []), "abstract"] if native_type is not None: - attributes = list(native_type.get("attributes", ())) + attributes = [*metadata.get("fortran_type_attributes", []), *native_type.get("attributes", ())] metadata["fortran_type_attributes"] = attributes normalized_attributes = {str(item).strip().casefold().replace(" ", "") for item in attributes} if "bind(c)" in normalized_attributes: @@ -632,6 +640,7 @@ def method_def( has_native_call: bool = False, release_gil: bool = False, error_status_policy: dict[str, object] | None = None, + deferred: bool = False, ) -> SemanticMethod: """Convert a class stub into a semantic method declaration. @@ -648,6 +657,10 @@ def method_def( drop_untyped_self=True, ) metadata = {BIND_TARGET_METADATA: native_name} if native_name is not None else {} + if deferred: + if native_name is not None: + raise ValueError("A deferred binding has no native target; remove its bind decorator") + metadata[DEFERRED_BINDING_METADATA] = True if has_native_call: metadata[NATIVE_PROJECTION_METADATA] = True passed_object_name, passed_object_position = self._complete_method_passed_object( @@ -846,6 +859,8 @@ def _apply_decorator(self, parsed: _Decorators, node: ast.expr, *, context: str) "native_type": self._apply_native_type_decorator, "prototype": self._apply_prototype_decorator, "pure": self._apply_pure_decorator, + "abstract": self._apply_abstract_decorator, + "abstractmethod": self._apply_abstract_method_decorator, "raises": self._apply_raises_decorator, } handler = next((value for name, value in handlers.items() if self.matches_name(target, name)), None) @@ -853,6 +868,37 @@ def _apply_decorator(self, parsed: _Decorators, node: ast.expr, *, context: str) raise ValueError(f"Unsupported {context} decorator: {ast.unparse(node)!r}") handler(parsed, node, context) + @staticmethod + def _reject_private_constructor(declaration_name: str, visibility: str) -> None: + """Refuse an accessibility marker that a constructor cannot express.""" + if declaration_name == "__init__" and visibility == "private": + raise ValueError( + "A constructor is published or absent; remove @private from __init__. " + "Mark the specific procedure it selects private instead." + ) + + @staticmethod + def _apply_abstract_decorator(parsed: _Decorators, node: ast.expr, context: str) -> None: + """Mark a class as an abstract native type that cannot be constructed.""" + if isinstance(node, ast.Call): + raise ValueError("abstract does not accept arguments") + if context != "class": + raise ValueError("abstract is only valid on a class declaration") + if parsed.abstract: + raise ValueError("Duplicate abstract decorator") + parsed.abstract = True + + @staticmethod + def _apply_abstract_method_decorator(parsed: _Decorators, node: ast.expr, context: str) -> None: + """Mark a type-bound declaration as a deferred binding with no native target.""" + if isinstance(node, ast.Call): + raise ValueError("abstractmethod does not accept arguments") + if context == "class": + raise ValueError("abstractmethod is only valid on a method declaration") + if parsed.abstract_method: + raise ValueError("Duplicate abstractmethod decorator") + parsed.abstract_method = True + @staticmethod def _apply_prototype_decorator(parsed: _Decorators, node: ast.expr, context: str) -> None: """Mark a module-level declaration as an exact native interface.""" @@ -1253,12 +1299,21 @@ def _class_overload_bound_position( ) -> int | None: """Locate the unique native wrapped-object argument for a class overload. - Static methods need no bound object. Instance methods must match one - target argument whose type is the owning class and whose removal leaves - the declared Python arguments in order; ambiguity is an error. + Static methods need no bound object. A constructor candidate produces + the object instead of receiving one, so a specific whose result is the + owning class has no bound argument either. Every other instance method + must match one target argument whose type is the owning class and whose + removal leaves the declared Python arguments in order; ambiguity is an + error. """ if isinstance(declaration, SemanticMethod) and declaration.is_static: return None + if ( + declaration.name == "__init__" + and target.return_type is not None + and target.return_type.name.casefold() == owner.name.casefold() + ): + return None remaining_names = [argument.name for argument in declaration.arguments] matching = [ index @@ -3277,7 +3332,9 @@ def _visit_FunctionDef(self, node: ast.FunctionDef) -> None: has_native_call=decorators.has_native_call, release_gil=decorators.release_gil, error_status_policy=decorators.error_status_policy, + deferred=decorators.abstract_method, ) + self.parser._reject_private_constructor(node.name, decorators.visibility) if node.name == "__init__" and decorators.bind_target is not None and decorators.overload_target is None: self.has_bound_constructor = True if decorators.overload_target is not None: @@ -3339,6 +3396,7 @@ def _visit_ClassDef(self, node: ast.ClassDef) -> None: node, visibility=decorators.visibility, native_type=decorators.native_type, + abstract=decorators.abstract, ) ) @@ -3403,6 +3461,7 @@ def _visit_ClassDef(self, node: ast.ClassDef) -> None: node, visibility=decorators.visibility, native_type=decorators.native_type, + abstract=decorators.abstract, ) ) diff --git a/tests/fortran/derived_types/end_to_end/fixtures/abstract_hierarchy.f90 b/tests/fortran/derived_types/end_to_end/fixtures/abstract_hierarchy.f90 new file mode 100644 index 000000000..a4fb41901 --- /dev/null +++ b/tests/fortran/derived_types/end_to_end/fixtures/abstract_hierarchy.f90 @@ -0,0 +1,93 @@ +module abstract_hierarchy + use, intrinsic :: iso_c_binding + implicit none + private + + public :: shape_base, circle, square, extent, describe + + !> Abstract base: no instance of this type can exist, but it publishes a + !> deferred contract and one implemented binding its extensions inherit. + type, public, abstract :: shape_base + private + integer(4) :: sides = 0 + contains + private + procedure(area_interface), deferred, public :: area + procedure(name_interface), deferred, public :: label + procedure, public, non_overridable :: side_count => shape_side_count + procedure, public, non_overridable :: bump_sides => shape_bump_sides + end type shape_base + + abstract interface + pure real(8) function area_interface(self) + import :: shape_base + class(shape_base), intent(in) :: self + end function area_interface + + pure subroutine name_interface(self, text) + import :: shape_base + class(shape_base), intent(in) :: self + character(len=8), intent(out) :: text + end subroutine name_interface + end interface + + type, extends(shape_base), public :: circle + real(8) :: radius = 1.0d0 + contains + procedure, public :: area => circle_area + procedure, public :: label => circle_label + end type circle + + type, extends(shape_base), public :: square + real(8) :: side = 1.0d0 + contains + procedure, public :: area => square_area + procedure, public :: label => square_label + end type square + + !> An interoperable type keeps its `bind(c)` layout alongside the hierarchy. + type, bind(c), public :: extent + real(c_double) :: width = 0.0_c_double + real(c_double) :: height = 0.0_c_double + end type extent + +contains + + integer(4) function shape_side_count(self) + class(shape_base), intent(in) :: self + shape_side_count = self%sides + end function shape_side_count + + subroutine shape_bump_sides(self) + class(shape_base), intent(inout) :: self + self%sides = self%sides + 1 + end subroutine shape_bump_sides + + pure real(8) function circle_area(self) + class(circle), intent(in) :: self + circle_area = 3.14159265358979d0 * self%radius * self%radius + end function circle_area + + pure subroutine circle_label(self, text) + class(circle), intent(in) :: self + character(len=8), intent(out) :: text + text = "circle " + end subroutine circle_label + + pure real(8) function square_area(self) + class(square), intent(in) :: self + square_area = self%side * self%side + end function square_area + + pure subroutine square_label(self, text) + class(square), intent(in) :: self + character(len=8), intent(out) :: text + text = "square " + end subroutine square_label + + real(c_double) function describe(box) + type(extent), intent(in) :: box + describe = box%width * box%height + end function describe + +end module abstract_hierarchy diff --git a/tests/fortran/derived_types/end_to_end/fixtures/generic_constructor.f90 b/tests/fortran/derived_types/end_to_end/fixtures/generic_constructor.f90 new file mode 100644 index 000000000..adb10afa8 --- /dev/null +++ b/tests/fortran/derived_types/end_to_end/fixtures/generic_constructor.f90 @@ -0,0 +1,41 @@ +module generic_constructor + implicit none + private + + public :: box, plain + + type, public :: box + integer(4) :: count = 0 + real(8) :: value = 0.0d0 + end type box + + !> An interface named for the type is that type's constructor. + interface box + module procedure box_empty, box_from_count, box_from_value + end interface box + + !> A type with no such interface keeps its keyword-field constructor. + type, public :: plain + integer(4) :: tag = 0 + end type plain + +contains + + pure type(box) function box_empty() result(new_box) + new_box%count = 0 + new_box%value = 0.0d0 + end function box_empty + + pure type(box) function box_from_count(count) result(new_box) + integer(4), intent(in) :: count + new_box%count = count + new_box%value = real(count, 8) + end function box_from_count + + pure type(box) function box_from_value(value) result(new_box) + real(8), intent(in) :: value + new_box%count = int(value, 4) + new_box%value = value + end function box_from_value + +end module generic_constructor diff --git a/tests/fortran/derived_types/end_to_end/test_abstract_hierarchy.py b/tests/fortran/derived_types/end_to_end/test_abstract_hierarchy.py new file mode 100644 index 000000000..d5f64f681 --- /dev/null +++ b/tests/fortran/derived_types/end_to_end/test_abstract_hierarchy.py @@ -0,0 +1,116 @@ +"""Generated Python surface for an abstract Fortran type hierarchy.""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest + +from tests.fortran._support.wrapper_build import _build_source_and_import + +pytestmark = pytest.mark.fortran_end_to_end + +SOURCE = Path(__file__).parent / "fixtures" / "abstract_hierarchy.f90" +GENERATED = { + "bind_c_abstract_hierarchy_wrapper.f90", + "abstract_hierarchy_wrapper.c", + "abstract_hierarchy_wrapper.h", +} + + +@pytest.fixture(scope="module") +def module(tmp_path_factory): + return _build_source_and_import(SOURCE, tmp_path_factory.mktemp("abstract_hierarchy"), GENERATED) + + +def test_abstract_type_cannot_be_instantiated(module): + """`type, abstract ::` has no instances, so its Python class has no constructor.""" + with pytest.raises(TypeError, match="abstract native type and cannot be instantiated"): + module.shape_base() + + assert "__init__" not in module.shape_base.__dict__ + + +def test_extensions_are_python_subclasses_of_the_abstract_base(module): + """Fortran `extends` becomes real Python inheritance, not copied members.""" + assert issubclass(module.circle, module.shape_base) + assert issubclass(module.square, module.shape_base) + assert module.circle.__mro__[:2] == (module.circle, module.shape_base) + + assert isinstance(module.circle(radius=np.float64(1.0)), module.shape_base) + + +def test_deferred_bindings_dispatch_to_each_concrete_override(module): + """A deferred binding names a contract; the dynamic type selects the body.""" + circle = module.circle(radius=np.float64(2.0)) + square = module.square(side=np.float64(3.0)) + + assert circle.area() == pytest.approx(12.566370614, rel=1e-9) + assert square.area() == pytest.approx(9.0) + assert circle.label() == "circle " + assert square.label() == "square " + + # The base declares the same bindings, and they resolve through the caller's + # concrete type rather than through anything the abstract type implements. + assert module.shape_base.area(circle) == pytest.approx(circle.area()) + assert module.shape_base.area(square) == pytest.approx(square.area()) + + +def test_inherited_bindings_and_components_reach_every_extension(module): + """An implemented binding on the abstract base serves its extensions.""" + circle = module.circle(radius=np.float64(1.0)) + + assert circle.side_count() == np.int32(0) + circle.bump_sides() + circle.bump_sides() + assert circle.side_count() == np.int32(2) + + +def test_private_components_stay_off_the_generated_classes(module): + """The hierarchy publishes only what its `private` statements allow.""" + assert {name for name in dir(module.shape_base) if not name.startswith("_")} == { + "area", + "label", + "side_count", + "bump_sides", + } + assert {name for name in dir(module.circle) if not name.startswith("_")} == { + "area", + "label", + "side_count", + "bump_sides", + "radius", + } + + +def test_interoperable_type_keeps_its_layout_beside_the_hierarchy(module): + """A `bind(c)` type in the same module still wraps through its own accessors.""" + box = module.extent(width=np.float64(3.0), height=np.float64(4.0)) + + assert box.width == np.float64(3.0) + assert module.describe(box) == pytest.approx(12.0) + + box.width = np.float64(5.0) + assert module.describe(box) == pytest.approx(20.0) + + +def test_build_writes_its_semantic_contract_beside_the_extension(tmp_path: Path): + """Every build leaves the contract describing the API it just generated.""" + from prik.pipeline.build import BUILD_CONTRACT_DIRECTORY_NAME, build_fortran_extension + from prik.preprocessing import PreprocessingConfig + from tests.fortran._support.wrapper_build import _compiler + + result = build_fortran_extension( + SOURCE, + output_dir=tmp_path, + preprocessing=PreprocessingConfig(mode="compiler", compiler=_compiler()), + ) + + contracts = result.output_dir / BUILD_CONTRACT_DIRECTORY_NAME + assert (contracts / "abstract_hierarchy.pyi").is_file() + assert (contracts / "__init__.pyi").read_text(encoding="utf-8").strip() == ("from . import abstract_hierarchy") + + text = (contracts / "abstract_hierarchy.pyi").read_text(encoding="utf-8") + assert "@abstract" in text + assert "@abstractmethod" in text diff --git a/tests/fortran/derived_types/end_to_end/test_generic_constructor.py b/tests/fortran/derived_types/end_to_end/test_generic_constructor.py new file mode 100644 index 000000000..eed4e6bc4 --- /dev/null +++ b/tests/fortran/derived_types/end_to_end/test_generic_constructor.py @@ -0,0 +1,77 @@ +"""Generated Python constructor for each Fortran constructor source.""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest + +from tests.fortran._support.wrapper_build import _build_source_and_import + +pytestmark = pytest.mark.fortran_end_to_end + +SOURCE = Path(__file__).parent / "fixtures" / "generic_constructor.f90" +GENERATED = { + "bind_c_generic_constructor_wrapper.f90", + "generic_constructor_wrapper.c", + "generic_constructor_wrapper.h", +} + + +@pytest.fixture(scope="module") +def module(tmp_path_factory): + return _build_source_and_import(SOURCE, tmp_path_factory.mktemp("generic_constructor"), GENERATED) + + +def test_type_without_a_constructor_interface_keeps_keyword_fields(module): + """No user constructor: the keyword-field `__init__` is unchanged.""" + value = module.plain(tag=np.int32(5)) + + assert value.tag == np.int32(5) + + +def test_constructor_interface_overloads_init_from_its_specifics(module): + """`interface `: each specific becomes an accepted signature.""" + empty = module.box() + from_count = module.box(np.int32(7)) + from_value = module.box(np.float64(2.5)) + + assert (empty.count, empty.value) == (np.int32(0), np.float64(0.0)) + assert (from_count.count, from_count.value) == (np.int32(7), np.float64(7.0)) + assert (from_value.count, from_value.value) == (np.int32(2), np.float64(2.5)) + + +def test_constructor_overload_rejects_an_unmatched_signature(module): + """A call matching no specific is refused rather than guessed at.""" + with pytest.raises(TypeError, match="no matching overload"): + module.box("not a supported signature") + + +def test_constructed_instances_are_independent_wrapper_objects(module): + """Each accepted signature produces its own wrapper-owned instance.""" + first = module.box(np.int32(1)) + second = module.box(np.int32(2)) + + assert first is not second + first.count = np.int32(9) + assert second.count == np.int32(2) + + +def test_constructor_contract_states_no_redundant_link_name(tmp_path: Path): + """A constructor's native generic is named for its type, so `@bind` is omitted. + + `@overload` names the specific this candidate selects; the class name already + states the generic that reaches it, exactly as an unrenamed method omits + `@bind`. + """ + from prik.pipeline.pyi import emit_module_stubs + from prik.parsers.fortran import parse_fortran_file + from prik.semantics.fortran2ir import fortran_file_to_semantic_modules + + modules = fortran_file_to_semantic_modules(parse_fortran_file(str(SOURCE))) + contract = emit_module_stubs(modules)["generic_constructor"] + + assert '@overload("box_from_count")' in contract + assert '@bind("box")' not in contract + assert "@private\n def __init__" not in contract 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 e21a68568..5806708e1 100644 --- a/tests/fortran/derived_types/policy/test_derived_accessor_policy.py +++ b/tests/fortran/derived_types/policy/test_derived_accessor_policy.py @@ -36,7 +36,8 @@ from prik.policy.models import ModuleObjectAccessMechanism -def test_abstract_type_and_deferred_binding_fail_in_completed_derived_policy(): +def test_abstract_type_completes_as_a_non_instantiable_derived_policy(): + """An abstract type is supported and records that it has no instances.""" semantic_class = SemanticClass( "shape", metadata={ @@ -48,12 +49,23 @@ def test_abstract_type_and_deferred_binding_fail_in_completed_derived_policy(): complete_semantic_policies(module) + policy = semantic_class.metadata[RESOLVED_DERIVED_TYPE_POLICY_METADATA] + assert policy.supported is True + assert policy.blockers == () + assert policy.abstract is True + assert policy.deferred_bindings == ("area",) + + +def test_deferred_binding_without_an_abstract_type_is_refused(): + """Only an abstract type may declare a binding it does not implement.""" + semantic_class = SemanticClass("shape", metadata={"fortran_deferred_bindings": ["area"]}) + module = SemanticModule("shapes", classes=[semantic_class]) + + complete_semantic_policies(module) + policy = semantic_class.metadata[RESOLVED_DERIVED_TYPE_POLICY_METADATA] assert policy.supported is False - assert policy.blockers == ( - "abstract derived types need a non-instantiable Python class policy", - "deferred type-bound procedure 'area' needs an override and dispatch policy", - ) + assert policy.blockers == ("deferred type-bound procedure 'area' needs a declaring abstract type",) def test_derived_field_setter_policy_uses_value_copy_write_through(): 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 c223fd023..6b08051eb 100644 --- a/tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py +++ b/tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py @@ -2,7 +2,6 @@ from pathlib import Path -import pytest from prik.semantics.fortran2ir import ( FortranToIRConverter, fortran_module_to_semantic_module, @@ -86,7 +85,12 @@ def test_public_generic_binds_private_inline_module_function_specifics_to_the_ge assert [candidate.metadata[BIND_TARGET_METADATA] for candidate in candidates] == ["shift", "shift"] -def test_converter_rejects_generic_constructor_interfaces_during_semantic_conversion(): +def test_converter_projects_a_generic_constructor_onto_its_class(): + """An interface named for a derived type is that type's constructor. + + Its specifics become the class's own `__init__` overload set rather than a + module-level generic, so the type name stays the only public spelling. + """ source = """ module constructor_generic_mod type :: item @@ -103,10 +107,13 @@ def test_converter_rejects_generic_constructor_interfaces_during_semantic_conver end module constructor_generic_mod """ - with pytest.raises(ValueError, match="cannot represent generic constructor") as exc_info: - fortran_module_to_semantic_module(parse_fortran_source(source)) + module = fortran_module_to_semantic_module(parse_fortran_source(source)) - assert "constructor_generic_mod.item" in str(exc_info.value) + assert [overload.name for overload in module.overload_sets] == [] + item = module.classes[0] + constructors = [overload for overload in item.overload_sets if overload.name == "__init__"] + assert len(constructors) == 1 + assert [procedure.metadata["overload_target"] for procedure in constructors[0].procedures] == ["make_item"] def test_converter_preserves_defined_operators_assignment_and_type_bound_operators(): From fdd48544f3a10911869fb3ad6bbdf543dab88655 Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 13:46:27 +0100 Subject: [PATCH 14/26] codex: Reload generics whose specifics project an output A generic interface whose specifics carry an `intent(out)` argument could not be reloaded from its own generated contract. The declaration states the public signature, so an output the projection turns into a result is not one of the arguments it accepts -- but the check compared the declaration against the specific's native argument list, which still contained it. Every such generic was rejected, which is the common shape in numerical Fortran: BSPLINE-FORTRAN's `db1ink`, `db1val`, and the type-bound `initialize` all failed. One projection rule now applies to both signatures, and the same rule drives a type-bound generic's receiver search. The projected-result comparison is additive, so a declaration that already matched its target's own return keeps matching. The three bspline contract modules now load; the remaining blocker for rebuilding that project from its contract is a callback argument (`procedure(b1fqad_func) :: fun`), which is separate work. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 10 +++ prik/semantics/pyi2ir.py | 82 ++++++++++++++++++- .../pipeline/test_classes_and_methods.py | 45 +++++++++- 3 files changed, 132 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e900ccb0e..dbf897a2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,16 @@ release tags add a leading `v` to the package version. ### Fixed +- A generic interface whose specifics project an `intent(out)` argument into a + result now reloads from its generated contract. The declaration states the + public signature, so an output the projection turned into a result is not one + of the arguments it accepts; comparing the declaration against the specific's + native argument list rejected every such generic — the common shape in + numerical Fortran — with "Overload declaration 'x' is incompatible with + specific procedure 'y'". The same comparison now drives a type-bound generic's + receiver search. Generated contracts for BSPLINE-FORTRAN's `db1ink`, + `db1val`, and `initialize` load again. + - A module whose only procedures are `bind(C)` now installs the bundled native support its derived-type accessors need. Compiled wrapper builds for such a module previously failed to link with `undefined symbol: diff --git a/prik/semantics/pyi2ir.py b/prik/semantics/pyi2ir.py index 271e16eee..71ff95f34 100644 --- a/prik/semantics/pyi2ir.py +++ b/prik/semantics/pyi2ir.py @@ -179,6 +179,10 @@ class _PendingOverload: generic_name: str | None = None +#: Sentinel for a projected result this comparison does not reconstruct. +_UNCOMPARED_PROJECTED_RETURN = object() + + class _PyiAstParser: """Stateful AST visitor that builds one semantic module from a contract. @@ -1227,11 +1231,16 @@ def _validate_overload_signature( form. A class overload may instead expose a projected bound-object return; every other mismatch raises ``ValueError``. """ - visible_declaration_arguments = [_PyiAstParser._visible_overload_argument(arg) for arg in declaration.arguments] - visible_call_arguments = [_PyiAstParser._visible_overload_argument(arg) for arg in call_arguments] + projected_arguments = _PyiAstParser._projected_overload_arguments(target, call_arguments) + declared_arguments = _PyiAstParser._projected_overload_arguments(declaration, declaration.arguments) + visible_declaration_arguments = [_PyiAstParser._visible_overload_argument(arg) for arg in declared_arguments] + visible_call_arguments = [_PyiAstParser._visible_overload_argument(arg) for arg in projected_arguments] + target_return = _PyiAstParser._projected_overload_return_type(target) if visible_declaration_arguments == visible_call_arguments and ( _PyiAstParser._visible_overload_type(declaration.return_type) == _PyiAstParser._visible_overload_type(target.return_type) + or target_return is _UNCOMPARED_PROJECTED_RETURN + or _PyiAstParser._matches_projected_return(declaration.return_type, target_return) or _PyiAstParser._matches_bound_projection_return(declaration, target, bound_position) ): return @@ -1240,6 +1249,65 @@ def _validate_overload_signature( f"specific procedure {target.native_name or target.name!r}" ) + @staticmethod + def _matches_projected_return(declared, target_return) -> bool: + """Compare a declared result with a target's, ignoring result ownership.""" + declared_type = _PyiAstParser._visible_overload_type(declared) + target_type = _PyiAstParser._visible_overload_type(target_return) + if declared_type is None or target_type is None: + return declared_type == target_type + expected = deepcopy(target_type) + expected.ownership = deepcopy(declared_type.ownership) + return declared_type == expected + + @staticmethod + def _projected_overload_arguments( + function: SemanticFunction, + arguments: list[SemanticArgument], + ) -> list[SemanticArgument]: + """Return only the arguments one projected signature still accepts. + + An output the projection turns into a result is not part of the public + signature, whether it is a native output argument on the specific or a + further returned value the declaration states. + """ + hidden = { + mapping.native_name + for mapping in function.projection + if mapping.python_position is None and mapping.result_position is not None + } + if not hidden: + return list(arguments) + return [argument for argument in arguments if argument.name not in hidden] + + @staticmethod + def _projected_overload_return_type(target: SemanticFunction): + """Return the result a projected target presents, or the uncompared marker. + + A projection that supplies exactly one result replaces an absent native + return with that argument's type. Several results compose a tuple the + declaration states directly, which this comparison does not rebuild. + """ + results = [mapping for mapping in target.projection if mapping.result_position is not None] + if not results: + return target.return_type + if target.return_type is not None or len(results) != 1: + # Several results compose a tuple the declaration states directly, + # and its extra members arrive as `return_position` arguments that + # the comparison above has already set aside. + return _UNCOMPARED_PROJECTED_RETURN + by_name = {argument.name: argument for argument in target.arguments} + projected = by_name.get(results[0].native_name) + if projected is None: + return _UNCOMPARED_PROJECTED_RETURN + # A projected output is declared as a native output argument; as a result + # it is an ordinary returned value, so its argument-passing storage is + # not part of the public type the declaration states. + returned = deepcopy(projected.semantic_type) + if returned.rank == 0 and returned.storage is not None and returned.storage.kind in {"address", "reference"}: + returned.storage = None + return returned + @staticmethod def _visible_overload_argument(argument: SemanticArgument) -> SemanticArgument: """Copy one overload argument with its type normalized for public comparison.""" @@ -1314,12 +1382,18 @@ def _class_overload_bound_position( and target.return_type.name.casefold() == owner.name.casefold() ): return None - remaining_names = [argument.name for argument in declaration.arguments] + # Compare public signatures: an output either side projects into a result + # is not one of the arguments a caller supplies. + declared_names = [ + argument.name + for argument in _PyiAstParser._projected_overload_arguments(declaration, declaration.arguments) + ] + visible_target_arguments = _PyiAstParser._projected_overload_arguments(target, target.arguments) matching = [ index for index, argument in enumerate(target.arguments) if argument.semantic_type.name.casefold() == owner.name.casefold() - and [arg.name for pos, arg in enumerate(target.arguments) if pos != index] == remaining_names + and [item.name for item in visible_target_arguments if item is not argument] == declared_names ] if len(matching) == 1: return matching[0] 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 d61695840..923b1cf01 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,7 +2,7 @@ import pytest from prik.parsers.fortran import parse_fortran_file as parse_fortran_source -from prik.pipeline.pyi import emit_module_stubs +from prik.pipeline.pyi import emit_module_stubs, pyi_text_to_semantic_module from prik.printers import PyiPrinter, emit_module from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik.semantics.models import ( @@ -508,3 +508,46 @@ def reset(self) -> None: ...""" @native_call([Return(0)]) def wrapper() -> None: ...""" ) + + +def test_generic_specifics_with_projected_outputs_round_trip(): + """A generic whose specifics project an `intent(out)` reloads from its contract. + + The declaration states the public signature, so the output the projection + turned into a result is not one of the arguments it accepts. Comparing the + declaration against the specific's native arguments rejected every such + generic, which is the common shape in numerical Fortran. + """ + source = """ +module projected_generic_mod + implicit none + private + public :: ink + interface ink + module procedure ink_default, ink_extended + end interface ink +contains + subroutine ink_default(x, n, iflag) + real(8), intent(in) :: x(:) + integer(4), intent(in) :: n + integer(4), intent(out) :: iflag + iflag = 0 + end subroutine ink_default + subroutine ink_extended(x, n, extra, iflag) + real(8), intent(in) :: x(:) + integer(4), intent(in) :: n + real(8), intent(in) :: extra + integer(4), intent(out) :: iflag + iflag = 0 + end subroutine ink_extended +end module projected_generic_mod +""" + + code = generate_pyi(source) + assert '@overload("ink_default")' in code + assert '@overload("ink_extended")' in code + + module = pyi_text_to_semantic_module(code, module_name="projected_generic_mod") + overloads = [item for item in module.overload_sets if item.name == "ink"] + assert len(overloads) == 1 + assert [procedure.name for procedure in overloads[0].procedures] == ["ink_default", "ink_extended"] From 357215a37fbaff1e482b534bfee60e05b7df38c1 Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 14:20:11 +0100 Subject: [PATCH 15/26] codex: Organize the C tests by feature and stage `tests/c/` was organized by pipeline stage alone -- `parsing/`, `semantics/`, `preprocessing/`, `probes/`, `cli/` -- while `tests/` documents one shape for the whole tree: tests//// That left C with no home for the `policy/`, `codegen/`, and `end_to_end/` owners its wrapper work needs, and it mixed documented behavior with internal mechanism in one directory. Every file moves; none is rewritten. Four were also misfiled rather than just mis-shaped: the C parser CLI coverage sat under `parsing/`, and the lexer, public-API, model-serialization, and JSON-shape tests protect internal mechanisms rather than documented C behavior, so they move to `infrastructure/` beside their production package. The shared conversion helpers move to `tests/c/_support/semantic_conversion.py`, matching the Fortran support module of the same name. `tests/c/fixtures/` stays where it is: it is read through `_support/fixture_outputs.py`, which anchors on `tests/c/`, and several features share it. 496 passed, 1 skipped -- the same counts as before the move. Co-Authored-By: Claude Opus 5 --- docs/user/examples/bspline-wrapper.md | 235 +++++++++++++++--- docs/user/examples/index.md | 2 +- examples/bspline/README.md | 88 ++++--- examples/bspline/routine_inventory.py | 10 +- .../bspline/tests/test_object_oriented_api.py | 24 ++ examples/bspline/tests/test_procedural_api.py | 179 ++++++++++++- .../bspline/tests/test_routine_coverage.py | 55 ++++ .../semantic_conversion.py} | 0 .../pipeline}/test_c_cli_argument_contract.py | 0 .../pipeline}/test_c_cli_output_contract.py | 0 .../pipeline}/test_c_cli_skeleton.py | 0 .../pipeline}/test_c_cli_stage_dispatch.py | 0 .../c/{ => data_types}/probes/test_c_types.py | 0 .../semantics}/test_types_and_constants.py | 2 +- .../parsing/test_c_functions.py | 0 .../test_functions_and_callbacks.py | 2 +- .../test_c_parser_developer_tutorial.py | 0 .../parsers}/test_c_json_sanity.py | 2 +- .../parsers}/test_c_lexer_preprocessor.py | 0 .../parsers}/test_c_model_serialization.py | 0 .../parsers}/test_c_public_api_skeleton.py | 0 .../test_c_structs_unions_enums_typedefs.py | 0 .../semantics}/test_records_and_enums.py | 2 +- .../test_c_conversion_properties.py | 0 .../test_projects_and_diagnostics.py | 2 +- .../pipeline/test_c_pyi_contract_fixtures.py | 0 .../semantics}/test_c_pyi_conversion.py | 0 .../parsing/test_c_compiler_extensions.py | 0 .../parsing/test_c_corpus.py | 2 +- .../test_c_declarations_and_declarators.py | 0 .../parsing/test_c_error_fixture_suite.py | 2 +- .../parsing/test_c_fixture_suite.py | 2 +- .../parsing/test_c_parser_benchmark.py | 0 .../parsing/test_c_parser_properties.py | 0 .../parsing/test_c_project_resolution.py | 0 .../preprocessing/test_c_preprocessing_cli.py | 0 .../test_c_preprocessing_configuration.py | 0 .../test_c_preprocessing_dependencies.py | 0 .../test_c_preprocessing_execution.py | 0 .../test_c_preprocessing_properties.py | 0 .../preprocessing/test_error_paths.py | 0 .../preprocessing/test_source_mappings.py | 0 tests/docs/test_examples.py | 1 + 43 files changed, 531 insertions(+), 79 deletions(-) create mode 100644 examples/bspline/tests/test_routine_coverage.py rename tests/c/{semantics/conversion/_support.py => _support/semantic_conversion.py} (100%) rename tests/c/{cli => command_line_interface/pipeline}/test_c_cli_argument_contract.py (100%) rename tests/c/{cli => command_line_interface/pipeline}/test_c_cli_output_contract.py (100%) rename tests/c/{parsing => command_line_interface/pipeline}/test_c_cli_skeleton.py (100%) rename tests/c/{cli => command_line_interface/pipeline}/test_c_cli_stage_dispatch.py (100%) rename tests/c/{ => data_types}/probes/test_c_types.py (100%) rename tests/c/{semantics/conversion => data_types/semantics}/test_types_and_constants.py (99%) rename tests/c/{ => functions}/parsing/test_c_functions.py (100%) rename tests/c/{semantics/conversion => functions/semantics}/test_functions_and_callbacks.py (99%) rename tests/c/{parsing => infrastructure/execution_examples}/test_c_parser_developer_tutorial.py (100%) rename tests/c/{parsing => infrastructure/parsers}/test_c_json_sanity.py (98%) rename tests/c/{parsing => infrastructure/parsers}/test_c_lexer_preprocessor.py (100%) rename tests/c/{parsing => infrastructure/parsers}/test_c_model_serialization.py (100%) rename tests/c/{parsing => infrastructure/parsers}/test_c_public_api_skeleton.py (100%) rename tests/c/{ => records}/parsing/test_c_structs_unions_enums_typedefs.py (100%) rename tests/c/{semantics/conversion => records/semantics}/test_records_and_enums.py (99%) rename tests/c/{semantics/conversion => semantic_ir/semantics}/test_c_conversion_properties.py (100%) rename tests/c/{semantics/conversion => semantic_ir/semantics}/test_projects_and_diagnostics.py (99%) rename tests/c/{ => semantic_pyi_format}/pipeline/test_c_pyi_contract_fixtures.py (100%) rename tests/c/{semantics/conversion => semantic_pyi_format/semantics}/test_c_pyi_conversion.py (100%) rename tests/c/{ => source_parsing}/parsing/test_c_compiler_extensions.py (100%) rename tests/c/{ => source_parsing}/parsing/test_c_corpus.py (97%) rename tests/c/{ => source_parsing}/parsing/test_c_declarations_and_declarators.py (100%) rename tests/c/{ => source_parsing}/parsing/test_c_error_fixture_suite.py (98%) rename tests/c/{ => source_parsing}/parsing/test_c_fixture_suite.py (99%) rename tests/c/{ => source_parsing}/parsing/test_c_parser_benchmark.py (100%) rename tests/c/{ => source_parsing}/parsing/test_c_parser_properties.py (100%) rename tests/c/{ => source_parsing}/parsing/test_c_project_resolution.py (100%) rename tests/c/{ => source_preprocessing}/preprocessing/test_c_preprocessing_cli.py (100%) rename tests/c/{ => source_preprocessing}/preprocessing/test_c_preprocessing_configuration.py (100%) rename tests/c/{ => source_preprocessing}/preprocessing/test_c_preprocessing_dependencies.py (100%) rename tests/c/{ => source_preprocessing}/preprocessing/test_c_preprocessing_execution.py (100%) rename tests/c/{ => source_preprocessing}/preprocessing/test_c_preprocessing_properties.py (100%) rename tests/c/{ => source_preprocessing}/preprocessing/test_error_paths.py (100%) rename tests/c/{ => source_preprocessing}/preprocessing/test_source_mappings.py (100%) diff --git a/docs/user/examples/bspline-wrapper.md b/docs/user/examples/bspline-wrapper.md index df64a8e91..ae4d69934 100644 --- a/docs/user/examples/bspline-wrapper.md +++ b/docs/user/examples/bspline-wrapper.md @@ -1,38 +1,124 @@ --- title: Build and Validate BSPLINE-FORTRAN with PRIK audience: users, advanced users -prerequisites: derived types, arrays -related: minpack-wrapper.md, ../guide/wrapping-derived-types.md +prerequisites: derived types, arrays, packaging +related: fftpack-wrapper.md, ../guide/wrapping-derived-types.md status: maintained publication: reviewed --- # Build and Validate BSPLINE-FORTRAN with PRIK -This example wraps [BSPLINE-FORTRAN](https://github.com/jacobwilliams/bspline-fortran) -and validates both of its public interfaces from Python. +This example takes the checked-in +[BSPLINE-FORTRAN](https://github.com/jacobwilliams/bspline-fortran) source and +builds an importable Python extension with the complete interpolation surface: +15 public procedural routines, eight order constants, and seven public classes. -It is the modern-Fortran example. The BLAS, LAPACK, FFTPACK, and MINPACK -projects are FORTRAN 77; this library is Fortran 2008, and PRIK wraps it -**unmodified**: +It evaluates B-splines from one to six dimensions. The tests compare results +with analytic functions and SciPy rather than treating the wrapper as its own +reference. -- an **abstract** derived type, `bspline_class`, with two **deferred** bindings; -- six concrete extensions that inherit from it; -- **generic constructors** declared as `interface bspline_1d`; -- **private components and bindings** kept off the Python surface; -- generic procedure interfaces with several specifics each. +### What this example shows -## Build and test +- Wrap a modern multi-file Fortran library as one Python extension. +- Construct and call derived types over an abstract Fortran base. +- Check procedural and object-oriented interpolation with NumPy arrays. + +You should already be comfortable with NumPy arrays, Python classes, and +building a local Fortran extension. + +--- + +## Versions used + +| Component | Version / source | +| --- | --- | +| PRIK | current repository checkout | +| BSPLINE-FORTRAN | [version 7.4.0, commit `047c7244`](https://github.com/jacobwilliams/bspline-fortran/tree/047c7244) | +| Python | 3.12 in the dedicated CI job | +| NumPy | 2.5.1 | +| SciPy | 1.18.0 | +| Fortran compiler | GNU Fortran 13 in CI; a compatible `gfortran` works locally | + +The repository owns the checked-in source snapshot under +`examples/bspline/native/`, so the example does not download code during its +build. + +--- + +## 1. Prepare the repository and toolchain + +Clone PRIK, create a virtual environment, and install the Python tools used by +the dedicated CI job: + +```bash +git clone https://github.com/PyNumLab/prik.git +cd prik +python3 -m venv .venv +. .venv/bin/activate +python3 -m pip install --upgrade pip +python3 -m pip install -e ".[qa]" "numpy==2.5.1" "scipy==1.18.0" +``` + +Install GNU Fortran separately. On Ubuntu: + +```bash +sudo apt-get update +sudo apt-get install --yes gfortran +gfortran --version +``` + +All remaining commands run from the repository root with the virtual +environment active. The complete runnable project lives under +[`examples/bspline/`](../../../examples/bspline/). + +--- + +## 2. Build the PRIK wrapper + +BSPLINE-FORTRAN separates its kind definitions, procedural routines, and +object-oriented types into ordered source files. The build command passes those +three public sources in dependency order: + + +```bash +export EXAMPLE_WORKSPACE="$PWD" +export BSPLINE_BUILD_ROOT="$(mktemp -d)" + +mkdir -p "$BSPLINE_BUILD_ROOT/prik/generated" +cd "$BSPLINE_BUILD_ROOT/prik" + +python3 -m prik \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_kinds_module.F90" \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_sub_module.f90" \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_oo_module.f90" \ + --out prik_bspline \ + --out-dir "$BSPLINE_BUILD_ROOT/prik/generated" \ + --compiler "$(command -v gfortran)" \ + --jobs 8 \ + --wrapper-fortran-flags="-O0 -g0" \ + --wrapper-c-flags="-O0 -g0" +``` + +The example uses `-O0` so the tests focus on correct results. PRIK compiles the +native source and generated bridge into one extension. + +For normal use, source the convenience entrypoint: ```bash source examples/bspline/build_all.sh -python3 -m pytest -q examples/bspline/tests -m real_library ``` -The build passes the three interpolation sources to PRIK in dependency order. -No `.pyi` contract is written and no source is edited. +It builds the extension and exports its directory on `PYTHONPATH` for the +current shell. + +--- + +## 3. Use the generated Python API -## The generated API +The object-oriented module exposes an abstract `bspline_class` and six concrete +dimension-specific subclasses. The `bspline_1d` generic constructor accepts an +empty form and a data-driven form: ```python import numpy as np @@ -45,9 +131,8 @@ value, iflag = spline.evaluate(np.float64(1.234), np.int32(0)) area, iflag = spline.integral(np.float64(0.0), np.float64(np.pi)) ``` -`bspline_1d(x, fcn, kx)` is the Fortran `interface bspline_1d` constructor; -`bspline_1d()` is its empty overload. The abstract base is exported but cannot -be constructed: +The abstract base is exported but cannot be constructed. Its concrete +extensions inherit the base bindings and answer its deferred operations: ```python bspline.bspline_class() @@ -57,22 +142,104 @@ bspline.bspline_class() issubclass(bspline.bspline_1d, bspline.bspline_class) # True ``` -## What is validated +The procedural module exposes the matching `db1ink` through `db6ink` setup +routines and `db1val` through `db6val` evaluators. Pass ordinary NumPy arrays; +PRIK performs the ABI conversion inside the generated wrapper. -| Test file | Covers | -| --- | --- | -| `test_object_oriented_api.py` | Abstract base, inheritance, deferred bindings, generic constructors, 1D and 2D interpolation, derivatives, definite integrals | -| `test_procedural_api.py` | Public procedures, order constants, generic interfaces, exactness on a cubic, derivatives, integrals, SciPy comparison | +--- + +## 4. Run the complete test suite + +After the build finishes, run: + +```bash +python3 -m pytest -q examples/bspline/tests +``` + +The tests cover every exported routine and class: -Numerical checks use analytic values and `scipy.interpolate.make_interp_spline` -as independent oracles rather than trusting the wrapper as its own reference. +| Family | Public surface | +| --- | ---: | +| Interpolation setup | 6 routines | +| Evaluation | 6 routines | +| Definite integrals | 2 routines | +| Status reporting | 1 routine | +| Order constants | 8 constants | +| Derived types | 1 abstract base + 6 concrete classes | + +The inventory test fails if an expected export disappears, an extra public +export appears, or a procedural routine has no named numerical test. + +--- + +## 5. See how results are validated + +The suite checks interpolation against analytic values and SciPy, along with +constructor behavior, inheritance, abstract-base dispatch, generated status, +and Fortran-order array handling. This test comes directly from the runnable +suite and shows the procedural one-dimensional definite integral: + + +```python +def test_db1sqad(bspline_sub): + x = np.linspace(0.0, np.pi, 60) + knots, bcoef, nx = _interpolant(bspline_sub, x, np.sin(x)) + work = np.zeros(3 * int(CUBIC), dtype=np.float64) + + value, iflag = bspline_sub.db1sqad(knots, bcoef, nx, CUBIC, np.float64(0.0), np.float64(np.pi), work) + assert iflag == np.int32(0) + assert value == pytest.approx(2.0, abs=1.0e-6) +``` + +It builds a cubic spline for `sin(x)`, integrates it from zero to π, and checks +the known value of two. + +--- + +## 6. Run focused examples + +After building the extension, run a family or one routine: + +```bash +python3 -m pytest -q examples/bspline/tests/test_object_oriented_api.py +python3 -m pytest -q \ + examples/bspline/tests/test_procedural_api.py::test_db1ink +python3 -m pytest -q examples/bspline/tests -k db6 +``` + +- Derived-type examples → + [`test_object_oriented_api.py`](../../../examples/bspline/tests/test_object_oriented_api.py) +- Procedural numerical examples → + [`test_procedural_api.py`](../../../examples/bspline/tests/test_procedural_api.py) +- Public surface and coverage check → + [`test_routine_coverage.py`](../../../examples/bspline/tests/test_routine_coverage.py) +- Reviewed inventory → + [`routine_inventory.py`](../../../examples/bspline/routine_inventory.py) +- Copyable project instructions → + [`examples/bspline/README.md`](../../../examples/bspline/README.md) + +--- + +## Troubleshooting + +- Confirm that `gfortran` is available on `PATH`. +- Use `source examples/bspline/build_all.sh`; executing it in a child shell does + not preserve the exported `PYTHONPATH`. +- Run one failing procedure with `-vv -s` to retain its compiler and wrapper + diagnostics. + +--- -## Scope and licence +## Source provenance -The upstream least-squares module and its BLAS bridge are outside this example; -the interpolation surface does not need them. -[`routine_inventory.py`](../../../examples/bspline/routine_inventory.py) records -the reviewed surface and that exclusion. +The native files under +[`examples/bspline/native/`](../../../examples/bspline/native/) are the +BSPLINE-FORTRAN 7.4.0 snapshot at +[commit `047c7244`](https://github.com/jacobwilliams/bspline-fortran/tree/047c7244). +The upstream `bspline_defc_module` least-squares fitter and its +`bspline_blas_module` bridge are intentionally outside this interpolation +example. -BSPLINE-FORTRAN is by Jacob Williams under a BSD-3-Clause licence, included with -the vendored sources at version 7.4.0. +See the [upstream repository](https://github.com/jacobwilliams/bspline-fortran) +and its bundled BSD-3-Clause license before redistributing the vendored native +source. diff --git a/docs/user/examples/index.md b/docs/user/examples/index.md index 777c6a071..6fbbd54be 100644 --- a/docs/user/examples/index.md +++ b/docs/user/examples/index.md @@ -37,4 +37,4 @@ PRIK_C_DOCS_END --> | Build complete Reference LAPACK and validate 127 float64 routines | [LAPACK wrapper](lapack-wrapper.md) | | Wrap and validate all 31 FFTPACK procedures with NumPy and SciPy | [FFTPACK wrapper](fftpack-wrapper.md) | | Wrap all 22 MINPACK procedures and use Python callbacks | [MINPACK wrapper](minpack-wrapper.md) | -| Wrap modern Fortran classes over an abstract base | [BSPLINE-FORTRAN wrapper](bspline-wrapper.md) | +| Build and validate modern Fortran classes and 15 interpolation routines | [BSPLINE-FORTRAN wrapper](bspline-wrapper.md) | diff --git a/examples/bspline/README.md b/examples/bspline/README.md index 7edd1ffb9..03e8a6a19 100644 --- a/examples/bspline/README.md +++ b/examples/bspline/README.md @@ -1,12 +1,13 @@ # Wrap BSPLINE-FORTRAN with PRIK -Build [BSPLINE-FORTRAN](https://github.com/jacobwilliams/bspline-fortran) with -PRIK and validate both of its public interfaces from Python: the -object-oriented classes and the procedural routines. +Build the bundled +[BSPLINE-FORTRAN](https://github.com/jacobwilliams/bspline-fortran) source with +PRIK and validate its complete interpolation surface: 15 public procedural +routines, eight order constants, and seven public classes. -This is the example that exercises PRIK's modern-Fortran surface. Unlike the -BLAS, LAPACK, FFTPACK, and MINPACK projects — which are FORTRAN 77 — this -library is written in Fortran 2008 and wraps **unmodified**: +This is the example that exercises PRIK's modern-Fortran derived-type surface. +Unlike BLAS, FFTPACK, and MINPACK, it is a Fortran 2008 library. PRIK wraps the +vendored source **unmodified**: - an **abstract** derived type (`bspline_class`) with two **deferred** bindings; - six concrete extensions that inherit from it; @@ -14,6 +15,10 @@ library is written in Fortran 2008 and wraps **unmodified**: - **private components and private bindings** kept off the Python surface; - generic procedure interfaces (`db1ink`, `db1val`) with several specifics. +Analytic functions and `scipy.interpolate.make_interp_spline` provide +independent numerical oracles. The inventory has no unsupported or skipped +procedures. + ## Requirements Install GNU Fortran. On Ubuntu: @@ -23,11 +28,10 @@ sudo apt-get update sudo apt-get install --yes gfortran ``` -Install the Python test tools. SciPy is optional; the comparison test skips -without it: +Install the pinned numerical tools: ```console -python3 -m pip install numpy pytest scipy +python3 -m pip install "numpy==2.5.1" "scipy==1.18.0" pytest ``` Run the remaining commands from the repository root. @@ -44,19 +48,34 @@ the test process. ## How the build works -`build_prik.sh` passes the three interpolation sources to PRIK in dependency -order and builds one extension: +The build passes the three interpolation sources to PRIK in dependency order: +the kind definitions, procedural interface, and object-oriented interface. +Every source is compiled once and no alternative wrapper is created. + +### Build the PRIK wrapper + ```bash +export EXAMPLE_WORKSPACE="$PWD" +export BSPLINE_BUILD_ROOT="$(mktemp -d)" + +mkdir -p "$BSPLINE_BUILD_ROOT/prik/generated" +cd "$BSPLINE_BUILD_ROOT/prik" + python3 -m prik \ - examples/bspline/native/bspline_kinds_module.F90 \ - examples/bspline/native/bspline_sub_module.f90 \ - examples/bspline/native/bspline_oo_module.f90 \ - --out prik_bspline + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_kinds_module.F90" \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_sub_module.f90" \ + "$EXAMPLE_WORKSPACE/examples/bspline/native/bspline_oo_module.f90" \ + --out prik_bspline \ + --out-dir "$BSPLINE_BUILD_ROOT/prik/generated" \ + --compiler "$(command -v gfortran)" \ + --jobs 8 \ + --wrapper-fortran-flags="-O0 -g0" \ + --wrapper-c-flags="-O0 -g0" ``` -No `.pyi` contract is written and no source is edited. The upstream files are -vendored byte-for-byte under `native/`. +`-O0` keeps the example focused on correctness. The build writes its generated +contract beside the extension; it does not edit the upstream source. ## The Python API @@ -84,26 +103,37 @@ bspline.bspline_class() issubclass(bspline.bspline_1d, bspline.bspline_class) # True ``` +## Run focused tests + +After the quick-start build, run one interface family or routine: + +```bash +python3 -m pytest -q examples/bspline/tests/test_object_oriented_api.py +python3 -m pytest -q examples/bspline/tests/test_procedural_api.py::test_db1ink +python3 -m pytest -q examples/bspline/tests -k db6 +``` + ## What is validated -| Test file | Covers | -| --- | --- | -| `tests/test_object_oriented_api.py` | Abstract base, inheritance, deferred bindings, generic constructors, 1D/2D interpolation, derivatives, definite integrals | -| `tests/test_procedural_api.py` | Public procedures, order constants, generic interfaces, interpolation exactness on a cubic, derivatives, integrals, SciPy comparison | +The suite builds every public procedural family from one to six dimensions, +then evaluates an affine function through every generated evaluator. It also +checks one-dimensional analytic values, derivatives, definite integrals, and +callback-driven integration, plus a SciPy interpolation comparison. The +object-oriented tests construct and evaluate every concrete spline class, and +check the abstract-base, inheritance, deferred-binding, and generic-constructor +contracts. -Numerical checks use independent oracles — analytic values, and -`scipy.interpolate.make_interp_spline` — rather than trusting the wrapper as -its own reference. +The routine-coverage test compares the reviewed inventory with the generated +exports and requires one named numerical test for every procedural routine. ## Scope The upstream `bspline_defc_module` (least-squares fitting) and its -`bspline_blas_module` bridge are not part of this example; the interpolation -surface does not need them. `routine_inventory.py` records the reviewed -surface and this exclusion. +`bspline_blas_module` bridge are intentionally outside this interpolation +example. [`routine_inventory.py`](routine_inventory.py) records that boundary. ## Upstream BSPLINE-FORTRAN is by Jacob Williams and is distributed under a BSD-3-Clause -licence, included at `native/LICENSE`. The vendored sources are version 7.4.0 -(commit `047c7244`). +licence, included at [`native/LICENSE`](native/LICENSE). The vendored sources +are version 7.4.0 (commit `047c7244`). diff --git a/examples/bspline/routine_inventory.py b/examples/bspline/routine_inventory.py index 2d074bd90..7bbe972c8 100644 --- a/examples/bspline/routine_inventory.py +++ b/examples/bspline/routine_inventory.py @@ -23,14 +23,17 @@ #: Public procedural routines, by dimension. The module keeps its knot, #: interval, and band-solver helpers private, so they are not part of the #: wrapped surface. -SUB_ROUTINE_GROUPS: dict[str, tuple[str, ...]] = { +PROCEDURAL_ROUTINE_GROUPS: dict[str, tuple[str, ...]] = { "Interpolation setup": ("db1ink", "db2ink", "db3ink", "db4ink", "db5ink", "db6ink"), "Evaluation": ("db1val", "db2val", "db3val", "db4val", "db5val", "db6val"), "Definite integrals": ("db1sqad", "db1fqad"), "Status reporting": ("get_status_message",), } -ALL_SUB_ROUTINES = tuple(routine for group in SUB_ROUTINE_GROUPS.values() for routine in group) +ALL_PROCEDURAL_ROUTINES = tuple(routine for group in PROCEDURAL_ROUTINE_GROUPS.values() for routine in group) +PRIK_TESTED_PROCEDURAL_ROUTINES = frozenset(ALL_PROCEDURAL_ROUTINES) +UNSUPPORTED_PROCEDURAL_ROUTINES: dict[str, str] = {} +EXPLICIT_PROCEDURAL_TEST_NAMES = {routine: f"test_{routine}" for routine in ALL_PROCEDURAL_ROUTINES} #: Public spline-order constants copied into the module at import. ORDER_CONSTANTS: dict[str, int] = { @@ -44,6 +47,9 @@ "bspline_order_octic": 9, } +ALL_OBJECT_EXPORTS = (ABSTRACT_BASE, *CLASSES) +ALL_PROCEDURAL_EXPORTS = (*ALL_PROCEDURAL_ROUTINES, *ORDER_CONSTANTS) + #: Upstream modules this example deliberately leaves out. UNSUPPORTED: dict[str, str] = { "bspline_defc_module": "least-squares fitting; not required by the interpolation surface", diff --git a/examples/bspline/tests/test_object_oriented_api.py b/examples/bspline/tests/test_object_oriented_api.py index 3db26d45f..67dadb7f6 100644 --- a/examples/bspline/tests/test_object_oriented_api.py +++ b/examples/bspline/tests/test_object_oriented_api.py @@ -24,6 +24,17 @@ def _sine_spline(bspline_oo, points=25): return spline +def _affine_grid(dimension): + """Return Fortran-order samples of the affine function in ``dimension`` axes.""" + axes = [np.linspace(0.0, 1.0, 5) for _ in range(dimension)] + values = np.zeros((5,) * dimension) + for axis, points in enumerate(axes): + shape = [1] * dimension + shape[axis] = points.size + values += points.reshape(shape) + return axes, np.asfortranarray(values) + + def test_every_reviewed_class_is_exported(bspline_oo): for name in (ABSTRACT_BASE, *CLASSES): assert hasattr(bspline_oo, name), name @@ -41,6 +52,19 @@ def test_every_class_extends_the_abstract_base(bspline_oo): assert issubclass(getattr(bspline_oo, name), base), name +@pytest.mark.parametrize("dimension", range(1, 7)) +def test_every_concrete_class_interpolates_an_affine_grid(bspline_oo, dimension): + """Every dimension-specific constructor and evaluator works end to end.""" + axes, values = _affine_grid(dimension) + spline = getattr(bspline_oo, f"bspline_{dimension}d")(*axes, values, *(CUBIC,) * dimension) + + value, iflag = spline.evaluate(*(np.float64(0.3),) * dimension, *(np.int32(0),) * dimension) + + assert spline.status_ok() + assert iflag == np.int32(0) + assert value == pytest.approx(0.3 * dimension, abs=1.0e-12) + + def test_every_class_answers_the_deferred_and_inherited_bindings(bspline_oo): for name in CLASSES: members = dir(getattr(bspline_oo, name)) diff --git a/examples/bspline/tests/test_procedural_api.py b/examples/bspline/tests/test_procedural_api.py index 2e1eed2a5..9ab4f8e01 100644 --- a/examples/bspline/tests/test_procedural_api.py +++ b/examples/bspline/tests/test_procedural_api.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from examples.bspline.routine_inventory import ALL_SUB_ROUTINES, ORDER_CONSTANTS +from examples.bspline.routine_inventory import ORDER_CONSTANTS pytestmark = [pytest.mark.fortran_end_to_end, pytest.mark.real_library] @@ -40,9 +40,39 @@ def _evaluate(bspline_sub, knots, bcoef, nx, point, derivative=0): return value -def test_every_reviewed_procedure_is_exported(bspline_sub): - missing = [name for name in ALL_SUB_ROUTINES if not hasattr(bspline_sub, name)] - assert not missing, f"missing procedures: {missing}" +def _multidimensional_inputs(dimension): + """Return a cubic affine interpolant's setup and evaluation arguments.""" + axes = [np.linspace(0.0, 1.0, 5) for _ in range(dimension)] + sizes = [np.int32(axis.size) for axis in axes] + values = np.zeros((5,) * dimension) + for axis, points in enumerate(axes): + shape = [1] * dimension + shape[axis] = points.size + values += points.reshape(shape) + values = np.asfortranarray(values) + knots = [np.zeros(axis.size + int(CUBIC), dtype=np.float64) for axis in axes] + coefficients = np.zeros(values.shape, dtype=np.float64, order="F") + setup_arguments = [] + for axis, size in zip(axes, sizes, strict=True): + setup_arguments.extend((axis, size)) + setup_arguments.extend((values, *(CUBIC,) * dimension, NOT_A_KNOT, *knots, coefficients)) + work_arrays = [ + np.zeros(tuple(int(CUBIC) for _ in range(dimension - index)), dtype=np.float64, order="F") + for index in range(1, dimension) + ] + evaluation_arguments = ( + *(np.float64(0.3),) * dimension, + *(np.int32(0),) * dimension, + *knots, + *sizes, + *(CUBIC,) * dimension, + coefficients, + *(np.int32(1),) * dimension, + *(np.int32(1),) * (dimension - 1), + *work_arrays, + np.zeros(3 * int(CUBIC), dtype=np.float64), + ) + return tuple(setup_arguments), evaluation_arguments def test_spline_order_constants_reach_python(bspline_sub): @@ -56,6 +86,36 @@ def test_generic_interfaces_publish_every_specific_signature(bspline_sub): assert bspline_sub.db1val.__doc__.count("db1val(xval:") == 2 +def test_db1ink(bspline_sub): + x = np.linspace(0.0, 2.0 * np.pi, 30) + knots = np.zeros(x.size + int(CUBIC), dtype=np.float64) + coefficients = np.zeros(x.size, dtype=np.float64) + + iflag = bspline_sub.db1ink(x, np.int32(x.size), np.sin(x), CUBIC, NOT_A_KNOT, knots, coefficients) + + assert iflag == np.int32(0) + + +def test_db1val(bspline_sub): + x = np.linspace(0.0, 2.0 * np.pi, 30) + knots, coefficients, nx = _interpolant(bspline_sub, x, np.sin(x)) + work = np.zeros(3 * int(CUBIC), dtype=np.float64) + + value, iflag, _inbvx = bspline_sub.db1val( + np.float64(1.2), + np.int32(0), + knots, + nx, + CUBIC, + coefficients, + np.int32(1), + work, + ) + + assert iflag == np.int32(0) + assert value == pytest.approx(np.sin(1.2), abs=1.0e-5) + + def test_interpolant_reproduces_the_sampled_function(bspline_sub): x = np.linspace(0.0, 2.0 * np.pi, 30) knots, bcoef, nx = _interpolant(bspline_sub, x, np.sin(x)) @@ -82,7 +142,7 @@ def test_first_derivative_matches_the_analytic_derivative(bspline_sub): assert value == pytest.approx(np.cos(point), abs=1.0e-4) -def test_definite_integral_matches_the_analytic_integral(bspline_sub): +def test_db1sqad(bspline_sub): x = np.linspace(0.0, np.pi, 60) knots, bcoef, nx = _interpolant(bspline_sub, x, np.sin(x)) work = np.zeros(3 * int(CUBIC), dtype=np.float64) @@ -92,6 +152,115 @@ def test_definite_integral_matches_the_analytic_integral(bspline_sub): assert value == pytest.approx(2.0, abs=1.0e-6) +def test_db1fqad(bspline_sub): + x = np.linspace(0.0, np.pi, 60) + knots, coefficients, nx = _interpolant(bspline_sub, x, np.sin(x)) + work = np.zeros(3 * int(CUBIC), dtype=np.float64) + + value, iflag = bspline_sub.db1fqad( + lambda _point: np.float64(1.0), + knots, + coefficients, + nx, + CUBIC, + np.int32(0), + np.float64(0.0), + np.float64(np.pi), + np.float64(1.0e-10), + work, + ) + + assert iflag == np.int32(0) + assert value == pytest.approx(2.0, abs=3.0e-8) + + +def test_db2ink(bspline_sub): + setup_arguments, _evaluation_arguments = _multidimensional_inputs(2) + + assert bspline_sub.db2ink(*setup_arguments) == np.int32(0) + + +def test_db2val(bspline_sub): + setup_arguments, evaluation_arguments = _multidimensional_inputs(2) + assert bspline_sub.db2ink(*setup_arguments) == np.int32(0) + + value, iflag, *_state = bspline_sub.db2val(*evaluation_arguments) + + assert iflag == np.int32(0) + assert value == pytest.approx(0.6, abs=1.0e-12) + + +def test_db3ink(bspline_sub): + setup_arguments, _evaluation_arguments = _multidimensional_inputs(3) + + assert bspline_sub.db3ink(*setup_arguments) == np.int32(0) + + +def test_db3val(bspline_sub): + setup_arguments, evaluation_arguments = _multidimensional_inputs(3) + assert bspline_sub.db3ink(*setup_arguments) == np.int32(0) + + value, iflag, *_state = bspline_sub.db3val(*evaluation_arguments) + + assert iflag == np.int32(0) + assert value == pytest.approx(0.9, abs=1.0e-12) + + +def test_db4ink(bspline_sub): + setup_arguments, _evaluation_arguments = _multidimensional_inputs(4) + + assert bspline_sub.db4ink(*setup_arguments) == np.int32(0) + + +def test_db4val(bspline_sub): + setup_arguments, evaluation_arguments = _multidimensional_inputs(4) + assert bspline_sub.db4ink(*setup_arguments) == np.int32(0) + + value, iflag, *_state = bspline_sub.db4val(*evaluation_arguments) + + assert iflag == np.int32(0) + assert value == pytest.approx(1.2, abs=1.0e-12) + + +def test_db5ink(bspline_sub): + setup_arguments, _evaluation_arguments = _multidimensional_inputs(5) + + assert bspline_sub.db5ink(*setup_arguments) == np.int32(0) + + +def test_db5val(bspline_sub): + setup_arguments, evaluation_arguments = _multidimensional_inputs(5) + assert bspline_sub.db5ink(*setup_arguments) == np.int32(0) + + value, iflag, *_state = bspline_sub.db5val(*evaluation_arguments) + + assert iflag == np.int32(0) + assert value == pytest.approx(1.5, abs=1.0e-12) + + +def test_db6ink(bspline_sub): + setup_arguments, _evaluation_arguments = _multidimensional_inputs(6) + + assert bspline_sub.db6ink(*setup_arguments) == np.int32(0) + + +def test_db6val(bspline_sub): + setup_arguments, evaluation_arguments = _multidimensional_inputs(6) + assert bspline_sub.db6ink(*setup_arguments) == np.int32(0) + + value, iflag, *_state = bspline_sub.db6val(*evaluation_arguments) + + assert iflag == np.int32(0) + assert value == pytest.approx(1.8, abs=1.0e-12) + + +def test_get_status_message(bspline_sub): + message = bspline_sub.get_status_message(np.int32(0)) + + assert isinstance(message, str) + assert message + + def test_scipy_agrees_with_the_wrapped_interpolant(bspline_sub): """An independent oracle checks the wrapper rather than the wrapper alone.""" scipy_interpolate = pytest.importorskip("scipy.interpolate") diff --git a/examples/bspline/tests/test_routine_coverage.py b/examples/bspline/tests/test_routine_coverage.py new file mode 100644 index 000000000..a21a710e9 --- /dev/null +++ b/examples/bspline/tests/test_routine_coverage.py @@ -0,0 +1,55 @@ +"""Fail closed when the reviewed BSPLINE-FORTRAN surface or tests drift.""" + +from __future__ import annotations + +import ast +from pathlib import Path + +import pytest + +from ..routine_inventory import ( + ALL_OBJECT_EXPORTS, + ALL_PROCEDURAL_EXPORTS, + ALL_PROCEDURAL_ROUTINES, + EXPLICIT_PROCEDURAL_TEST_NAMES, + PRIK_TESTED_PROCEDURAL_ROUTINES, + PROCEDURAL_ROUTINE_GROUPS, + UNSUPPORTED_PROCEDURAL_ROUTINES, +) + + +pytestmark = [pytest.mark.fortran_end_to_end, pytest.mark.real_library] +TEST_FILE = Path(__file__).with_name("test_procedural_api.py") + + +def _test_functions() -> dict[str, ast.FunctionDef]: + """Return the explicitly named public-routine tests in this suite.""" + tree = ast.parse(TEST_FILE.read_text(encoding="utf-8"), filename=str(TEST_FILE)) + return { + node.name: node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name.startswith("test_") + } + + +def test_every_public_procedural_routine_has_one_visible_numerical_test(): + functions = _test_functions() + source_text = TEST_FILE.read_text(encoding="utf-8") + + assert len(ALL_PROCEDURAL_ROUTINES) == len(set(ALL_PROCEDURAL_ROUTINES)) + assert set(ALL_PROCEDURAL_ROUTINES) == PRIK_TESTED_PROCEDURAL_ROUTINES + assert UNSUPPORTED_PROCEDURAL_ROUTINES == {} + + for routine, test_name in EXPLICIT_PROCEDURAL_TEST_NAMES.items(): + source = ast.get_source_segment(source_text, functions[test_name]) + assert source is not None + assert f"bspline_sub.{routine}" in source, f"{test_name} does not visibly invoke {routine}" + + +def test_inventory_groups_cover_each_generated_public_export_once(bspline_oo, bspline_sub): + grouped = tuple(routine for group in PROCEDURAL_ROUTINE_GROUPS.values() for routine in group) + object_exports = {name for name in dir(bspline_oo) if not name.startswith("_")} + procedural_exports = {name for name in dir(bspline_sub) if not name.startswith("_")} + + assert grouped == ALL_PROCEDURAL_ROUTINES + assert len(grouped) == len(set(grouped)) + assert object_exports == set(ALL_OBJECT_EXPORTS) + assert procedural_exports == set(ALL_PROCEDURAL_EXPORTS) diff --git a/tests/c/semantics/conversion/_support.py b/tests/c/_support/semantic_conversion.py similarity index 100% rename from tests/c/semantics/conversion/_support.py rename to tests/c/_support/semantic_conversion.py diff --git a/tests/c/cli/test_c_cli_argument_contract.py b/tests/c/command_line_interface/pipeline/test_c_cli_argument_contract.py similarity index 100% rename from tests/c/cli/test_c_cli_argument_contract.py rename to tests/c/command_line_interface/pipeline/test_c_cli_argument_contract.py diff --git a/tests/c/cli/test_c_cli_output_contract.py b/tests/c/command_line_interface/pipeline/test_c_cli_output_contract.py similarity index 100% rename from tests/c/cli/test_c_cli_output_contract.py rename to tests/c/command_line_interface/pipeline/test_c_cli_output_contract.py diff --git a/tests/c/parsing/test_c_cli_skeleton.py b/tests/c/command_line_interface/pipeline/test_c_cli_skeleton.py similarity index 100% rename from tests/c/parsing/test_c_cli_skeleton.py rename to tests/c/command_line_interface/pipeline/test_c_cli_skeleton.py diff --git a/tests/c/cli/test_c_cli_stage_dispatch.py b/tests/c/command_line_interface/pipeline/test_c_cli_stage_dispatch.py similarity index 100% rename from tests/c/cli/test_c_cli_stage_dispatch.py rename to tests/c/command_line_interface/pipeline/test_c_cli_stage_dispatch.py diff --git a/tests/c/probes/test_c_types.py b/tests/c/data_types/probes/test_c_types.py similarity index 100% rename from tests/c/probes/test_c_types.py rename to tests/c/data_types/probes/test_c_types.py diff --git a/tests/c/semantics/conversion/test_types_and_constants.py b/tests/c/data_types/semantics/test_types_and_constants.py similarity index 99% rename from tests/c/semantics/conversion/test_types_and_constants.py rename to tests/c/data_types/semantics/test_types_and_constants.py index bc13ed17f..3103ad132 100644 --- a/tests/c/semantics/conversion/test_types_and_constants.py +++ b/tests/c/data_types/semantics/test_types_and_constants.py @@ -51,7 +51,7 @@ c_struct_to_semantic_class, c_type_to_semantic_type, ) -from tests.c.semantics.conversion._support import ( +from tests.c._support.semantic_conversion import ( _assert_c_origin, _assert_unsupported_type, _function, diff --git a/tests/c/parsing/test_c_functions.py b/tests/c/functions/parsing/test_c_functions.py similarity index 100% rename from tests/c/parsing/test_c_functions.py rename to tests/c/functions/parsing/test_c_functions.py diff --git a/tests/c/semantics/conversion/test_functions_and_callbacks.py b/tests/c/functions/semantics/test_functions_and_callbacks.py similarity index 99% rename from tests/c/semantics/conversion/test_functions_and_callbacks.py rename to tests/c/functions/semantics/test_functions_and_callbacks.py index 5aa978931..45fb683b5 100644 --- a/tests/c/semantics/conversion/test_functions_and_callbacks.py +++ b/tests/c/functions/semantics/test_functions_and_callbacks.py @@ -22,7 +22,7 @@ CVoid, ) from prik.semantics.c2ir import CToIRConverter, c_file_to_semantic_modules, c_function_to_semantic_function -from tests.c.semantics.conversion._support import ( +from tests.c._support.semantic_conversion import ( _assert_c_origin, _function, ) diff --git a/tests/c/parsing/test_c_parser_developer_tutorial.py b/tests/c/infrastructure/execution_examples/test_c_parser_developer_tutorial.py similarity index 100% rename from tests/c/parsing/test_c_parser_developer_tutorial.py rename to tests/c/infrastructure/execution_examples/test_c_parser_developer_tutorial.py diff --git a/tests/c/parsing/test_c_json_sanity.py b/tests/c/infrastructure/parsers/test_c_json_sanity.py similarity index 98% rename from tests/c/parsing/test_c_json_sanity.py rename to tests/c/infrastructure/parsers/test_c_json_sanity.py index ef5a3b85b..2f28dd0a4 100644 --- a/tests/c/parsing/test_c_json_sanity.py +++ b/tests/c/infrastructure/parsers/test_c_json_sanity.py @@ -3,7 +3,7 @@ import json from pathlib import Path -_FIXTURES_DIR = Path(__file__).resolve().parents[1] / "fixtures" / "parser" / "fixtures" +_FIXTURES_DIR = Path(__file__).resolve().parents[2] / "fixtures" / "parser" / "fixtures" _PARSER_FIXTURE_GROUPS = ("general", "json", "tinyexpr", "linmath", "nanosvg", "stb") diff --git a/tests/c/parsing/test_c_lexer_preprocessor.py b/tests/c/infrastructure/parsers/test_c_lexer_preprocessor.py similarity index 100% rename from tests/c/parsing/test_c_lexer_preprocessor.py rename to tests/c/infrastructure/parsers/test_c_lexer_preprocessor.py diff --git a/tests/c/parsing/test_c_model_serialization.py b/tests/c/infrastructure/parsers/test_c_model_serialization.py similarity index 100% rename from tests/c/parsing/test_c_model_serialization.py rename to tests/c/infrastructure/parsers/test_c_model_serialization.py diff --git a/tests/c/parsing/test_c_public_api_skeleton.py b/tests/c/infrastructure/parsers/test_c_public_api_skeleton.py similarity index 100% rename from tests/c/parsing/test_c_public_api_skeleton.py rename to tests/c/infrastructure/parsers/test_c_public_api_skeleton.py diff --git a/tests/c/parsing/test_c_structs_unions_enums_typedefs.py b/tests/c/records/parsing/test_c_structs_unions_enums_typedefs.py similarity index 100% rename from tests/c/parsing/test_c_structs_unions_enums_typedefs.py rename to tests/c/records/parsing/test_c_structs_unions_enums_typedefs.py diff --git a/tests/c/semantics/conversion/test_records_and_enums.py b/tests/c/records/semantics/test_records_and_enums.py similarity index 99% rename from tests/c/semantics/conversion/test_records_and_enums.py rename to tests/c/records/semantics/test_records_and_enums.py index 1b514d924..b18816ca6 100644 --- a/tests/c/semantics/conversion/test_records_and_enums.py +++ b/tests/c/records/semantics/test_records_and_enums.py @@ -40,7 +40,7 @@ SemanticType, SemanticVariable, ) -from tests.c.semantics.conversion._support import ( +from tests.c._support.semantic_conversion import ( _assert_c_origin, _function, ) diff --git a/tests/c/semantics/conversion/test_c_conversion_properties.py b/tests/c/semantic_ir/semantics/test_c_conversion_properties.py similarity index 100% rename from tests/c/semantics/conversion/test_c_conversion_properties.py rename to tests/c/semantic_ir/semantics/test_c_conversion_properties.py diff --git a/tests/c/semantics/conversion/test_projects_and_diagnostics.py b/tests/c/semantic_ir/semantics/test_projects_and_diagnostics.py similarity index 99% rename from tests/c/semantics/conversion/test_projects_and_diagnostics.py rename to tests/c/semantic_ir/semantics/test_projects_and_diagnostics.py index cbfeb52bf..6c95afa23 100644 --- a/tests/c/semantics/conversion/test_projects_and_diagnostics.py +++ b/tests/c/semantic_ir/semantics/test_projects_and_diagnostics.py @@ -27,7 +27,7 @@ c_type_to_semantic_type, ) from prik.semantics.models import SemanticArgument, SemanticModule, SemanticOrigin, SemanticType -from tests.c.semantics.conversion._support import ( +from tests.c._support.semantic_conversion import ( _assert_c_origin, _function, ) diff --git a/tests/c/pipeline/test_c_pyi_contract_fixtures.py b/tests/c/semantic_pyi_format/pipeline/test_c_pyi_contract_fixtures.py similarity index 100% rename from tests/c/pipeline/test_c_pyi_contract_fixtures.py rename to tests/c/semantic_pyi_format/pipeline/test_c_pyi_contract_fixtures.py diff --git a/tests/c/semantics/conversion/test_c_pyi_conversion.py b/tests/c/semantic_pyi_format/semantics/test_c_pyi_conversion.py similarity index 100% rename from tests/c/semantics/conversion/test_c_pyi_conversion.py rename to tests/c/semantic_pyi_format/semantics/test_c_pyi_conversion.py diff --git a/tests/c/parsing/test_c_compiler_extensions.py b/tests/c/source_parsing/parsing/test_c_compiler_extensions.py similarity index 100% rename from tests/c/parsing/test_c_compiler_extensions.py rename to tests/c/source_parsing/parsing/test_c_compiler_extensions.py diff --git a/tests/c/parsing/test_c_corpus.py b/tests/c/source_parsing/parsing/test_c_corpus.py similarity index 97% rename from tests/c/parsing/test_c_corpus.py rename to tests/c/source_parsing/parsing/test_c_corpus.py index f6e77edc8..d8b0d5126 100644 --- a/tests/c/parsing/test_c_corpus.py +++ b/tests/c/source_parsing/parsing/test_c_corpus.py @@ -10,7 +10,7 @@ import pytest -_CJSON_DIR = Path(__file__).resolve().parents[1] / "fixtures" / "native" / "json" +_CJSON_DIR = Path(__file__).resolve().parents[2] / "fixtures" / "native" / "json" def _preprocessed_cjson_source(filename: str) -> str: diff --git a/tests/c/parsing/test_c_declarations_and_declarators.py b/tests/c/source_parsing/parsing/test_c_declarations_and_declarators.py similarity index 100% rename from tests/c/parsing/test_c_declarations_and_declarators.py rename to tests/c/source_parsing/parsing/test_c_declarations_and_declarators.py diff --git a/tests/c/parsing/test_c_error_fixture_suite.py b/tests/c/source_parsing/parsing/test_c_error_fixture_suite.py similarity index 98% rename from tests/c/parsing/test_c_error_fixture_suite.py rename to tests/c/source_parsing/parsing/test_c_error_fixture_suite.py index 7059fe6b2..555f4d6d0 100644 --- a/tests/c/parsing/test_c_error_fixture_suite.py +++ b/tests/c/source_parsing/parsing/test_c_error_fixture_suite.py @@ -7,7 +7,7 @@ import pytest -_C_ROOT = Path(__file__).resolve().parents[1] +_C_ROOT = Path(__file__).resolve().parents[2] _ERRORS_DIR = _C_ROOT / "fixtures" / "native" / "errors" / "parser" _EXPECTED_ERRORS_DIR = _C_ROOT / "fixtures" / "parser" / "fixtures" / "errors" _SOURCE_SUFFIXES = {".c", ".h", ".i"} diff --git a/tests/c/parsing/test_c_fixture_suite.py b/tests/c/source_parsing/parsing/test_c_fixture_suite.py similarity index 99% rename from tests/c/parsing/test_c_fixture_suite.py rename to tests/c/source_parsing/parsing/test_c_fixture_suite.py index 0268d7da1..0f0932475 100644 --- a/tests/c/parsing/test_c_fixture_suite.py +++ b/tests/c/source_parsing/parsing/test_c_fixture_suite.py @@ -8,7 +8,7 @@ import pytest -_C_ROOT = Path(__file__).resolve().parents[1] +_C_ROOT = Path(__file__).resolve().parents[2] _DATA_DIR = _C_ROOT / "fixtures" / "native" _SOURCE_SUFFIXES = {".c", ".h", ".i"} _SOURCE_ORDER = {".c": 0, ".h": 1, ".i": 2} diff --git a/tests/c/parsing/test_c_parser_benchmark.py b/tests/c/source_parsing/parsing/test_c_parser_benchmark.py similarity index 100% rename from tests/c/parsing/test_c_parser_benchmark.py rename to tests/c/source_parsing/parsing/test_c_parser_benchmark.py diff --git a/tests/c/parsing/test_c_parser_properties.py b/tests/c/source_parsing/parsing/test_c_parser_properties.py similarity index 100% rename from tests/c/parsing/test_c_parser_properties.py rename to tests/c/source_parsing/parsing/test_c_parser_properties.py diff --git a/tests/c/parsing/test_c_project_resolution.py b/tests/c/source_parsing/parsing/test_c_project_resolution.py similarity index 100% rename from tests/c/parsing/test_c_project_resolution.py rename to tests/c/source_parsing/parsing/test_c_project_resolution.py diff --git a/tests/c/preprocessing/test_c_preprocessing_cli.py b/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_cli.py similarity index 100% rename from tests/c/preprocessing/test_c_preprocessing_cli.py rename to tests/c/source_preprocessing/preprocessing/test_c_preprocessing_cli.py diff --git a/tests/c/preprocessing/test_c_preprocessing_configuration.py b/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_configuration.py similarity index 100% rename from tests/c/preprocessing/test_c_preprocessing_configuration.py rename to tests/c/source_preprocessing/preprocessing/test_c_preprocessing_configuration.py diff --git a/tests/c/preprocessing/test_c_preprocessing_dependencies.py b/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_dependencies.py similarity index 100% rename from tests/c/preprocessing/test_c_preprocessing_dependencies.py rename to tests/c/source_preprocessing/preprocessing/test_c_preprocessing_dependencies.py diff --git a/tests/c/preprocessing/test_c_preprocessing_execution.py b/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_execution.py similarity index 100% rename from tests/c/preprocessing/test_c_preprocessing_execution.py rename to tests/c/source_preprocessing/preprocessing/test_c_preprocessing_execution.py diff --git a/tests/c/preprocessing/test_c_preprocessing_properties.py b/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_properties.py similarity index 100% rename from tests/c/preprocessing/test_c_preprocessing_properties.py rename to tests/c/source_preprocessing/preprocessing/test_c_preprocessing_properties.py diff --git a/tests/c/preprocessing/test_error_paths.py b/tests/c/source_preprocessing/preprocessing/test_error_paths.py similarity index 100% rename from tests/c/preprocessing/test_error_paths.py rename to tests/c/source_preprocessing/preprocessing/test_error_paths.py diff --git a/tests/c/preprocessing/test_source_mappings.py b/tests/c/source_preprocessing/preprocessing/test_source_mappings.py similarity index 100% rename from tests/c/preprocessing/test_source_mappings.py rename to tests/c/source_preprocessing/preprocessing/test_source_mappings.py diff --git a/tests/docs/test_examples.py b/tests/docs/test_examples.py index c4a4001d7..2563260f5 100644 --- a/tests/docs/test_examples.py +++ b/tests/docs/test_examples.py @@ -22,6 +22,7 @@ DOC_PATHS = [ ROOT / "README.md", ROOT / "examples/blas/README.md", + ROOT / "examples/bspline/README.md", ROOT / "examples/fftpack/README.md", ROOT / "examples/lapack/README.md", ROOT / "examples/minpack/README.md", From 0c36d23d06be19ae625c5361b8df9d564391a40c Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 14:31:46 +0100 Subject: [PATCH 16/26] codex: Split the C enum tests into their own feature owner The reorganised C tree kept enums inside `records/`, so the directory vocabulary claimed structs and unions but silently held enum coverage too. Move the enum-owned tests to `enumerations/parsing/` and `enumerations/semantics/`, matching the Fortran tree's feature names. Tests that assert on records *and* enums in one parse (duplicate tag diagnostics) stay in `records/`, since their invariant spans both. Co-Authored-By: Claude Opus 5 --- .../parsing/test_c_enum_syntax.py | 40 +++++ .../semantics/test_c_enum_semantics.py | 170 ++++++++++++++++++ ...s.py => test_c_structs_unions_typedefs.py} | 41 +---- ...nd_enums.py => test_c_record_semantics.py} | 156 +--------------- 4 files changed, 213 insertions(+), 194 deletions(-) create mode 100644 tests/c/enumerations/parsing/test_c_enum_syntax.py create mode 100644 tests/c/enumerations/semantics/test_c_enum_semantics.py rename tests/c/records/parsing/{test_c_structs_unions_enums_typedefs.py => test_c_structs_unions_typedefs.py} (92%) rename tests/c/records/semantics/{test_records_and_enums.py => test_c_record_semantics.py} (73%) diff --git a/tests/c/enumerations/parsing/test_c_enum_syntax.py b/tests/c/enumerations/parsing/test_c_enum_syntax.py new file mode 100644 index 000000000..307154d8b --- /dev/null +++ b/tests/c/enumerations/parsing/test_c_enum_syntax.py @@ -0,0 +1,40 @@ +"""C enum declaration parser tests.""" + + +def test_enum_constants_preserve_explicit_implicit_and_symbolic_values(): + from prik.parsers.c import parse_c_file + + parsed = parse_c_file( + """ +enum status { + STATUS_OK = 0, + STATUS_WARN, + STATUS_ERROR = 10, + STATUS_NEXT = STATUS_ERROR + 1 +}; +""", + filename="enum.h", + ) + + assert [(item.name, item.value) for item in parsed.enums[0].constants] == [ + ("STATUS_OK", "0"), + ("STATUS_WARN", None), + ("STATUS_ERROR", "10"), + ("STATUS_NEXT", "STATUS_ERROR + 1"), + ] + + +def test_typedef_enum_and_trailing_tag_variable_are_separate_objects(): + from prik.parsers.c import CEnum, CStruct, parse_c_file + + parsed = parse_c_file( + "typedef enum { FLAG_NONE = 0, FLAG_READ = 1 } flag_t;\nstruct point { int x; } origin;\n", + filename="tag_declarators.h", + ) + + assert parsed.enums[0].anonymous_id + assert isinstance(parsed.typedefs[0].type, CEnum) + assert parsed.typedefs[0].type is parsed.enums[0] + assert parsed.variables[0].name == "origin" + assert isinstance(parsed.variables[0].type, CStruct) + assert parsed.variables[0].type is parsed.structs[0] diff --git a/tests/c/enumerations/semantics/test_c_enum_semantics.py b/tests/c/enumerations/semantics/test_c_enum_semantics.py new file mode 100644 index 000000000..bc32d4778 --- /dev/null +++ b/tests/c/enumerations/semantics/test_c_enum_semantics.py @@ -0,0 +1,170 @@ +"""C enum conversion into the semantic IR.""" + +from dataclasses import asdict + +from prik.printers import emit_module +from prik.parsers.c import parse_c_file, parse_c_project +from prik.parsers.c.models import ( + CMacro, +) +from prik.pipeline.pyi import pyi_text_to_semantic_module as parse_pyi_text +from prik.semantics.c2ir import ( + CToIRConverter, + c_file_to_semantic_module, + c_file_to_semantic_modules, + c_project_to_semantic_module, + c_project_to_semantic_modules, +) +from prik.semantics.models import ( + SemanticVariable, +) +from tests.c._support.semantic_conversion import ( + _assert_c_origin, + _function, +) + + +def test_c2ir_converts_enum_constants_and_simple_macro_constants(): + parsed = parse_c_file( + """ +enum status { STATUS_OK = 0, STATUS_WARN, STATUS_ERROR = 10 }; +""", + filename="constants.h", + ) + parsed.macros = [CMacro(name="API_VERSION", value="3")] + module = c_file_to_semantic_modules(parsed)[0] + + constants = {var.name: var for var in module.variables} + assert constants["API_VERSION"].default_value == "3" + assert constants["API_VERSION"].semantic_type.constraints[0].name == "Constant" + assert constants["STATUS_WARN"].default_value == "1" + assert constants["STATUS_ERROR"].default_value == "10" + api_version = constants["API_VERSION"] + assert isinstance(api_version, SemanticVariable) + assert api_version.semantic_type.name == "Int32" + assert api_version.semantic_type.dtype == "Int32" + assert [asdict(constraint) for constraint in api_version.semantic_type.constraints] == [ + {"name": "Constant", "arguments": []} + ] + _assert_c_origin( + api_version.origin, + native_name="API_VERSION", + source_kind="macro", + ) + status_ok = constants["STATUS_OK"] + assert module.classes == [] + assert status_ok.semantic_type.name == "Int" + assert status_ok.semantic_type.dtype == "Int32" + assert status_ok.semantic_type.metadata["enum_name"] == "status" + assert status_ok.semantic_type.metadata["c_kind"] == "enum" + assert status_ok.semantic_type.metadata["c_enum"] == "enum status" + assert status_ok.semantic_type.metadata["c_underlying_type"] == "Int" + assert status_ok.semantic_type.coercions == [] + _assert_c_origin( + status_ok.origin, + native_name="STATUS_OK", + native_scope="enum status", + source_kind="enum_constant", + source_location={ + "filename": "constants.h", + "line": 2, + "column": 1, + "source_line": "enum status { STATUS_OK = 0, STATUS_WARN, STATUS_ERROR = 10 };", + }, + ) + + +def test_c2ir_names_anonymous_typedef_enums_and_keeps_enumerators_unscoped(): + source = "typedef enum { FLAG_NONE = 0, FLAG_READ = 1 } flag_t; flag_t get_flags(void);" + parsed = parse_c_file(source, filename="flags.h") + + module = c_file_to_semantic_module(parsed) + project_module = c_project_to_semantic_module(parse_c_project({"flags.h": source}), name="flags") + + assert module.classes == [] + assert project_module.classes == [] + assert [variable.name for variable in module.variables] == ["FLAG_NONE", "FLAG_READ"] + assert [variable.name for variable in project_module.variables] == ["FLAG_NONE", "FLAG_READ"] + assert [variable.semantic_type.name for variable in module.variables] == ["Int", "Int"] + assert module.variables[0].semantic_type.metadata["enum_name"] == "flag_t" + assert _function(module, "get_flags").return_type.name == "Int" + assert _function(project_module, "get_flags").return_type.name == "Int" + + +def test_c2ir_enum_values_emit_only_python_compatible_expressions(): + parsed = parse_c_file( + "enum flags { FLAG_ONE = 1U, FLAG_OCTAL = 010, FLAG_SHIFT = FLAG_ONE << 1, FLAG_CHAR = 'A' };", + filename="flags.h", + ) + module = c_file_to_semantic_module(parsed) + + code = emit_module(module) + + assert "FLAG_ONE: Final[Int] = 1" in code + assert "FLAG_OCTAL: Final[Int] = 8" in code + assert "FLAG_SHIFT: Final[Int] = FLAG_ONE << 1" in code + assert "FLAG_CHAR: Final[Int]" in code + assert {variable.name: variable.default_value for variable in module.variables} == { + "FLAG_ONE": "1U", + "FLAG_OCTAL": "010", + "FLAG_SHIFT": "FLAG_ONE << 1", + "FLAG_CHAR": "'A'", + } + assert [variable.name for variable in parse_pyi_text(code, module_name="flags").variables] == [ + "FLAG_ONE", + "FLAG_OCTAL", + "FLAG_SHIFT", + "FLAG_CHAR", + ] + + +def test_c2ir_cross_header_enum_references_import_the_owner_enum(): + project = parse_c_project( + { + "types.h": "enum status { STATUS_OK = 0 };", + "api.h": "enum status get_status(void);", + } + ) + + modules = {module.name: module for module in c_project_to_semantic_modules(project)} + + assert modules["api"].classes == [] + assert modules["types"].classes == [] + assert _function(modules["api"], "get_status").return_type.name == "Int" + assert _function(modules["api"], "get_status").return_type.metadata["c_enum"] == "enum status" + + anonymous_project = parse_c_project( + { + "types.h": "typedef enum { FLAG_NONE = 0 } flag_t;", + "api.h": "flag_t get_flags(void);", + } + ) + anonymous_modules = {module.name: module for module in c_project_to_semantic_modules(anonymous_project)} + assert _function(anonymous_modules["api"], "get_flags").return_type.name == "Int" + + +def test_c2ir_uses_enum_specific_underlying_type_facts_when_supplied(): + parsed = parse_c_file( + "enum status { STATUS_OK = 0, STATUS_ERROR = 255 }; enum status get_status(void);", + filename="status.h", + ) + module = CToIRConverter( + standard_type_report={ + "types": { + "enum status": { + "available": True, + "kind": "integer", + "signed": False, + "bits": 8, + "underlying_c_type": "unsigned char", + } + } + } + ).visit(parsed) + + return_type = _function(module, "get_status").return_type + assert module.classes == [] + assert return_type.name == "UInt8" + assert return_type.dtype == "UInt8" + assert return_type.metadata["c_kind"] == "enum" + assert return_type.metadata["c_enum_type_fact_source"] == "compiler_probe" diff --git a/tests/c/records/parsing/test_c_structs_unions_enums_typedefs.py b/tests/c/records/parsing/test_c_structs_unions_typedefs.py similarity index 92% rename from tests/c/records/parsing/test_c_structs_unions_enums_typedefs.py rename to tests/c/records/parsing/test_c_structs_unions_typedefs.py index b7d1eb17d..b508b84ca 100644 --- a/tests/c/records/parsing/test_c_structs_unions_enums_typedefs.py +++ b/tests/c/records/parsing/test_c_structs_unions_typedefs.py @@ -1,4 +1,4 @@ -"""C aggregate type, enum, and typedef parser tests.""" +"""C aggregate type and typedef parser tests.""" import pytest @@ -173,45 +173,6 @@ def test_repeated_union_and_enum_tags_normalize_with_duplicate_diagnostics(): ] -def test_enum_constants_preserve_explicit_implicit_and_symbolic_values(): - from prik.parsers.c import parse_c_file - - parsed = parse_c_file( - """ -enum status { - STATUS_OK = 0, - STATUS_WARN, - STATUS_ERROR = 10, - STATUS_NEXT = STATUS_ERROR + 1 -}; -""", - filename="enum.h", - ) - - assert [(item.name, item.value) for item in parsed.enums[0].constants] == [ - ("STATUS_OK", "0"), - ("STATUS_WARN", None), - ("STATUS_ERROR", "10"), - ("STATUS_NEXT", "STATUS_ERROR + 1"), - ] - - -def test_typedef_enum_and_trailing_tag_variable_are_separate_objects(): - from prik.parsers.c import CEnum, CStruct, parse_c_file - - parsed = parse_c_file( - "typedef enum { FLAG_NONE = 0, FLAG_READ = 1 } flag_t;\nstruct point { int x; } origin;\n", - filename="tag_declarators.h", - ) - - assert parsed.enums[0].anonymous_id - assert isinstance(parsed.typedefs[0].type, CEnum) - assert parsed.typedefs[0].type is parsed.enums[0] - assert parsed.variables[0].name == "origin" - assert isinstance(parsed.variables[0].type, CStruct) - assert parsed.variables[0].type is parsed.structs[0] - - def test_recursive_struct_pointer_uses_an_incomplete_struct_component_without_cycles(): from prik.parsers.c import CComposedType, CPointer, CStruct, parse_c_file diff --git a/tests/c/records/semantics/test_records_and_enums.py b/tests/c/records/semantics/test_c_record_semantics.py similarity index 73% rename from tests/c/records/semantics/test_records_and_enums.py rename to tests/c/records/semantics/test_c_record_semantics.py index b18816ca6..c10e26a03 100644 --- a/tests/c/records/semantics/test_records_and_enums.py +++ b/tests/c/records/semantics/test_c_record_semantics.py @@ -1,10 +1,8 @@ -"""Tests split by stable ownership concept from `test_functions_and_callbacks.py`.""" - -from dataclasses import asdict +"""C struct, union, and opaque-handle conversion into the semantic IR.""" 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 import parse_c_file from prik.parsers.c.models import ( CArray, CComposedType, @@ -13,7 +11,6 @@ CFunction, CInitializer, CInt, - CMacro, CParameter, CPointer, CSourceLocation, @@ -28,8 +25,6 @@ CToIRConverter, c_file_to_semantic_module, c_file_to_semantic_modules, - c_project_to_semantic_module, - c_project_to_semantic_modules, ) from prik.semantics.models import ( SemanticArgument, @@ -38,7 +33,6 @@ SemanticModule, SemanticOrigin, SemanticType, - SemanticVariable, ) from tests.c._support.semantic_conversion import ( _assert_c_origin, @@ -227,152 +221,6 @@ def test_c2ir_externalizes_only_private_opaque_classes_with_external_origins(): } -def test_c2ir_converts_enum_constants_and_simple_macro_constants(): - parsed = parse_c_file( - """ -enum status { STATUS_OK = 0, STATUS_WARN, STATUS_ERROR = 10 }; -""", - filename="constants.h", - ) - parsed.macros = [CMacro(name="API_VERSION", value="3")] - module = c_file_to_semantic_modules(parsed)[0] - - constants = {var.name: var for var in module.variables} - assert constants["API_VERSION"].default_value == "3" - assert constants["API_VERSION"].semantic_type.constraints[0].name == "Constant" - assert constants["STATUS_WARN"].default_value == "1" - assert constants["STATUS_ERROR"].default_value == "10" - api_version = constants["API_VERSION"] - assert isinstance(api_version, SemanticVariable) - assert api_version.semantic_type.name == "Int32" - assert api_version.semantic_type.dtype == "Int32" - assert [asdict(constraint) for constraint in api_version.semantic_type.constraints] == [ - {"name": "Constant", "arguments": []} - ] - _assert_c_origin( - api_version.origin, - native_name="API_VERSION", - source_kind="macro", - ) - status_ok = constants["STATUS_OK"] - assert module.classes == [] - assert status_ok.semantic_type.name == "Int" - assert status_ok.semantic_type.dtype == "Int32" - assert status_ok.semantic_type.metadata["enum_name"] == "status" - assert status_ok.semantic_type.metadata["c_kind"] == "enum" - assert status_ok.semantic_type.metadata["c_enum"] == "enum status" - assert status_ok.semantic_type.metadata["c_underlying_type"] == "Int" - assert status_ok.semantic_type.coercions == [] - _assert_c_origin( - status_ok.origin, - native_name="STATUS_OK", - native_scope="enum status", - source_kind="enum_constant", - source_location={ - "filename": "constants.h", - "line": 2, - "column": 1, - "source_line": "enum status { STATUS_OK = 0, STATUS_WARN, STATUS_ERROR = 10 };", - }, - ) - - -def test_c2ir_names_anonymous_typedef_enums_and_keeps_enumerators_unscoped(): - source = "typedef enum { FLAG_NONE = 0, FLAG_READ = 1 } flag_t; flag_t get_flags(void);" - parsed = parse_c_file(source, filename="flags.h") - - module = c_file_to_semantic_module(parsed) - project_module = c_project_to_semantic_module(parse_c_project({"flags.h": source}), name="flags") - - assert module.classes == [] - assert project_module.classes == [] - assert [variable.name for variable in module.variables] == ["FLAG_NONE", "FLAG_READ"] - assert [variable.name for variable in project_module.variables] == ["FLAG_NONE", "FLAG_READ"] - assert [variable.semantic_type.name for variable in module.variables] == ["Int", "Int"] - assert module.variables[0].semantic_type.metadata["enum_name"] == "flag_t" - assert _function(module, "get_flags").return_type.name == "Int" - assert _function(project_module, "get_flags").return_type.name == "Int" - - -def test_c2ir_enum_values_emit_only_python_compatible_expressions(): - parsed = parse_c_file( - "enum flags { FLAG_ONE = 1U, FLAG_OCTAL = 010, FLAG_SHIFT = FLAG_ONE << 1, FLAG_CHAR = 'A' };", - filename="flags.h", - ) - module = c_file_to_semantic_module(parsed) - - code = emit_module(module) - - assert "FLAG_ONE: Final[Int] = 1" in code - assert "FLAG_OCTAL: Final[Int] = 8" in code - assert "FLAG_SHIFT: Final[Int] = FLAG_ONE << 1" in code - assert "FLAG_CHAR: Final[Int]" in code - assert {variable.name: variable.default_value for variable in module.variables} == { - "FLAG_ONE": "1U", - "FLAG_OCTAL": "010", - "FLAG_SHIFT": "FLAG_ONE << 1", - "FLAG_CHAR": "'A'", - } - assert [variable.name for variable in parse_pyi_text(code, module_name="flags").variables] == [ - "FLAG_ONE", - "FLAG_OCTAL", - "FLAG_SHIFT", - "FLAG_CHAR", - ] - - -def test_c2ir_cross_header_enum_references_import_the_owner_enum(): - project = parse_c_project( - { - "types.h": "enum status { STATUS_OK = 0 };", - "api.h": "enum status get_status(void);", - } - ) - - modules = {module.name: module for module in c_project_to_semantic_modules(project)} - - assert modules["api"].classes == [] - assert modules["types"].classes == [] - assert _function(modules["api"], "get_status").return_type.name == "Int" - assert _function(modules["api"], "get_status").return_type.metadata["c_enum"] == "enum status" - - anonymous_project = parse_c_project( - { - "types.h": "typedef enum { FLAG_NONE = 0 } flag_t;", - "api.h": "flag_t get_flags(void);", - } - ) - anonymous_modules = {module.name: module for module in c_project_to_semantic_modules(anonymous_project)} - assert _function(anonymous_modules["api"], "get_flags").return_type.name == "Int" - - -def test_c2ir_uses_enum_specific_underlying_type_facts_when_supplied(): - parsed = parse_c_file( - "enum status { STATUS_OK = 0, STATUS_ERROR = 255 }; enum status get_status(void);", - filename="status.h", - ) - module = CToIRConverter( - standard_type_report={ - "types": { - "enum status": { - "available": True, - "kind": "integer", - "signed": False, - "bits": 8, - "underlying_c_type": "unsigned char", - } - } - } - ).visit(parsed) - - return_type = _function(module, "get_status").return_type - assert module.classes == [] - assert return_type.name == "UInt8" - assert return_type.dtype == "UInt8" - assert return_type.metadata["c_kind"] == "enum" - assert return_type.metadata["c_enum_type_fact_source"] == "compiler_probe" - - def test_c2ir_uses_standard_type_probe_opaque_handle_facts(): parsed = parse_c_file("void close_file(FILE *stream);\n", filename="stdio_api.h") converter = CToIRConverter( From 7b5b05c52dafc735dc9d183763cb548ad059c0a2 Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 14:34:03 +0100 Subject: [PATCH 17/26] codex: Point the deferred C parser doc at the reorganised test paths The C test tree moved to `//`, leaving every test path in the deferred parser reference stale. Update them, and list the new enum parsing owner alongside the records one. Co-Authored-By: Claude Opus 5 --- docs/developer/deferred/c-parser.md | 44 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/developer/deferred/c-parser.md b/docs/developer/deferred/c-parser.md index 56a7b907d..21aafa7ba 100644 --- a/docs/developer/deferred/c-parser.md +++ b/docs/developer/deferred/c-parser.md @@ -850,8 +850,8 @@ Useful local checks for the parse-only frontend: ```bash python -m prik tests/c/fixtures/native/general/math_api.h --language c --parse --json python tests/c/fixtures/parser/generate_c_parser_goldens.py tests/c/fixtures/native/general/math_api.h -pytest -q tests/c/parsing/test_c_declarations_and_declarators.py -pytest -q tests/c/parsing/test_c_fixture_suite.py +pytest -q tests/c/source_parsing/parsing/test_c_declarations_and_declarators.py +pytest -q tests/c/source_parsing/parsing/test_c_fixture_suite.py pytest -q tests/c pytest -q ``` @@ -861,30 +861,32 @@ Focused test files by implementation area: ## CLI Workflow @@ -1116,10 +1118,10 @@ Executable references: - Shared CLI behavior: `tests/fortran/command_line_interface/pipeline/` Fixture layout should be separate from Fortran: @@ -1159,7 +1161,7 @@ that Linux reference environment. The fixture suite also checks same-stem grouping order and representative raw preprocessing failures. Fatal diagnostic goldens are regenerated with -`C_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/c/parsing/test_c_error_fixture_suite.py`. +`C_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/c/source_parsing/parsing/test_c_error_fixture_suite.py`. The standalone error generator remains available for targeted refreshes. By policy, a paired project records source-to-header include edges but parses each supplied `.c`, `.h`, or `.i` member separately; include traversal is not From 185617916fad62b7d731206412a1ad53939d008f Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 15:17:19 +0100 Subject: [PATCH 18/26] codex: Separate language features from test infrastructure --- AGENTS.md | 2 +- CHANGELOG.md | 8 ++ README.md | 2 +- docs/developer/deferred/c-parser.md | 30 ++--- docs/developer/feature-to-code-map.md | 18 +-- docs/developer/packages/compiler.md | 6 +- docs/developer/packages/contracts.md | 4 +- docs/developer/packages/parsers.md | 10 +- docs/developer/packages/pipeline.md | 8 +- docs/developer/packages/policy.md | 4 +- docs/developer/packages/preprocessing.md | 4 +- docs/developer/packages/printers.md | 4 +- docs/developer/packages/runtime.md | 2 +- docs/developer/packages/semantics.md | 8 +- .../fortran-test-suite-cleanup-checklist.md | 22 +-- .../native-entrypoint-adoption-checklist.md | 6 +- .../roadmap/semantic-pyi-wrapper-checklist.md | 34 ++--- docs/developer/testing-strategy.md | 15 ++- docs/index.md | 2 +- .../recipes/build-and-import-python-api.md | 2 +- .../examples/recipes/control-cli-output.md | 8 +- .../examples/recipes/inspect-fortran-api.md | 10 +- .../recipes/semantic-pyi-contracts.md | 2 +- docs/user/language-support/feature-matrix.md | 26 ++-- docs/user/reference/configuration-files.md | 4 +- docs/user/reference/fortran-wrapper.md | 4 +- docs/user/reference/generated-classes.md | 2 +- docs/user/reference/generated-functions.md | 2 +- docs/user/reference/generated-modules.md | 6 +- docs/user/reference/python-api.md | 2 +- prik/compiler/README.md | 4 +- prik/parsers/fortran/README.md | 6 +- prik/preprocessing/README.md | 2 +- prik/semantics/README.md | 4 +- pyproject.toml | 8 +- tests/README.md | 39 +++--- tests/c/README.md | 30 +++-- .../pipeline/test_c_cli_argument_contract.py | 0 .../pipeline/test_c_cli_output_contract.py | 0 .../cli}/pipeline/test_c_cli_skeleton.py | 0 .../pipeline/test_c_cli_stage_dispatch.py | 0 .../parsing/test_c_compiler_extensions.py | 0 .../parsing/test_c_corpus.py | 0 .../test_c_declarations_and_declarators.py | 0 .../parsing/test_c_error_fixture_suite.py | 0 .../parsing/test_c_fixture_suite.py | 0 .../test_c_json_sanity.py | 0 .../test_c_lexer_preprocessor.py | 0 .../test_c_model_serialization.py | 0 .../parsing/test_c_parser_benchmark.py | 0 .../parsing/test_c_parser_properties.py | 0 .../parsing/test_c_project_resolution.py | 0 .../test_c_public_api_skeleton.py | 0 .../preprocessing/test_c_preprocessing_cli.py | 0 .../test_c_preprocessing_configuration.py | 0 .../test_c_preprocessing_dependencies.py | 0 .../test_c_preprocessing_execution.py | 0 .../test_c_preprocessing_properties.py | 0 .../preprocessing/test_error_paths.py | 0 .../preprocessing/test_source_mappings.py | 0 .../semantics/test_c_conversion_properties.py | 0 .../test_projects_and_diagnostics.py | 0 .../pipeline/test_c_pyi_contract_fixtures.py | 0 .../semantics/test_c_pyi_conversion.py | 0 tests/fortran/CONTRACT_COVERAGE.md | 126 +++++++++--------- tests/fortran/README.md | 72 ++++++---- tests/fortran/_support/fixture_outputs.py | 6 +- tests/fortran/_support/wrapper_build.py | 2 +- tests/fortran/conftest.py | 17 +-- .../end_to_end/test_external_procedures.py | 2 +- .../building}/README.md | 2 +- .../compiling/test_compiler_verbose.py | 0 .../compiling/test_example_native_library.py | 0 .../compiling/test_support_probe_artifacts.py | 0 .../combined_modules/__init__.pyi | 0 .../combined_modules/box_ops.pyi | 0 .../combined_modules/first_math.pyi | 0 .../combined_modules/second_math.pyi | 0 .../combined_modules/shared_types.pyi | 0 .../contracts/runtime_abi/__init__.pyi | 0 .../runtime_abi/fruntime_abi_f90.pyi | 0 .../end_to_end/fixtures/native/double_value.f | 0 .../fixtures/native/fdefault_output.f | 0 .../end_to_end/fixtures/native/first_api.f90 | 0 .../fixtures/native/fruntime_abi_f90.f90 | 0 .../fixtures/native/home_points.f90 | 0 .../end_to_end/fixtures/native/scale.f90 | 0 .../end_to_end/fixtures/native/second_api.f90 | 0 .../fixtures/native/standalone_api.f | 0 .../fixtures/native/verbose_api.f90 | 0 .../native/multi_source_direct_bind_c_f90.f90 | 0 .../native/multi_source_direct_helper_f90.f90 | 0 .../native/multi_source_mixed_bind_c_f90.f90 | 0 .../native/multi_source_mixed_helper_f90.f90 | 0 .../end_to_end/real_libraries/__init__.py | 0 .../end_to_end/real_libraries/_support.py | 0 .../real_libraries/test_fftpack_routines.py | 2 +- .../real_libraries/test_minpack_routines.py | 2 +- .../test_build_direct_entrypoint_routing.py | 0 .../end_to_end/test_multi_source_builds.py | 0 .../end_to_end/test_native_bundles.py | 2 +- .../end_to_end/test_runtime_compatibility.py | 0 .../end_to_end/test_source_build_modes.py | 0 .../fdefault_output/__init__.pyi | 0 .../fruntime_abi_f90/__init__.pyi | 0 .../fruntime_abi_f90/fruntime_abi_f90.pyi | 0 .../source_builds/verbose_api/__init__.pyi | 0 .../source_builds/verbose_api/verbose_api.pyi | 0 .../pipeline/test_generated_wrapper_build.py | 0 .../pipeline/test_parallel_compilation.py | 0 .../pipeline/test_pyi_build_modes.py | 0 .../building}/pipeline/test_root_build_api.py | 0 .../test_source_generated_contracts.py | 0 .../cli}/pipeline/_support.py | 2 +- .../cli}/pipeline/test_argument_contract.py | 2 +- .../cli}/pipeline/test_output_contract.py | 6 +- .../cli}/pipeline/test_stage_dispatch.py | 6 +- .../errors/err_duplicate_argument_name.f90 | 0 .../errors/err_duplicate_argument_name.json | 0 .../err_duplicate_declaration_procedure.f90 | 0 .../err_duplicate_declaration_procedure.json | 0 .../err_duplicate_field_derived_type.f90 | 0 .../err_duplicate_field_derived_type.json | 0 .../errors/err_duplicate_parameter.f90 | 0 .../errors/err_duplicate_parameter.json | 0 .../errors/err_duplicate_procedure_global.f90 | 0 .../err_duplicate_procedure_global.json | 0 .../errors/err_duplicate_procedure_module.f90 | 0 .../err_duplicate_procedure_module.json | 0 .../errors/err_duplicate_variable_module.f90 | 0 .../errors/err_duplicate_variable_module.json | 0 .../err_implicit_none_undeclared_arg.f90 | 0 .../err_implicit_none_undeclared_arg.json | 0 .../err_implicit_none_undeclared_result.f90 | 0 .../err_implicit_none_undeclared_result.json | 0 ...err_parameter_without_type_implicit_none.f | 0 ..._parameter_without_type_implicit_none.json | 0 .../errors/err_result_shadows_argument.f90 | 0 .../errors/err_result_shadows_argument.json | 0 .../errors/err_unknown_function_result.f90 | 0 .../errors/err_unknown_function_result.json | 0 .../errors/err_unknown_type_derived_type.f90 | 0 .../errors/err_unknown_type_derived_type.json | 0 .../errors/err_unknown_type_module.f90 | 0 .../errors/err_unknown_type_module.json | 0 .../errors/err_unknown_type_procedure.f90 | 0 .../errors/err_unknown_type_procedure.json | 0 .../assumed_shape_and_derived_args.f90 | 0 .../assumed_shape_and_derived_args.json | 0 .../fixtures/general/basic_subroutine.f90 | 0 .../fixtures/general/basic_subroutine.json | 0 .../general/compile_time_all_exprs.f90 | 0 .../general/compile_time_all_exprs.json | 0 .../general/compile_time_shape_exprs.f90 | 0 .../general/compile_time_shape_exprs.json | 0 .../parsing/fixtures/general/derived_type.f90 | 0 .../fixtures/general/derived_type.json | 0 .../general/derived_types_and_methods.f90 | 0 .../general/derived_types_and_methods.json | 0 .../parsing/fixtures/general/f77_subroutine.f | 0 .../fixtures/general/f77_subroutine.json | 0 .../fixtures/general/modern_pyi_example.f90 | 0 .../fixtures/general/modern_pyi_example.json | 0 .../fixtures/general/module_vars_use.f90 | 0 .../fixtures/general/module_vars_use.json | 0 .../general/procedures_and_functions.f90 | 0 .../general/procedures_and_functions.json | 0 .../general/scope_name_reuse_combinations.f90 | 0 .../scope_name_reuse_combinations.json | 0 .../fixtures/json_sanity_allowlist.json | 0 .../parsing/generate_error_goldens.py | 0 .../parsing/generate_parser_goldens.py | 0 .../test_declaration_and_interface_edges.py | 0 .../test_declaration_and_scope_regressions.py | 0 .../test_derived_types_and_program_units.py | 0 .../parsing/test_developer_tutorial.py | 0 .../parsing/test_error_fixture_suite.py | 0 .../parsing/test_error_handling.py | 0 .../parsing/test_fortran_fixture_suite.py | 0 ...ortran_parser_procedures_and_interfaces.py | 0 .../parsing/test_fortran_parser_properties.py | 0 .../parsing/test_json_sanity.py | 0 .../parsing/test_parser_benchmarks.py | 0 .../parsing/test_public_entrypoints.py | 0 ...test_real_world_interaction_regressions.py | 0 ...source_form_and_diagnostics_regressions.py | 0 .../test_native_array_handles.py | 0 .../{semantics => policy}/test_ownership.py | 0 .../test_policy_completion.py | 0 .../test_wrapper_policy.py | 0 .../preprocessing/_support.py | 0 .../preprocessing/test_cli.py | 2 +- .../test_configuration_and_adapters.py | 2 +- .../test_dependencies_and_includes.py | 0 .../preprocessing/test_execution.py | 0 .../preprocessing/test_parser_boundaries.py | 0 .../test_preprocessing_properties.py | 0 .../assumed_shape_and_derived_args.json | 0 .../general/expected/basic_subroutine.json | 0 .../expected/compile_time_all_exprs.json | 0 .../expected/compile_time_shape_exprs.json | 0 .../general/expected/derived_type.json | 0 .../expected/derived_types_and_methods.json | 0 .../general/expected/f77_subroutine.json | 0 .../general/expected/modern_pyi_example.json | 0 .../general/expected/module_vars_use.json | 0 .../expected/procedures_and_functions.json | 0 .../scope_name_reuse_combinations.json | 0 .../semantics/generate_semantic_fixtures.py | 0 .../semantics/test_compile_time_values.py | 0 .../test_fortran_conversion_properties.py | 0 .../test_semantic_conversion_smoke.py | 0 ...test_semantic_specialization_properties.py | 0 .../semantic_pyi}/README.md | 4 +- .../contracts}/calls_and_results/README.md | 2 +- .../codegen/test_call_and_result_lowering.py | 0 .../hidden_array_output/__init__.pyi | 0 .../hidden_array_output/foutputs_f90.pyi | 0 .../immutable_replacements/__init__.pyi | 0 .../fnative_call_examples_f90.pyi | 0 .../native_order/__init__.pyi | 0 .../fnative_call_examples_f90.pyi | 0 .../projected_results/__init__.pyi | 0 .../fnative_call_examples_f90.pyi | 0 .../native/fnative_call_examples_f90.f90 | 0 .../fixtures/native/foutputs_f90.f90 | 0 .../end_to_end/test_edited_call_surfaces.py | 0 .../test_projected_entrypoint_routes.py | 0 .../policy/test_call_and_result_policy.py | 0 .../contracts}/exports_and_modules/README.md | 2 +- .../test_module_initializer_lowering.py | 0 .../module_exports/aliases.pyi | 0 .../module_exports/collision.pyi | 0 .../module_exports/facade.pyi | 0 .../module_exports/flatten.pyi | 0 .../module_exports/module1_added_binding.pyi | 0 .../module_variables_visibility/__init__.pyi | 0 .../fmodule_vars_f90.pyi | 0 .../contracts/fnaming_f90/__init__.pyi | 0 .../contracts/fnaming_f90/fnaming_f90.pyi | 0 .../visibility/native/fnaming_f90.f90 | 0 .../end_to_end/test_package_exports.py | 2 +- .../test_visibility_and_initialization.py | 2 +- .../end_to_end/test_visibility_naming.py | 0 .../test_naming_generated_contracts.py | 0 .../test_export_and_initializer_policy.py | 0 .../semantics/test_module_initializers.py | 0 .../functions_and_classes/README.md | 2 +- .../codegen/test_constructor_lowering.py | 0 .../method_and_constructor/__init__.pyi | 0 .../method_and_constructor/fclasses_f90.pyi | 0 .../overloaded_api/__init__.pyi | 0 .../overloaded_api/foverloads_f90.pyi | 0 .../__init__.pyi | 0 .../foverloads_f90.pyi | 0 .../__init__.pyi | 0 .../foverloads_f90.pyi | 0 .../pruned_surface/__init__.pyi | 0 .../pruned_surface/foverloads_f90.pyi | 0 .../without_constructor_member/__init__.pyi | 0 .../foverloads_f90.pyi | 0 .../end_to_end/test_edited_class_surfaces.py | 4 +- .../policy/test_class_surface_policy.py | 0 .../test_method_and_constructor_contracts.py | 0 .../test_authoritative_contract_runtime.py | 0 .../test_contract_package_runtime.py | 2 +- .../parsing/test_python_ast_contracts.py | 0 .../generated/__init__.pyi | 0 .../contract_import_graph/generated/deep.pyi | 0 .../contract_import_graph/generated/m1.pyi | 0 .../generated/__init__.pyi | 0 .../generated/contract_math_mod.pyi | 0 .../contract_same_name/generated/__init__.pyi | 0 .../generated/contract_same_name.pyi | 0 .../generated/__init__.pyi | 0 .../incomplete_native_call.pyi | 0 .../pipeline/fixtures/modern_math_physics.pyi | 0 .../fixtures/native/contract_import_graph.f90 | 0 .../native/contract_mixed_module_external.f90 | 0 .../fixtures/native/contract_multi_module.f90 | 0 .../fixtures/native/contract_same_name.f90 | 0 .../native/contract_standalone_only.f90 | 0 .../test_calls_and_policy_metadata.py | 0 .../pipeline/test_classes_and_methods.py | 0 .../pipeline/test_contract_loading.py | 0 .../test_contract_package_generation.py | 0 .../pipeline/test_modern_example.py | 9 +- .../test_native_abi_source_round_trip.py | 0 .../test_pyi_printer_conversion_smoke.py | 0 .../test_pyi_printer_imports_and_packages.py | 0 .../pipeline/test_types_and_declarations.py | 0 .../semantics/test_calls_and_projections.py | 0 .../semantics/test_classes_and_overloads.py | 0 .../semantics/test_imports_and_packages.py | 0 .../semantics/test_native_abi.py | 0 .../semantics/test_round_trip_properties.py | 0 .../semantics/test_types_and_values.py | 0 .../end_to_end/test_raw_native_addresses.py | 4 +- .../policy/test_subroutine_output_policy.py | 11 +- tools/run_fortran_toolchain_lane.py | 6 +- 300 files changed, 341 insertions(+), 312 deletions(-) rename tests/c/{command_line_interface => infrastructure/cli}/pipeline/test_c_cli_argument_contract.py (100%) rename tests/c/{command_line_interface => infrastructure/cli}/pipeline/test_c_cli_output_contract.py (100%) rename tests/c/{command_line_interface => infrastructure/cli}/pipeline/test_c_cli_skeleton.py (100%) rename tests/c/{command_line_interface => infrastructure/cli}/pipeline/test_c_cli_stage_dispatch.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_compiler_extensions.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_corpus.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_declarations_and_declarators.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_error_fixture_suite.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_fixture_suite.py (100%) rename tests/c/infrastructure/{parsers => parsing}/test_c_json_sanity.py (100%) rename tests/c/infrastructure/{parsers => parsing}/test_c_lexer_preprocessor.py (100%) rename tests/c/infrastructure/{parsers => parsing}/test_c_model_serialization.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_parser_benchmark.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_parser_properties.py (100%) rename tests/c/{source_parsing => infrastructure}/parsing/test_c_project_resolution.py (100%) rename tests/c/infrastructure/{parsers => parsing}/test_c_public_api_skeleton.py (100%) rename tests/c/{source_preprocessing => infrastructure}/preprocessing/test_c_preprocessing_cli.py (100%) rename tests/c/{source_preprocessing => infrastructure}/preprocessing/test_c_preprocessing_configuration.py (100%) rename tests/c/{source_preprocessing => infrastructure}/preprocessing/test_c_preprocessing_dependencies.py (100%) rename tests/c/{source_preprocessing => infrastructure}/preprocessing/test_c_preprocessing_execution.py (100%) rename tests/c/{source_preprocessing => infrastructure}/preprocessing/test_c_preprocessing_properties.py (100%) rename tests/c/{source_preprocessing => infrastructure}/preprocessing/test_error_paths.py (100%) rename tests/c/{source_preprocessing => infrastructure}/preprocessing/test_source_mappings.py (100%) rename tests/c/{ => infrastructure}/semantic_ir/semantics/test_c_conversion_properties.py (100%) rename tests/c/{ => infrastructure}/semantic_ir/semantics/test_projects_and_diagnostics.py (100%) rename tests/c/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_c_pyi_contract_fixtures.py (100%) rename tests/c/{semantic_pyi_format => infrastructure/semantic_pyi}/semantics/test_c_pyi_conversion.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/README.md (94%) rename tests/fortran/{building_shared_library => infrastructure/building}/compiling/test_compiler_verbose.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/compiling/test_example_native_library.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/compiling/test_support_probe_artifacts.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/contracts/multiple_files/combined_modules/__init__.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/contracts/multiple_files/combined_modules/box_ops.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/contracts/multiple_files/combined_modules/first_math.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/contracts/multiple_files/combined_modules/second_math.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/contracts/multiple_files/combined_modules/shared_types.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/contracts/runtime_abi/__init__.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/contracts/runtime_abi/fruntime_abi_f90.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/double_value.f (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/fdefault_output.f (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/first_api.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/fruntime_abi_f90.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/home_points.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/scale.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/second_api.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/standalone_api.f (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/native/verbose_api.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/routing/native/multi_source_direct_bind_c_f90.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/routing/native/multi_source_direct_helper_f90.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/routing/native/multi_source_mixed_bind_c_f90.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/fixtures/routing/native/multi_source_mixed_helper_f90.f90 (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/real_libraries/__init__.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/real_libraries/_support.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/real_libraries/test_fftpack_routines.py (96%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/real_libraries/test_minpack_routines.py (96%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/test_build_direct_entrypoint_routing.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/test_multi_source_builds.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/test_native_bundles.py (99%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/test_runtime_compatibility.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/end_to_end/test_source_build_modes.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/fixtures/generated_contracts/source_builds/fdefault_output/__init__.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/__init__.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/fruntime_abi_f90.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/fixtures/generated_contracts/source_builds/verbose_api/__init__.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/fixtures/generated_contracts/source_builds/verbose_api/verbose_api.pyi (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/test_generated_wrapper_build.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/test_parallel_compilation.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/test_pyi_build_modes.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/test_root_build_api.py (100%) rename tests/fortran/{building_shared_library => infrastructure/building}/pipeline/test_source_generated_contracts.py (100%) rename tests/fortran/{command_line_interface => infrastructure/cli}/pipeline/_support.py (96%) rename tests/fortran/{command_line_interface => infrastructure/cli}/pipeline/test_argument_contract.py (99%) rename tests/fortran/{command_line_interface => infrastructure/cli}/pipeline/test_output_contract.py (99%) rename tests/fortran/{command_line_interface => infrastructure/cli}/pipeline/test_stage_dispatch.py (99%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_argument_name.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_argument_name.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_declaration_procedure.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_declaration_procedure.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_field_derived_type.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_field_derived_type.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_parameter.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_parameter.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_procedure_global.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_procedure_global.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_procedure_module.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_procedure_module.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_variable_module.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_duplicate_variable_module.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_implicit_none_undeclared_arg.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_implicit_none_undeclared_arg.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_implicit_none_undeclared_result.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_implicit_none_undeclared_result.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_parameter_without_type_implicit_none.f (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_parameter_without_type_implicit_none.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_result_shadows_argument.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_result_shadows_argument.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_function_result.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_function_result.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_type_derived_type.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_type_derived_type.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_type_module.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_type_module.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_type_procedure.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/errors/err_unknown_type_procedure.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/assumed_shape_and_derived_args.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/assumed_shape_and_derived_args.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/basic_subroutine.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/basic_subroutine.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/compile_time_all_exprs.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/compile_time_all_exprs.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/compile_time_shape_exprs.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/compile_time_shape_exprs.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/derived_type.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/derived_type.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/derived_types_and_methods.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/derived_types_and_methods.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/f77_subroutine.f (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/f77_subroutine.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/modern_pyi_example.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/modern_pyi_example.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/module_vars_use.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/module_vars_use.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/procedures_and_functions.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/procedures_and_functions.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/scope_name_reuse_combinations.f90 (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/general/scope_name_reuse_combinations.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/fixtures/json_sanity_allowlist.json (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/generate_error_goldens.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/generate_parser_goldens.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_declaration_and_interface_edges.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_declaration_and_scope_regressions.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_derived_types_and_program_units.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_developer_tutorial.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_error_fixture_suite.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_error_handling.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_fortran_fixture_suite.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_fortran_parser_procedures_and_interfaces.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_fortran_parser_properties.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_json_sanity.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_parser_benchmarks.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_public_entrypoints.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_real_world_interaction_regressions.py (100%) rename tests/fortran/{source_parsing => infrastructure}/parsing/test_source_form_and_diagnostics_regressions.py (100%) rename tests/fortran/infrastructure/{semantics => policy}/test_native_array_handles.py (100%) rename tests/fortran/infrastructure/{semantics => policy}/test_ownership.py (100%) rename tests/fortran/infrastructure/{semantics => policy}/test_policy_completion.py (100%) rename tests/fortran/infrastructure/{semantics => policy}/test_wrapper_policy.py (100%) rename tests/fortran/{source_preprocessing => infrastructure}/preprocessing/_support.py (100%) rename tests/fortran/{source_preprocessing => infrastructure}/preprocessing/test_cli.py (98%) rename tests/fortran/{source_preprocessing => infrastructure}/preprocessing/test_configuration_and_adapters.py (99%) rename tests/fortran/{source_preprocessing => infrastructure}/preprocessing/test_dependencies_and_includes.py (100%) rename tests/fortran/{source_preprocessing => infrastructure}/preprocessing/test_execution.py (100%) rename tests/fortran/{source_preprocessing => infrastructure}/preprocessing/test_parser_boundaries.py (100%) rename tests/fortran/{source_preprocessing => infrastructure}/preprocessing/test_preprocessing_properties.py (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/assumed_shape_and_derived_args.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/basic_subroutine.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/compile_time_all_exprs.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/compile_time_shape_exprs.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/derived_type.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/derived_types_and_methods.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/f77_subroutine.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/modern_pyi_example.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/module_vars_use.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/procedures_and_functions.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/fixtures/general/expected/scope_name_reuse_combinations.json (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/generate_semantic_fixtures.py (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/test_compile_time_values.py (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/test_fortran_conversion_properties.py (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/test_semantic_conversion_smoke.py (100%) rename tests/fortran/{ => infrastructure}/semantic_ir/semantics/test_semantic_specialization_properties.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/README.md (89%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/README.md (93%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/codegen/test_call_and_result_lowering.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/foutputs_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/fnative_call_examples_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/fnative_call_examples_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/fnative_call_examples_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/native/fnative_call_examples_f90.f90 (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/fixtures/native/foutputs_f90.f90 (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/test_edited_call_surfaces.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/end_to_end/test_projected_entrypoint_routes.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/calls_and_results/policy/test_call_and_result_policy.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/README.md (92%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/codegen/test_module_initializer_lowering.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/aliases.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/collision.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/facade.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/flatten.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/module1_added_binding.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/fmodule_vars_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/fnaming_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/fixtures/visibility/native/fnaming_f90.f90 (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/test_package_exports.py (98%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/test_visibility_and_initialization.py (96%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/end_to_end/test_visibility_naming.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/pipeline/test_naming_generated_contracts.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/policy/test_export_and_initializer_policy.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/exports_and_modules/semantics/test_module_initializers.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/README.md (93%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/codegen/test_constructor_lowering.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/fclasses_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/foverloads_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/foverloads_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/foverloads_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/foverloads_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/__init__.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/foverloads_f90.pyi (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/end_to_end/test_edited_class_surfaces.py (97%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/policy/test_class_surface_policy.py (100%) rename tests/fortran/{pyi_contracts => infrastructure/semantic_pyi/contracts}/functions_and_classes/semantics/test_method_and_constructor_contracts.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/end_to_end/test_authoritative_contract_runtime.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/end_to_end/test_contract_package_runtime.py (96%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/parsing/test_python_ast_contracts.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_import_graph/generated/__init__.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_import_graph/generated/deep.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_import_graph/generated/m1.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_mixed_module_external/generated/__init__.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_mixed_module_external/generated/contract_math_mod.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_same_name/generated/__init__.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_same_name/generated/contract_same_name.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/contracts/contract_standalone_only/generated/__init__.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/invalid/projection_metadata/incomplete_native_call.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/modern_math_physics.pyi (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/native/contract_import_graph.f90 (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/native/contract_mixed_module_external.f90 (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/native/contract_multi_module.f90 (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/native/contract_same_name.f90 (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/fixtures/native/contract_standalone_only.f90 (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_calls_and_policy_metadata.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_classes_and_methods.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_contract_loading.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_contract_package_generation.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_modern_example.py (88%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_native_abi_source_round_trip.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_pyi_printer_conversion_smoke.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_pyi_printer_imports_and_packages.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/pipeline/test_types_and_declarations.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/semantics/test_calls_and_projections.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/semantics/test_classes_and_overloads.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/semantics/test_imports_and_packages.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/semantics/test_native_abi.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/semantics/test_round_trip_properties.py (100%) rename tests/fortran/{semantic_pyi_format => infrastructure/semantic_pyi}/semantics/test_types_and_values.py (100%) diff --git a/AGENTS.md b/AGENTS.md index ca30e8e30..3bdf2bcfa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,7 +116,7 @@ Changes limited to wrapper planning, direct bridge/binding lowering, or native compilation should use the focused owners under `tests/fortran/infrastructure/codegen/`, feature-local `tests/fortran/*/codegen/` directories, and -`tests/fortran/building_shared_library/compiling/` as applicable. Include the +`tests/fortran/infrastructure/building/compiling/` as applicable. Include the relevant end-to-end feature tests whenever a generated or compiled mechanism changes; run a broader suite when behavior spans multiple stages. Do not run LAPACK wrapper tests locally unless the user explicitly asks for them. Local verification may run everything else, including BLAS-only real-library tests; leave LAPACK coverage to GitHub Actions by default. diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf897a2e..db618acad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,14 @@ release tags add a leading `v` to the package version. binding carries `@abstractmethod`, both re-exported from `prik.contracts`; a deferred binding never carries `@bind`, because it has no native symbol. +### Changed + +- Reorganized the C and Fortran test suites around a strict ownership rule: + language features remain under `/`, while shared parsing, + preprocessing, CLI, semantic-representation, contract, build, and policy + evidence live under `infrastructure/`. Focused commands and documentation now + use the corresponding infrastructure owners. + ### Fixed - A generic interface whose specifics project an `intent(out)` argument into a diff --git a/README.md b/README.md index d696bb3f0..9e835a670 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ python3 -m prik points.f90 --out geometry Create `points.f90`: - + ```fortran module points implicit none diff --git a/docs/developer/deferred/c-parser.md b/docs/developer/deferred/c-parser.md index 21aafa7ba..d6b24c6cf 100644 --- a/docs/developer/deferred/c-parser.md +++ b/docs/developer/deferred/c-parser.md @@ -850,8 +850,8 @@ Useful local checks for the parse-only frontend: ```bash python -m prik tests/c/fixtures/native/general/math_api.h --language c --parse --json python tests/c/fixtures/parser/generate_c_parser_goldens.py tests/c/fixtures/native/general/math_api.h -pytest -q tests/c/source_parsing/parsing/test_c_declarations_and_declarators.py -pytest -q tests/c/source_parsing/parsing/test_c_fixture_suite.py +pytest -q tests/c/infrastructure/parsing/test_c_declarations_and_declarators.py +pytest -q tests/c/infrastructure/parsing/test_c_fixture_suite.py pytest -q tests/c pytest -q ``` @@ -861,10 +861,10 @@ Focused test files by implementation area: @@ -1115,12 +1115,12 @@ Testing should grow in this order: Executable references: -- Shared CLI behavior: `tests/fortran/command_line_interface/pipeline/` +- Shared CLI behavior: `tests/fortran/infrastructure/cli/pipeline/` @@ -1161,7 +1161,7 @@ that Linux reference environment. The fixture suite also checks same-stem grouping order and representative raw preprocessing failures. Fatal diagnostic goldens are regenerated with -`C_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/c/source_parsing/parsing/test_c_error_fixture_suite.py`. +`C_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/c/infrastructure/parsing/test_c_error_fixture_suite.py`. The standalone error generator remains available for targeted refreshes. By policy, a paired project records source-to-header include edges but parses each supplied `.c`, `.h`, or `.i` member separately; include traversal is not diff --git a/docs/developer/feature-to-code-map.md b/docs/developer/feature-to-code-map.md index ddbc534b4..acf5a05c0 100644 --- a/docs/developer/feature-to-code-map.md +++ b/docs/developer/feature-to-code-map.md @@ -25,19 +25,19 @@ change crosses a stage boundary. | Capability | Relevant documentation | Change route | Focused evidence | | --- | --- | --- | --- | -| Fortran inspection and semantic IR | [Parsers](packages/parsers.md) | `prik/parsers/fortran/parser.py` → `prik/semantics/fortran2ir.py` → `prik/semantics/models.py` | `tests/fortran/source_parsing/parsing/`, `tests/fortran/semantic_ir/semantics/` | -| CLI commands and reports | [Beginner workflow](../user/getting-started/beginner-workflow.md) | `prik/cli.py` → `prik/parsers/fortran/cli.py` | `tests/fortran/command_line_interface/pipeline/`, `tests/docs/test_examples.py` | -| Source preparation and target types | [Preprocessing](packages/preprocessing.md) | `prik/preprocessing/source.py` → `prik/preprocessing/fortran.py` → `prik/preprocessing/probes/fortran_types.py` → `prik/semantics/scalar_types.py` → `prik/codegen/primitive_scalar_types.py` | `tests/fortran/source_preprocessing/preprocessing/`, `tests/fortran/data_types/` | -| Semantic `.pyi` generation and editing | [.pyi contracts](../user/reference/pyi-contracts/index.md) | `prik/parsers/pyi/parser.py` → `prik/semantics/pyi2ir.py` → `prik/pipeline/pyi.py` → `prik/printers/pyi.py` | `tests/fortran/semantic_pyi_format/parsing/`, `tests/fortran/semantic_pyi_format/semantics/`, `tests/fortran/semantic_pyi_format/pipeline/` | -| Source-first extension builds | [Building the shared library](../user/guide/building-shared-library.md) | `prik/pipeline/build.py` → `prik/pipeline/wrapper.py` → `prik/compiler/compilers.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` | -| Contract-first extension builds | [.pyi contracts](../user/reference/pyi-contracts/index.md) | `prik/pipeline/build.py` → `prik/pipeline/pyi.py` → `prik/semantics/pyi2ir.py` | `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/pyi_contracts/exports_and_modules/` | -| Calls, results, and optional arguments | [Functions](../user/guide/wrapping-functions.md), [subroutines](../user/guide/wrapping-subroutines.md) | `prik/semantics/fortran2ir.py` → `prik/policy/completion.py` → `prik/planning/planner.py` → `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py` | `tests/fortran/functions/`, `tests/fortran/optional_arguments/`, `tests/fortran/pyi_contracts/calls_and_results/` | +| Fortran inspection and semantic IR | [Parsers](packages/parsers.md) | `prik/parsers/fortran/parser.py` → `prik/semantics/fortran2ir.py` → `prik/semantics/models.py` | `tests/fortran/infrastructure/parsing/`, `tests/fortran/infrastructure/semantic_ir/semantics/` | +| CLI commands and reports | [Beginner workflow](../user/getting-started/beginner-workflow.md) | `prik/cli.py` → `prik/parsers/fortran/cli.py` | `tests/fortran/infrastructure/cli/pipeline/`, `tests/docs/test_examples.py` | +| Source preparation and target types | [Preprocessing](packages/preprocessing.md) | `prik/preprocessing/source.py` → `prik/preprocessing/fortran.py` → `prik/preprocessing/probes/fortran_types.py` → `prik/semantics/scalar_types.py` → `prik/codegen/primitive_scalar_types.py` | `tests/fortran/infrastructure/preprocessing/`, `tests/fortran/data_types/` | +| Semantic `.pyi` generation and editing | [.pyi contracts](../user/reference/pyi-contracts/index.md) | `prik/parsers/pyi/parser.py` → `prik/semantics/pyi2ir.py` → `prik/pipeline/pyi.py` → `prik/printers/pyi.py` | `tests/fortran/infrastructure/semantic_pyi/parsing/`, `tests/fortran/infrastructure/semantic_pyi/semantics/`, `tests/fortran/infrastructure/semantic_pyi/pipeline/` | +| Source-first extension builds | [Building the shared library](../user/guide/building-shared-library.md) | `prik/pipeline/build.py` → `prik/pipeline/wrapper.py` → `prik/compiler/compilers.py` | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py`, `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py` | +| Contract-first extension builds | [.pyi contracts](../user/reference/pyi-contracts/index.md) | `prik/pipeline/build.py` → `prik/pipeline/pyi.py` → `prik/semantics/pyi2ir.py` | `tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py`, `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/` | +| Calls, results, and optional arguments | [Functions](../user/guide/wrapping-functions.md), [subroutines](../user/guide/wrapping-subroutines.md) | `prik/semantics/fortran2ir.py` → `prik/policy/completion.py` → `prik/planning/planner.py` → `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py` | `tests/fortran/functions/`, `tests/fortran/optional_arguments/`, `tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/` | | Arrays | [Arrays](../user/guide/arrays.md) | `prik/semantics/fortran2ir.py` → `prik/policy/completion.py` → `prik/planning/planner.py` → `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py` | `tests/fortran/arrays/` | -| Modules, interfaces, constants, and exported names | [Modules](../user/guide/wrapping-modules.md), [interfaces](../user/guide/generic-interfaces.md), [enumerations](../user/guide/enumerations.md) | `prik/parsers/fortran/parser.py` → `prik/semantics/fortran2ir.py` → `prik/policy/exports.py` → `prik/naming/policy.py` | `tests/fortran/modules/`, `tests/fortran/generic_interfaces/`, `tests/fortran/pyi_contracts/exports_and_modules/` | +| Modules, interfaces, constants, and exported names | [Modules](../user/guide/wrapping-modules.md), [interfaces](../user/guide/generic-interfaces.md), [enumerations](../user/guide/enumerations.md) | `prik/parsers/fortran/parser.py` → `prik/semantics/fortran2ir.py` → `prik/policy/exports.py` → `prik/naming/policy.py` | `tests/fortran/modules/`, `tests/fortran/generic_interfaces/`, `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/` | | Derived objects, allocatables, pointers, and lifetimes | [Derived types](../user/guide/wrapping-derived-types.md), [allocatables](../user/guide/allocatables.md), [pointers](../user/guide/pointers.md), [memory management](../user/guide/memory-management.md) | `prik/policy/ownership.py` → `prik/policy/construction.py` → `prik/policy/native_array_handles.py` → `prik/planning/planner.py` → `prik/runtime/handles.py` | `tests/fortran/derived_types/`, `tests/fortran/allocatables/`, `tests/fortran/pointers/` | | Callbacks | [Callbacks](../user/guide/callbacks.md) | `prik/policy/models.py` → `prik/policy/completion.py` → `prik/planning/planner.py` → `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py` | `tests/fortran/callbacks/` | | Projected errors | [Error handling](../user/guide/error-handling.md) | `prik/policy/models.py` → `prik/policy/completion.py` → `prik/planning/planner.py` → `prik/codegen/c/binding.py` and `prik/codegen/fortran/bridge.py` | `tests/fortran/error_handling/` | -| Native compilation, extension runtime, and public build API | [Compiler](packages/compiler.md), [Quality Assurance](workflows/quality-assurance.md) | `prik/__init__.py` → `prik/pipeline/build.py` → `prik/compiler/objects.py` → `prik/compiler/compilers.py` → `prik/compiler/native_support.py` → `prik/runtime/native_support/` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py`, `tests/fortran/source_parsing/parsing/test_public_entrypoints.py` | +| Native compilation, extension runtime, and public build API | [Compiler](packages/compiler.md), [Quality Assurance](workflows/quality-assurance.md) | `prik/__init__.py` → `prik/pipeline/build.py` → `prik/compiler/objects.py` → `prik/compiler/compilers.py` → `prik/compiler/native_support.py` → `prik/runtime/native_support/` | `tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py`, `tests/fortran/infrastructure/parsing/test_public_entrypoints.py` | Each change route begins with the first owner for a capability; it is not a complete call graph. When a change crosses a representation boundary, the diff --git a/docs/developer/packages/compiler.md b/docs/developer/packages/compiler.md index 015ea8e27..10fdb2d10 100644 --- a/docs/developer/packages/compiler.md +++ b/docs/developer/packages/compiler.md @@ -185,9 +185,9 @@ and conditional support installation. | Evidence | What it establishes | | --- | --- | -| [Compiler profile and command construction](../../../tests/fortran/building_shared_library/compiling/test_compiler_verbose.py) | Coherent C/Fortran driver selection, explicit overrides, profile and user-flag order, optional-flag probing, record-only mode, and preserved link-input order. | -| [Generated-wrapper build handoff](../../../tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py) | Generated sources, conditional support installation, explicit C and Fortran object requests, and the final ordered link request passed from the pipeline. | -| [Source build modes](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py) | The selected source-build mode produces an importable native extension. | +| [Compiler profile and command construction](../../../tests/fortran/infrastructure/building/compiling/test_compiler_verbose.py) | Coherent C/Fortran driver selection, explicit overrides, profile and user-flag order, optional-flag probing, record-only mode, and preserved link-input order. | +| [Generated-wrapper build handoff](../../../tests/fortran/infrastructure/building/pipeline/test_generated_wrapper_build.py) | Generated sources, conditional support installation, explicit C and Fortran object requests, and the final ordered link request passed from the pipeline. | +| [Source build modes](../../../tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py) | The selected source-build mode produces an importable native extension. | | [Native-support surface](../../../tests/fortran/infrastructure/runtime/test_native_support.py) | The bundled payload remains header-only and exposes the small native binding API expected by generated sources. | ## Change Routes diff --git a/docs/developer/packages/contracts.md b/docs/developer/packages/contracts.md index 014138088..d72bdd2b6 100644 --- a/docs/developer/packages/contracts.md +++ b/docs/developer/packages/contracts.md @@ -81,8 +81,8 @@ later stages interpret those facts. | Evidence | What it establishes | | --- | --- | | [Contract runtime tests](../../../tests/fortran/data_types/runtime/) | Concrete scalar constructors and invalid constructor use. | -| [Semantic `.pyi` parser tests](../../../tests/fortran/semantic_pyi_format/parsing/) | Recognition of the public vocabulary and annotation syntax. | -| [Semantic `.pyi` pipeline tests](../../../tests/fortran/semantic_pyi_format/pipeline/) | Contract loading, semantic conversion, and re-emission. | +| [Semantic `.pyi` parser tests](../../../tests/fortran/infrastructure/semantic_pyi/parsing/) | Recognition of the public vocabulary and annotation syntax. | +| [Semantic `.pyi` pipeline tests](../../../tests/fortran/infrastructure/semantic_pyi/pipeline/) | Contract loading, semantic conversion, and re-emission. | The import path and public names are part of the file format. A name being valid Python syntax does not by itself make the corresponding wrapper behavior diff --git a/docs/developer/packages/parsers.md b/docs/developer/packages/parsers.md index bc9924176..49c6af1b9 100644 --- a/docs/developer/packages/parsers.md +++ b/docs/developer/packages/parsers.md @@ -246,11 +246,11 @@ conversion remains the next stage's responsibility. | Evidence | What it establishes | | --- | --- | -| [Fortran parser suite](../../../tests/fortran/source_parsing/parsing/) | Source forms, units, declarations, scopes, diagnostics, project assembly, and parser models. | -| [Public parser entrypoints](../../../tests/fortran/source_parsing/parsing/test_public_entrypoints.py) | File, project, and singular-unit entrypoint contracts. | -| [Source forms and diagnostics](../../../tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py) | Logical source preparation, unit boundaries, and public diagnostic metadata. | -| [Parser CLI](../../../tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py) | Module launcher, report modes, diagnostic presentation, and explicit semantic/`.pyi` inspection modes. | -| [Semantic `.pyi` parsing](../../../tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py) | Raw `ast.Module` results and the AST-to-semantic-conversion handoff. | +| [Fortran parser suite](../../../tests/fortran/infrastructure/parsing/) | Source forms, units, declarations, scopes, diagnostics, project assembly, and parser models. | +| [Public parser entrypoints](../../../tests/fortran/infrastructure/parsing/test_public_entrypoints.py) | File, project, and singular-unit entrypoint contracts. | +| [Source forms and diagnostics](../../../tests/fortran/infrastructure/parsing/test_source_form_and_diagnostics_regressions.py) | Logical source preparation, unit boundaries, and public diagnostic metadata. | +| [Parser CLI](../../../tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py) | Module launcher, report modes, diagnostic presentation, and explicit semantic/`.pyi` inspection modes. | +| [Semantic `.pyi` parsing](../../../tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py) | Raw `ast.Module` results and the AST-to-semantic-conversion handoff. | ## Change Routes diff --git a/docs/developer/packages/pipeline.md b/docs/developer/packages/pipeline.md index 39afa79ba..b71d28844 100644 --- a/docs/developer/packages/pipeline.md +++ b/docs/developer/packages/pipeline.md @@ -200,10 +200,10 @@ measured fact, semantic identity, and NumPy projection separate. | Evidence | What it establishes | | --- | --- | | [Pipeline infrastructure](../../../tests/fortran/infrastructure/pipeline/) | Plan-to-rendered-wrapper assembly and cross-stage records. | -| [Semantic `.pyi` pipeline](../../../tests/fortran/semantic_pyi_format/pipeline/) | Contract loading, reconciliation, and stub emission. | -| [Build pipeline](../../../tests/fortran/building_shared_library/pipeline/) | Artifact output, manifests, build modes, and build-plan handoffs. | -| [Compilation integration](../../../tests/fortran/building_shared_library/compiling/) | Native command integration. | -| [End-to-end builds](../../../tests/fortran/building_shared_library/end_to_end/) | Build, import, and generated-extension behavior. | +| [Semantic `.pyi` pipeline](../../../tests/fortran/infrastructure/semantic_pyi/pipeline/) | Contract loading, reconciliation, and stub emission. | +| [Build pipeline](../../../tests/fortran/infrastructure/building/pipeline/) | Artifact output, manifests, build modes, and build-plan handoffs. | +| [Compilation integration](../../../tests/fortran/infrastructure/building/compiling/) | Native command integration. | +| [End-to-end builds](../../../tests/fortran/infrastructure/building/end_to_end/) | Build, import, and generated-extension behavior. | ## Change Routes diff --git a/docs/developer/packages/policy.md b/docs/developer/packages/policy.md index 79dd47433..a9894470b 100644 --- a/docs/developer/packages/policy.md +++ b/docs/developer/packages/policy.md @@ -311,8 +311,8 @@ generate source; that begins only after planning. | Evidence | What it establishes | | --- | --- | -| [Policy completion](../../../tests/fortran/infrastructure/semantics/test_policy_completion.py) | Completion precedes lowering; accessor, projection, and missing-conversion failures remain explicit. | -| [Wrapper policy](../../../tests/fortran/infrastructure/semantics/test_wrapper_policy.py) | Function, result, call-slot, array, export, status, and support policies are complete before planning. | +| [Policy completion](../../../tests/fortran/infrastructure/policy/test_policy_completion.py) | Completion precedes lowering; accessor, projection, and missing-conversion failures remain explicit. | +| [Wrapper policy](../../../tests/fortran/infrastructure/policy/test_wrapper_policy.py) | Function, result, call-slot, array, export, status, and support policies are complete before planning. | | [Ownership policy](../../../tests/fortran/memory_management/policy/test_memory_ownership_policy.py) | Contradictory explicit ownership contracts fail before lowering. | | [Descriptor handle policy](../../../tests/fortran/allocatables/policy/test_allocatable_handle_policy.py) | Allocatable descriptor-handle decisions, ownership, access, and support blockers. | | [Planner boundary](../../../tests/fortran/infrastructure/codegen/test_planner.py) | Planning rejects a missing completed wrapper policy instead of filling it in. | diff --git a/docs/developer/packages/preprocessing.md b/docs/developer/packages/preprocessing.md index 99980cb76..f9e58eb8d 100644 --- a/docs/developer/packages/preprocessing.md +++ b/docs/developer/packages/preprocessing.md @@ -189,8 +189,8 @@ compiler, rather than PRIK, supplied the fact. | Evidence | What it establishes | | --- | --- | -| [Fortran preprocessing](../../../tests/fortran/source_preprocessing/preprocessing/) | Adapters, recipes, mappings, native includes, diagnostics, and parser handoffs. | -| [Parser boundaries](../../../tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py) | Prepared source reaches parsing with preserved facts and unsupported raw constructs stop at the correct boundary. | +| [Fortran preprocessing](../../../tests/fortran/infrastructure/preprocessing/) | Adapters, recipes, mappings, native includes, diagnostics, and parser handoffs. | +| [Parser boundaries](../../../tests/fortran/infrastructure/preprocessing/test_parser_boundaries.py) | Prepared source reaches parsing with preserved facts and unsupported raw constructs stop at the correct boundary. | | [Fortran type probes](../../../tests/fortran/data_types/probes/test_fortran_type_probes.py) | Compiler facts, requirement evaluation, cache separation, and report validation. | ## Change Routes diff --git a/docs/developer/packages/printers.md b/docs/developer/packages/printers.md index 0db27473e..f6f7c850d 100644 --- a/docs/developer/packages/printers.md +++ b/docs/developer/packages/printers.md @@ -164,8 +164,8 @@ wrapper policy. | Evidence | What it establishes | | --- | --- | | [Native source printers](../../../tests/fortran/infrastructure/printers/test_source_printers.py) | C and Fortran serialization, rejection of wrapper plans, line wrapping, literal preservation, and unsplittable-line diagnostics. | -| [Semantic `.pyi` conversion smoke](../../../tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_conversion_smoke.py) | Emitted contract fixtures can be parsed and converted through the normal semantic-`.pyi` route. | -| [`.pyi` imports and packages](../../../tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py) | Isolated emission state, imports, aliases, packages, name collisions, and opaque dependencies. | +| [Semantic `.pyi` conversion smoke](../../../tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_conversion_smoke.py) | Emitted contract fixtures can be parsed and converted through the normal semantic-`.pyi` route. | +| [`.pyi` imports and packages](../../../tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_imports_and_packages.py) | Isolated emission state, imports, aliases, packages, name collisions, and opaque dependencies. | ## Change Routes diff --git a/docs/developer/packages/runtime.md b/docs/developer/packages/runtime.md index b4cbc6242..bb6269727 100644 --- a/docs/developer/packages/runtime.md +++ b/docs/developer/packages/runtime.md @@ -95,7 +95,7 @@ the compiler installs it into a generated `binding_support/` directory. | [Pointer runtime tests](../../../tests/fortran/pointers/runtime/) | Association, nullification, pointer descriptors, and views. | | [Memory-management runtime tests](../../../tests/fortran/memory_management/runtime/) | Owner retention, release, and array handoffs. | | [Native-support tests](../../../tests/fortran/infrastructure/runtime/) | Bundled payload discovery and installation inputs. | -| [Compiled runtime compatibility](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) | The payload and Python runtime working through a real extension. | +| [Compiled runtime compatibility](../../../tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py) | The payload and Python runtime working through a real extension. | An outstanding zero-copy NumPy view cannot be revoked after native reallocation, deallocation, or pointer reassociation. Users must discard or diff --git a/docs/developer/packages/semantics.md b/docs/developer/packages/semantics.md index e4b13667a..004a63437 100644 --- a/docs/developer/packages/semantics.md +++ b/docs/developer/packages/semantics.md @@ -283,11 +283,11 @@ before policy completion or any backend lowering begins. | Evidence | What it establishes | | --- | --- | -| [Semantic IR conversion](../../../tests/fortran/semantic_ir/semantics/) | Fortran-model conversion, compile-time requirements, specialization, and semantic graph properties. | +| [Semantic IR conversion](../../../tests/fortran/infrastructure/semantic_ir/semantics/) | Fortran-model conversion, compile-time requirements, specialization, and semantic graph properties. | | [Fortran datatype semantics](../../../tests/fortran/data_types/semantics/) | Stable scalar identities, storage facts, and compiler-measurement handoffs. | -| [Semantic `.pyi` conversion](../../../tests/fortran/semantic_pyi_format/semantics/) | Contract constructs, imports, external references, projections, classes, overloads, and round trips. | -| [Native array handles](../../../tests/fortran/infrastructure/semantics/test_native_array_handles.py) | Descriptor marking and separation of handle, data, and element facts. | -| [Native contract validation](../../../tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py) | Native-contract preparation, validation, and diagnostic ownership. | +| [Semantic `.pyi` conversion](../../../tests/fortran/infrastructure/semantic_pyi/semantics/) | Contract constructs, imports, external references, projections, classes, overloads, and round trips. | +| [Native array handles](../../../tests/fortran/infrastructure/policy/test_native_array_handles.py) | Descriptor marking and separation of handle, data, and element facts. | +| [Native contract validation](../../../tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py) | Native-contract preparation, validation, and diagnostic ownership. | ## Change Routes diff --git a/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md b/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md index 5a3c1afc1..afaa90c7b 100644 --- a/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md +++ b/docs/developer/roadmap/fortran-test-suite-cleanup-checklist.md @@ -235,7 +235,7 @@ Rules: - [x] Public argument parsing and output formatting belong in the owning input language's command-line feature. - [x] Cross-feature Fortran command contracts belong in - `tests/fortran/command_line_interface/pipeline/`. + `tests/fortran/infrastructure/cli/pipeline/`. - [x] A CLI test that builds, imports, calls, and verifies a Fortran extension belongs in the owning feature's `end_to_end/` directory, normally `building_shared_library/end_to_end/`. @@ -387,12 +387,12 @@ directory. Audit and place every artifact beside its final behavioral owner. | `tests/data/fortran/general/` | Owning feature/stage; feature-neutral setup is minimized beside its final public-capability owner | | `tests/data/fortran/errors/` | Fixture directory of the first rejecting stage | | `tests/data/fortran/blas/` and `lapack/` | `examples/blas/native/` and `examples/lapack/native/` | -| Parser regressions extracted from SciFortran | `tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py` | +| Parser regressions extracted from SciFortran | `tests/fortran/infrastructure/parsing/test_real_world_interaction_regressions.py` | | Parser source/JSON pairs | Beside their parser owner | -| Language-neutral `.pyi` syntax | `tests/fortran/semantic_pyi_format/` | -| Fortran `.pyi` build fixtures | `tests/fortran/semantic_pyi_format/{pipeline,end_to_end}/fixtures/` | +| Language-neutral `.pyi` syntax | `tests/fortran/infrastructure/semantic_pyi/` | +| Fortran `.pyi` build fixtures | `tests/fortran/infrastructure/semantic_pyi/{pipeline,end_to_end}/fixtures/` | | Generated contract goldens | Beside their generation/package-shape owner | -| Edited contracts | `tests/fortran/pyi_contracts//end_to_end/fixtures/` | +| Edited contracts | `tests/fortran/infrastructure/semantic_pyi/contracts//end_to_end/fixtures/` | | Invalid `.pyi` contracts | Fixture directory of the first rejecting stage | ### Native sources @@ -466,9 +466,9 @@ An edited contract is authoritative input, not expected generated output. | Owner | What it proves | | --- | --- | -| `tests/fortran/semantic_pyi_format/pipeline/` | Loading, import graph, package assembly, build plan, and diagnostics | -| `tests/fortran/semantic_pyi_format/end_to_end/` | An ordinary contract is authoritative input and produces a working extension | -| `tests/fortran/pyi_contracts//end_to_end/` | A documented edit changes the built API or runtime behavior | +| `tests/fortran/infrastructure/semantic_pyi/pipeline/` | Loading, import graph, package assembly, build plan, and diagnostics | +| `tests/fortran/infrastructure/semantic_pyi/end_to_end/` | An ordinary contract is authoritative input and produces a working extension | +| `tests/fortran/infrastructure/semantic_pyi/contracts//end_to_end/` | A documented edit changes the built API or runtime behavior | The end-to-end baseline contains: @@ -1089,7 +1089,7 @@ attributed all 303 SciFortran sources to upstream revision measured 37 lines plus 27 branches that the focused parser suite had not reached. A follow-up contextual-coverage audit traced all 64 items to 12 source units and reduced them to five named inline tests in -`tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py`. +`tests/fortran/infrastructure/parsing/test_real_world_interaction_regressions.py`. The focused parser suite now executes all 64 formerly unique items without the third-party project. Existing focused tests retain the historical `CLASS(...)`, CPP, scope, `EXTERNAL`, `SAVE`/local-type, `USE`-rename, and @@ -1413,8 +1413,8 @@ compilation, linking, loading, and the same runtime smoke all succeed. - [ ] Implement GNU, Intel ifx, LLVM Flang, and NVIDIA nvfortran one profile at a time. - [x] Add focused command/capability tests under - `tests/fortran/building_shared_library/compiling/` and - `tests/fortran/source_preprocessing/preprocessing/`. + `tests/fortran/infrastructure/building/compiling/` and + `tests/fortran/infrastructure/preprocessing/`. - [ ] Carry compiler-derived target facts through semantics and the shared plan; bridge/binding generators do not infer semantic policy from compiler family. - [x] Give unknown and unsupported compilers explicit diagnostics. diff --git a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md index d9305e163..04ddd8d33 100644 --- a/docs/developer/roadmap/native-entrypoint-adoption-checklist.md +++ b/docs/developer/roadmap/native-entrypoint-adoption-checklist.md @@ -760,8 +760,8 @@ invariants rather than duplicating those assertions in every feature. Fortran source/object absence. - Zero-adapter materialization, compile scheduling, link-driver selection, Makefiles, manifests, and progress records: - `tests/fortran/building_shared_library/pipeline/` and - `tests/fortran/building_shared_library/compiling/`. + `tests/fortran/infrastructure/building/pipeline/` and + `tests/fortran/infrastructure/building/compiling/`. - Compiled Fortran feature behavior: the owning `tests/fortran//end_to_end/` directory. The scalar adoption starts by replacing the current assumption that every procedure in @@ -772,7 +772,7 @@ invariants rather than duplicating those assertions in every feature. tooling tests under `tests/tools/`. These supplement rather than replace feature-local correctness evidence. - Generated and edited semantic-contract parity: - `tests/fortran/semantic_pyi_format/` plus feature-local end-to-end fixtures. + `tests/fortran/infrastructure/semantic_pyi/` plus feature-local end-to-end fixtures. Artifact assertions protect observable generated and build behavior: whether an adapter source/object exists, which native operations it exports, which diff --git a/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md b/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md index 9d3b99810..7dc1c66d1 100644 --- a/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md +++ b/docs/developer/roadmap/semantic-pyi-wrapper-checklist.md @@ -132,12 +132,12 @@ Runtime wrapper tests are organized by stable subjects under `build_from_pyi/modified_contracts/basic_subroutine/flatten_m1.pyi`, `build_from_pyi/modified_contracts/basic_subroutine/alias_increment.pyi`, and - `tests/fortran/semantic_pyi_format/pipeline/fixtures/invalid/projection_metadata/incomplete_native_call.pyi`. + `tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/invalid/projection_metadata/incomplete_native_call.pyi`. - [x] Generated `.pyi` packages are checked fixtures. Runtime wrapper contract packages live under `tests/wrapper/fortran//contracts//`; explicit `--pyi --out` package-shape fixtures that do not compile wrappers live under - `tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/`. + `tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/`. Refresh is explicit through `WRAPPER_UPDATE_PYI_FIXTURES=1`. - [x] Modified runtime fixtures use `.pyi`, record their intentional difference @@ -146,7 +146,7 @@ Runtime wrapper tests are organized by stable subjects under - [x] `.py` files are rejected as semantic `.pyi` contract inputs by the Python API. - [x] The reviewed packages under - `tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/` + `tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/` are the canonical exact `.pyi` generation-regression corpus and are not used as edited runtime contracts. - [x] Explicit Fortran `--pyi --out` package-shape fixtures that do not compile @@ -311,8 +311,8 @@ PRIK_C_DOCS_END --> ### Stage 6 — Replayable JSON, Native Compilation, And Makefiles Runtime evidence lives in -`tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py`, -`tests/fortran/semantic_pyi_format/end_to_end/`, and CLI surface +`tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py`, +`tests/fortran/infrastructure/semantic_pyi/end_to_end/`, and CLI surface evidence lives in `tests/cli/`. - [x] Python API `.pyi` builds accept output directory, extension naming, @@ -353,7 +353,7 @@ evidence lives in `tests/cli/`. Real BLAS/LAPACK artifact-shape evidence lives in `examples/blas/` and `examples/lapack/`. Native bundle, order, transitive-library, and failure-path evidence lives in -`tests/fortran/building_shared_library/end_to_end/test_native_bundles.py`. +`tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py`. - [x] Full real BLAS and LAPACK source corpora under `examples/blas/native/` and `examples/lapack/native/` @@ -418,8 +418,8 @@ PRIK_C_DOCS_END --> `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/fortran/infrastructure/semantics/test_policy_completion.py`, - `tests/fortran/infrastructure/semantics/test_ownership.py`, + `tests/fortran/infrastructure/policy/test_policy_completion.py`, + `tests/fortran/infrastructure/policy/test_ownership.py`, feature-local `tests/fortran/*/policy/`, `tests/fortran/infrastructure/codegen/`, and `prik/semantics/README.md`. @@ -427,7 +427,7 @@ PRIK_C_DOCS_END --> `prik/parsers/pyi/parser.py` parses text/files to Python AST, and `prik/semantics/pyi2ir.py` converts that AST into `SemanticModule` objects before semantic policy completion runs. Evidence: - `tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_pyi_parser_returns_python_ast_only`, + `tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py::test_pyi_parser_returns_python_ast_only`, `prik/semantics/README.md`, and `docs/developer/architecture.md` and the detailed architecture component guides. @@ -451,13 +451,13 @@ PRIK_C_DOCS_END --> loader semantic errors prefix messages with the `.pyi` contract path while syntax errors keep Python's filename field. Evidence: `docs/user/reference/semantic-pyi-format.md` and - `tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_pyi_file_to_semantic_module_and_modules_forward_module_name_encoding_and_filename`. + `tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_pyi_file_to_semantic_module_and_modules_forward_module_name_encoding_and_filename`. - [x] A modified module `.pyi` can remove a public function and hide public declarations with `@private` or `private[...]` while preserving unaffected runtime behavior. Evidence: - `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py` + `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py` and - `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/`. + `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/`. - [x] A dedicated user guide documents the supported editable contract surface, including what users may remove, hide, add, rename, project, validate, make immutable, and declare as ownership/lifetime policy. It separates editable @@ -470,13 +470,13 @@ PRIK_C_DOCS_END --> member, and individual overload candidate from the Python API. They can also add renamed `@bind(...)` declarations and a renamed module overload group without reparsing native source. Evidence: - `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py` + `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py` and - `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/`. + `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/`. - [x] Module overload candidates can override the linked specific's native call with `@bind("native_generic")`, and the printer round-trips that metadata. Evidence: - `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets` + `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets` and `docs/user/reference/semantic-pyi-format.md`. - [x] Explicit owner, transfer, and destruction triples are validated as a complete lifetime policy instead of independent switches. Supported triples @@ -531,8 +531,8 @@ PRIK_C_DOCS_END --> `tests/fortran/error_handling/semantics/test_status_contract_semantics.py`, `tests/fortran/error_handling/codegen/test_status_error_lowering.py`, `tests/fortran/error_handling/end_to_end/test_status_projection.py`, - `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py`, - `tests/fortran/pyi_contracts/exports_and_modules/`, and + `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py`, + `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/`, and `tests/wrapper/CHECKLIST_COVERAGE.md`. + ```fortran real(8) function scale(value, factor) result(output) real(8), intent(in) :: value diff --git a/docs/user/examples/recipes/build-and-import-python-api.md b/docs/user/examples/recipes/build-and-import-python-api.md index 986b80934..1d512dc4d 100644 --- a/docs/user/examples/recipes/build-and-import-python-api.md +++ b/docs/user/examples/recipes/build-and-import-python-api.md @@ -25,7 +25,7 @@ import numpy as np from prik import build_fortran_extension -source = Path("tests/fortran/building_shared_library/end_to_end/fixtures/native/fruntime_abi_f90.f90") +source = Path("tests/fortran/infrastructure/building/end_to_end/fixtures/native/fruntime_abi_f90.f90") with TemporaryDirectory() as output_dir: build = build_fortran_extension(source, output_dir=output_dir) module = build.import_module() diff --git a/docs/user/examples/recipes/control-cli-output.md b/docs/user/examples/recipes/control-cli-output.md index 7c332791c..fdfbd2244 100644 --- a/docs/user/examples/recipes/control-cli-output.md +++ b/docs/user/examples/recipes/control-cli-output.md @@ -19,7 +19,7 @@ need to inspect module variables and derived-type fields: ```bash -python3 -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.f90 \ +python3 -m prik parse tests/fortran/infrastructure/parsing/fixtures/general/modern_pyi_example.f90 \ --show-vars ``` @@ -29,7 +29,7 @@ Use `--print-limit` to keep long reports readable while preserving totals: ```bash -python3 -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.f90 \ +python3 -m prik parse tests/fortran/infrastructure/parsing/fixtures/general/modern_pyi_example.f90 \ --show-vars --print-limit 1 ``` @@ -37,7 +37,7 @@ Expected output: ```text -File: tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.f90 +File: tests/fortran/infrastructure/parsing/fixtures/general/modern_pyi_example.f90 Modules: 1 - module modern_math_physics (vars=2, uses=0) Variables: 2 @@ -60,7 +60,7 @@ Choose one inspection stage per command. For parser details, run: ```bash -python3 -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 +python3 -m prik parse tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 ``` ## Notes diff --git a/docs/user/examples/recipes/inspect-fortran-api.md b/docs/user/examples/recipes/inspect-fortran-api.md index 0021db601..0c576383a 100644 --- a/docs/user/examples/recipes/inspect-fortran-api.md +++ b/docs/user/examples/recipes/inspect-fortran-api.md @@ -14,7 +14,7 @@ building a wrapper. ## Input - + ```fortran module m1 contains @@ -29,14 +29,14 @@ end module m1 ```bash -python3 -m prik parse tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 +python3 -m prik parse tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 ``` Expected output: ```text -File: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 +File: tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 Modules: 1 - module m1 (vars=0, uses=0) Procedures: 1 @@ -47,14 +47,14 @@ File: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 ```bash -python3 -m prik generate --pyi tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 +python3 -m prik generate --pyi tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 ``` Expected output: ```python -File: tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 +File: tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 Root contract: basic_subroutine/basic_subroutine.pyi from . import m1 diff --git a/docs/user/examples/recipes/semantic-pyi-contracts.md b/docs/user/examples/recipes/semantic-pyi-contracts.md index 40e15fbc3..9d8927b3a 100644 --- a/docs/user/examples/recipes/semantic-pyi-contracts.md +++ b/docs/user/examples/recipes/semantic-pyi-contracts.md @@ -15,7 +15,7 @@ semantic contract. ## Generate A Starter Contract ```bash -python3 -m prik generate --pyi tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 \ +python3 -m prik generate --pyi tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 \ --out contracts/basic_subroutine ``` diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index 95870ed12..4cfbcf71a 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -59,7 +59,7 @@ limitation for each feature. | Scalar functions, subroutines, and baseline arrays | Supported | [Functions](../guide/wrapping-functions.md), [subroutines](../guide/wrapping-subroutines.md) | [Wrapper pipeline](../../developer/architecture.md#build-architecture) | [Verified baseline tests](../../../tests/fortran/data_types/end_to_end/test_verified_baseline.py) | Native scalar arguments require exact NumPy dtypes where documented. | | Generic procedure interfaces | Supported | [Generic interfaces](../guide/generic-interfaces.md) | [Feature route](../../developer/feature-to-code-map.md#feature-routes) | [Generic interface tests](../../../tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py) | Defined operators and assignment are tracked separately. | | Defined operators and assignment overloads | Supported | [Defined operators](../guide/generic-interfaces.md) | [Bridge and binding generation](../../developer/codebase-map.md#cross-stage-hotspots) | [Defined operator tests](../../../tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py) | Supported operators are those covered by the wrapper guide and runtime tests. | -| Output arguments and multiple results | Supported | [Subroutine projection](../guide/wrapping-subroutines.md) | [Ownership and lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Calls and results tests](../../../tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py), [function result tests](../../../tests/fortran/functions/end_to_end/test_documented_function_journeys.py) | Tuple ordering and caller-provided array behavior follow the wrapper guide. | +| Output arguments and multiple results | Supported | [Subroutine projection](../guide/wrapping-subroutines.md) | [Ownership and lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Calls and results tests](../../../tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py), [function result tests](../../../tests/fortran/functions/end_to_end/test_documented_function_journeys.py) | Tuple ordering and caller-provided array behavior follow the wrapper guide. | | Optional arguments | Supported | [Optional arguments](../guide/optional-arguments.md) | [Binding generation](../../developer/codebase-map.md#cross-stage-hotspots) | [Optional argument tests](../../../tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py) | Unsupported optional combinations fail during wrapper planning. | | Allocatable array handles, descriptor arguments, and owned results | Supported | [Allocatables](../guide/allocatables.md) | [Ownership policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Allocatable runtime tests](../../../tests/fortran/allocatables/end_to_end/test_allocatable_handles.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py) | Array module/field handles borrow their owner; result handles own persistent descriptor storage. Wrapper-owned scalar-derived allocatables use typed holders; module scalar allocatables use reversible `move_alloc` transactions for compatible dummies. | | Pointer scalar projections and array handles | Partially supported | [Pointers](../guide/pointers.md) | [Ownership policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Pointer handle tests](../../../tests/fortran/pointers/end_to_end/test_pointer_handles.py), [pointer policy tests](../../../tests/fortran/pointers/policy/test_pointer_ownership_policy.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py) | Descriptor arguments, module/field handles, strided views, wrapper-owned pointer-array results and outputs, scalar-derived pointer holders, and module pointer reassociation transactions are supported. Target deallocation and writable reassociation remain policy-gated. | @@ -67,19 +67,19 @@ limitation for each feature. | NumPy array argument contracts | Supported | [Arrays](../guide/arrays.md) | [Bridge and binding generation](../../developer/codebase-map.md#cross-stage-hotspots) | [Array contract tests](../../../tests/fortran/arrays/end_to_end/test_array_contract_validation.py), [multidimensional tests](../../../tests/fortran/arrays/end_to_end/test_layout_and_strided_arrays.py) | Wrong dtype, rank, shape, contiguity, alignment, or mutability is rejected. | | Derived-type scalar boundaries and methods | Supported | [Derived types](../guide/wrapping-derived-types.md) | [Class lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Derived boundary tests](../../../tests/fortran/derived_types/end_to_end/test_derived_boundaries.py), [method tests](../../../tests/fortran/derived_types/end_to_end/test_type_bound_methods.py) | Derived-type arrays and some polymorphic forms are not included. | | Default and keyword constructors with finalizers | Supported | [Constructors and finalizers](../guide/wrapping-derived-types.md#key-concepts) | [Ownership policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Constructor/finalizer tests](../../../tests/fortran/derived_types/end_to_end/test_default_constructors_and_finalizers.py), [borrowed finalizer tests](../../../tests/fortran/derived_types/end_to_end/test_borrowed_components.py) | Construction commits ownership only after initialization; borrowed wrappers never run an owning finalizer. | -| Generic constructor interfaces and overloaded runtime initialization | Supported | [Constructors](../guide/wrapping-derived-types.md#custom-constructor) | [Class policy and lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Edited class surface tests](../../../tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py), [class policy tests](../../../tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates require distinguishable completed Python signatures; incomplete or ambiguous sets are blocked before emission. | +| Generic constructor interfaces and overloaded runtime initialization | Supported | [Constructors](../guide/wrapping-derived-types.md#custom-constructor) | [Class policy and lowering](../../developer/codebase-map.md#cross-stage-hotspots) | [Edited class surface tests](../../../tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py), [class policy tests](../../../tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates require distinguishable completed Python signatures; incomplete or ambiguous sets are blocked before emission. | | Module variables, constants, saved state, and common-block procedure state | Supported | [Wrapping modules](../guide/wrapping-modules.md) | [Module state route](../../developer/feature-to-code-map.md#feature-routes) | [Module state tests](../../../tests/fortran/modules/end_to_end/test_module_variables_and_state.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py), [common-block tests](../../../tests/fortran/modules/end_to_end/test_common_blocks.py) | Common-block storage is not exported as Python variables. Rank-zero derived module objects use direct, scoped, allocation-transaction, or pointer-transaction handoff selected before lowering. `character` module state is supported in every form: a declared-length scalar reads and writes as `str` at exactly its declared byte width, an `allocatable` or `pointer` scalar reads as a detached `str` or `None`, and arrays reach Python as fixed-width bytes. Only declared-length non-descriptor scalars are writable by assignment; descriptor scalars are read-only snapshots for numeric and `character` state alike, and arrays are mutated in place through their view or handle rather than rebound. | | Fortran enum constants | Supported | [Enumerations](../guide/enumerations.md) | [Semantic constants route](../../developer/codebase-map.md#cross-stage-hotspots) | [Enum runtime tests](../../../tests/fortran/enumerations/end_to_end/test_enum_runtime.py), [enum semantic tests](../../../tests/fortran/enumerations/semantics/test_enum_semantics.py), [enum diagnostics](../../../tests/fortran/enumerations/parsing/test_enum_diagnostics.py) | No Python `Enum` or `IntEnum` classes are generated. | | Scalar character arguments, results, and fields | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character argument tests](../../../tests/fortran/strings/end_to_end/test_character_boundaries.py), [edge-case tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Scalar `character` `allocatable` and `pointer` values are supported for `intent(in)`, `intent(out)`, `intent(inout)`, and function results, at deferred (`len=:`) and declared (`len=n`) length; a mutable dummy returns the value the procedure left behind, or `None`. prik copies out of native pointer storage and never frees it, so a procedure that allocates a fresh target per call leaks unless it frees its own. | | Scalar kind coverage | Supported | [Data types](../guide/data-types.md) | [Fortran type probe](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py) | Quad precision (`real(16)`, `complex(16)`) is blocked because it has no portable NumPy dtype. All `logical` kinds are supported and adapt to one-byte NumPy Booleans at the boundary. | -| Caller-ordered multi-source builds, Makefiles, verbose mode, and output placement | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Multi-source tests](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py), [compiler verbose tests](../../../tests/fortran/building_shared_library/compiling/test_compiler_verbose.py) | prik does not discover, reorder, or resolve all external source dependencies. | -| Visibility, naming, keyword escaping, and collision policy | Supported | [Visibility and naming](../reference/fortran-wrapper.md#visibility-naming-and-the-python-surface) | [Naming policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Visibility/naming tests](../../../tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_naming.py) | Strict mode rejects names that default mode can normalize. | +| Caller-ordered multi-source builds, Makefiles, verbose mode, and output placement | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Multi-source tests](../../../tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py), [compiler verbose tests](../../../tests/fortran/infrastructure/building/compiling/test_compiler_verbose.py) | prik does not discover, reorder, or resolve all external source dependencies. | +| Visibility, naming, keyword escaping, and collision policy | Supported | [Visibility and naming](../reference/fortran-wrapper.md#visibility-naming-and-the-python-surface) | [Naming policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Visibility/naming tests](../../../tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_naming.py) | Strict mode rejects names that default mode can normalize. | | Immediate call-scoped Python callbacks | Supported | [Callbacks](../guide/callbacks.md) | [Callback bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Callback plan tests](../../../tests/fortran/callbacks/codegen/test_callback_planning.py), [scalar callback tests](../../../tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py), [array callback tests](../../../tests/fortran/callbacks/end_to_end/test_array_callbacks.py), [combined shape tests](../../../tests/fortran/callbacks/end_to_end/test_supported_callback_shapes.py) | Direct wrapper-plan generation supports entering-thread callbacks only. Stored, optional, asynchronous, or cross-thread callbacks are unsupported. | -| Runtime error projection, GIL policy, recursion, OpenMP path, and GNU ABI checks | Supported | [Error handling](../guide/error-handling.md) | [Runtime route](../../developer/codebase-map.md#cross-stage-hotspots) | [Status projection runtime](../../../tests/fortran/error_handling/end_to_end/test_status_projection.py), [status and GIL lowering](../../../tests/fortran/error_handling/codegen/test_status_error_lowering.py), [recursion tests](../../../tests/fortran/error_handling/end_to_end/test_runtime_recursion.py), [OpenMP tests](../../../tests/fortran/error_handling/end_to_end/test_openmp_runtime.py), [ABI tests](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) | OpenMP and ABI evidence is compiler/platform-specific; callers still own native synchronization. | -| Fortran source wrapper builds | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Build modes](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py), [runtime ABI](../../../tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py) | Implemented for ordered Fortran source inputs. | +| Runtime error projection, GIL policy, recursion, OpenMP path, and GNU ABI checks | Supported | [Error handling](../guide/error-handling.md) | [Runtime route](../../developer/codebase-map.md#cross-stage-hotspots) | [Status projection runtime](../../../tests/fortran/error_handling/end_to_end/test_status_projection.py), [status and GIL lowering](../../../tests/fortran/error_handling/codegen/test_status_error_lowering.py), [recursion tests](../../../tests/fortran/error_handling/end_to_end/test_runtime_recursion.py), [OpenMP tests](../../../tests/fortran/error_handling/end_to_end/test_openmp_runtime.py), [ABI tests](../../../tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py) | OpenMP and ABI evidence is compiler/platform-specific; callers still own native synchronization. | +| Fortran source wrapper builds | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Build modes](../../../tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py), [runtime ABI](../../../tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py) | Implemented for ordered Fortran source inputs. | @@ -88,11 +88,11 @@ PRIK_C_DOCS_END --> | Feature | Status | User docs | Source owner | Evidence | Limitations | | --- | --- | --- | --- | --- | --- | -| Fortran parse, semantic IR, and `.pyi` inspection | Supported | [Fortran inspection recipe](../examples/recipes/inspect-fortran-api.md), [semantic IR](../reference/semantic-ir.md) | [Fortran parser route](../../developer/codebase-map.md#cross-stage-hotspots) | [Fortran parser fixtures](../../../tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py), [Fortran semantic tests](../../../tests/fortran/semantic_ir/semantics/) | Inspection support does not by itself prove runtime wrapper support. | -| Semantic `.pyi` wrapper builds from explicit native artifacts | Partially supported | [Semantic `.pyi` contracts](../examples/recipes/semantic-pyi-contracts.md), [`.pyi` format](../reference/semantic-pyi-format.md) | [`.pyi` build route](../../developer/architecture.md#build-architecture) | [format and authoritative-input tests](../../../tests/fortran/semantic_pyi_format/), [multi-source contract tests](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py), [native build plan tests](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py) | Current runtime parity is limited; source/generated/modified multi-source package parity is covered, and broader parity remains tracked in the checklist. | +| Fortran parse, semantic IR, and `.pyi` inspection | Supported | [Fortran inspection recipe](../examples/recipes/inspect-fortran-api.md), [semantic IR](../reference/semantic-ir.md) | [Fortran parser route](../../developer/codebase-map.md#cross-stage-hotspots) | [Fortran parser fixtures](../../../tests/fortran/infrastructure/parsing/test_fortran_fixture_suite.py), [Fortran semantic tests](../../../tests/fortran/infrastructure/semantic_ir/semantics/) | Inspection support does not by itself prove runtime wrapper support. | +| Semantic `.pyi` wrapper builds from explicit native artifacts | Partially supported | [Semantic `.pyi` contracts](../examples/recipes/semantic-pyi-contracts.md), [`.pyi` format](../reference/semantic-pyi-format.md) | [`.pyi` build route](../../developer/architecture.md#build-architecture) | [format and authoritative-input tests](../../../tests/fortran/infrastructure/semantic_pyi/), [multi-source contract tests](../../../tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py), [native build plan tests](../../../tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py) | Current runtime parity is limited; source/generated/modified multi-source package parity is covered, and broader parity remains tracked in the checklist. | | Scalar inheritance and polymorphic dispatch | Partially supported | [Inheritance and polymorphism](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class lowering route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py) | Abstract types wrap as non-instantiable Python base classes and deferred bindings resolve through the caller's concrete type. Polymorphic results, mutable dummies, arrays, allocatable/pointer scalars, and `class(*)` are blocked. | | Assumed-size, assumed-rank, and lower-bound array contracts | Partially supported | [Arrays](../guide/arrays.md) | [Array bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Assumed-rank tests](../../../tests/fortran/arrays/end_to_end/test_assumed_rank_arrays.py) | Assumed type and derived-type arrays remain blocked. Character arrays require fixed-width NumPy bytes dtype. | -| Generated reference pages for modules, functions, and classes | Partially supported | [Reference index](../reference/index.md) | [Codebase map](../../developer/codebase-map.md) | [Documentation reference checks](../../../tests/docs/test_reference_and_codebase_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. | +| Generated reference pages for modules, functions, and classes | Partially supported | [Reference index](../reference/index.md) | [Codebase map](../../developer/codebase-map.md) | [Documentation reference checks](../../../tests/docs/test_reference_and_codebase_map.py), [semantic contract tests](../../../tests/fortran/infrastructure/semantic_pyi/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. | | Feature | Status | User docs | Source owner | Evidence | Limitations | | --- | --- | --- | --- | --- | --- | -| Full semantic `.pyi` parity across all wrapper scenarios | Planned | [Semantic `.pyi` format](../reference/semantic-pyi-format.md) | [`.pyi` route](../../developer/architecture.md#build-architecture) | [semantic `.pyi` feature tests](../../../tests/fortran/semantic_pyi_format/) | Only the documented implemented subset is supported. | +| Full semantic `.pyi` parity across all wrapper scenarios | Planned | [Semantic `.pyi` format](../reference/semantic-pyi-format.md) | [`.pyi` route](../../developer/architecture.md#build-architecture) | [semantic `.pyi` feature tests](../../../tests/fortran/infrastructure/semantic_pyi/) | Only the documented implemented subset is supported. | diff --git a/docs/user/reference/configuration-files.md b/docs/user/reference/configuration-files.md index 3327a939a..6db3040a2 100644 --- a/docs/user/reference/configuration-files.md +++ b/docs/user/reference/configuration-files.md @@ -195,9 +195,9 @@ boundaries, reference links, and documentation checklist synchronization. ## Evidence And Maintenance Manifest and Makefile replay behavior is covered by -[`test_pyi_build_modes.py`](../../../tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py) and +[`test_pyi_build_modes.py`](../../../tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py) and source-build Makefile behavior by -[`test_build_modes.py`](../../../tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py). +[`test_build_modes.py`](../../../tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py). Tooling configuration is covered by [`test_reference_and_codebase_map.py`](../../../tests/docs/test_reference_and_codebase_map.py), diff --git a/docs/user/reference/fortran-wrapper.md b/docs/user/reference/fortran-wrapper.md index 54e571534..a385d52b6 100644 --- a/docs/user/reference/fortran-wrapper.md +++ b/docs/user/reference/fortran-wrapper.md @@ -94,7 +94,7 @@ PRIK_C_DOCS_END --> Build the checked scalar example: ```bash -python3 -m prik tests/fortran/building_shared_library/end_to_end/fixtures/native/fruntime_abi_f90.f90 \ +python3 -m prik tests/fortran/infrastructure/building/end_to_end/fixtures/native/fruntime_abi_f90.f90 \ --out-dir build/fruntime_abi ``` @@ -389,7 +389,7 @@ The equivalent Python entrypoint returns structured artifact paths: from prik import build_fortran_extension result = build_fortran_extension( - "tests/fortran/building_shared_library/end_to_end/fixtures/native/fruntime_abi_f90.f90", + "tests/fortran/infrastructure/building/end_to_end/fixtures/native/fruntime_abi_f90.f90", output_dir="build/fruntime_abi", ) print(result.module_name) diff --git a/docs/user/reference/generated-classes.md b/docs/user/reference/generated-classes.md index d958eeff4..16c9908ac 100644 --- a/docs/user/reference/generated-classes.md +++ b/docs/user/reference/generated-classes.md @@ -157,7 +157,7 @@ Generated class behavior is covered by [`test_inheritance_and_polymorphism.py`](../../../tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py). Exact class-method and constructor overloads, including explicit bound construction, are covered by -[`test_edited_class_surfaces.py`](../../../tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py). +[`test_edited_class_surfaces.py`](../../../tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py). When class behavior changes, update this page with the derived-type user guide, semantic `.pyi` reference, generated contract fixtures, and ownership evidence. diff --git a/docs/user/reference/generated-functions.md b/docs/user/reference/generated-functions.md index 9b822e6ee..6c4906741 100644 --- a/docs/user/reference/generated-functions.md +++ b/docs/user/reference/generated-functions.md @@ -138,7 +138,7 @@ target without replacing that linked contract. ## Evidence And Maintenance Function and subroutine call surfaces are covered by -[`test_edited_call_surfaces.py`](../../../tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py), +[`test_edited_call_surfaces.py`](../../../tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py), [`test_documented_function_journeys.py`](../../../tests/fortran/functions/end_to_end/test_documented_function_journeys.py), [`test_optional_runtime.py`](../../../tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py), and [`test_generic_interfaces.py`](../../../tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py). diff --git a/docs/user/reference/generated-modules.md b/docs/user/reference/generated-modules.md index 03750eecd..c1d78c7f9 100644 --- a/docs/user/reference/generated-modules.md +++ b/docs/user/reference/generated-modules.md @@ -131,9 +131,9 @@ requests; colliding names fail. Module package shape, child namespaces, variable access, and import policy are covered by [`test_module_variables_and_state.py`](../../../tests/fortran/modules/end_to_end/test_module_variables_and_state.py), -[`test_contract_package_runtime.py`](../../../tests/fortran/semantic_pyi_format/end_to_end/test_contract_package_runtime.py), -[`test_multi_source_builds.py`](../../../tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py), and -[`test_source_generated_pyi_contracts.py`](../../../tests/fortran/building_shared_library/pipeline/test_source_generated_contracts.py). +[`test_contract_package_runtime.py`](../../../tests/fortran/infrastructure/semantic_pyi/end_to_end/test_contract_package_runtime.py), +[`test_multi_source_builds.py`](../../../tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py), and +[`test_source_generated_pyi_contracts.py`](../../../tests/fortran/infrastructure/building/pipeline/test_source_generated_contracts.py). When module namespace behavior changes, update this page, generated package fixtures, [Semantic `.pyi` Format](semantic-pyi-format.md), and the module diff --git a/docs/user/reference/python-api.md b/docs/user/reference/python-api.md index e00d9c0bb..05a33fd26 100644 --- a/docs/user/reference/python-api.md +++ b/docs/user/reference/python-api.md @@ -47,7 +47,7 @@ from tempfile import TemporaryDirectory from prik import build_fortran_extension -source = Path("tests/fortran/building_shared_library/end_to_end/fixtures/native/fruntime_abi_f90.f90") +source = Path("tests/fortran/infrastructure/building/end_to_end/fixtures/native/fruntime_abi_f90.f90") with TemporaryDirectory() as output_dir: build = build_fortran_extension(source, output_dir=output_dir) print(build.module_name) diff --git a/prik/compiler/README.md b/prik/compiler/README.md index db11de094..de5088aab 100644 --- a/prik/compiler/README.md +++ b/prik/compiler/README.md @@ -89,5 +89,5 @@ policy completion. Those decisions happen before generated sources reach this pa - Pipeline package guide: `docs/developer/packages/pipeline.md` - Quality and static checks: `docs/developer/workflows/quality-assurance.md` - Source navigation: `docs/developer/codebase-map.md`, `docs/developer/feature-to-code-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` +- Build-mode tests: `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py` +- Runtime ABI tests: `tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py` diff --git a/prik/parsers/fortran/README.md b/prik/parsers/fortran/README.md index f33b816c4..516d69c9f 100644 --- a/prik/parsers/fortran/README.md +++ b/prik/parsers/fortran/README.md @@ -23,9 +23,9 @@ re-export parser functions or models. - Package reference: `docs/developer/packages/parsers.md` - User recipe: `docs/user/examples/recipes/inspect-fortran-api.md` - Source navigation: `docs/developer/codebase-map.md`, `docs/developer/feature-to-code-map.md` -- Parser tests: `tests/fortran/source_parsing/parsing/` -- Fixture suite: `tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py` -- Semantic handoff tests: `tests/fortran/semantic_ir/semantics/` +- Parser tests: `tests/fortran/infrastructure/parsing/` +- Fixture suite: `tests/fortran/infrastructure/parsing/test_fortran_fixture_suite.py` +- Semantic handoff tests: `tests/fortran/infrastructure/semantic_ir/semantics/` Parser support alone does not establish native binding support. Wrapper features need semantic lowering, completed policy, codegen, compilation, and diff --git a/prik/preprocessing/README.md b/prik/preprocessing/README.md index ad24f1ac6..6f8302250 100644 --- a/prik/preprocessing/README.md +++ b/prik/preprocessing/README.md @@ -37,7 +37,7 @@ extension. `prik.compiler` supplies reusable compiler mechanisms; - `tests/c/preprocessing/` - `tests/c/probes/` -- `tests/fortran/source_preprocessing/preprocessing/` +- `tests/fortran/infrastructure/preprocessing/` - `tests/fortran/data_types/probes/` - `docs/developer/packages/preprocessing.md` - `docs/developer/codebase-map.md` diff --git a/prik/semantics/README.md b/prik/semantics/README.md index f8493f18b..16682e618 100644 --- a/prik/semantics/README.md +++ b/prik/semantics/README.md @@ -110,6 +110,6 @@ completion remains the next shared stage after those converters produce - Source navigation: `docs/developer/codebase-map.md`, `docs/developer/feature-to-code-map.md` - Architecture: `docs/developer/architecture.md` - Semantics package guide: `docs/developer/packages/semantics.md` -- Semantic tests: `tests/fortran/semantic_ir/semantics/` -- `.pyi` tests: `tests/fortran/semantic_pyi_format/` +- Semantic tests: `tests/fortran/infrastructure/semantic_ir/semantics/` +- `.pyi` tests: `tests/fortran/infrastructure/semantic_pyi/` - Wrapper behavior that reaches the typed plan: `tests/fortran/` diff --git a/pyproject.toml b/pyproject.toml index f8e9fb788..2e16608fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,8 +126,8 @@ extend-exclude = [ "tests/c/fixtures/pyi", "tests/fortran/*/end_to_end/fixtures", "tests/fortran/*/pipeline/fixtures", - "tests/fortran/pyi_contracts/*/end_to_end/fixtures", - "tests/fortran/semantic_pyi_format/pipeline/fixtures", + "tests/fortran/infrastructure/semantic_pyi/contracts/*/end_to_end/fixtures", + "tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures", "prik.egg-info", ] @@ -168,8 +168,8 @@ exclude = [ "tests/c/fixtures/pyi/", "tests/fortran/*/end_to_end/fixtures/", "tests/fortran/*/pipeline/fixtures/", - "tests/fortran/pyi_contracts/*/end_to_end/fixtures/", - "tests/fortran/semantic_pyi_format/pipeline/fixtures/", + "tests/fortran/infrastructure/semantic_pyi/contracts/*/end_to_end/fixtures/", + "tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/", "prik.egg-info/", ] min_confidence = 80 diff --git a/tests/README.md b/tests/README.md index 6772de29e..8e134323f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,15 +1,16 @@ # Test Suite Map -Product-behavior tests are organized language first. Fortran tests are then -organized by documented feature and pipeline stage: +Product-behavior tests are organized language first. Within a language, +documented language features use a feature-first, stage-second layout: ```text -tests/fortran/// +tests//// ``` -Documentation is the top-level `tests/docs/` feature. Only other genuinely -internal product behavior mirrors its production package below -`tests/fortran/infrastructure/`. Maintainer tooling has the independent +Parsing, preprocessing, command-line handling, semantic IR and `.pyi` +conversion, build orchestration, and other cross-feature mechanisms are +infrastructure. They live below `tests//infrastructure/`, even when +they also have user documentation. Maintainer tooling has the independent `tests/tools/` owner, while exceptional automation-safety checks live under `tests/workflows/`. Generated C and CPython binding code used by a Fortran wrapper remains evidence @@ -74,16 +75,15 @@ semantic tests preserve names, imports, and native callable provenance; the arrays policy tests classify dependency roles and unsupported native calls; and the arrays end-to-end tests compile representative dimensions, inquiry forms, reductions, conditionals, powers, and logical-kind arrays. Contract-batch -reconciliation belongs with `tests/fortran/semantic_pyi_format/`, where -editable `.pyi` imports and prototypes are exercised. +reconciliation belongs with `tests/fortran/infrastructure/semantic_pyi/`, +where editable `.pyi` imports and prototypes are exercised. -Public cross-feature capabilities have explicit owners: -`source_parsing/`, `source_preprocessing/`, `command_line_interface/`, and -`semantic_ir/`. Only internal frameworks with no honest public-capability owner -belong under `tests/fortran/infrastructure/`. A user-visible behavior stays -with its feature even when its test crosses several pipeline stages. Minimized -real-world parser interactions belong under `source_parsing/parsing/`; full -third-party snapshots are temporary analysis inputs, not permanent fixtures. +Cross-feature mechanisms have explicit infrastructure owners: `parsing/`, +`preprocessing/`, `cli/`, `semantic_ir/`, `semantic_pyi/`, `building/`, and +`policy/`. A user-visible language behavior stays with its feature even when +its test crosses several pipeline stages. Minimized real-world parser +interactions belong under `infrastructure/parsing/`; full third-party snapshots +are temporary analysis inputs, not permanent fixtures. ## Independent suite gates @@ -117,7 +117,7 @@ selection: - `toolchain_smoke` selects only the bounded portable compiler-profile subset declared by `tests/fortran/conftest.py`. -The smoke suite is eight exact nodes reused from ordinary feature end-to-end +The smoke suite is eight exact nodes reused from ordinary Fortran end-to-end tests. Strict mode requires a resolved compiler, rejects skips and xfails, and prints the selected nodes with their mechanism and compilation fixture: @@ -157,15 +157,16 @@ CLI/API diagnostic test only when propagation is itself public behavior. Feature-local fixtures live below their feature; cross-feature helpers require an explicit infrastructure owner. -After choosing feature ownership, place genuinely internal mechanisms under -their owning production package when that makes the invariant easier to find: +First decide whether the invariant is a language feature or a cross-feature +mechanism. For a cross-feature mechanism, place it under its infrastructure +owner when that makes the invariant easier to find: ```text tests/fortran/infrastructure//test_.py ``` For example, `prik/policy/ownership.py` uses -`infrastructure/semantics/test_ownership.py`, while +`infrastructure/policy/test_ownership.py`, while `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`. diff --git a/tests/c/README.md b/tests/c/README.md index 6f150a380..39d7aab77 100644 --- a/tests/c/README.md +++ b/tests/c/README.md @@ -4,22 +4,30 @@ CPython binding code used to implement a Fortran wrapper remains under the owning Fortran feature. -C receives a mechanical quarantine during the language-first migration. Move -existing C parsing, probes, preprocessing, semantic conversion, pipeline, CLI -dispatch, property tests, fixtures, and helpers without redesigning their -behavior. Preserve node IDs where path changes permit, parameters, markers, -skips, xfails, and fixture contents. +C language features use the same feature-first, stage-second shape as Fortran: + +```text +tests/c/// +``` + +Parsing, preprocessing, command-line handling, semantic IR and `.pyi` +conversion, and other cross-feature mechanisms live under +`tests/c/infrastructure/`. Preserve node IDs where path changes permit, +parameters, markers, skips, xfails, and fixture contents. The quarantined owners are: | Owner | Scope | | --- | --- | -| `cli/` | C-input command dispatch and C-specific argument/output contracts | -| `parsing/` | C lexer, parser, project, corpus, fixture, and public-entrypoint behavior | -| `probes/` | C compiler type probes | -| `preprocessing/` | C recipes, dependencies, mappings, execution, and diagnostics | -| `semantics/conversion/` | C parser model and C semantic `.pyi` conversion | -| `pipeline/` | C source/generated-contract parity | +| `data_types//` | C scalar type facts and compiler type probes | +| `functions//` | C function declarations and their semantic projection | +| `records//` | C structs, unions, and typedefs | +| `enumerations//` | C enum syntax and semantic projection | +| `infrastructure/cli/` | C-input command dispatch and C-specific argument/output contracts | +| `infrastructure/parsing/` | C lexer, parser, project, corpus, fixture, and public-entrypoint behavior | +| `infrastructure/preprocessing/` | C recipes, dependencies, mappings, execution, and diagnostics | +| `infrastructure/semantic_ir/` | C parser-model conversion to semantic IR | +| `infrastructure/semantic_pyi/` | C semantic `.pyi` conversion and source/generated-contract parity | | `fixtures/native/` | C source and include inputs | | `fixtures/parser/` | C parser snapshots and update commands | | `fixtures/pyi/` | checked C generated-contract packages | diff --git a/tests/c/command_line_interface/pipeline/test_c_cli_argument_contract.py b/tests/c/infrastructure/cli/pipeline/test_c_cli_argument_contract.py similarity index 100% rename from tests/c/command_line_interface/pipeline/test_c_cli_argument_contract.py rename to tests/c/infrastructure/cli/pipeline/test_c_cli_argument_contract.py diff --git a/tests/c/command_line_interface/pipeline/test_c_cli_output_contract.py b/tests/c/infrastructure/cli/pipeline/test_c_cli_output_contract.py similarity index 100% rename from tests/c/command_line_interface/pipeline/test_c_cli_output_contract.py rename to tests/c/infrastructure/cli/pipeline/test_c_cli_output_contract.py diff --git a/tests/c/command_line_interface/pipeline/test_c_cli_skeleton.py b/tests/c/infrastructure/cli/pipeline/test_c_cli_skeleton.py similarity index 100% rename from tests/c/command_line_interface/pipeline/test_c_cli_skeleton.py rename to tests/c/infrastructure/cli/pipeline/test_c_cli_skeleton.py diff --git a/tests/c/command_line_interface/pipeline/test_c_cli_stage_dispatch.py b/tests/c/infrastructure/cli/pipeline/test_c_cli_stage_dispatch.py similarity index 100% rename from tests/c/command_line_interface/pipeline/test_c_cli_stage_dispatch.py rename to tests/c/infrastructure/cli/pipeline/test_c_cli_stage_dispatch.py diff --git a/tests/c/source_parsing/parsing/test_c_compiler_extensions.py b/tests/c/infrastructure/parsing/test_c_compiler_extensions.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_compiler_extensions.py rename to tests/c/infrastructure/parsing/test_c_compiler_extensions.py diff --git a/tests/c/source_parsing/parsing/test_c_corpus.py b/tests/c/infrastructure/parsing/test_c_corpus.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_corpus.py rename to tests/c/infrastructure/parsing/test_c_corpus.py diff --git a/tests/c/source_parsing/parsing/test_c_declarations_and_declarators.py b/tests/c/infrastructure/parsing/test_c_declarations_and_declarators.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_declarations_and_declarators.py rename to tests/c/infrastructure/parsing/test_c_declarations_and_declarators.py diff --git a/tests/c/source_parsing/parsing/test_c_error_fixture_suite.py b/tests/c/infrastructure/parsing/test_c_error_fixture_suite.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_error_fixture_suite.py rename to tests/c/infrastructure/parsing/test_c_error_fixture_suite.py diff --git a/tests/c/source_parsing/parsing/test_c_fixture_suite.py b/tests/c/infrastructure/parsing/test_c_fixture_suite.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_fixture_suite.py rename to tests/c/infrastructure/parsing/test_c_fixture_suite.py diff --git a/tests/c/infrastructure/parsers/test_c_json_sanity.py b/tests/c/infrastructure/parsing/test_c_json_sanity.py similarity index 100% rename from tests/c/infrastructure/parsers/test_c_json_sanity.py rename to tests/c/infrastructure/parsing/test_c_json_sanity.py diff --git a/tests/c/infrastructure/parsers/test_c_lexer_preprocessor.py b/tests/c/infrastructure/parsing/test_c_lexer_preprocessor.py similarity index 100% rename from tests/c/infrastructure/parsers/test_c_lexer_preprocessor.py rename to tests/c/infrastructure/parsing/test_c_lexer_preprocessor.py diff --git a/tests/c/infrastructure/parsers/test_c_model_serialization.py b/tests/c/infrastructure/parsing/test_c_model_serialization.py similarity index 100% rename from tests/c/infrastructure/parsers/test_c_model_serialization.py rename to tests/c/infrastructure/parsing/test_c_model_serialization.py diff --git a/tests/c/source_parsing/parsing/test_c_parser_benchmark.py b/tests/c/infrastructure/parsing/test_c_parser_benchmark.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_parser_benchmark.py rename to tests/c/infrastructure/parsing/test_c_parser_benchmark.py diff --git a/tests/c/source_parsing/parsing/test_c_parser_properties.py b/tests/c/infrastructure/parsing/test_c_parser_properties.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_parser_properties.py rename to tests/c/infrastructure/parsing/test_c_parser_properties.py diff --git a/tests/c/source_parsing/parsing/test_c_project_resolution.py b/tests/c/infrastructure/parsing/test_c_project_resolution.py similarity index 100% rename from tests/c/source_parsing/parsing/test_c_project_resolution.py rename to tests/c/infrastructure/parsing/test_c_project_resolution.py diff --git a/tests/c/infrastructure/parsers/test_c_public_api_skeleton.py b/tests/c/infrastructure/parsing/test_c_public_api_skeleton.py similarity index 100% rename from tests/c/infrastructure/parsers/test_c_public_api_skeleton.py rename to tests/c/infrastructure/parsing/test_c_public_api_skeleton.py diff --git a/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_cli.py b/tests/c/infrastructure/preprocessing/test_c_preprocessing_cli.py similarity index 100% rename from tests/c/source_preprocessing/preprocessing/test_c_preprocessing_cli.py rename to tests/c/infrastructure/preprocessing/test_c_preprocessing_cli.py diff --git a/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_configuration.py b/tests/c/infrastructure/preprocessing/test_c_preprocessing_configuration.py similarity index 100% rename from tests/c/source_preprocessing/preprocessing/test_c_preprocessing_configuration.py rename to tests/c/infrastructure/preprocessing/test_c_preprocessing_configuration.py diff --git a/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_dependencies.py b/tests/c/infrastructure/preprocessing/test_c_preprocessing_dependencies.py similarity index 100% rename from tests/c/source_preprocessing/preprocessing/test_c_preprocessing_dependencies.py rename to tests/c/infrastructure/preprocessing/test_c_preprocessing_dependencies.py diff --git a/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_execution.py b/tests/c/infrastructure/preprocessing/test_c_preprocessing_execution.py similarity index 100% rename from tests/c/source_preprocessing/preprocessing/test_c_preprocessing_execution.py rename to tests/c/infrastructure/preprocessing/test_c_preprocessing_execution.py diff --git a/tests/c/source_preprocessing/preprocessing/test_c_preprocessing_properties.py b/tests/c/infrastructure/preprocessing/test_c_preprocessing_properties.py similarity index 100% rename from tests/c/source_preprocessing/preprocessing/test_c_preprocessing_properties.py rename to tests/c/infrastructure/preprocessing/test_c_preprocessing_properties.py diff --git a/tests/c/source_preprocessing/preprocessing/test_error_paths.py b/tests/c/infrastructure/preprocessing/test_error_paths.py similarity index 100% rename from tests/c/source_preprocessing/preprocessing/test_error_paths.py rename to tests/c/infrastructure/preprocessing/test_error_paths.py diff --git a/tests/c/source_preprocessing/preprocessing/test_source_mappings.py b/tests/c/infrastructure/preprocessing/test_source_mappings.py similarity index 100% rename from tests/c/source_preprocessing/preprocessing/test_source_mappings.py rename to tests/c/infrastructure/preprocessing/test_source_mappings.py diff --git a/tests/c/semantic_ir/semantics/test_c_conversion_properties.py b/tests/c/infrastructure/semantic_ir/semantics/test_c_conversion_properties.py similarity index 100% rename from tests/c/semantic_ir/semantics/test_c_conversion_properties.py rename to tests/c/infrastructure/semantic_ir/semantics/test_c_conversion_properties.py diff --git a/tests/c/semantic_ir/semantics/test_projects_and_diagnostics.py b/tests/c/infrastructure/semantic_ir/semantics/test_projects_and_diagnostics.py similarity index 100% rename from tests/c/semantic_ir/semantics/test_projects_and_diagnostics.py rename to tests/c/infrastructure/semantic_ir/semantics/test_projects_and_diagnostics.py diff --git a/tests/c/semantic_pyi_format/pipeline/test_c_pyi_contract_fixtures.py b/tests/c/infrastructure/semantic_pyi/pipeline/test_c_pyi_contract_fixtures.py similarity index 100% rename from tests/c/semantic_pyi_format/pipeline/test_c_pyi_contract_fixtures.py rename to tests/c/infrastructure/semantic_pyi/pipeline/test_c_pyi_contract_fixtures.py diff --git a/tests/c/semantic_pyi_format/semantics/test_c_pyi_conversion.py b/tests/c/infrastructure/semantic_pyi/semantics/test_c_pyi_conversion.py similarity index 100% rename from tests/c/semantic_pyi_format/semantics/test_c_pyi_conversion.py rename to tests/c/infrastructure/semantic_pyi/semantics/test_c_pyi_conversion.py diff --git a/tests/fortran/CONTRACT_COVERAGE.md b/tests/fortran/CONTRACT_COVERAGE.md index 8ced24f5f..9aec3a78b 100644 --- a/tests/fortran/CONTRACT_COVERAGE.md +++ b/tests/fortran/CONTRACT_COVERAGE.md @@ -36,10 +36,10 @@ Authoritative sources: | Documentation contract | Status | Dimensions | Stage evidence | Runtime evidence | Negative evidence | CI lane | | --- | --- | --- | --- | --- | --- | --- | -| [Inspect a Fortran API: Parse Source Facts](../../docs/user/examples/recipes/inspect-fortran-api.md#parse-source-facts) | Supported | public string, file, path-sequence, and project parser entry points; model traversal; stable source diagnostics | `tests/fortran/source_parsing/parsing/test_public_entrypoints.py::test_parser_public_entrypoint_aliases_and_singular_contracts_use_inline_sources` | — | — | canonical | -| [Compiler Preprocessing: Direct Compiler Settings](../../docs/user/examples/recipes/compiler-preprocessing.md#direct-compiler-settings) | Supported | explicit compiler; include directories; macros; standard; compiler arguments; exact preprocessing recipe | `tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py::test_direct_fortran_preprocess_invocation_uses_exact_compiler_and_cpp` | — | — | canonical | -| [CLI Commands: Parse And Semantics](../../docs/user/reference/cli-commands.md#parse-and-semantics) | Supported | public parser module and top-level command modes; parse, semantics, `.pyi`, and diagnostic dispatch | `tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py::test_fortran_parser_main_public_api_modes_from_inline_source` | — | — | canonical | -| [Semantic IR: Round Trips And Provenance](../../docs/user/reference/semantic-ir.md#round-trips-and-provenance) | Supported | deterministic source-to-IR conversion; preserved wrapper-relevant facts; checked fixture serialization | `tests/fortran/semantic_ir/semantics/test_fortran_conversion_properties.py::test_generated_fortran_ast_to_semantic_ir_is_deterministic` | — | — | canonical | +| [Inspect a Fortran API: Parse Source Facts](../../docs/user/examples/recipes/inspect-fortran-api.md#parse-source-facts) | Supported | public string, file, path-sequence, and project parser entry points; model traversal; stable source diagnostics | `tests/fortran/infrastructure/parsing/test_public_entrypoints.py::test_parser_public_entrypoint_aliases_and_singular_contracts_use_inline_sources` | — | — | canonical | +| [Compiler Preprocessing: Direct Compiler Settings](../../docs/user/examples/recipes/compiler-preprocessing.md#direct-compiler-settings) | Supported | explicit compiler; include directories; macros; standard; compiler arguments; exact preprocessing recipe | `tests/fortran/infrastructure/preprocessing/test_configuration_and_adapters.py::test_direct_fortran_preprocess_invocation_uses_exact_compiler_and_cpp` | — | — | canonical | +| [CLI Commands: Parse And Semantics](../../docs/user/reference/cli-commands.md#parse-and-semantics) | Supported | public parser module and top-level command modes; parse, semantics, `.pyi`, and diagnostic dispatch | `tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py::test_fortran_parser_main_public_api_modes_from_inline_source` | — | — | canonical | +| [Semantic IR: Round Trips And Provenance](../../docs/user/reference/semantic-ir.md#round-trips-and-provenance) | Supported | deterministic source-to-IR conversion; preserved wrapper-relevant facts; checked fixture serialization | `tests/fortran/infrastructure/semantic_ir/semantics/test_fortran_conversion_properties.py::test_generated_fortran_ast_to_semantic_ir_is_deterministic` | — | — | canonical | | [Data Types: Example](../../docs/user/guide/data-types.md#example) | Supported | source generation; reviewed generated `.pyi`; source build; generated-`.pyi` replay | `tests/fortran/data_types/pipeline/test_generated_scalar_contract.py::test_generated_primitive_scalar_contract_matches_reviewed_package` | `tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py::test_scalar_kind_coverage_uses_compiler_probed_wrapper_types[source]`
`tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py::test_scalar_kind_coverage_uses_compiler_probed_wrapper_types[generated-pyi]` | — | canonical | | [Data Types: Calling from Python](../../docs/user/guide/data-types.md#calling-from-python) | Supported | signed integer; real; complex; Boolean; exact visible values and scalar result types | `tests/fortran/data_types/codegen/test_primitive_scalar_result_lowering.py::test_direct_scalar_results_preserve_numpy_types_with_python_bool_as_the_exception[Complex128-NPY_COMPLEX128-numpy]` | `tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py::test_scalar_kind_coverage_uses_compiler_probed_wrapper_types[source]` | — | canonical | | [Data Types: Scalar Type Mapping](../../docs/user/guide/data-types.md#scalar-type-mapping) | Supported | `Bool`/`Bool8/16/32/64`; `Int8/16/32/64`; `Float32/64`; `Complex64/128`; compiler-probed intrinsic, ISO environment, and ISO C kinds | `tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py::test_intrinsic_builtin_kinds_map_to_semantic_types`
`tests/fortran/data_types/probes/test_fortran_type_probes.py::test_fortran_type_probe_evaluates_collected_semantic_requirements`
`tests/fortran/data_types/probes/test_fortran_type_probes.py::test_fortran_type_probe_resolves_supported_logical_storage_widths` | `tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py::test_scalar_kind_coverage_uses_compiler_probed_wrapper_types[source]` | — | canonical | @@ -63,7 +63,7 @@ Authoritative sources: | [Strings: String Arrays](../../docs/user/guide/strings.md#string-arrays) | Supported | fixed itemsize; input and in-place mutation; fixed array result; rank/shape/dtype/writeability; zero size | `tests/fortran/strings/codegen/test_character_array_lowering.py::test_fixed_width_character_array_results_reuse_the_ordinary_array_copy_plan` | `tests/fortran/strings/end_to_end/test_documented_string_journey.py::test_documented_edited_pyi_distinguishes_values_scalar_storage_and_string_arrays`
`tests/fortran/strings/end_to_end/test_character_boundaries.py::test_modern_fortran_character_arguments_and_results[source]` | `tests/fortran/strings/end_to_end/test_documented_string_journey.py::test_documented_edited_pyi_distinguishes_values_scalar_storage_and_string_arrays` (`runtime`) | canonical | | [Strings: Length And Encoding](../../docs/user/guide/strings.md#length-and-encoding) | Supported | length 1, representative width 8, runtime length, Unicode UTF-8 byte length, blanks, empty values, embedded NUL rejection, conservative no-`intent`, ambiguous mutable deferred scalar rejection | `tests/fortran/strings/parsing/test_character_length_parsing.py::test_character_entity_lengths_and_assumed_bounds_are_preserved`
`tests/fortran/strings/codegen/test_string_input_lowering.py::test_required_string_values_reuse_argument_plan_with_character_handoff_facts` | `tests/fortran/strings/end_to_end/test_character_boundaries.py::test_modern_fortran_character_arguments_and_results[source]` | `tests/fortran/strings/semantics/test_string_pyi_semantics.py::test_bare_string_slice_is_rejected_as_ambiguous` (`semantics`)
`tests/fortran/strings/end_to_end/test_documented_string_journey.py::test_documented_edited_pyi_distinguishes_values_scalar_storage_and_string_arrays` (`runtime`) | canonical | | [Wrapping Functions: Basic Scalar Function](../../docs/user/guide/wrapping-functions.md#basic-scalar-function) | Supported | direct scalar result; exact NumPy inputs; visible value | `tests/fortran/functions/semantics/test_fortran_function_semantics.py::test_function_result` | `tests/fortran/functions/end_to_end/test_documented_function_journeys.py::test_function_results_outputs_arrays_and_no_intent_replacements_follow_documented_order` | `tests/fortran/functions/end_to_end/test_documented_function_journeys.py::test_function_results_outputs_arrays_and_no_intent_replacements_follow_documented_order` (`runtime`) | canonical | -| [Wrapping Functions: Python And Native Names](../../docs/user/guide/wrapping-functions.md#python-and-native-names) | Supported | edited `.pyi`; standalone external; `@bind`; changed Python name; unchanged native ABI; exact signature | — | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` (`runtime`) | canonical | +| [Wrapping Functions: Python And Native Names](../../docs/user/guide/wrapping-functions.md#python-and-native-names) | Supported | edited `.pyi`; standalone external; `@bind`; changed Python name; unchanged native ABI; exact signature | — | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` (`runtime`) | canonical | | [Wrapping Functions: Array Return Values](../../docs/user/guide/wrapping-functions.md#array-return-values) | Supported | automatic shape; new NumPy array; Fortran layout; values | `tests/fortran/arrays/codegen/test_array_result_lowering.py::test_array_results_record_producer_shape_copy_ownership_and_shared_hidden_slot` | `tests/fortran/functions/end_to_end/test_documented_function_journeys.py::test_function_results_outputs_arrays_and_no_intent_replacements_follow_documented_order` | — | canonical | | [Wrapping Functions: Functions with Output Arguments](../../docs/user/guide/wrapping-functions.md#functions-with-output-arguments) | Supported | direct result first; hidden scalar output second; caller array excluded from tuple; stable tuple order | `tests/fortran/functions/policy/test_function_result_policy.py::test_multiple_scalar_result_policy_completes_order_and_hidden_address_before_planning`
`tests/fortran/functions/codegen/test_multiple_function_results.py::test_multiple_scalar_results_lower_to_binding_tuple_and_one_bridge_function_call` | `tests/fortran/functions/end_to_end/test_documented_function_journeys.py::test_function_results_outputs_arrays_and_no_intent_replacements_follow_documented_order` | `tests/fortran/functions/codegen/test_multiple_function_results.py::test_multiple_scalar_result_validation_rejects_position_and_consumer_drift` (`codegen`) | canonical | | [Wrapping Functions: Important Rules](../../docs/user/guide/wrapping-functions.md#important-rules) | Supported | exact dtype; array copy result; projected scalar tuple order; caller array mutation; conservative no-`intent` scalar replacement after direct result | `tests/fortran/functions/semantics/test_fortran_function_semantics.py::test_missing_intent_scalar_uses_conservative_replacement_projection`
`tests/fortran/functions/policy/test_function_result_policy.py::test_scalar_copy_in_out_policy_completes_writeback_before_planning`
`tests/fortran/functions/codegen/test_scalar_function_writeback.py::test_scalar_writeback_is_an_explicit_binding_lifecycle_result` | `tests/fortran/functions/end_to_end/test_documented_function_journeys.py::test_function_results_outputs_arrays_and_no_intent_replacements_follow_documented_order` | — | canonical | @@ -71,12 +71,12 @@ Authoritative sources: | [Wrapping Subroutines: Complete Example](../../docs/user/guide/wrapping-subroutines.md#complete-example) | Supported | source build; hidden bounds tuple; in-place array scaling; scalar replacement; caller output storage | `tests/fortran/subroutines/policy/test_subroutine_output_policy.py::test_source_hidden_scalar_output_completes_call_local_address_before_planning` | `tests/fortran/subroutines/end_to_end/test_documented_subroutine_journey.py::test_subroutine_outputs_and_caller_storage_follow_documented_projection_rules` | — | canonical | | [Wrapping Subroutines: Python Usage](../../docs/user/guide/wrapping-subroutines.md#python-usage) | Supported | exact NumPy values; scalar object unchanged; arrays mutated in place; visible outputs | — | `tests/fortran/subroutines/end_to_end/test_documented_subroutine_journey.py::test_subroutine_outputs_and_caller_storage_follow_documented_projection_rules` | `tests/fortran/subroutines/end_to_end/test_documented_subroutine_journey.py::test_subroutine_outputs_and_caller_storage_follow_documented_projection_rules` (`runtime`) | canonical | | [Wrapping Subroutines: Key Rules](../../docs/user/guide/wrapping-subroutines.md#key-rules) | Supported | hidden scalar ordering; explicit scalar writeback lifecycle; ordinary arrays and derived objects excluded from result; native-created allocatable returned; `.pyi` projection authority | `tests/fortran/subroutines/codegen/test_hidden_scalar_outputs.py::test_hidden_scalar_result_is_one_bridge_output_and_one_python_result`
`tests/fortran/subroutines/codegen/test_scalar_subroutine_writeback_validation.py::test_generator_rejects_writeback_without_python_result_target` | `tests/fortran/subroutines/end_to_end/test_documented_subroutine_journey.py::test_subroutine_outputs_and_caller_storage_follow_documented_projection_rules` | `tests/fortran/subroutines/codegen/test_scalar_subroutine_writeback_validation.py::test_generator_rejects_writeback_from_an_unavailable_handoff` (`codegen`) | canonical | -| [Wrapping Modules: Basic Usage](../../docs/user/guide/wrapping-modules.md#basic-usage) | Supported | generated package entry; child native-module namespace; isolated import | `tests/fortran/modules/pipeline/test_generated_module_contracts.py::test_generated_module_contract_matches_fixture[module_exports]` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | — | canonical | -| [Wrapping Modules: Procedures](../../docs/user/guide/wrapping-modules.md#procedures) | Supported | module functions; standalone external at root; multiple native modules in one source | `tests/fortran/modules/pipeline/test_generated_module_contracts.py::test_generated_module_contract_matches_fixture[module_exports]` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | — | canonical | +| [Wrapping Modules: Basic Usage](../../docs/user/guide/wrapping-modules.md#basic-usage) | Supported | generated package entry; child native-module namespace; isolated import | `tests/fortran/modules/pipeline/test_generated_module_contracts.py::test_generated_module_contract_matches_fixture[module_exports]` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | — | canonical | +| [Wrapping Modules: Procedures](../../docs/user/guide/wrapping-modules.md#procedures) | Supported | module functions; standalone external at root; multiple native modules in one source | `tests/fortran/modules/pipeline/test_generated_module_contracts.py::test_generated_module_contract_matches_fixture[module_exports]` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | — | canonical | | [Wrapping Modules: Public Variables and Constants](../../docs/user/guide/wrapping-modules.md#public-variables-and-constants) | Supported | writable scalar state; true parameter; Python-local constant shadow; native state unchanged | `tests/fortran/modules/policy/test_module_variable_policy.py::test_scalar_module_variable_policy_completes_access_and_storage_before_planning`
`tests/fortran/modules/codegen/test_scalar_module_variable_lowering.py::test_module_variable_plan_contains_only_completed_dispatch_facts` | `tests/fortran/modules/end_to_end/test_module_variables_and_state.py::test_scalar_module_variables_use_attributes_and_parameters_have_no_native_setter[source]` | — | canonical | | [Wrapping Modules: Module Arrays and Saved State](../../docs/user/guide/wrapping-modules.md#module-arrays-saved-state) | Supported | allocatable module array; persistent handle; live NumPy view; mutation; deallocation; procedure-local `save`; shared state across imports | `tests/fortran/modules/policy/test_module_variable_policy.py::test_scalar_module_variable_policy_completes_access_and_storage_before_planning` | `tests/fortran/modules/end_to_end/test_scalar_module_variable_plan.py::test_whole_scalar_module_variable_behavior_uses_canonical_plan`
`tests/fortran/modules/end_to_end/test_module_variables_and_state.py::test_scalar_module_variables_use_attributes_and_parameters_have_no_native_setter[source]` | — | canonical | -| [Wrapping Modules: Shape the Module API With the Contract](../../docs/user/guide/wrapping-modules.md#shape-the-module-api-with-the-contract) | Supported | mutable literal initializer; hidden variable; private procedure; removed declaration; true `Final` constant | `tests/fortran/pyi_contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_literal_defaults_are_preserved`
`tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | `tests/fortran/pyi_contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_expression_defaults_are_rejected[from prik.contracts import Int32\ncounter: Int32 = f(42)\n]` (`semantics`)
`tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_unsupported_module_variable_initializer_completes_an_unsupported_policy` (`policy`) | canonical | -| [Wrapping Modules: Flatten Module Namespaces](../../docs/user/guide/wrapping-modules.md#flatten-module-namespaces) | Supported | child namespaces; wildcard flattening; selective imports; explicit aliases; unchanged native targets | `tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_rejects_colliding_wildcard_exports` (`pipeline`) | canonical | +| [Wrapping Modules: Shape the Module API With the Contract](../../docs/user/guide/wrapping-modules.md#shape-the-module-api-with-the-contract) | Supported | mutable literal initializer; hidden variable; private procedure; removed declaration; true `Final` constant | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_literal_defaults_are_preserved`
`tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_expression_defaults_are_rejected[from prik.contracts import Int32\ncounter: Int32 = f(42)\n]` (`semantics`)
`tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_unsupported_module_variable_initializer_completes_an_unsupported_policy` (`policy`) | canonical | +| [Wrapping Modules: Flatten Module Namespaces](../../docs/user/guide/wrapping-modules.md#flatten-module-namespaces) | Supported | child namespaces; wildcard flattening; selective imports; explicit aliases; unchanged native targets | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_rejects_colliding_wildcard_exports` (`pipeline`) | canonical | | [Wrapping Modules: Important Rules](../../docs/user/guide/wrapping-modules.md#important-rules) | Supported | private declarations hidden; common-block storage internal; shared native state; source-derived extension identity | `tests/fortran/modules/semantics/test_module_contract_semantics.py::test_module_common_block_storage_stays_internal` | `tests/fortran/modules/end_to_end/test_common_blocks.py::test_common_block_storage_stays_internal_to_wrapped_fortran[source]`
`tests/fortran/modules/end_to_end/test_module_variables_and_state.py::test_scalar_module_variables_use_attributes_and_parameters_have_no_native_setter[source]` | — | canonical | | [Optional Arguments: Complete Example](../../docs/user/guide/optional-arguments.md#complete-example) | Supported | source generation; reviewed generated `.pyi`; optional scalar input; optional ordinary array output; native `present(...)` | `tests/fortran/optional_arguments/pipeline/test_generated_optional_contracts.py::test_generated_optional_contract_matches_fixture[foptional_f90]` | `tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py::test_optional_arguments_drive_fortran_present_behavior[source]`
`tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py::test_optional_arguments_drive_fortran_present_behavior[generated-pyi]` | — | canonical | | [Optional Arguments: Usage in Python](../../docs/user/guide/optional-arguments.md#usage-in-python) | Supported | omission; explicit `None`; positional value; keyword value; skipped earlier positions | `tests/fortran/optional_arguments/codegen/test_optional_lowering.py::test_optional_scalar_lowering_distinguishes_absent_or_none_from_value` | `tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py::test_optional_arguments_drive_fortran_present_behavior[source]` | `tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py::test_optional_arguments_drive_fortran_present_behavior[source]` (`runtime`) | canonical | @@ -88,21 +88,21 @@ Authoritative sources: | [Generic Interfaces: Generated Contract](../../docs/user/guide/generic-interfaces.md#generated-contract) | Supported | private link targets; one exact overload candidate per declaration; public-generic `@bind`; native target precedence | `tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_resolves_prik_overload_by_explicit_specific_name`
`tests/fortran/generic_interfaces/policy/test_generic_policy.py::test_module_overload_bind_takes_precedence_per_candidate` | `tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py::test_fortran_generic_interfaces_dispatch_in_generated_c_extension[source]` | — | canonical | | [Generic Interfaces: Usage in Python](../../docs/user/guide/generic-interfaces.md#usage-in-python) | Supported | exact `Int32`, `Float64`, and `Complex128`; scalar and rank-one dispatch; generated-class dispatch; no implicit coercion | `tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_plan_records_one_exact_numpy_scalar_predicate_per_candidate` | `tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py::test_fortran_generic_interfaces_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py::test_fortran_generic_interfaces_dispatch_in_generated_c_extension[source]` (`runtime`) | canonical | | [Generic Interfaces: Inspect the Overloads](../../docs/user/guide/generic-interfaces.md#inspect-the-overloads) | Supported | one public callable; all accepted signatures; hidden concrete procedures and internal names | — | `tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py::test_fortran_generic_interfaces_dispatch_in_generated_c_extension[source]` | — | canonical | -| [Generic Interfaces: Extend an Overload Set](../../docs/user/guide/generic-interfaces.md#extend-an-overload-set) | Supported | edited `.pyi`; renamed public binding; added overload group; private-specific routing through public generic; absent candidate rejection | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_module_specifics_without_bind-missing_targets0]` (`compiling`) | canonical | +| [Generic Interfaces: Extend an Overload Set](../../docs/user/guide/generic-interfaces.md#extend-an-overload-set) | Supported | edited `.pyi`; renamed public binding; added overload group; private-specific routing through public generic; absent candidate rejection | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_module_specifics_without_bind-missing_targets0]` (`compiling`) | canonical | | [Generic Interfaces: Key Rules](../../docs/user/guide/generic-interfaces.md#key-rules) | Supported | exact dtype/rank/class match; no-match `TypeError`; ambiguous signature rejection; exact-once specific links; `@bind`; private visibility; type-bound generics; defined operators; defined assignment | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_module_and_type_bound_generic_overload_sets`
`tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators`
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_plan_records_one_exact_numpy_scalar_predicate_per_candidate` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generator_rejects_ambiguous_edited_overload_plan_before_emission` (`codegen`)
`tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_rejects_invalid_prik_overload_links[@overload("missing")\ndef convert(value: Int32) -> Int32: ...\n-missing specific procedure 'missing']` (`semantics`) | canonical | | [Generic Interfaces: Limitations](../../docs/user/guide/generic-interfaces.md#limitations) | Blocked | source generic constructor inference; assumed-type `class(*)`; arrays of derived values | — | — | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_rejects_generic_constructor_interfaces_during_semantic_conversion` (`semantics`)
`tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py::test_assumed_type_generic_candidate_is_rejected_at_parsing` (`parsing`)
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generic_candidate_with_array_of_derived_values_is_blocked_before_lowering` (`codegen`) | canonical | | [Wrapping Derived Types: Complete Example](../../docs/user/guide/wrapping-derived-types.md#complete-example) | Supported | derived declarations; public and nested fields; source generation; reviewed generated `.pyi`; source build; generated-`.pyi` replay | `tests/fortran/derived_types/parsing/test_derived_type_declarations.py::test_derived_type_fields_and_methods_detection`
`tests/fortran/derived_types/pipeline/test_generated_derived_contracts.py::test_generated_derived_contract_matches_fixture[fderived_boundary_f90]` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[source]`
`tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[generated-pyi]` | — | canonical | | [Wrapping Derived Types: Usage in Python](../../docs/user/guide/wrapping-derived-types.md#usage-in-python) | Supported | keyword construction; public field get/set; `intent(inout)` identity; owned result; nested borrowed component | `tests/fortran/derived_types/policy/test_derived_policy_defaults.py::test_recursive_module_policy_map_includes_nested_fields_and_functions` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[source]` | — | canonical | -| [Wrapping Derived Types: Inspect the Class](../../docs/user/guide/wrapping-derived-types.md#inspect-the-class) | Supported | class, constructor, field, method, parameter, return, and overload docstrings; no native implementation names | `tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_and_method_reuse_completed_direct_function_plans`
`tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | +| [Wrapping Derived Types: Inspect the Class](../../docs/user/guide/wrapping-derived-types.md#inspect-the-class) | Supported | class, constructor, field, method, parameter, return, and overload docstrings; no native implementation names | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_and_method_reuse_completed_direct_function_plans`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | | [Wrapping Derived Types: Key Concepts](../../docs/user/guide/wrapping-derived-types.md#key-concepts) | Supported | Python-owned construction/result; parent-retained component; in-place output/inout/no-`intent`; primitive writable fields; nested types; keyword defaults; destruction | `tests/fortran/derived_types/policy/test_derived_accessor_policy.py::test_derived_field_setter_policy_uses_value_copy_write_through`
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_projected_derived_argument_returns_the_exact_caller_wrapper_without_release` | `tests/fortran/derived_types/end_to_end/test_default_constructors_and_finalizers.py::test_fortran_default_constructor_keywords_and_finalization[source]`
`tests/fortran/derived_types/end_to_end/test_borrowed_components.py::test_borrowed_child_wrapper_never_finalizes_native_component[source]` | — | canonical | -| [Wrapping Derived Types: Custom Constructor](../../docs/user/guide/wrapping-derived-types.md#custom-constructor) | Supported | edited `.pyi`; `@bind`; exactly one `Pass()`; reordered `Addr(Arg)` values; replacement of generated keyword initializer; constructor docs | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bound_constructor_uses_explicit_pass_position_and_native_target`
`tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_pass_disambiguates_same_type_arguments_and_keeps_module_export` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected[\nclass state:\n @bind("init_state")\n @native_call([Addr(Arg(0))])\n def __init__(self, seed: Int32) -> None: ...\n-Bound constructor native_call requires exactly one Pass() entry]` (`semantics`) | canonical | +| [Wrapping Derived Types: Custom Constructor](../../docs/user/guide/wrapping-derived-types.md#custom-constructor) | Supported | edited `.pyi`; `@bind`; exactly one `Pass()`; reordered `Addr(Arg)` values; replacement of generated keyword initializer; constructor docs | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bound_constructor_uses_explicit_pass_position_and_native_target`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_pass_disambiguates_same_type_arguments_and_keeps_module_export` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected[\nclass state:\n @bind("init_state")\n @native_call([Addr(Arg(0))])\n def __init__(self, seed: Int32) -> None: ...\n-Bound constructor native_call requires exactly one Pass() entry]` (`semantics`) | canonical | | [Wrapping Derived Types: Type-Bound Methods](../../docs/user/guide/wrapping-derived-types.md#type-bound-methods) | Supported | passed object becomes `self`; mutation preserves Python identity; direct and generated-`.pyi` replay | `tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py::test_converter_covers_derived_dispatch_methods_and_kind_edges` | `tests/fortran/derived_types/end_to_end/test_type_bound_methods.py::test_modern_fortran_derived_type_exposes_class_and_type_bound_methods[source]`
`tests/fortran/derived_types/end_to_end/test_type_bound_methods.py::test_modern_fortran_derived_type_exposes_class_and_type_bound_methods[generated-pyi]` | — | canonical | -| [Wrapping Derived Types: Expose a Module Procedure as a Method](../../docs/user/guide/wrapping-derived-types.md#expose-a-module-procedure-as-a-method) | Supported | edited class method; `Pass()` receiver; independent module declaration; same or bound native name; optional private module surface; method docs | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_method_and_module_declarations_keep_native_targets_independent`
`tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_module_procedure_method_visibility_is_completed_independently` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | -| [Wrapping Derived Types: Type-Bound Generics](../../docs/user/guide/wrapping-derived-types.md#type-bound-generics) | Supported | private specifics; public generic bind; exact `Int32`/`Float64` dispatch; wrapped receiver fixed by class; no trial calls | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets`
`tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` (`runtime`) | canonical | +| [Wrapping Derived Types: Expose a Module Procedure as a Method](../../docs/user/guide/wrapping-derived-types.md#expose-a-module-procedure-as-a-method) | Supported | edited class method; `Pass()` receiver; independent module declaration; same or bound native name; optional private module surface; method docs | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_method_and_module_declarations_keep_native_targets_independent`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_module_procedure_method_visibility_is_completed_independently` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | +| [Wrapping Derived Types: Type-Bound Generics](../../docs/user/guide/wrapping-derived-types.md#type-bound-generics) | Supported | private specifics; public generic bind; exact `Int32`/`Float64` dispatch; wrapped receiver fixed by class; no trial calls | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` (`runtime`) | canonical | | [Wrapping Derived Types: Defined Operators](../../docs/user/guide/wrapping-derived-types.md#defined-operators) | Supported | direct/reflected binary; unary; comparison; logical; named operators; defined assignment; exact wrapped/scalar dispatch; operator docstrings | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` (`runtime`) | canonical | | [Fortran Wrapper: Derived Types Across Procedure Boundaries](../../docs/user/reference/fortran-wrapper.md#derived-types-across-procedure-boundaries) | Supported | complete scalar actual/dummy matrix; module and nonmodule storage; ordinary, target, allocatable, allocatable-target, pointer; six dummy forms; identity, writeback, empty states, rollback, lifetime, and deliberate blockers | `tests/fortran/derived_types/codegen/test_scalar_actual_dummy_plan.py::test_every_dummy_form_has_one_exhaustive_completed_matrix[object_dummy-object]` | `tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_all_sixty_actual_dummy_cells[A-module_object]`
`tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_one_call_uses_all_six_dummy_forms_and_optional_arguments_stay_linear`
`tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_later_acquisition_failure_rolls_back_earlier_origins` | `tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_reassociable_pointer_dummy_requires_pointer_storage[module_object]` (`runtime`)
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_unsupported_derived_shapes_fail_on_exact_completed_policy_blockers[\nfrom prik.contracts import Float64\n\nclass point:\n x: Float64\n\ndef consume(value: point[:]) -> None: ...\n-unsupported array of derived values]` (`codegen`) | canonical | | [Fortran Wrapper: Inheritance And Polymorphism](../../docs/user/reference/fortran-wrapper.md#inheritance-and-polymorphism) | Partially supported | scalar extension inheritance; closed `class(base), intent(in)` dispatch; exact extension classes; unsupported polymorphic results, mutation, arrays, descriptor scalars, and assumed type | `tests/fortran/derived_types/codegen/test_class_surfaces.py::test_inheritance_and_polymorphism_are_completed_before_planning` | `tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py::test_fortran_extension_types_generate_python_inheritance[source]`
`tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py::test_fortran_extension_types_generate_python_inheritance[generated-pyi]` | `tests/fortran/derived_types/codegen/test_class_surfaces.py::test_invalid_class_graph_fails_before_emission` (`codegen`)
`tests/fortran/derived_types/policy/test_derived_accessor_policy.py::test_abstract_type_and_deferred_binding_fail_in_completed_derived_policy` (`policy`) | canonical | -| [Fortran Wrapper: Constructors, Initialization, And Finalizers](../../docs/user/reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | Supported | generated keyword constructor; default field values; custom direct constructor; overloaded constructors; commit-on-success; exact finalization; borrowed non-finalization | `tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py::test_derived_type_initializers_and_finalizers_reach_semantic_ir`
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_owned_derived_result_has_explicit_failure_and_release_lifecycle` | `tests/fortran/derived_types/end_to_end/test_default_constructors_and_finalizers.py::test_fortran_default_constructor_keywords_and_finalization[source]`
`tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract`
`tests/fortran/derived_types/end_to_end/test_borrowed_components.py::test_borrowed_child_wrapper_never_finalizes_native_component[source]` | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected[\nclass state:\n def __init__(self, seed: Int32) -> None: ...\n-Non-generated __init__ declarations must use @bind("specific_name")]` (`semantics`) | canonical | +| [Fortran Wrapper: Constructors, Initialization, And Finalizers](../../docs/user/reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | Supported | generated keyword constructor; default field values; custom direct constructor; overloaded constructors; commit-on-success; exact finalization; borrowed non-finalization | `tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py::test_derived_type_initializers_and_finalizers_reach_semantic_ir`
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_owned_derived_result_has_explicit_failure_and_release_lifecycle` | `tests/fortran/derived_types/end_to_end/test_default_constructors_and_finalizers.py::test_fortran_default_constructor_keywords_and_finalization[source]`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract`
`tests/fortran/derived_types/end_to_end/test_borrowed_components.py::test_borrowed_child_wrapper_never_finalizes_native_component[source]` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected[\nclass state:\n def __init__(self, seed: Int32) -> None: ...\n-Non-generated __init__ declarations must use @bind("specific_name")]` (`semantics`) | canonical | | [Fortran Wrapper: Derived-Type Layout And Interoperability](../../docs/user/reference/fortran-wrapper.md#derived-type-layout-and-interoperability) | Supported | opaque accessor storage for ordinary, `bind(C)`, and `sequence`; field get/set; by-value copy; no direct C aggregate access | `tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py::test_bind_c_and_sequence_types_preserve_accessor_layout_metadata`
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_exact_typed_value_lowering_uses_fortran_value_semantics_and_opaque_binding` | `tests/fortran/derived_types/end_to_end/test_opaque_layout.py::test_bind_c_derived_types_use_accessors_and_fortran_value_copy[source]`
`tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_sequence_derived_value_uses_the_same_typed_opaque_call_path` | — | canonical | | [Allocatables: Key Concepts](../../docs/user/guide/allocatables.md#key-concepts) | Supported | scalar value versus array handle; allocated, unallocated, and zero-sized states; live views; module, field, result, and caller-created descriptor origins | `tests/fortran/allocatables/semantics/test_pyi_allocatable_semantics.py::test_persistent_allocatable_descriptors_preserve_scalar_and_array_kinds`
`tests/fortran/allocatables/policy/test_allocatable_handle_policy.py::test_allocatable_array_field_is_wrapper_owned_borrowed_view` | `tests/fortran/allocatables/end_to_end/test_allocatable_handles.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles[source]` | — | canonical | | [Allocatables: When To Use An Allocatable Handle](../../docs/user/guide/allocatables.md#when-to-use-an-allocatable-handle) | Supported | descriptor arguments versus ordinary arrays; present-empty caller handle; dtype/rank compatibility; plain NumPy rejection | `tests/fortran/allocatables/runtime/test_allocatable_descriptor_abi.py::test_allocatable_descriptor_hook_accepts_unallocated_descriptor_without_numpy_conversion`
`tests/fortran/allocatables/runtime/test_allocatable_array_actual_abi.py::test_array_actual_argument_abi_packer_uses_allocatable_native_array_actual_without_numpy_conversion` | `tests/fortran/allocatables/end_to_end/test_external_allocatable.py::test_standalone_allocatable_argument_accepts_a_caller_created_handle` | `tests/fortran/allocatables/runtime/test_allocatable_contract_handles.py::test_generated_storage_rejects_incompatible_allocatable_contract_handles[-float64-1-TypeError-fresh contract handle]` (`runtime`) | canonical | @@ -179,57 +179,57 @@ Authoritative sources: | [Error Handling: Best Practices](../../docs/user/guide/error-handling.md#best-practices) | Supported | full diagnostic first; verbose command replay; debug traceback only on demand; edited-contract inspection; risky callback isolation | `tests/fortran/error_handling/parsing/test_fortran_diagnostics.py::test_parse_error_message_includes_filename_and_lineno`
`tests/fortran/error_handling/compiling/test_verbose_commands.py::test_run_command_verbose_prints_replayable_command` | `tests/fortran/error_handling/pipeline/test_debug_cli_tracebacks.py::test_cli_debug_flag_reraises_parse_errors`
`tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py::test_immediate_scalar_dummy_procedure_calls_python_callback[source]` | — | canonical | | [Fortran Wrapper: Wrapper Errors And Fortran Errors](../../docs/user/reference/fortran-wrapper.md#wrapper-errors-and-fortran-errors) | Supported | ordinary wrapper exceptions; no inferred application convention; opt-in status/message projection; cleanup after failure; native termination remains unrecoverable | `tests/fortran/error_handling/codegen/test_status_error_lowering.py::test_direct_binding_lowering_places_only_opted_in_native_call_outside_the_gil`
`tests/fortran/error_handling/codegen/test_status_error_lowering.py::test_fixed_message_bridge_copy_requires_its_completed_reason` | `tests/fortran/error_handling/end_to_end/test_status_projection.py::test_status_projection_consumes_outputs_raises_message_and_recovers` | `tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py::test_immediate_scalar_dummy_procedure_calls_python_callback[source]` (`runtime`) | canonical | | [Feature Matrix: Runtime Error Projection, GIL Policy, Recursion, OpenMP Path, And GNU ABI Checks](../../docs/user/language-support/feature-matrix.md#supported-runtime-features) | Supported | status error and message; completed GIL envelope; recursion/OpenMP/ABI remain separately owned; no caller synchronization inference | `tests/fortran/error_handling/codegen/test_status_error_lowering.py::test_planner_records_editable_native_runtime_and_status_error_facts` | `tests/fortran/error_handling/end_to_end/test_status_projection.py::test_status_projection_consumes_outputs_raises_message_and_recovers` | — | canonical | -| [Building The Shared Library: Build](../../docs/user/guide/building-shared-library.md#build) | Supported | source input; default and explicit module names; build directory; generated sources; importable shared library | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_source_build_result_records_structured_native_plan`
`tests/fortran/building_shared_library/pipeline/test_source_generated_contracts.py::test_source_build_generated_pyi_contract_matches_fixture[fruntime_abi_f90]` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_documented_readme_points_example_builds_and_imports` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_empty_source_list` (`pipeline`)
`tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_missing_source` (`pipeline`) | canonical | -| [Building The Shared Library: Import](../../docs/user/guide/building-shared-library.md#import) | Supported | ABI-suffixed artifact; stable module import name; explicit output name; root-function name collision avoidance | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_dir_separates_abi_artifact_from_cli_alias` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_names_importable_shared_library`
`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` | — | canonical | -| [Building The Shared Library: Multiple Source Files](../../docs/user/guide/building-shared-library.md#multiple-source-files) | Supported | caller order; contained-module namespaces; standalone externals; one merged extension; generated and edited contract parity | `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package` | `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_file_modules_build_one_merged_extension`
`tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_file_standalone_procedures_build_one_merged_extension` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_missing_module_directory_reports_compile_error` (`compiling`) | canonical | -| [Building The Shared Library: Use A Makefile](../../docs/user/guide/building-shared-library.md#use-a-makefile) | Supported | generation without compilation; editable compiler and flags; ordered source dependencies; GNU Make build; manifest regeneration and replay | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_pyi_makefile_manifest_and_replay_workflows` | `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_generation_verbose_combination[makefile]` (`pipeline`) | canonical | -| [Building The Shared Library: Compatibility](../../docs/user/guide/building-shared-library.md#compatibility) | Supported | target ABI; debug and optimized wrappers; top-level kind flags; platform-specific extension; rebuildable native artifacts | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py::test_top_level_native_kind_flags_drive_internal_type_measurement` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py::test_debug_and_optimized_wrapper_builds_preserve_runtime_abi` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_incompatible_native_artifact_reports_linker_error` (`compiling`) | canonical | -| [Fortran Wrapper: Building And Importing A Wrapper](../../docs/user/reference/fortran-wrapper.md#building-and-importing-a-wrapper) | Supported | fixed and free source forms; direct source and source-free `.pyi` entry routes; explicit native artifacts; output placement; verbose commands | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_generated_pyi_fixture_builds_from_native_object_without_source_reparse`
`tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_verbose_mode_prints_full_direct_build_commands` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_scale_runtime_contract[source]`
`tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_scale_runtime_contract[generated-pyi]` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_pyi_cli_requires_a_native_link_input` (`pipeline`) | canonical | -| [Fortran Wrapper: Wrapper Build Mechanism](../../docs/user/reference/fortran-wrapper.md#wrapper-build-mechanism) | Supported | ordered source preprocessing through parsing, semantics, completed policy, wrapper plan, direct lowering, compilation, and one extension link | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_generate_sources_cli_writes_wrapper_sources_without_native_outputs`
`tests/fortran/building_shared_library/pipeline/test_source_generated_contracts.py::test_source_build_generated_pyi_contract_matches_fixture[verbose_api]` | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_internal_preprocessing_mode_still_builds_importable_runtime_wrapper` | — | canonical | -| [Fortran Wrapper: Native Build Plan In Build Results](../../docs/user/reference/fortran-wrapper.md#native-build-plan-in-build-results) | Supported | semantic sources separate from compilation units; produced and prebuilt artifacts; module/include/library directories; ordered object, archive, shared, named-library, and linker-argument items | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_native_link_plan_serializes_interleaved_item_kinds`
`tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_pyi_cli_preserves_explicit_ordered_link_items` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_mixed_module_external_bundle_resolves_all_native_input_kinds` | — | canonical | -| [Fortran Wrapper: Multiple Sources And Build Modes](../../docs/user/reference/fortran-wrapper.md#multiple-sources-and-build-modes) | Supported | compiler-valid caller order; module and external merging; source/generated contract runtime parity; modified entry exports | `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package` | `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`
`tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_source_modified_entry_preserves_modules_and_adds_documented_alias` | — | canonical | -| [Fortran Wrapper: Semantic Stub Output](../../docs/user/reference/fortran-wrapper.md#semantic-stub-output) | Supported | one flat combined package; one entry; native module leaves; no per-source or synthetic directory; entry-only semantic input | `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package`
`tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_generated_pyi_matches_checked_in_fixture` | `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` | — | canonical | -| [Fortran Wrapper: Editable Makefile](../../docs/user/reference/fortran-wrapper.md#editable-makefile) | Supported | resolved compiler; Fortran and C wrapper flags; ordered source prerequisites; manifest-backed `.pyi` generation and replay | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_pyi_makefile_manifest_and_replay_workflows` | `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build` | — | canonical | -| [Fortran Wrapper: Advanced Multi-Source Integration](../../docs/user/reference/fortran-wrapper.md#advanced-multi-source-integration) | Partially supported | explicit caller-ordered sources, module directories, libraries, and runtime paths; no automatic dependency, prebuilt-module, or external-library discovery | `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` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_required_transitive_named_library_resolves_runtime_symbol` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_missing_module_directory_reports_compile_error` (`compiling`) | canonical | -| [Semantic `.pyi`: Native Artifacts And Link Resolution](../../docs/user/reference/semantic-pyi-format.md#native-artifacts-and-link-resolution) | Supported | no filename inference; objects, archives, direct and named shared libraries; transitive providers; archive groups; missing/duplicate/incompatible artifact diagnostics | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_pyi_cli_preserves_explicit_ordered_link_items`
`tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_static_archive_groups_resolve_cyclic_archive_dependencies` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_mixed_module_external_bundle_resolves_all_native_input_kinds` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_missing_symbol_reports_native_link_or_loader_error` (`import`)
`tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_duplicate_native_definitions_report_linker_error` (`compiling`) | canonical | -| [Semantic `.pyi`: Contract Imports](../../docs/user/reference/semantic-pyi-format.md#contract-imports) | Supported | explicit `prik.contracts` imports; arbitrary aliases; missing imports rejected; ordinary and relative imports preserved | `tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_convert_pyi_to_ir_requires_imported_contract_types`
`tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_follows_arbitrary_contract_aliases` | — | `tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_convert_pyi_to_ir_requires_imported_contract_types` (`semantics`) | canonical | -| [Semantic `.pyi`: Misuse, Diagnostics And Risk](../../docs/user/reference/semantic-pyi-format.md#misuse-diagnostics-and-risk) | Supported | syntax, semantic shape, native contract, policy, and unsafe-boundary diagnostics; filename-aware failures; no silent fallback | `tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_pyi_file_to_semantic_module_and_modules_forward_module_name_encoding_and_filename` | — | `tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_pyi_parser_reports_unsupported_lines_and_invalid_helpers` (`parsing`)
`tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` (`pipeline`) | canonical | -| [Semantic `.pyi`: File Shape](../../docs/user/reference/semantic-pyi-format.md#file-shape) | Supported | Python AST boundary; imports, annotated declarations, classes, ellipsis-only functions, and supported decorators | `tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_pyi_parser_returns_python_ast_only`
`tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_convert_pyi_to_ir_accepts_parsed_pyi_ast_only` | — | `tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_pyi_parser_reports_unsupported_lines_and_invalid_helpers` (`parsing`) | canonical | -| [Semantic `.pyi`: Imported Derived-Type Identity](../../docs/user/reference/semantic-pyi-format.md#imported-derived-type-identity) | Supported | direct, aliased, relative, qualified, opaque, and edited wrapped external type identity | `tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_reconciles_opaque_and_edited_external_types`
`tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_reconciles_relative_namespace_type_refs` | — | `tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_handles_duplicate_roots_and_ambiguous_module_names` (`semantics`) | canonical | -| [Semantic `.pyi`: Contract Files And Native Procedure Placement](../../docs/user/reference/semantic-pyi-format.md#contract-files-and-native-procedure-placement) | Supported | entry contract; native module leaves; standalone root declarations; multiple modules; same-name module collision | `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py::test_multi_module_generation_keeps_each_native_namespace`
`tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py::test_same_named_module_uses_init_entry_and_keeps_externals_at_root` | `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | -| [Semantic `.pyi`: Contained Module Procedures](../../docs/user/reference/semantic-pyi-format.md#contained-module-procedures) | Supported | filename-selected native module scope; child Python namespace; exact native procedure name | `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py::test_module_generation_writes_explicit_package_entry_and_native_leaf`
`tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_generated_native_scope_comes_from_contract_filename` | `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | -| [Semantic `.pyi`: Standalone Procedures](../../docs/user/reference/semantic-pyi-format.md#standalone-procedures) | Supported | `@standalone`; entry placement; multiple root procedures; no invented module scope | `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py::test_standalone_generation_writes_explicit_package_entry`
`tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_generated_standalone_contract_retains_standalone_native_placement` | — | — | canonical | -| [Semantic `.pyi`: Source-To-Contract Layout](../../docs/user/reference/semantic-pyi-format.md#source-to-contract-layout) | Supported | module-only, standalone-only, mixed, multi-module, same-name, and transitive-import source layouts | `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py::test_import_graph_generation_writes_entry_and_native_leaves`
`tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py::test_multi_module_generation_keeps_each_native_namespace` | `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | -| [Semantic `.pyi`: Root Export Contract](../../docs/user/reference/semantic-pyi-format.md#root-export-contract) | Supported | module import, selective symbol export, alias, support-import exclusion, and collision rejection | `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_checked_entry_discovers_its_complete_contract_package[contract_import_graph]`
`tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_rejects_colliding_wildcard_exports` (`pipeline`) | canonical | -| [Semantic `.pyi`: Entry Contract And Extension Identity](../../docs/user/reference/semantic-pyi-format.md#entry-contract-and-extension-identity) | Supported | `__init__.pyi` parent identity; explicit output identity; leaf identity; ABI-suffixed shared object | `tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py::test_same_named_module_uses_init_entry_and_keeps_externals_at_root` | `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback`
`tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_dir_separates_abi_artifact_from_cli_alias` | — | canonical | -| [Semantic `.pyi`: Contract Import Graph](../../docs/user/reference/semantic-pyi-format.md#contract-import-graph) | Supported | recursive relative imports; deterministic discovery order; parse cache; missing file and cycle diagnostics | `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_pyi_contract_bundle_reuses_import_discovery_conversion_cache`
`tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_checked_entry_discovers_its_complete_contract_package[contract_import_graph]` | — | `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_recursive_graph_reports_missing_relative_contract_before_native_validation` (`pipeline`)
`tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_recursive_graph_reports_cycles_before_codegen` (`pipeline`) | canonical | -| [Semantic `.pyi`: Semantic Type Names](../../docs/user/reference/semantic-pyi-format.md#semantic-type-names) | Supported | canonical primitive, wrapper, nested, qualified, aliased, callback, and storage type spellings | `tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_dispatches_nested_and_qualified_semantic_types`
`tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_accepts_aliased_contract_wrapper_names` | — | — | canonical | -| [Semantic `.pyi`: Metadata With `Annotated`](../../docs/user/reference/semantic-pyi-format.md#metadata-with-annotated) | Supported | constraints; source names; layout/copy; immutability; native descriptor and provenance metadata; stable round trip | `tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py::test_pyi_parser_preserves_generic_constraints_as_annotation_metadata`
`tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_preserves_extended_array_metadata_and_nested_selector` | — | `tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_rejects_additional_invalid_storage_forms[value: Annotated[Int32, 'bad']\n-Unsupported Annotated metadata: "'bad'"]` (`semantics`) | canonical | -| [Semantic `.pyi`: Classes And Native Type Markers](../../docs/user/reference/semantic-pyi-format.md#classes-and-native-type-markers) | Supported | ordinary wrapped classes; opaque external classes; field declarations; irreducible native markers | `tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_reconciles_opaque_and_edited_external_types`
`tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_value_projection_round_trips_as_argument_specific_native_transport` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[generated-pyi]` | — | canonical | -| [Semantic `.pyi`: Functions, Methods And Returns](../../docs/user/reference/semantic-pyi-format.md#functions-methods-and-returns) | Supported | direct and tuple returns; named replacement outputs; native-order identity; method receiver; explicit projection | `tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py::test_plain_tuple_return_types_parse_component_returns`
`tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py::test_native_order_outputs_do_not_get_projected_without_native_call` | `tests/fortran/functions/end_to_end/test_documented_function_journeys.py::test_function_results_outputs_arrays_and_no_intent_replacements_follow_documented_order` | — | canonical | +| [Building The Shared Library: Build](../../docs/user/guide/building-shared-library.md#build) | Supported | source input; default and explicit module names; build directory; generated sources; importable shared library | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_source_build_result_records_structured_native_plan`
`tests/fortran/infrastructure/building/pipeline/test_source_generated_contracts.py::test_source_build_generated_pyi_contract_matches_fixture[fruntime_abi_f90]` | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_documented_readme_points_example_builds_and_imports` | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_empty_source_list` (`pipeline`)
`tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_missing_source` (`pipeline`) | canonical | +| [Building The Shared Library: Import](../../docs/user/guide/building-shared-library.md#import) | Supported | ABI-suffixed artifact; stable module import name; explicit output name; root-function name collision avoidance | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_dir_separates_abi_artifact_from_cli_alias` | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_names_importable_shared_library`
`tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_fortran_wrapper_default_module_name_does_not_collide_with_root_function` | — | canonical | +| [Building The Shared Library: Multiple Source Files](../../docs/user/guide/building-shared-library.md#multiple-source-files) | Supported | caller order; contained-module namespaces; standalone externals; one merged extension; generated and edited contract parity | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package` | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_file_modules_build_one_merged_extension`
`tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_file_standalone_procedures_build_one_merged_extension` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_missing_module_directory_reports_compile_error` (`compiling`) | canonical | +| [Building The Shared Library: Use A Makefile](../../docs/user/guide/building-shared-library.md#use-a-makefile) | Supported | generation without compilation; editable compiler and flags; ordered source dependencies; GNU Make build; manifest regeneration and replay | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_pyi_makefile_manifest_and_replay_workflows` | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build` | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_wrapper_build_rejects_generation_verbose_combination[makefile]` (`pipeline`) | canonical | +| [Building The Shared Library: Compatibility](../../docs/user/guide/building-shared-library.md#compatibility) | Supported | target ABI; debug and optimized wrappers; top-level kind flags; platform-specific extension; rebuildable native artifacts | `tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py::test_top_level_native_kind_flags_drive_internal_type_measurement` | `tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py::test_debug_and_optimized_wrapper_builds_preserve_runtime_abi` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_incompatible_native_artifact_reports_linker_error` (`compiling`) | canonical | +| [Fortran Wrapper: Building And Importing A Wrapper](../../docs/user/reference/fortran-wrapper.md#building-and-importing-a-wrapper) | Supported | fixed and free source forms; direct source and source-free `.pyi` entry routes; explicit native artifacts; output placement; verbose commands | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_generated_pyi_fixture_builds_from_native_object_without_source_reparse`
`tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_verbose_mode_prints_full_direct_build_commands` | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_scale_runtime_contract[source]`
`tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_scale_runtime_contract[generated-pyi]` | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_pyi_cli_requires_a_native_link_input` (`pipeline`) | canonical | +| [Fortran Wrapper: Wrapper Build Mechanism](../../docs/user/reference/fortran-wrapper.md#wrapper-build-mechanism) | Supported | ordered source preprocessing through parsing, semantics, completed policy, wrapper plan, direct lowering, compilation, and one extension link | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_generate_sources_cli_writes_wrapper_sources_without_native_outputs`
`tests/fortran/infrastructure/building/pipeline/test_source_generated_contracts.py::test_source_build_generated_pyi_contract_matches_fixture[verbose_api]` | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_internal_preprocessing_mode_still_builds_importable_runtime_wrapper` | — | canonical | +| [Fortran Wrapper: Native Build Plan In Build Results](../../docs/user/reference/fortran-wrapper.md#native-build-plan-in-build-results) | Supported | semantic sources separate from compilation units; produced and prebuilt artifacts; module/include/library directories; ordered object, archive, shared, named-library, and linker-argument items | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_native_link_plan_serializes_interleaved_item_kinds`
`tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_pyi_cli_preserves_explicit_ordered_link_items` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_mixed_module_external_bundle_resolves_all_native_input_kinds` | — | canonical | +| [Fortran Wrapper: Multiple Sources And Build Modes](../../docs/user/reference/fortran-wrapper.md#multiple-sources-and-build-modes) | Supported | compiler-valid caller order; module and external merging; source/generated contract runtime parity; modified entry exports | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package` | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_source_generated_contract_build_matches_source_runtime_and_link_order`
`tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_source_modified_entry_preserves_modules_and_adds_documented_alias` | — | canonical | +| [Fortran Wrapper: Semantic Stub Output](../../docs/user/reference/fortran-wrapper.md#semantic-stub-output) | Supported | one flat combined package; one entry; native module leaves; no per-source or synthetic directory; entry-only semantic input | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package`
`tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_generated_pyi_matches_checked_in_fixture` | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_multi_source_generated_contract_build_matches_source_runtime_and_link_order` | — | canonical | +| [Fortran Wrapper: Editable Makefile](../../docs/user/reference/fortran-wrapper.md#editable-makefile) | Supported | resolved compiler; Fortran and C wrapper flags; ordered source prerequisites; manifest-backed `.pyi` generation and replay | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_pyi_makefile_manifest_and_replay_workflows` | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build` | — | canonical | +| [Fortran Wrapper: Advanced Multi-Source Integration](../../docs/user/reference/fortran-wrapper.md#advanced-multi-source-integration) | Partially supported | explicit caller-ordered sources, module directories, libraries, and runtime paths; no automatic dependency, prebuilt-module, or external-library discovery | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_source_build_reuses_native_plan_for_additional_compile_and_link_inputs` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_required_transitive_named_library_resolves_runtime_symbol` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_missing_module_directory_reports_compile_error` (`compiling`) | canonical | +| [Semantic `.pyi`: Native Artifacts And Link Resolution](../../docs/user/reference/semantic-pyi-format.md#native-artifacts-and-link-resolution) | Supported | no filename inference; objects, archives, direct and named shared libraries; transitive providers; archive groups; missing/duplicate/incompatible artifact diagnostics | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_pyi_cli_preserves_explicit_ordered_link_items`
`tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_static_archive_groups_resolve_cyclic_archive_dependencies` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_mixed_module_external_bundle_resolves_all_native_input_kinds` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_missing_symbol_reports_native_link_or_loader_error` (`import`)
`tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_duplicate_native_definitions_report_linker_error` (`compiling`) | canonical | +| [Semantic `.pyi`: Contract Imports](../../docs/user/reference/semantic-pyi-format.md#contract-imports) | Supported | explicit `prik.contracts` imports; arbitrary aliases; missing imports rejected; ordinary and relative imports preserved | `tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_convert_pyi_to_ir_requires_imported_contract_types`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_follows_arbitrary_contract_aliases` | — | `tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_convert_pyi_to_ir_requires_imported_contract_types` (`semantics`) | canonical | +| [Semantic `.pyi`: Misuse, Diagnostics And Risk](../../docs/user/reference/semantic-pyi-format.md#misuse-diagnostics-and-risk) | Supported | syntax, semantic shape, native contract, policy, and unsafe-boundary diagnostics; filename-aware failures; no silent fallback | `tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_pyi_file_to_semantic_module_and_modules_forward_module_name_encoding_and_filename` | — | `tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py::test_pyi_parser_reports_unsupported_lines_and_invalid_helpers` (`parsing`)
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` (`pipeline`) | canonical | +| [Semantic `.pyi`: File Shape](../../docs/user/reference/semantic-pyi-format.md#file-shape) | Supported | Python AST boundary; imports, annotated declarations, classes, ellipsis-only functions, and supported decorators | `tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py::test_pyi_parser_returns_python_ast_only`
`tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py::test_convert_pyi_to_ir_accepts_parsed_pyi_ast_only` | — | `tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py::test_pyi_parser_reports_unsupported_lines_and_invalid_helpers` (`parsing`) | canonical | +| [Semantic `.pyi`: Imported Derived-Type Identity](../../docs/user/reference/semantic-pyi-format.md#imported-derived-type-identity) | Supported | direct, aliased, relative, qualified, opaque, and edited wrapped external type identity | `tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_reconciles_opaque_and_edited_external_types`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_reconciles_relative_namespace_type_refs` | — | `tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_handles_duplicate_roots_and_ambiguous_module_names` (`semantics`) | canonical | +| [Semantic `.pyi`: Contract Files And Native Procedure Placement](../../docs/user/reference/semantic-pyi-format.md#contract-files-and-native-procedure-placement) | Supported | entry contract; native module leaves; standalone root declarations; multiple modules; same-name module collision | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py::test_multi_module_generation_keeps_each_native_namespace`
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py::test_same_named_module_uses_init_entry_and_keeps_externals_at_root` | `tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | +| [Semantic `.pyi`: Contained Module Procedures](../../docs/user/reference/semantic-pyi-format.md#contained-module-procedures) | Supported | filename-selected native module scope; child Python namespace; exact native procedure name | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py::test_module_generation_writes_explicit_package_entry_and_native_leaf`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_generated_native_scope_comes_from_contract_filename` | `tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | +| [Semantic `.pyi`: Standalone Procedures](../../docs/user/reference/semantic-pyi-format.md#standalone-procedures) | Supported | `@standalone`; entry placement; multiple root procedures; no invented module scope | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py::test_standalone_generation_writes_explicit_package_entry`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_generated_standalone_contract_retains_standalone_native_placement` | — | — | canonical | +| [Semantic `.pyi`: Source-To-Contract Layout](../../docs/user/reference/semantic-pyi-format.md#source-to-contract-layout) | Supported | module-only, standalone-only, mixed, multi-module, same-name, and transitive-import source layouts | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py::test_import_graph_generation_writes_entry_and_native_leaves`
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py::test_multi_module_generation_keeps_each_native_namespace` | `tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | +| [Semantic `.pyi`: Root Export Contract](../../docs/user/reference/semantic-pyi-format.md#root-export-contract) | Supported | module import, selective symbol export, alias, support-import exclusion, and collision rejection | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_checked_entry_discovers_its_complete_contract_package[contract_import_graph]`
`tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_rejects_colliding_wildcard_exports` (`pipeline`) | canonical | +| [Semantic `.pyi`: Entry Contract And Extension Identity](../../docs/user/reference/semantic-pyi-format.md#entry-contract-and-extension-identity) | Supported | `__init__.pyi` parent identity; explicit output identity; leaf identity; ABI-suffixed shared object | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py::test_same_named_module_uses_init_entry_and_keeps_externals_at_root` | `tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback`
`tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_dir_separates_abi_artifact_from_cli_alias` | — | canonical | +| [Semantic `.pyi`: Contract Import Graph](../../docs/user/reference/semantic-pyi-format.md#contract-import-graph) | Supported | recursive relative imports; deterministic discovery order; parse cache; missing file and cycle diagnostics | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_pyi_contract_bundle_reuses_import_discovery_conversion_cache`
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_checked_entry_discovers_its_complete_contract_package[contract_import_graph]` | — | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_recursive_graph_reports_missing_relative_contract_before_native_validation` (`pipeline`)
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_recursive_graph_reports_cycles_before_codegen` (`pipeline`) | canonical | +| [Semantic `.pyi`: Semantic Type Names](../../docs/user/reference/semantic-pyi-format.md#semantic-type-names) | Supported | canonical primitive, wrapper, nested, qualified, aliased, callback, and storage type spellings | `tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_dispatches_nested_and_qualified_semantic_types`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_accepts_aliased_contract_wrapper_names` | — | — | canonical | +| [Semantic `.pyi`: Metadata With `Annotated`](../../docs/user/reference/semantic-pyi-format.md#metadata-with-annotated) | Supported | constraints; source names; layout/copy; immutability; native descriptor and provenance metadata; stable round trip | `tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py::test_pyi_parser_preserves_generic_constraints_as_annotation_metadata`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_preserves_extended_array_metadata_and_nested_selector` | — | `tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_rejects_additional_invalid_storage_forms[value: Annotated[Int32, 'bad']\n-Unsupported Annotated metadata: "'bad'"]` (`semantics`) | canonical | +| [Semantic `.pyi`: Classes And Native Type Markers](../../docs/user/reference/semantic-pyi-format.md#classes-and-native-type-markers) | Supported | ordinary wrapped classes; opaque external classes; field declarations; irreducible native markers | `tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py::test_pyi_paths_to_semantic_modules_reconciles_opaque_and_edited_external_types`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_value_projection_round_trips_as_argument_specific_native_transport` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[generated-pyi]` | — | canonical | +| [Semantic `.pyi`: Functions, Methods And Returns](../../docs/user/reference/semantic-pyi-format.md#functions-methods-and-returns) | Supported | direct and tuple returns; named replacement outputs; native-order identity; method receiver; explicit projection | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_plain_tuple_return_types_parse_component_returns`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_native_order_outputs_do_not_get_projected_without_native_call` | `tests/fortran/functions/end_to_end/test_documented_function_journeys.py::test_function_results_outputs_arrays_and_no_intent_replacements_follow_documented_order` | — | canonical | | [Semantic `.pyi`: Generic Procedure Overloads](../../docs/user/reference/semantic-pyi-format.md#generic-procedure-overloads) | Supported | explicit specific links; private link targets; native bind; exact signature resolution; deterministic errors | `tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_resolves_prik_overload_by_explicit_specific_name` | `tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py::test_fortran_generic_interfaces_dispatch_in_generated_c_extension[generated-pyi]` | `tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_rejects_invalid_prik_overload_links[@overload("missing")\ndef convert(value: Int32) -> Int32: ...\n-missing specific procedure 'missing']` (`semantics`) | canonical | | [Semantic `.pyi`: Defined Operators And Assignment](../../docs/user/reference/semantic-pyi-format.md#defined-operators-and-assignment) | Supported | direct/reflected/unary/comparison/named operators; explicit mutating assignment method | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[generated-pyi]` | — | canonical | | [Semantic `.pyi`: Allocatable Array Handles](../../docs/user/reference/semantic-pyi-format.md#allocatable-array-handles) | Supported | persistent handle syntax; allocated/unallocated state; live views; field/module/result ownership; explicit copy | `tests/fortran/allocatables/semantics/test_pyi_allocatable_semantics.py::test_persistent_allocatable_descriptors_preserve_scalar_and_array_kinds` | `tests/fortran/allocatables/end_to_end/test_allocatable_handles.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles[generated-pyi]` | `tests/fortran/allocatables/runtime/test_allocatable_contract_handles.py::test_generated_storage_rejects_a_closed_contract_handle` (`runtime`) | canonical | -| [Semantic `.pyi`: Visibility And Names](../../docs/user/reference/semantic-pyi-format.md#visibility-and-names) | Supported | decorator and type-wrapper privacy; source-name metadata; invalid Python identifiers; native binding retained | `tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_preserves_user_private_bound_function_contract`
`tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py::test_generated_pyi_escaping_round_trips_native_names` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | — | canonical | -| [Semantic `.pyi`: Projection Metadata](../../docs/user/reference/semantic-pyi-format.md#projection-metadata) | Supported | ordered `Arg`, `Addr`, `Value`, `Return`, descriptor, length, shape, presence, literal, pass, and workspace entries | `tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py::test_native_call_accepts_hidden_native_values`
`tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py::test_emit_native_call_hidden_native_values` | — | `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` (`pipeline`) | canonical | -| [Semantic `.pyi`: Current Generated Coverage](../../docs/user/reference/semantic-pyi-format.md#current-generated-coverage) | Partially supported | canonical parser/printer round trip; reviewed package layout; authoritative runtime input; documented generated and loaded subsets | `tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py::test_generated_semantic_ir_round_trips_through_pyi`
`tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_checked_contract_package_has_reviewed_files` | `tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | -| [Semantic `.pyi`: Rejected Or Not Yet Supported](../../docs/user/reference/semantic-pyi-format.md#rejected-or-not-yet-supported) | Blocked | unknown types; invalid subscriptions, depth, callable shapes, decorators, bodies, arguments, and overload/projection combinations | — | — | `tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py::test_convert_pyi_to_ir_rejects_invalid_projection_and_type_forms[value: Unknown\n-Unknown semantic type is not allowed in .pyi annotations]` (`semantics`)
`tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_rejects_additional_invalid_storage_forms[value: Float64[ORDER_F]\n-Non-dimensional type subscriptions are not supported; use Final[...] for constants and Annotated[...] for constraints or array metadata]` (`semantics`) | canonical | -| [Semantic `.pyi`: Remaining Format And Runtime Work](../../docs/user/reference/semantic-pyi-format.md#remaining-format-and-runtime-work) | Partially supported | implemented ordered projection and policy dispatch; broader polymorphism, pointer lifetimes, and IDE-only stub separation remain limited | `tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py::test_fortran_to_pyi_and_back_preserves_mixed_input_output_projection` | — | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_rejects_generic_constructor_interfaces_during_semantic_conversion` (`semantics`)
`tests/fortran/allocatables/policy/test_allocatable_result_policy.py::test_direct_allocatable_scalar_function_result_is_blocked_before_codegen` (`policy`) | canonical | -| [`.pyi` Exports And Modules: Choose The Package Shape](../../docs/user/reference/pyi-contracts/exports-and-modules.md#choose-the-package-shape) | Supported | child namespaces; wildcard flattening; selective imports; symbol and module aliases; nested aliases; support-import exclusion; reachable declarations only | `tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering`
`tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_checked_entry_discovers_its_complete_contract_package[contract_import_graph]` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_rejects_colliding_wildcard_exports` (`pipeline`)
`tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_recursive_graph_reports_missing_relative_contract_before_native_validation` (`pipeline`)
`tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_recursive_graph_reports_cycles_before_codegen` (`pipeline`) | canonical | -| [`.pyi` Exports And Modules: Remove Or Hide A Declaration](../../docs/user/reference/pyi-contracts/exports-and-modules.md#remove-or-hide-a-declaration) | Supported | deleted function and variable; `@private`; `private[...]`; class constructor suppression; later class/member/overload runtime owner retained | `tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering`
`tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_removing_constructor_suppresses_generated_keyword_initialization` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | — | canonical | -| [`.pyi` Exports And Modules: Add Or Rename A Native Procedure](../../docs/user/reference/pyi-contracts/exports-and-modules.md#add-or-rename-a-native-procedure) | Supported | added module-leaf declaration; `@bind`; renamed standalone `@standalone`; unchanged native targets; no invented implementation | `tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py::test_convert_pyi_to_ir_preserves_user_private_bound_function_contract` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | — | canonical | -| [`.pyi` Exports And Modules: Set Module Values At Import](../../docs/user/reference/pyi-contracts/exports-and-modules.md#set-module-values-at-import) | Supported | mutable Boolean, integer, real, and complex literals; import-time write-through; `Final` constant distinction; unsupported setter/storage and expression rejection | `tests/fortran/pyi_contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_literal_defaults_are_preserved`
`tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_module_variable_initializer_policy_is_complete_before_ir_lowering`
`tests/fortran/pyi_contracts/exports_and_modules/codegen/test_module_initializer_lowering.py::test_module_variable_literal_families_select_their_c_spelling` | `tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | `tests/fortran/pyi_contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_expression_defaults_are_rejected[from prik.contracts import Int32\ncounter: Int32 = f(42)\n]` (`semantics`)
`tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_unsupported_module_variable_initializer_completes_an_unsupported_policy` (`policy`) | canonical | -| [`.pyi` Functions And Classes: Expose A Module Procedure As A Method](../../docs/user/reference/pyi-contracts/functions-and-classes.md#expose-a-module-procedure-as-a-method) | Supported | retained module declaration; `Pass()` receiver placement; public or private module surface; same or bound method target | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_method_and_module_declarations_keep_native_targets_independent`
`tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_module_procedure_method_visibility_is_completed_independently` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | -| [`.pyi` Functions And Classes: Edit An Overload Set](../../docs/user/reference/pyi-contracts/functions-and-classes.md#edit-an-overload-set) | Supported | deleted and added candidates; exact dtype dispatch; module and class `@bind`; private-specific routing; native-private accessibility retained | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets`
`tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract`
`tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_editable_contract_removes_class_method_constructor_member_and_overload` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_module_specifics_without_bind-missing_targets0]` (`compiling`)
`tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_type_bound_specifics_without_bind-missing_targets1]` (`compiling`) | canonical | -| [`.pyi` Functions And Classes: Replace The Constructor](../../docs/user/reference/pyi-contracts/functions-and-classes.md#replace-the-constructor) | Supported | direct native initializer; one explicit `Pass()`; reordered native position; generated constructor replacement or removal; overload constructor | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bound_constructor_uses_explicit_pass_position_and_native_target`
`tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_and_method_reuse_completed_direct_function_plans`
`tests/fortran/pyi_contracts/functions_and_classes/codegen/test_constructor_lowering.py::test_bound_constructor_generates_one_initializer_without_keyword_default` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function`
`tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected[\nclass state:\n @bind("init_state")\n @native_call([Addr(Arg(0))])\n def __init__(self, seed: Int32) -> None: ...\n-Bound constructor native_call requires exactly one Pass() entry]` (`semantics`) | canonical | -| [`.pyi` Functions And Classes: Type-Bound And Magic Methods](../../docs/user/reference/pyi-contracts/functions-and-classes.md#type-bound-and-magic-methods) | Supported | concrete native targets; passed object; bound Python/native names; overloaded type-bound calls; operators and assignment retain exact candidate mapping | `tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets`
`tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators` | `tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract`
`tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_rejects_invalid_prik_overload_links[\ndef compare(left: item, right: item) -> Bool: ...\nclass item:\n @overload("compare", generic="operator(.eqv.)")\n def __add__(self, right: item) -> Bool: ...\n-generic 'operator\\(\\.eqv\\.\\)' is incompatible with method '__add__']` (`semantics`) | canonical | -| [`.pyi` Calls And Results: Expose Native Arguments Directly](../../docs/user/reference/pyi-contracts/calls-and-results.md#expose-native-arguments-directly) | Supported | no `@native_call`; native-order scalar, rank-zero storage, array, fixed string, and derived object arguments; visible caller mutation and discarded string-temporary mutation | `tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py::test_native_order_and_projected_result_positions_are_completed_before_planning` | `tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_native_order_exposes_writable_slots_without_projection` | — | canonical | -| [`.pyi` Calls And Results: Reorder Arguments And Project Outputs](../../docs/user/reference/pyi-contracts/calls-and-results.md#reorder-arguments-and-project-outputs) | Supported | reordered `Arg`/`Addr(Arg)`; hidden scalar, fixed string, and fixed-array results; caller arrays and derived objects; multiple-result tuple order; typed literals and complete projection grammar | `tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py::test_native_order_and_projected_result_positions_are_completed_before_planning`
`tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py::test_plan_records_reordered_arguments_gil_behavior_and_hidden_result_slots`
`tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py::test_native_call_accepts_hidden_native_values` | `tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_native_call_reorders_arguments_and_projects_mixed_results`
`tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_hidden_fixed_shape_array_output_is_allocated_and_returned` | `tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` (`pipeline`) | canonical | -| [`.pyi` Calls And Results: Control Mutation](../../docs/user/reference/pyi-contracts/calls-and-results.md#control-mutation) | Supported | immutable scalar, fixed string, array, and derived replacement results; unchanged Python inputs; copy-in/copy-out and identity writeback paths | `tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py::test_immutable_replacement_policy_is_complete_before_ir_lowering`
`tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py::test_replacement_writeback_dispatches_selected_scalar_result_behavior[copy_in_out]` | `tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_immutable_values_return_replacements_without_mutating_inputs` | `tests/fortran/memory_management/policy/test_memory_ownership_policy.py::test_contradictory_ownership_contract_fails_before_lowering` (`policy`) | canonical | +| [Semantic `.pyi`: Visibility And Names](../../docs/user/reference/semantic-pyi-format.md#visibility-and-names) | Supported | decorator and type-wrapper privacy; source-name metadata; invalid Python identifiers; native binding retained | `tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_preserves_user_private_bound_function_contract`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_round_trip_properties.py::test_generated_pyi_escaping_round_trips_native_names` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | — | canonical | +| [Semantic `.pyi`: Projection Metadata](../../docs/user/reference/semantic-pyi-format.md#projection-metadata) | Supported | ordered `Arg`, `Addr`, `Value`, `Return`, descriptor, length, shape, presence, literal, pass, and workspace entries | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_native_call_accepts_hidden_native_values`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_emit_native_call_hidden_native_values` | — | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` (`pipeline`) | canonical | +| [Semantic `.pyi`: Current Generated Coverage](../../docs/user/reference/semantic-pyi-format.md#current-generated-coverage) | Partially supported | canonical parser/printer round trip; reviewed package layout; authoritative runtime input; documented generated and loaded subsets | `tests/fortran/infrastructure/semantic_pyi/semantics/test_round_trip_properties.py::test_generated_semantic_ir_round_trips_through_pyi`
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_checked_contract_package_has_reviewed_files` | `tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | +| [Semantic `.pyi`: Rejected Or Not Yet Supported](../../docs/user/reference/semantic-pyi-format.md#rejected-or-not-yet-supported) | Blocked | unknown types; invalid subscriptions, depth, callable shapes, decorators, bodies, arguments, and overload/projection combinations | — | — | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_convert_pyi_to_ir_rejects_invalid_projection_and_type_forms[value: Unknown\n-Unknown semantic type is not allowed in .pyi annotations]` (`semantics`)
`tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_rejects_additional_invalid_storage_forms[value: Float64[ORDER_F]\n-Non-dimensional type subscriptions are not supported; use Final[...] for constants and Annotated[...] for constraints or array metadata]` (`semantics`) | canonical | +| [Semantic `.pyi`: Remaining Format And Runtime Work](../../docs/user/reference/semantic-pyi-format.md#remaining-format-and-runtime-work) | Partially supported | implemented ordered projection and policy dispatch; broader polymorphism, pointer lifetimes, and IDE-only stub separation remain limited | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_fortran_to_pyi_and_back_preserves_mixed_input_output_projection` | — | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_rejects_generic_constructor_interfaces_during_semantic_conversion` (`semantics`)
`tests/fortran/allocatables/policy/test_allocatable_result_policy.py::test_direct_allocatable_scalar_function_result_is_blocked_before_codegen` (`policy`) | canonical | +| [`.pyi` Exports And Modules: Choose The Package Shape](../../docs/user/reference/pyi-contracts/exports-and-modules.md#choose-the-package-shape) | Supported | child namespaces; wildcard flattening; selective imports; symbol and module aliases; nested aliases; support-import exclusion; reachable declarations only | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering`
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_checked_entry_discovers_its_complete_contract_package[contract_import_graph]` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_rejects_colliding_wildcard_exports` (`pipeline`)
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_recursive_graph_reports_missing_relative_contract_before_native_validation` (`pipeline`)
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_recursive_graph_reports_cycles_before_codegen` (`pipeline`) | canonical | +| [`.pyi` Exports And Modules: Remove Or Hide A Declaration](../../docs/user/reference/pyi-contracts/exports-and-modules.md#remove-or-hide-a-declaration) | Supported | deleted function and variable; `@private`; `private[...]`; class constructor suppression; later class/member/overload runtime owner retained | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_removing_constructor_suppresses_generated_keyword_initialization` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | — | canonical | +| [`.pyi` Exports And Modules: Add Or Rename A Native Procedure](../../docs/user/reference/pyi-contracts/exports-and-modules.md#add-or-rename-a-native-procedure) | Supported | added module-leaf declaration; `@bind`; renamed standalone `@standalone`; unchanged native targets; no invented implementation | `tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_preserves_user_private_bound_function_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | — | canonical | +| [`.pyi` Exports And Modules: Set Module Values At Import](../../docs/user/reference/pyi-contracts/exports-and-modules.md#set-module-values-at-import) | Supported | mutable Boolean, integer, real, and complex literals; import-time write-through; `Final` constant distinction; unsupported setter/storage and expression rejection | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_literal_defaults_are_preserved`
`tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_module_variable_initializer_policy_is_complete_before_ir_lowering`
`tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/codegen/test_module_initializer_lowering.py::test_module_variable_literal_families_select_their_c_spelling` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/semantics/test_module_initializers.py::test_mutable_module_expression_defaults_are_rejected[from prik.contracts import Int32\ncounter: Int32 = f(42)\n]` (`semantics`)
`tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_unsupported_module_variable_initializer_completes_an_unsupported_policy` (`policy`) | canonical | +| [`.pyi` Functions And Classes: Expose A Module Procedure As A Method](../../docs/user/reference/pyi-contracts/functions-and-classes.md#expose-a-module-procedure-as-a-method) | Supported | retained module declaration; `Pass()` receiver placement; public or private module surface; same or bound method target | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_method_and_module_declarations_keep_native_targets_independent`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_module_procedure_method_visibility_is_completed_independently` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | +| [`.pyi` Functions And Classes: Edit An Overload Set](../../docs/user/reference/pyi-contracts/functions-and-classes.md#edit-an-overload-set) | Supported | deleted and added candidates; exact dtype dispatch; module and class `@bind`; private-specific routing; native-private accessibility retained | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_editable_contract_removes_class_method_constructor_member_and_overload` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_module_specifics_without_bind-missing_targets0]` (`compiling`)
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_type_bound_specifics_without_bind-missing_targets1]` (`compiling`) | canonical | +| [`.pyi` Functions And Classes: Replace The Constructor](../../docs/user/reference/pyi-contracts/functions-and-classes.md#replace-the-constructor) | Supported | direct native initializer; one explicit `Pass()`; reordered native position; generated constructor replacement or removal; overload constructor | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bound_constructor_uses_explicit_pass_position_and_native_target`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_and_method_reuse_completed_direct_function_plans`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/codegen/test_constructor_lowering.py::test_bound_constructor_generates_one_initializer_without_keyword_default` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected[\nclass state:\n @bind("init_state")\n @native_call([Addr(Arg(0))])\n def __init__(self, seed: Int32) -> None: ...\n-Bound constructor native_call requires exactly one Pass() entry]` (`semantics`) | canonical | +| [`.pyi` Functions And Classes: Type-Bound And Magic Methods](../../docs/user/reference/pyi-contracts/functions-and-classes.md#type-bound-and-magic-methods) | Supported | concrete native targets; passed object; bound Python/native names; overloaded type-bound calls; operators and assignment retain exact candidate mapping | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets`
`tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract`
`tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_rejects_invalid_prik_overload_links[\ndef compare(left: item, right: item) -> Bool: ...\nclass item:\n @overload("compare", generic="operator(.eqv.)")\n def __add__(self, right: item) -> Bool: ...\n-generic 'operator\\(\\.eqv\\.\\)' is incompatible with method '__add__']` (`semantics`) | canonical | +| [`.pyi` Calls And Results: Expose Native Arguments Directly](../../docs/user/reference/pyi-contracts/calls-and-results.md#expose-native-arguments-directly) | Supported | no `@native_call`; native-order scalar, rank-zero storage, array, fixed string, and derived object arguments; visible caller mutation and discarded string-temporary mutation | `tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/policy/test_call_and_result_policy.py::test_native_order_and_projected_result_positions_are_completed_before_planning` | `tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_native_order_exposes_writable_slots_without_projection` | — | canonical | +| [`.pyi` Calls And Results: Reorder Arguments And Project Outputs](../../docs/user/reference/pyi-contracts/calls-and-results.md#reorder-arguments-and-project-outputs) | Supported | reordered `Arg`/`Addr(Arg)`; hidden scalar, fixed string, and fixed-array results; caller arrays and derived objects; multiple-result tuple order; typed literals and complete projection grammar | `tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/policy/test_call_and_result_policy.py::test_native_order_and_projected_result_positions_are_completed_before_planning`
`tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/codegen/test_call_and_result_lowering.py::test_plan_records_reordered_arguments_gil_behavior_and_hidden_result_slots`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_native_call_accepts_hidden_native_values` | `tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_native_call_reorders_arguments_and_projects_mixed_results`
`tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_hidden_fixed_shape_array_output_is_allocated_and_returned` | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` (`pipeline`) | canonical | +| [`.pyi` Calls And Results: Control Mutation](../../docs/user/reference/pyi-contracts/calls-and-results.md#control-mutation) | Supported | immutable scalar, fixed string, array, and derived replacement results; unchanged Python inputs; copy-in/copy-out and identity writeback paths | `tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/policy/test_call_and_result_policy.py::test_immutable_replacement_policy_is_complete_before_ir_lowering`
`tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/codegen/test_call_and_result_lowering.py::test_replacement_writeback_dispatches_selected_scalar_result_behavior[copy_in_out]` | `tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py::test_immutable_values_return_replacements_without_mutating_inputs` | `tests/fortran/memory_management/policy/test_memory_ownership_policy.py::test_contradictory_ownership_contract_fails_before_lowering` (`policy`) | canonical | | [`.pyi` Calls And Results: Edit Types Shapes Layout And Optionality](../../docs/user/reference/pyi-contracts/calls-and-results.md#edit-types-shapes-layout-and-optionality) | Supported | fixed/open shapes; exact dtype, rank, layout, writeability, byte order, alignment, and zero-size checks; Fortran-order default; supported nullable/defaulted native optionals | `tests/fortran/arrays/codegen/test_dense_array_shape_lowering.py::test_dense_array_lowering_uses_planned_shape_checks_and_bridge_orientation`
`tests/fortran/optional_arguments/policy/test_optional_policy.py::test_optional_scalar_policy_completes_nullable_value_presence_before_planning` | `tests/fortran/arrays/end_to_end/test_array_contract_validation.py::test_remaining_array_contracts_are_validated_before_fortran_calls[source]`
`tests/fortran/optional_arguments/end_to_end/test_optional_runtime.py::test_optional_arguments_drive_fortran_present_behavior[source]` | `tests/fortran/optional_arguments/policy/test_optional_policy.py::test_optional_passed_procedure_is_blocked_before_codegen` (`policy`) | canonical | | [`.pyi` Calls And Results: Translate Status Results Into Exceptions](../../docs/user/reference/pyi-contracts/calls-and-results.md#translate-status-results-into-exceptions) | Supported | named hidden scalar integer status; optional hidden string message; configurable success value; consumed projected outputs | `tests/fortran/error_handling/semantics/test_status_contract_semantics.py::test_status_projection_accepts_an_optional_missing_message_target`
`tests/fortran/error_handling/codegen/test_status_error_lowering.py::test_runtime_plan_edits_dispatch_to_named_lowering_and_validate_roles` | `tests/fortran/error_handling/end_to_end/test_status_projection.py::test_status_projection_consumes_outputs_raises_message_and_recovers` | `tests/fortran/error_handling/semantics/test_status_contract_semantics.py::test_runtime_status_policy_rejects_invalid_output_contracts[@raises(status="status", message="message")\ndef solve() -> tuple[Returns["status", Int32], Returns["message", Int32]]: ...-must be a scalar string hidden output]` (`policy`) | canonical | | [`.pyi` Calls And Results: Release The GIL For A Native Call](../../docs/user/reference/pyi-contracts/calls-and-results.md#release-the-gil-for-a-native-call) | Supported | ordinary held call; explicit released call; status conversion after reacquisition; callback trampoline reacquisition | `tests/fortran/error_handling/semantics/test_status_contract_semantics.py::test_runtime_policy_decorators_round_trip_through_pyi`
`tests/fortran/error_handling/codegen/test_status_error_lowering.py::test_direct_binding_lowering_places_only_opted_in_native_call_outside_the_gil` | `tests/fortran/callbacks/end_to_end/test_supported_callback_shapes.py::test_immediate_callbacks_cover_all_supported_argument_shapes[source]` | — | canonical | -| [Feature Matrix: Caller-Ordered Multi-Source Builds, Makefiles, Verbose Mode, And Output Placement](../../docs/user/language-support/feature-matrix.md#supported-runtime-features) | Supported | caller order; direct and Makefile builds; replayable verbose commands; ABI artifact and stable alias placement | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_verbose_mode_prints_full_direct_build_commands` | `tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build`
`tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_dir_separates_abi_artifact_from_cli_alias` | — | canonical | -| [Feature Matrix: Fortran Source Wrapper Builds](../../docs/user/language-support/feature-matrix.md#supported-runtime-features) | Supported | ordered Fortran source inputs; generated contracts; structured native plan; ABI-compatible import | `tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::test_source_build_result_records_structured_native_plan`
`tests/fortran/building_shared_library/pipeline/test_source_generated_contracts.py::test_source_build_generated_pyi_contract_matches_fixture[fdefault_output]` | `tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py::test_debug_and_optimized_wrapper_builds_preserve_runtime_abi` | — | canonical | -| [Feature Matrix: Semantic `.pyi` Wrapper Builds From Explicit Native Artifacts](../../docs/user/language-support/feature-matrix.md#supported-inspection-features) | Partially supported | exactly one entry contract; explicit native input; source-free object build; ordered link items; current runtime subset | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_pyi_python_api_accepts_exactly_one_entry_contract`
`tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_generated_pyi_fixture_builds_from_native_object_without_source_reparse` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_scale_runtime_contract[generated-pyi]` | `tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py::test_pyi_python_api_rejects_a_missing_native_artifact` (`pipeline`) | canonical | -| [Feature Matrix: Advanced Multi-Source Dependency Discovery And External-Library Integration](../../docs/user/language-support/feature-matrix.md#unsupported-or-blocked-forms) | Blocked | source dependency graphs, prebuilt module paths, and external-library discovery are caller/build-system responsibilities; explicit paths remain supported | `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` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_imported_contracts_resolve_from_one_archive_or_shared_library[archive]` | `tests/fortran/building_shared_library/end_to_end/test_native_bundles.py::test_missing_module_directory_reports_compile_error` (`compiling`) | canonical | +| [Feature Matrix: Caller-Ordered Multi-Source Builds, Makefiles, Verbose Mode, And Output Placement](../../docs/user/language-support/feature-matrix.md#supported-runtime-features) | Supported | caller order; direct and Makefile builds; replayable verbose commands; ABI artifact and stable alias placement | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_verbose_mode_prints_full_direct_build_commands` | `tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build`
`tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_fortran_wrapper_out_dir_separates_abi_artifact_from_cli_alias` | — | canonical | +| [Feature Matrix: Fortran Source Wrapper Builds](../../docs/user/language-support/feature-matrix.md#supported-runtime-features) | Supported | ordered Fortran source inputs; generated contracts; structured native plan; ABI-compatible import | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_source_build_result_records_structured_native_plan`
`tests/fortran/infrastructure/building/pipeline/test_source_generated_contracts.py::test_source_build_generated_pyi_contract_matches_fixture[fdefault_output]` | `tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py::test_debug_and_optimized_wrapper_builds_preserve_runtime_abi` | — | canonical | +| [Feature Matrix: Semantic `.pyi` Wrapper Builds From Explicit Native Artifacts](../../docs/user/language-support/feature-matrix.md#supported-inspection-features) | Partially supported | exactly one entry contract; explicit native input; source-free object build; ordered link items; current runtime subset | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_pyi_python_api_accepts_exactly_one_entry_contract`
`tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_generated_pyi_fixture_builds_from_native_object_without_source_reparse` | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_scale_runtime_contract[generated-pyi]` | `tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py::test_pyi_python_api_rejects_a_missing_native_artifact` (`pipeline`) | canonical | +| [Feature Matrix: Advanced Multi-Source Dependency Discovery And External-Library Integration](../../docs/user/language-support/feature-matrix.md#unsupported-or-blocked-forms) | Blocked | source dependency graphs, prebuilt module paths, and external-library discovery are caller/build-system responsibilities; explicit paths remain supported | `tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::test_source_build_reuses_native_plan_for_additional_compile_and_link_inputs` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_imported_contracts_resolve_from_one_archive_or_shared_library[archive]` | `tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py::test_missing_module_directory_reports_compile_error` (`compiling`) | canonical | diff --git a/tests/fortran/README.md b/tests/fortran/README.md index 4e775cebe..fcf6ce586 100644 --- a/tests/fortran/README.md +++ b/tests/fortran/README.md @@ -4,18 +4,24 @@ Fortran, including semantic `.pyi` wrapper builds and the generated Fortran/C/CPython implementation of that contract. -The final organization is feature first and stage second: +Language-feature evidence is feature first and stage second: ```text -tests/fortran/// +tests/fortran/// ``` -Documented features are direct children of `tests/fortran/`; the -`infrastructure/` directory remains the single container for internal -cross-feature frameworks. Only create a feature or stage directory when it -owns a real test or fixture. +Cross-feature mechanisms use explicit infrastructure owners: -## Documentation feature map +```text +tests/fortran/infrastructure// +``` + +A documentation page does not by itself make a mechanism a language feature. +Parsing, preprocessing, CLI, semantic representation and `.pyi` conversion, +building, and shared policy are infrastructure. Only create a feature, stage, +or infrastructure owner when it owns a real test or fixture. + +## Fortran language-feature map | Documentation | Final feature directory | Focused pytest command | | --- | --- | --- | @@ -35,15 +41,6 @@ owns a real test or fixture. | [Enumerations](../../docs/user/guide/enumerations.md) | `enumerations/` | `python3 -m pytest -q tests/fortran/enumerations` | | [Raw Addresses](../../docs/user/guide/raw-addresses.md) | `raw_addresses/` | `python3 -m pytest -q tests/fortran/raw_addresses` | | [Error Handling](../../docs/user/guide/error-handling.md) | `error_handling/` | `python3 -m pytest -q tests/fortran/error_handling` | -| [Building the Shared Library](../../docs/user/guide/building-shared-library.md) | `building_shared_library/` | `python3 -m pytest -q tests/fortran/building_shared_library` | -| [Inspect a Fortran API](../../docs/user/examples/recipes/inspect-fortran-api.md) | `source_parsing/` | `python3 -m pytest -q tests/fortran/source_parsing` | -| [Compiler Preprocessing](../../docs/user/examples/recipes/compiler-preprocessing.md) | `source_preprocessing/` | `python3 -m pytest -q tests/fortran/source_preprocessing` | -| [CLI Commands](../../docs/user/reference/cli-commands.md) | `command_line_interface/` | `python3 -m pytest -q tests/fortran/command_line_interface` | -| [Semantic IR](../../docs/user/reference/semantic-ir.md) | `semantic_ir/` | `python3 -m pytest -q tests/fortran/semantic_ir` | -| [Semantic `.pyi` Format](../../docs/user/reference/semantic-pyi-format.md) | `semantic_pyi_format/` | `python3 -m pytest -q tests/fortran/semantic_pyi_format` | -| [Exports and Modules](../../docs/user/reference/pyi-contracts/exports-and-modules.md) | `pyi_contracts/exports_and_modules/` | `python3 -m pytest -q tests/fortran/pyi_contracts/exports_and_modules` | -| [Functions and Classes](../../docs/user/reference/pyi-contracts/functions-and-classes.md) | `pyi_contracts/functions_and_classes/` | `python3 -m pytest -q tests/fortran/pyi_contracts/functions_and_classes` | -| [Calls and Results](../../docs/user/reference/pyi-contracts/calls-and-results.md) | `pyi_contracts/calls_and_results/` | `python3 -m pytest -q tests/fortran/pyi_contracts/calls_and_results` | Each feature uses only the stages it needs: `parsing`, `probes`, `preprocessing`, `semantics`, `policy`, `codegen`, `compiling`, @@ -54,22 +51,39 @@ Array declaration-expression coverage is intentionally split by evidence: `arrays/policy/` proves completed dependency roles and named blockers, and `arrays/end_to_end/` compiles supported dimensions and logical array kinds. Cross-module editable-contract reconciliation remains under -the semantic `.pyi` format stage, not under a code-generation test. +`infrastructure/semantic_pyi/`, not under a language-feature code-generation +test. + +## Cross-feature infrastructure map + +| Documentation or mechanism | Infrastructure owner | Focused pytest command | +| --- | --- | --- | +| [Inspect a Fortran API](../../docs/user/examples/recipes/inspect-fortran-api.md) | `infrastructure/parsing/` | `python3 -m pytest -q tests/fortran/infrastructure/parsing` | +| [Compiler Preprocessing](../../docs/user/examples/recipes/compiler-preprocessing.md) | `infrastructure/preprocessing/` | `python3 -m pytest -q tests/fortran/infrastructure/preprocessing` | +| [CLI Commands](../../docs/user/reference/cli-commands.md) | `infrastructure/cli/` | `python3 -m pytest -q tests/fortran/infrastructure/cli` | +| [Semantic IR](../../docs/user/reference/semantic-ir.md) | `infrastructure/semantic_ir/` | `python3 -m pytest -q tests/fortran/infrastructure/semantic_ir` | +| [Semantic `.pyi` Format](../../docs/user/reference/semantic-pyi-format.md) and [contract guides](../../docs/user/reference/pyi-contracts/index.md) | `infrastructure/semantic_pyi/` | `python3 -m pytest -q tests/fortran/infrastructure/semantic_pyi` | +| [Building the Shared Library](../../docs/user/guide/building-shared-library.md) | `infrastructure/building/` | `python3 -m pytest -q tests/fortran/infrastructure/building` | +| Completed ownership and wrapper-policy decisions | `infrastructure/policy/` | `python3 -m pytest -q tests/fortran/infrastructure/policy` | ## Infrastructure owners -Infrastructure contains only internal cross-feature frameworks with no honest -public-capability or documentation-feature owner. Tests of public parsing, -preprocessing, command-line, semantic-IR, contract-printing, and build behavior -belong to their named feature even when they span several lower-level -mechanisms. Infrastructure tests normally start from completed internal models -or synthetic implementation nodes; the starting representation is supporting -evidence, not the ownership rule. +Infrastructure contains every cross-feature mechanism, whether internal-only or +user-invocable. A language feature stays feature-owned when it crosses parsing, +policy, planning, and lowering. Infrastructure tests normally start from +completed internal models or synthetic implementation nodes; the starting +representation is supporting evidence, not the ownership rule. | Final directory | Owner | | --- | --- | | `infrastructure/runtime/` | Native runtime-support package contracts that have no public feature owner | -| `infrastructure/semantics/` | Internal semantic ownership, policy completion, and completed wrapper-policy mechanics | +| `infrastructure/parsing/` | Shared parser, source fixture, and parser-model behavior | +| `infrastructure/preprocessing/` | Shared source preparation, compiler invocation, and source mapping behavior | +| `infrastructure/cli/` | Shared command-line parsing and output behavior | +| `infrastructure/semantic_ir/` | Source and parser-model conversion into semantic IR | +| `infrastructure/semantic_pyi/` | Semantic `.pyi` parsing, conversion, contracts, and loading | +| `infrastructure/building/` | Shared native build modes, compiler integration, and runtime ABI behavior | +| `infrastructure/policy/` | Internal ownership, policy completion, and completed wrapper-policy mechanics | | `infrastructure/codegen/` | Internal plan, planner, generator, binding, bridge, printer, docstring, advisory review, and visitor mechanics | | `infrastructure/naming/` | Internal generated-name and public-name policy owned by `prik/naming/` | | `infrastructure/pipeline/` | Generated-wrapper orchestration and transport owned by `prik/pipeline/` | @@ -85,8 +99,8 @@ inheritance choices, field inventories, and incidental call structure remain review recommendations. Minimized real-source parser regressions live in -`source_parsing/parsing/test_real_world_interaction_regressions.py`. A -third-party project is a temporary discovery input, not a permanent fixture: +`infrastructure/parsing/test_real_world_interaction_regressions.py`. +A third-party project is a temporary discovery input, not a permanent fixture: extract its named parser facts, prove that the focused suite covers its unique lines and branches, then remove the snapshot. Parser regressions are never end-to-end or smoke evidence. @@ -96,7 +110,7 @@ end-to-end or smoke evidence. Feature-specific fixtures stay beneath their feature. End-to-end projects use: ```text -/end_to_end/fixtures//native/ +/end_to_end/fixtures//native/ ``` Generated build products always use pytest temporary directories. `_support/` @@ -110,7 +124,7 @@ artifact-consumer, and support-consumer inventories live under ## Markers -- Every pytest node below a feature `end_to_end/` carries +- Every pytest node below a Fortran `end_to_end/` directory carries `fortran_end_to_end`, and no other node does. - Only the complete `examples/blas/` and `examples/lapack/` correctness projects and BLAS/LAPACK native-source integration nodes additionally carry diff --git a/tests/fortran/_support/fixture_outputs.py b/tests/fortran/_support/fixture_outputs.py index ed8e53d06..5a944e212 100644 --- a/tests/fortran/_support/fixture_outputs.py +++ b/tests/fortran/_support/fixture_outputs.py @@ -6,9 +6,11 @@ from prik.semantics.fortran2ir import fortran_module_to_semantic_module FORTRAN_ROOT = Path(__file__).resolve().parents[1] -PARSER_FIXTURE_ROOT = FORTRAN_ROOT / "source_parsing" / "parsing" / "fixtures" +PARSER_FIXTURE_ROOT = FORTRAN_ROOT / "infrastructure" / "parsing" / "fixtures" GENERAL_FORTRAN_DIR = PARSER_FIXTURE_ROOT / "general" -SEMANTICS_FIXTURE_DIR = FORTRAN_ROOT / "semantic_ir" / "semantics" / "fixtures" / "general" / "expected" +SEMANTICS_FIXTURE_DIR = ( + FORTRAN_ROOT / "infrastructure" / "semantic_ir" / "semantics" / "fixtures" / "general" / "expected" +) FORTRAN_SUFFIXES = {".f", ".f90", ".f95", ".f03", ".f08", ".for", ".f77", ".ftn"} diff --git a/tests/fortran/_support/wrapper_build.py b/tests/fortran/_support/wrapper_build.py index de773a659..fb4bdd764 100644 --- a/tests/fortran/_support/wrapper_build.py +++ b/tests/fortran/_support/wrapper_build.py @@ -55,7 +55,7 @@ "fmath_arrays_f90.f90": REPO_ROOT / "tests/fortran/arrays/end_to_end/fixtures/baseline/native/fmath_arrays_f90.f90", "fmath_f90.f90": REPO_ROOT / "tests/fortran/data_types/end_to_end/fixtures/baseline/native/fmath_f90.f90", "fnaming_f90.f90": REPO_ROOT - / "tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/visibility/native/fnaming_f90.f90", + / "tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/visibility/native/fnaming_f90.f90", "fopenmp_runtime_f90.f90": REPO_ROOT / "tests/fortran/error_handling/end_to_end/fixtures/runtime/native/fopenmp_runtime_f90.f90", "free_external.f90": REPO_ROOT / "tests/fortran/functions/end_to_end/fixtures/external/native/free_external.f90", diff --git a/tests/fortran/conftest.py b/tests/fortran/conftest.py index 8d9dc9960..d050ba5f0 100644 --- a/tests/fortran/conftest.py +++ b/tests/fortran/conftest.py @@ -53,7 +53,7 @@ class ToolchainSmokeCase: "test_fortran_generic_interfaces_dispatch_in_generated_c_extension[source]" ): ToolchainSmokeCase("generic_overload_dispatch", "compiled_generic_module"), ( - "tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py::" + "tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py::" "test_generated_contract_rebuilds_without_native_source_fallback" ): ToolchainSmokeCase("source_generated_pyi_rebuild", "compiled_contract_rebuild"), } @@ -141,14 +141,9 @@ def _relative_test_path(item: pytest.Item) -> Path: return Path(str(item.path)).resolve().relative_to(REPO_ROOT) -def _is_fortran_feature_end_to_end(item: pytest.Item) -> bool: +def _is_fortran_end_to_end(item: pytest.Item) -> bool: parts = _relative_test_path(item).parts - return ( - len(parts) >= 5 - and parts[:2] == ("tests", "fortran") - and parts[2] not in {"_support", "infrastructure"} - and "end_to_end" in parts[3:-1] - ) + return len(parts) >= 5 and parts[:2] == ("tests", "fortran") and "end_to_end" in parts[3:-1] def _is_platform_mark(name: str) -> bool: @@ -159,8 +154,8 @@ def _validate_smoke_item(item: pytest.Item, errors: list[str]) -> None: marker = item.get_closest_marker("toolchain_smoke") if marker is None: return - if not _is_fortran_feature_end_to_end(item): - errors.append(f"toolchain_smoke is outside a feature end_to_end directory: {item.nodeid}") + if not _is_fortran_end_to_end(item): + errors.append(f"toolchain_smoke is outside a Fortran end_to_end directory: {item.nodeid}") if item.get_closest_marker("fortran_end_to_end") is None: errors.append(f"toolchain_smoke lacks fortran_end_to_end: {item.nodeid}") if marker.args or set(marker.kwargs) != {"mechanism", "build_fixture"}: @@ -197,7 +192,7 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item errors = [] for item in items: - is_end_to_end = _is_fortran_feature_end_to_end(item) + is_end_to_end = _is_fortran_end_to_end(item) has_end_to_end_mark = item.get_closest_marker("fortran_end_to_end") is not None if is_end_to_end != has_end_to_end_mark: errors.append( diff --git a/tests/fortran/functions/end_to_end/test_external_procedures.py b/tests/fortran/functions/end_to_end/test_external_procedures.py index 93d95abac..9ba32fa01 100644 --- a/tests/fortran/functions/end_to_end/test_external_procedures.py +++ b/tests/fortran/functions/end_to_end/test_external_procedures.py @@ -26,7 +26,7 @@ C_ORDER_FLAT_BUFFER = wrapper_source("c_order_flat_buffer.f90") BLAS_LIKE_FILENAMES = ("daxpy_like.f90", "ddot_like.f90") BLAS_LIKE_SOURCES = tuple(wrapper_source(filename) for filename in BLAS_LIKE_FILENAMES) -BASIC_SOURCE = REPO_ROOT / "tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90" +BASIC_SOURCE = REPO_ROOT / "tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90" CONTRACT_FIXTURES = Path(__file__).parent / "fixtures" / "external" / "contracts" C_ORDER_FLAT_CONTRACT = ( REPO_ROOT diff --git a/tests/fortran/building_shared_library/README.md b/tests/fortran/infrastructure/building/README.md similarity index 94% rename from tests/fortran/building_shared_library/README.md rename to tests/fortran/infrastructure/building/README.md index 8319d9992..6672da888 100644 --- a/tests/fortran/building_shared_library/README.md +++ b/tests/fortran/infrastructure/building/README.md @@ -17,7 +17,7 @@ Evidence is split by the stage that establishes it: Run the complete feature with: ```bash -python3 -m pytest -q tests/fortran/building_shared_library +python3 -m pytest -q tests/fortran/infrastructure/building ``` Full BLAS and LAPACK corpus coverage lives in `examples/blas/` and diff --git a/tests/fortran/building_shared_library/compiling/test_compiler_verbose.py b/tests/fortran/infrastructure/building/compiling/test_compiler_verbose.py similarity index 100% rename from tests/fortran/building_shared_library/compiling/test_compiler_verbose.py rename to tests/fortran/infrastructure/building/compiling/test_compiler_verbose.py diff --git a/tests/fortran/building_shared_library/compiling/test_example_native_library.py b/tests/fortran/infrastructure/building/compiling/test_example_native_library.py similarity index 100% rename from tests/fortran/building_shared_library/compiling/test_example_native_library.py rename to tests/fortran/infrastructure/building/compiling/test_example_native_library.py diff --git a/tests/fortran/building_shared_library/compiling/test_support_probe_artifacts.py b/tests/fortran/infrastructure/building/compiling/test_support_probe_artifacts.py similarity index 100% rename from tests/fortran/building_shared_library/compiling/test_support_probe_artifacts.py rename to tests/fortran/infrastructure/building/compiling/test_support_probe_artifacts.py diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/__init__.pyi b/tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/__init__.pyi similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/__init__.pyi rename to tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/__init__.pyi diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/box_ops.pyi b/tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/box_ops.pyi similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/box_ops.pyi rename to tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/box_ops.pyi diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/first_math.pyi b/tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/first_math.pyi similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/first_math.pyi rename to tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/first_math.pyi diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/second_math.pyi b/tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/second_math.pyi similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/second_math.pyi rename to tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/second_math.pyi diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/shared_types.pyi b/tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/shared_types.pyi similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/contracts/multiple_files/combined_modules/shared_types.pyi rename to tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/multiple_files/combined_modules/shared_types.pyi diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/contracts/runtime_abi/__init__.pyi b/tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/runtime_abi/__init__.pyi similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/contracts/runtime_abi/__init__.pyi rename to tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/runtime_abi/__init__.pyi diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/contracts/runtime_abi/fruntime_abi_f90.pyi b/tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/runtime_abi/fruntime_abi_f90.pyi similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/contracts/runtime_abi/fruntime_abi_f90.pyi rename to tests/fortran/infrastructure/building/end_to_end/fixtures/contracts/runtime_abi/fruntime_abi_f90.pyi diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/double_value.f b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/double_value.f similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/double_value.f rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/double_value.f diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/fdefault_output.f b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/fdefault_output.f similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/fdefault_output.f rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/fdefault_output.f diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/first_api.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/first_api.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/first_api.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/first_api.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/fruntime_abi_f90.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/fruntime_abi_f90.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/fruntime_abi_f90.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/fruntime_abi_f90.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/home_points.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/home_points.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/home_points.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/home_points.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/scale.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/scale.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/scale.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/scale.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/second_api.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/second_api.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/second_api.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/second_api.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/standalone_api.f b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/standalone_api.f similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/standalone_api.f rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/standalone_api.f diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/native/verbose_api.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/native/verbose_api.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/native/verbose_api.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/native/verbose_api.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_direct_bind_c_f90.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_direct_bind_c_f90.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_direct_bind_c_f90.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_direct_bind_c_f90.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_direct_helper_f90.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_direct_helper_f90.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_direct_helper_f90.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_direct_helper_f90.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_mixed_bind_c_f90.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_mixed_bind_c_f90.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_mixed_bind_c_f90.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_mixed_bind_c_f90.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_mixed_helper_f90.f90 b/tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_mixed_helper_f90.f90 similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/fixtures/routing/native/multi_source_mixed_helper_f90.f90 rename to tests/fortran/infrastructure/building/end_to_end/fixtures/routing/native/multi_source_mixed_helper_f90.f90 diff --git a/tests/fortran/building_shared_library/end_to_end/real_libraries/__init__.py b/tests/fortran/infrastructure/building/end_to_end/real_libraries/__init__.py similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/real_libraries/__init__.py rename to tests/fortran/infrastructure/building/end_to_end/real_libraries/__init__.py diff --git a/tests/fortran/building_shared_library/end_to_end/real_libraries/_support.py b/tests/fortran/infrastructure/building/end_to_end/real_libraries/_support.py similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/real_libraries/_support.py rename to tests/fortran/infrastructure/building/end_to_end/real_libraries/_support.py diff --git a/tests/fortran/building_shared_library/end_to_end/real_libraries/test_fftpack_routines.py b/tests/fortran/infrastructure/building/end_to_end/real_libraries/test_fftpack_routines.py similarity index 96% rename from tests/fortran/building_shared_library/end_to_end/real_libraries/test_fftpack_routines.py rename to tests/fortran/infrastructure/building/end_to_end/real_libraries/test_fftpack_routines.py index ad91c46b3..d192ced64 100644 --- a/tests/fortran/building_shared_library/end_to_end/real_libraries/test_fftpack_routines.py +++ b/tests/fortran/infrastructure/building/end_to_end/real_libraries/test_fftpack_routines.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from tests.fortran.building_shared_library.end_to_end.real_libraries._support import ( +from tests.fortran.infrastructure.building.end_to_end.real_libraries._support import ( build_real_fortran_library, real_library_source_dir, ) diff --git a/tests/fortran/building_shared_library/end_to_end/real_libraries/test_minpack_routines.py b/tests/fortran/infrastructure/building/end_to_end/real_libraries/test_minpack_routines.py similarity index 96% rename from tests/fortran/building_shared_library/end_to_end/real_libraries/test_minpack_routines.py rename to tests/fortran/infrastructure/building/end_to_end/real_libraries/test_minpack_routines.py index 89dfc06c4..b775f1cf4 100644 --- a/tests/fortran/building_shared_library/end_to_end/real_libraries/test_minpack_routines.py +++ b/tests/fortran/infrastructure/building/end_to_end/real_libraries/test_minpack_routines.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from tests.fortran.building_shared_library.end_to_end.real_libraries._support import ( +from tests.fortran.infrastructure.building.end_to_end.real_libraries._support import ( build_real_fortran_library, real_library_source_dir, ) diff --git a/tests/fortran/building_shared_library/end_to_end/test_build_direct_entrypoint_routing.py b/tests/fortran/infrastructure/building/end_to_end/test_build_direct_entrypoint_routing.py similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/test_build_direct_entrypoint_routing.py rename to tests/fortran/infrastructure/building/end_to_end/test_build_direct_entrypoint_routing.py diff --git a/tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py b/tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/test_multi_source_builds.py rename to tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py diff --git a/tests/fortran/building_shared_library/end_to_end/test_native_bundles.py b/tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py similarity index 99% rename from tests/fortran/building_shared_library/end_to_end/test_native_bundles.py rename to tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py index ea8767ef0..bbac1a43c 100644 --- a/tests/fortran/building_shared_library/end_to_end/test_native_bundles.py +++ b/tests/fortran/infrastructure/building/end_to_end/test_native_bundles.py @@ -12,7 +12,7 @@ import pytest from prik import build_pyi_extension -from tests.fortran.building_shared_library.end_to_end.test_multi_source_builds import ( +from tests.fortran.infrastructure.building.end_to_end.test_multi_source_builds import ( _assert_combined_runtime, _compile_native_objects, _generate_combined_contract, diff --git a/tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py b/tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/test_runtime_compatibility.py rename to tests/fortran/infrastructure/building/end_to_end/test_runtime_compatibility.py diff --git a/tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py b/tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py similarity index 100% rename from tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py rename to tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py diff --git a/tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/fdefault_output/__init__.pyi b/tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/fdefault_output/__init__.pyi similarity index 100% rename from tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/fdefault_output/__init__.pyi rename to tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/fdefault_output/__init__.pyi diff --git a/tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/__init__.pyi b/tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/__init__.pyi similarity index 100% rename from tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/__init__.pyi rename to tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/__init__.pyi diff --git a/tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/fruntime_abi_f90.pyi b/tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/fruntime_abi_f90.pyi similarity index 100% rename from tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/fruntime_abi_f90.pyi rename to tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/fruntime_abi_f90/fruntime_abi_f90.pyi diff --git a/tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/verbose_api/__init__.pyi b/tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/verbose_api/__init__.pyi similarity index 100% rename from tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/verbose_api/__init__.pyi rename to tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/verbose_api/__init__.pyi diff --git a/tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/verbose_api/verbose_api.pyi b/tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/verbose_api/verbose_api.pyi similarity index 100% rename from tests/fortran/building_shared_library/pipeline/fixtures/generated_contracts/source_builds/verbose_api/verbose_api.pyi rename to tests/fortran/infrastructure/building/pipeline/fixtures/generated_contracts/source_builds/verbose_api/verbose_api.pyi diff --git a/tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py b/tests/fortran/infrastructure/building/pipeline/test_generated_wrapper_build.py similarity index 100% rename from tests/fortran/building_shared_library/pipeline/test_generated_wrapper_build.py rename to tests/fortran/infrastructure/building/pipeline/test_generated_wrapper_build.py diff --git a/tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py b/tests/fortran/infrastructure/building/pipeline/test_parallel_compilation.py similarity index 100% rename from tests/fortran/building_shared_library/pipeline/test_parallel_compilation.py rename to tests/fortran/infrastructure/building/pipeline/test_parallel_compilation.py diff --git a/tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py b/tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py similarity index 100% rename from tests/fortran/building_shared_library/pipeline/test_pyi_build_modes.py rename to tests/fortran/infrastructure/building/pipeline/test_pyi_build_modes.py diff --git a/tests/fortran/building_shared_library/pipeline/test_root_build_api.py b/tests/fortran/infrastructure/building/pipeline/test_root_build_api.py similarity index 100% rename from tests/fortran/building_shared_library/pipeline/test_root_build_api.py rename to tests/fortran/infrastructure/building/pipeline/test_root_build_api.py diff --git a/tests/fortran/building_shared_library/pipeline/test_source_generated_contracts.py b/tests/fortran/infrastructure/building/pipeline/test_source_generated_contracts.py similarity index 100% rename from tests/fortran/building_shared_library/pipeline/test_source_generated_contracts.py rename to tests/fortran/infrastructure/building/pipeline/test_source_generated_contracts.py diff --git a/tests/fortran/command_line_interface/pipeline/_support.py b/tests/fortran/infrastructure/cli/pipeline/_support.py similarity index 96% rename from tests/fortran/command_line_interface/pipeline/_support.py rename to tests/fortran/infrastructure/cli/pipeline/_support.py index 42427e5ca..cf224bce9 100644 --- a/tests/fortran/command_line_interface/pipeline/_support.py +++ b/tests/fortran/infrastructure/cli/pipeline/_support.py @@ -3,7 +3,7 @@ import prik.cli as prik_cli -TEST_FILE = Path(__file__).parents[2] / "source_parsing" / "parsing" / "fixtures" / "general" / "basic_subroutine.f90" +TEST_FILE = Path(__file__).parents[2] / "parsing" / "fixtures" / "general" / "basic_subroutine.f90" class _MainParserError(Exception): diff --git a/tests/fortran/command_line_interface/pipeline/test_argument_contract.py b/tests/fortran/infrastructure/cli/pipeline/test_argument_contract.py similarity index 99% rename from tests/fortran/command_line_interface/pipeline/test_argument_contract.py rename to tests/fortran/infrastructure/cli/pipeline/test_argument_contract.py index 6cc17133d..a8174f9b7 100644 --- a/tests/fortran/command_line_interface/pipeline/test_argument_contract.py +++ b/tests/fortran/infrastructure/cli/pipeline/test_argument_contract.py @@ -10,7 +10,7 @@ import prik.cli as prik_cli from prik.preprocessing import PreprocessingError -from tests.fortran.command_line_interface.pipeline._support import ( +from tests.fortran.infrastructure.cli.pipeline._support import ( TEST_FILE, _MainParserError, _install_main_parser, diff --git a/tests/fortran/command_line_interface/pipeline/test_output_contract.py b/tests/fortran/infrastructure/cli/pipeline/test_output_contract.py similarity index 99% rename from tests/fortran/command_line_interface/pipeline/test_output_contract.py rename to tests/fortran/infrastructure/cli/pipeline/test_output_contract.py index 7794cab92..9337c9c21 100644 --- a/tests/fortran/command_line_interface/pipeline/test_output_contract.py +++ b/tests/fortran/infrastructure/cli/pipeline/test_output_contract.py @@ -21,7 +21,7 @@ PreprocessingDiagnostic, PreprocessingError, ) -from tests.fortran.command_line_interface.pipeline._support import ( +from tests.fortran.infrastructure.cli.pipeline._support import ( TEST_FILE, _MainParserError, _install_main_parser, @@ -650,9 +650,7 @@ def test_subcommand_help_tailors_shared_compiler_options(command, expected, excl def test_cli_parse_shows_module_derived_types_and_derived_arg_kinds(): - fixture = ( - Path(__file__).parents[2] / "source_parsing" / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" - ) + fixture = Path(__file__).parents[2] / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" cmd = [sys.executable, "-m", "prik", "parse", str(fixture)] res = subprocess.run(cmd, capture_output=True, text=True, check=True) diff --git a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py b/tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py similarity index 99% rename from tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py rename to tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py index 8e4049cdc..66ec5c829 100644 --- a/tests/fortran/command_line_interface/pipeline/test_stage_dispatch.py +++ b/tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py @@ -19,7 +19,7 @@ PreprocessingError, ) from prik.semantics.fortran2ir import collect_semantic_compile_time_requirements -from tests.fortran.command_line_interface.pipeline._support import ( +from tests.fortran.infrastructure.cli.pipeline._support import ( TEST_FILE, _install_main_parser, _main_args, @@ -572,9 +572,7 @@ def fail_parse(_paths, _preprocessing): def test_cli_parse_modern_fixture_prints_derived_block_verbatim(): - fixture = ( - Path(__file__).parents[2] / "source_parsing" / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" - ) + fixture = Path(__file__).parents[2] / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" cmd = [sys.executable, "-m", "prik", "parse", str(fixture)] res = subprocess.run(cmd, capture_output=True, text=True, check=True) diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_argument_name.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_argument_name.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_argument_name.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_argument_name.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_argument_name.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_declaration_procedure.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_declaration_procedure.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_declaration_procedure.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_declaration_procedure.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_declaration_procedure.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_declaration_procedure.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_declaration_procedure.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_declaration_procedure.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_field_derived_type.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_field_derived_type.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_field_derived_type.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_field_derived_type.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_field_derived_type.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_field_derived_type.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_field_derived_type.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_field_derived_type.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_parameter.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_parameter.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_parameter.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_parameter.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_parameter.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_parameter.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_parameter.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_parameter.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_global.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_global.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_global.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_global.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_global.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_global.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_global.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_global.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_module.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_module.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_module.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_module.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_module.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_module.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_procedure_module.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_procedure_module.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_variable_module.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_variable_module.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_variable_module.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_variable_module.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_variable_module.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_variable_module.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_duplicate_variable_module.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_duplicate_variable_module.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_arg.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_arg.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_arg.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_arg.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_arg.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_arg.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_arg.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_arg.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_result.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_result.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_result.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_result.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_result.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_result.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_implicit_none_undeclared_result.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_implicit_none_undeclared_result.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_parameter_without_type_implicit_none.f b/tests/fortran/infrastructure/parsing/fixtures/errors/err_parameter_without_type_implicit_none.f similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_parameter_without_type_implicit_none.f rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_parameter_without_type_implicit_none.f diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_parameter_without_type_implicit_none.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_parameter_without_type_implicit_none.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_parameter_without_type_implicit_none.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_parameter_without_type_implicit_none.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_result_shadows_argument.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_result_shadows_argument.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_result_shadows_argument.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_result_shadows_argument.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_result_shadows_argument.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_result_shadows_argument.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_result_shadows_argument.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_result_shadows_argument.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_function_result.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_function_result.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_function_result.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_function_result.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_function_result.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_function_result.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_function_result.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_function_result.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_derived_type.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_derived_type.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_derived_type.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_derived_type.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_derived_type.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_derived_type.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_derived_type.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_derived_type.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_module.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_module.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_module.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_module.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_module.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_module.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_module.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_module.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_procedure.f90 b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_procedure.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_procedure.f90 rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_procedure.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_procedure.json b/tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_procedure.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/errors/err_unknown_type_procedure.json rename to tests/fortran/infrastructure/parsing/fixtures/errors/err_unknown_type_procedure.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/assumed_shape_and_derived_args.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/assumed_shape_and_derived_args.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/assumed_shape_and_derived_args.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/assumed_shape_and_derived_args.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/assumed_shape_and_derived_args.json b/tests/fortran/infrastructure/parsing/fixtures/general/assumed_shape_and_derived_args.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/assumed_shape_and_derived_args.json rename to tests/fortran/infrastructure/parsing/fixtures/general/assumed_shape_and_derived_args.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.json b/tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/basic_subroutine.json rename to tests/fortran/infrastructure/parsing/fixtures/general/basic_subroutine.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/compile_time_all_exprs.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/compile_time_all_exprs.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/compile_time_all_exprs.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/compile_time_all_exprs.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/compile_time_all_exprs.json b/tests/fortran/infrastructure/parsing/fixtures/general/compile_time_all_exprs.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/compile_time_all_exprs.json rename to tests/fortran/infrastructure/parsing/fixtures/general/compile_time_all_exprs.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/compile_time_shape_exprs.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/compile_time_shape_exprs.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/compile_time_shape_exprs.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/compile_time_shape_exprs.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/compile_time_shape_exprs.json b/tests/fortran/infrastructure/parsing/fixtures/general/compile_time_shape_exprs.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/compile_time_shape_exprs.json rename to tests/fortran/infrastructure/parsing/fixtures/general/compile_time_shape_exprs.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/derived_type.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/derived_type.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/derived_type.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/derived_type.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/derived_type.json b/tests/fortran/infrastructure/parsing/fixtures/general/derived_type.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/derived_type.json rename to tests/fortran/infrastructure/parsing/fixtures/general/derived_type.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/derived_types_and_methods.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/derived_types_and_methods.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.json b/tests/fortran/infrastructure/parsing/fixtures/general/derived_types_and_methods.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/derived_types_and_methods.json rename to tests/fortran/infrastructure/parsing/fixtures/general/derived_types_and_methods.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/f77_subroutine.f b/tests/fortran/infrastructure/parsing/fixtures/general/f77_subroutine.f similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/f77_subroutine.f rename to tests/fortran/infrastructure/parsing/fixtures/general/f77_subroutine.f diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/f77_subroutine.json b/tests/fortran/infrastructure/parsing/fixtures/general/f77_subroutine.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/f77_subroutine.json rename to tests/fortran/infrastructure/parsing/fixtures/general/f77_subroutine.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/modern_pyi_example.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/modern_pyi_example.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.json b/tests/fortran/infrastructure/parsing/fixtures/general/modern_pyi_example.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/modern_pyi_example.json rename to tests/fortran/infrastructure/parsing/fixtures/general/modern_pyi_example.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/module_vars_use.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/module_vars_use.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/module_vars_use.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/module_vars_use.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/module_vars_use.json b/tests/fortran/infrastructure/parsing/fixtures/general/module_vars_use.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/module_vars_use.json rename to tests/fortran/infrastructure/parsing/fixtures/general/module_vars_use.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/procedures_and_functions.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/procedures_and_functions.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/procedures_and_functions.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/procedures_and_functions.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/procedures_and_functions.json b/tests/fortran/infrastructure/parsing/fixtures/general/procedures_and_functions.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/procedures_and_functions.json rename to tests/fortran/infrastructure/parsing/fixtures/general/procedures_and_functions.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.f90 b/tests/fortran/infrastructure/parsing/fixtures/general/scope_name_reuse_combinations.f90 similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.f90 rename to tests/fortran/infrastructure/parsing/fixtures/general/scope_name_reuse_combinations.f90 diff --git a/tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.json b/tests/fortran/infrastructure/parsing/fixtures/general/scope_name_reuse_combinations.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/general/scope_name_reuse_combinations.json rename to tests/fortran/infrastructure/parsing/fixtures/general/scope_name_reuse_combinations.json diff --git a/tests/fortran/source_parsing/parsing/fixtures/json_sanity_allowlist.json b/tests/fortran/infrastructure/parsing/fixtures/json_sanity_allowlist.json similarity index 100% rename from tests/fortran/source_parsing/parsing/fixtures/json_sanity_allowlist.json rename to tests/fortran/infrastructure/parsing/fixtures/json_sanity_allowlist.json diff --git a/tests/fortran/source_parsing/parsing/generate_error_goldens.py b/tests/fortran/infrastructure/parsing/generate_error_goldens.py similarity index 100% rename from tests/fortran/source_parsing/parsing/generate_error_goldens.py rename to tests/fortran/infrastructure/parsing/generate_error_goldens.py diff --git a/tests/fortran/source_parsing/parsing/generate_parser_goldens.py b/tests/fortran/infrastructure/parsing/generate_parser_goldens.py similarity index 100% rename from tests/fortran/source_parsing/parsing/generate_parser_goldens.py rename to tests/fortran/infrastructure/parsing/generate_parser_goldens.py diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_interface_edges.py b/tests/fortran/infrastructure/parsing/test_declaration_and_interface_edges.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_declaration_and_interface_edges.py rename to tests/fortran/infrastructure/parsing/test_declaration_and_interface_edges.py diff --git a/tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py b/tests/fortran/infrastructure/parsing/test_declaration_and_scope_regressions.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_declaration_and_scope_regressions.py rename to tests/fortran/infrastructure/parsing/test_declaration_and_scope_regressions.py diff --git a/tests/fortran/source_parsing/parsing/test_derived_types_and_program_units.py b/tests/fortran/infrastructure/parsing/test_derived_types_and_program_units.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_derived_types_and_program_units.py rename to tests/fortran/infrastructure/parsing/test_derived_types_and_program_units.py diff --git a/tests/fortran/source_parsing/parsing/test_developer_tutorial.py b/tests/fortran/infrastructure/parsing/test_developer_tutorial.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_developer_tutorial.py rename to tests/fortran/infrastructure/parsing/test_developer_tutorial.py diff --git a/tests/fortran/source_parsing/parsing/test_error_fixture_suite.py b/tests/fortran/infrastructure/parsing/test_error_fixture_suite.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_error_fixture_suite.py rename to tests/fortran/infrastructure/parsing/test_error_fixture_suite.py diff --git a/tests/fortran/source_parsing/parsing/test_error_handling.py b/tests/fortran/infrastructure/parsing/test_error_handling.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_error_handling.py rename to tests/fortran/infrastructure/parsing/test_error_handling.py diff --git a/tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py b/tests/fortran/infrastructure/parsing/test_fortran_fixture_suite.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_fortran_fixture_suite.py rename to tests/fortran/infrastructure/parsing/test_fortran_fixture_suite.py diff --git a/tests/fortran/source_parsing/parsing/test_fortran_parser_procedures_and_interfaces.py b/tests/fortran/infrastructure/parsing/test_fortran_parser_procedures_and_interfaces.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_fortran_parser_procedures_and_interfaces.py rename to tests/fortran/infrastructure/parsing/test_fortran_parser_procedures_and_interfaces.py diff --git a/tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py b/tests/fortran/infrastructure/parsing/test_fortran_parser_properties.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_fortran_parser_properties.py rename to tests/fortran/infrastructure/parsing/test_fortran_parser_properties.py diff --git a/tests/fortran/source_parsing/parsing/test_json_sanity.py b/tests/fortran/infrastructure/parsing/test_json_sanity.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_json_sanity.py rename to tests/fortran/infrastructure/parsing/test_json_sanity.py diff --git a/tests/fortran/source_parsing/parsing/test_parser_benchmarks.py b/tests/fortran/infrastructure/parsing/test_parser_benchmarks.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_parser_benchmarks.py rename to tests/fortran/infrastructure/parsing/test_parser_benchmarks.py diff --git a/tests/fortran/source_parsing/parsing/test_public_entrypoints.py b/tests/fortran/infrastructure/parsing/test_public_entrypoints.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_public_entrypoints.py rename to tests/fortran/infrastructure/parsing/test_public_entrypoints.py diff --git a/tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py b/tests/fortran/infrastructure/parsing/test_real_world_interaction_regressions.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_real_world_interaction_regressions.py rename to tests/fortran/infrastructure/parsing/test_real_world_interaction_regressions.py diff --git a/tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py b/tests/fortran/infrastructure/parsing/test_source_form_and_diagnostics_regressions.py similarity index 100% rename from tests/fortran/source_parsing/parsing/test_source_form_and_diagnostics_regressions.py rename to tests/fortran/infrastructure/parsing/test_source_form_and_diagnostics_regressions.py diff --git a/tests/fortran/infrastructure/semantics/test_native_array_handles.py b/tests/fortran/infrastructure/policy/test_native_array_handles.py similarity index 100% rename from tests/fortran/infrastructure/semantics/test_native_array_handles.py rename to tests/fortran/infrastructure/policy/test_native_array_handles.py diff --git a/tests/fortran/infrastructure/semantics/test_ownership.py b/tests/fortran/infrastructure/policy/test_ownership.py similarity index 100% rename from tests/fortran/infrastructure/semantics/test_ownership.py rename to tests/fortran/infrastructure/policy/test_ownership.py diff --git a/tests/fortran/infrastructure/semantics/test_policy_completion.py b/tests/fortran/infrastructure/policy/test_policy_completion.py similarity index 100% rename from tests/fortran/infrastructure/semantics/test_policy_completion.py rename to tests/fortran/infrastructure/policy/test_policy_completion.py diff --git a/tests/fortran/infrastructure/semantics/test_wrapper_policy.py b/tests/fortran/infrastructure/policy/test_wrapper_policy.py similarity index 100% rename from tests/fortran/infrastructure/semantics/test_wrapper_policy.py rename to tests/fortran/infrastructure/policy/test_wrapper_policy.py diff --git a/tests/fortran/source_preprocessing/preprocessing/_support.py b/tests/fortran/infrastructure/preprocessing/_support.py similarity index 100% rename from tests/fortran/source_preprocessing/preprocessing/_support.py rename to tests/fortran/infrastructure/preprocessing/_support.py diff --git a/tests/fortran/source_preprocessing/preprocessing/test_cli.py b/tests/fortran/infrastructure/preprocessing/test_cli.py similarity index 98% rename from tests/fortran/source_preprocessing/preprocessing/test_cli.py rename to tests/fortran/infrastructure/preprocessing/test_cli.py index 2ce3ce4cf..16abb1eef 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_cli.py +++ b/tests/fortran/infrastructure/preprocessing/test_cli.py @@ -5,7 +5,7 @@ import subprocess import sys -from tests.fortran.source_preprocessing.preprocessing._support import _fake_compiler +from tests.fortran.infrastructure.preprocessing._support import _fake_compiler def test_cli_help_documents_exact_compiler_and_preprocessing_examples(): diff --git a/tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py b/tests/fortran/infrastructure/preprocessing/test_configuration_and_adapters.py similarity index 99% rename from tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py rename to tests/fortran/infrastructure/preprocessing/test_configuration_and_adapters.py index 7a26d31c8..99448152f 100644 --- a/tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py +++ b/tests/fortran/infrastructure/preprocessing/test_configuration_and_adapters.py @@ -15,7 +15,7 @@ run_compiler_preprocessor_with_recipe, validate_macro_name, ) -from tests.fortran.source_preprocessing.preprocessing._support import _assert_preprocessing_error +from tests.fortran.infrastructure.preprocessing._support import _assert_preprocessing_error def test_direct_fortran_preprocess_invocation_uses_exact_compiler_and_cpp(tmp_path: Path): diff --git a/tests/fortran/source_preprocessing/preprocessing/test_dependencies_and_includes.py b/tests/fortran/infrastructure/preprocessing/test_dependencies_and_includes.py similarity index 100% rename from tests/fortran/source_preprocessing/preprocessing/test_dependencies_and_includes.py rename to tests/fortran/infrastructure/preprocessing/test_dependencies_and_includes.py diff --git a/tests/fortran/source_preprocessing/preprocessing/test_execution.py b/tests/fortran/infrastructure/preprocessing/test_execution.py similarity index 100% rename from tests/fortran/source_preprocessing/preprocessing/test_execution.py rename to tests/fortran/infrastructure/preprocessing/test_execution.py diff --git a/tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py b/tests/fortran/infrastructure/preprocessing/test_parser_boundaries.py similarity index 100% rename from tests/fortran/source_preprocessing/preprocessing/test_parser_boundaries.py rename to tests/fortran/infrastructure/preprocessing/test_parser_boundaries.py diff --git a/tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py b/tests/fortran/infrastructure/preprocessing/test_preprocessing_properties.py similarity index 100% rename from tests/fortran/source_preprocessing/preprocessing/test_preprocessing_properties.py rename to tests/fortran/infrastructure/preprocessing/test_preprocessing_properties.py diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/assumed_shape_and_derived_args.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/assumed_shape_and_derived_args.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/assumed_shape_and_derived_args.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/assumed_shape_and_derived_args.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/basic_subroutine.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/basic_subroutine.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/basic_subroutine.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/basic_subroutine.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/compile_time_all_exprs.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/compile_time_all_exprs.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/compile_time_all_exprs.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/compile_time_all_exprs.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/compile_time_shape_exprs.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/compile_time_shape_exprs.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/compile_time_shape_exprs.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/compile_time_shape_exprs.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/derived_type.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/derived_type.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/derived_type.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/derived_type.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/derived_types_and_methods.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/derived_types_and_methods.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/derived_types_and_methods.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/derived_types_and_methods.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/f77_subroutine.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/f77_subroutine.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/f77_subroutine.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/f77_subroutine.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/modern_pyi_example.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/modern_pyi_example.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/modern_pyi_example.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/modern_pyi_example.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/module_vars_use.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/module_vars_use.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/module_vars_use.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/module_vars_use.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/procedures_and_functions.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/procedures_and_functions.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/procedures_and_functions.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/procedures_and_functions.json diff --git a/tests/fortran/semantic_ir/semantics/fixtures/general/expected/scope_name_reuse_combinations.json b/tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/scope_name_reuse_combinations.json similarity index 100% rename from tests/fortran/semantic_ir/semantics/fixtures/general/expected/scope_name_reuse_combinations.json rename to tests/fortran/infrastructure/semantic_ir/semantics/fixtures/general/expected/scope_name_reuse_combinations.json diff --git a/tests/fortran/semantic_ir/semantics/generate_semantic_fixtures.py b/tests/fortran/infrastructure/semantic_ir/semantics/generate_semantic_fixtures.py similarity index 100% rename from tests/fortran/semantic_ir/semantics/generate_semantic_fixtures.py rename to tests/fortran/infrastructure/semantic_ir/semantics/generate_semantic_fixtures.py diff --git a/tests/fortran/semantic_ir/semantics/test_compile_time_values.py b/tests/fortran/infrastructure/semantic_ir/semantics/test_compile_time_values.py similarity index 100% rename from tests/fortran/semantic_ir/semantics/test_compile_time_values.py rename to tests/fortran/infrastructure/semantic_ir/semantics/test_compile_time_values.py diff --git a/tests/fortran/semantic_ir/semantics/test_fortran_conversion_properties.py b/tests/fortran/infrastructure/semantic_ir/semantics/test_fortran_conversion_properties.py similarity index 100% rename from tests/fortran/semantic_ir/semantics/test_fortran_conversion_properties.py rename to tests/fortran/infrastructure/semantic_ir/semantics/test_fortran_conversion_properties.py diff --git a/tests/fortran/semantic_ir/semantics/test_semantic_conversion_smoke.py b/tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_conversion_smoke.py similarity index 100% rename from tests/fortran/semantic_ir/semantics/test_semantic_conversion_smoke.py rename to tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_conversion_smoke.py diff --git a/tests/fortran/semantic_ir/semantics/test_semantic_specialization_properties.py b/tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_specialization_properties.py similarity index 100% rename from tests/fortran/semantic_ir/semantics/test_semantic_specialization_properties.py rename to tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_specialization_properties.py diff --git a/tests/fortran/semantic_pyi_format/README.md b/tests/fortran/infrastructure/semantic_pyi/README.md similarity index 89% rename from tests/fortran/semantic_pyi_format/README.md rename to tests/fortran/infrastructure/semantic_pyi/README.md index eaabb530c..3837a3b71 100644 --- a/tests/fortran/semantic_pyi_format/README.md +++ b/tests/fortran/infrastructure/semantic_pyi/README.md @@ -24,7 +24,7 @@ and call/result behavior remains owned by the three later Run the feature with: ```bash -python3 -m pytest -q tests/fortran/semantic_pyi_format +python3 -m pytest -q tests/fortran/infrastructure/semantic_pyi ``` Refresh the reviewed contract packages only after reviewing a deliberate @@ -32,5 +32,5 @@ format change: ```bash WRAPPER_UPDATE_PYI_FIXTURES=1 python3 -m pytest -q \ - tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py + tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py ``` diff --git a/tests/fortran/pyi_contracts/calls_and_results/README.md b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/README.md similarity index 93% rename from tests/fortran/pyi_contracts/calls_and_results/README.md rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/README.md index e8963fb2a..78e36078b 100644 --- a/tests/fortran/pyi_contracts/calls_and_results/README.md +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/README.md @@ -26,5 +26,5 @@ owners. Run the focused feature with: ```bash -python3 -m pytest -q tests/fortran/pyi_contracts/calls_and_results +python3 -m pytest -q tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results ``` diff --git a/tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/codegen/test_call_and_result_lowering.py similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/codegen/test_call_and_result_lowering.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/codegen/test_call_and_result_lowering.py diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/__init__.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/foutputs_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/foutputs_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/foutputs_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/hidden_array_output/foutputs_f90.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/__init__.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/fnative_call_examples_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/fnative_call_examples_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/fnative_call_examples_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/immutable_replacements/fnative_call_examples_f90.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/__init__.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/fnative_call_examples_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/fnative_call_examples_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/fnative_call_examples_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/native_order/fnative_call_examples_f90.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/__init__.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/fnative_call_examples_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/fnative_call_examples_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/fnative_call_examples_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/edited_contracts/projected_results/fnative_call_examples_f90.pyi diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/native/fnative_call_examples_f90.f90 b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/native/fnative_call_examples_f90.f90 similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/native/fnative_call_examples_f90.f90 rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/native/fnative_call_examples_f90.f90 diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/native/foutputs_f90.f90 b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/native/foutputs_f90.f90 similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/fixtures/native/foutputs_f90.f90 rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/fixtures/native/foutputs_f90.f90 diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_edited_call_surfaces.py diff --git a/tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_projected_entrypoint_routes.py b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_projected_entrypoint_routes.py similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/end_to_end/test_projected_entrypoint_routes.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/end_to_end/test_projected_entrypoint_routes.py diff --git a/tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py b/tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/policy/test_call_and_result_policy.py similarity index 100% rename from tests/fortran/pyi_contracts/calls_and_results/policy/test_call_and_result_policy.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/calls_and_results/policy/test_call_and_result_policy.py diff --git a/tests/fortran/pyi_contracts/exports_and_modules/README.md b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/README.md similarity index 92% rename from tests/fortran/pyi_contracts/exports_and_modules/README.md rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/README.md index 3ef1e7700..91cc1cd3c 100644 --- a/tests/fortran/pyi_contracts/exports_and_modules/README.md +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/README.md @@ -24,5 +24,5 @@ overload edits remain owned by the later `pyi_contracts` features. Run the focused feature with: ```bash -python3 -m pytest -q tests/fortran/pyi_contracts/exports_and_modules +python3 -m pytest -q tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules ``` diff --git a/tests/fortran/pyi_contracts/exports_and_modules/codegen/test_module_initializer_lowering.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/codegen/test_module_initializer_lowering.py similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/codegen/test_module_initializer_lowering.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/codegen/test_module_initializer_lowering.py diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/aliases.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/aliases.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/aliases.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/aliases.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/collision.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/collision.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/collision.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/collision.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/facade.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/facade.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/facade.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/facade.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/flatten.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/flatten.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/flatten.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/flatten.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/module1_added_binding.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/module1_added_binding.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/module1_added_binding.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_exports/module1_added_binding.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/__init__.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/fmodule_vars_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/fmodule_vars_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/fmodule_vars_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/edited_contracts/module_variables_visibility/fmodule_vars_f90.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/__init__.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/fnaming_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/fnaming_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/fnaming_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/visibility/contracts/fnaming_f90/fnaming_f90.pyi diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/visibility/native/fnaming_f90.f90 b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/visibility/native/fnaming_f90.f90 similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/fixtures/visibility/native/fnaming_f90.f90 rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/fixtures/visibility/native/fnaming_f90.f90 diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py similarity index 98% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py index e00f95425..965ec5504 100644 --- a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_package_exports.py +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py @@ -14,7 +14,7 @@ ) from prik import build_pyi_extension -MODULE_FIXTURES = Path(__file__).parents[3] / "modules" / "end_to_end" / "fixtures" +MODULE_FIXTURES = Path(__file__).parents[5] / "modules" / "end_to_end" / "fixtures" EDITED_ENTRIES = Path(__file__).parent / "fixtures" / "edited_contracts" / "module_exports" SOURCE = MODULE_FIXTURES / "module_exports.f90" BASE_CONTRACT = MODULE_FIXTURES / "contracts" / "module_exports" diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py similarity index 96% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py index e1f0d92e5..f20eff1e3 100644 --- a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py @@ -12,7 +12,7 @@ ) from prik import build_pyi_extension -MODULE_FIXTURES = Path(__file__).parents[3] / "modules" / "end_to_end" / "fixtures" +MODULE_FIXTURES = Path(__file__).parents[5] / "modules" / "end_to_end" / "fixtures" FEATURE_FIXTURES = Path(__file__).parent / "fixtures" MODULE_VARIABLE_SOURCE = MODULE_FIXTURES / "fmodule_vars_f90.f90" MODIFIED_CONTRACT = FEATURE_FIXTURES / "edited_contracts" / "module_variables_visibility" / "__init__.pyi" diff --git a/tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_naming.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_naming.py similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/end_to_end/test_visibility_naming.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_naming.py diff --git a/tests/fortran/pyi_contracts/exports_and_modules/pipeline/test_naming_generated_contracts.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/pipeline/test_naming_generated_contracts.py similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/pipeline/test_naming_generated_contracts.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/pipeline/test_naming_generated_contracts.py diff --git a/tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/policy/test_export_and_initializer_policy.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py diff --git a/tests/fortran/pyi_contracts/exports_and_modules/semantics/test_module_initializers.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/semantics/test_module_initializers.py similarity index 100% rename from tests/fortran/pyi_contracts/exports_and_modules/semantics/test_module_initializers.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/semantics/test_module_initializers.py diff --git a/tests/fortran/pyi_contracts/functions_and_classes/README.md b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/README.md similarity index 93% rename from tests/fortran/pyi_contracts/functions_and_classes/README.md rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/README.md index b41a3d2b2..7e2f3a609 100644 --- a/tests/fortran/pyi_contracts/functions_and_classes/README.md +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/README.md @@ -25,5 +25,5 @@ remain with the later Calls and Results feature. Run the focused feature with: ```bash -python3 -m pytest -q tests/fortran/pyi_contracts/functions_and_classes +python3 -m pytest -q tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes ``` diff --git a/tests/fortran/pyi_contracts/functions_and_classes/codegen/test_constructor_lowering.py b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/codegen/test_constructor_lowering.py similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/codegen/test_constructor_lowering.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/codegen/test_constructor_lowering.py diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/__init__.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/fclasses_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/fclasses_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/fclasses_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/method_and_constructor/fclasses_f90.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/__init__.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/foverloads_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/foverloads_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/foverloads_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/overloaded_api/foverloads_f90.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/__init__.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/foverloads_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/foverloads_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/foverloads_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_module_specifics_without_bind/foverloads_f90.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/__init__.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/foverloads_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/foverloads_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/foverloads_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/private_type_bound_specifics_without_bind/foverloads_f90.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/__init__.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/foverloads_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/foverloads_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/foverloads_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/pruned_surface/foverloads_f90.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/__init__.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/__init__.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/foverloads_f90.pyi b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/foverloads_f90.pyi similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/foverloads_f90.pyi rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/fixtures/edited_contracts/without_constructor_member/foverloads_f90.pyi diff --git a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py similarity index 97% rename from tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py index bee0f8865..4e89160f3 100644 --- a/tests/fortran/pyi_contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py @@ -13,8 +13,8 @@ from prik import build_pyi_extension FEATURE_ROOT = Path(__file__).parent / "fixtures" / "edited_contracts" -DERIVED_FIXTURES = Path(__file__).parents[3] / "derived_types" / "end_to_end" / "fixtures" -GENERIC_FIXTURES = Path(__file__).parents[3] / "generic_interfaces" / "end_to_end" / "fixtures" +DERIVED_FIXTURES = Path(__file__).parents[5] / "derived_types" / "end_to_end" / "fixtures" +GENERIC_FIXTURES = Path(__file__).parents[5] / "generic_interfaces" / "end_to_end" / "fixtures" CLASS_SOURCE = DERIVED_FIXTURES / "fclasses_f90.f90" OVERLOAD_SOURCE = GENERIC_FIXTURES / "foverloads_f90.f90" pytestmark = pytest.mark.fortran_end_to_end diff --git a/tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/policy/test_class_surface_policy.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py diff --git a/tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py similarity index 100% rename from tests/fortran/pyi_contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py rename to tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py diff --git a/tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py b/tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py similarity index 100% rename from tests/fortran/semantic_pyi_format/end_to_end/test_authoritative_contract_runtime.py rename to tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py diff --git a/tests/fortran/semantic_pyi_format/end_to_end/test_contract_package_runtime.py b/tests/fortran/infrastructure/semantic_pyi/end_to_end/test_contract_package_runtime.py similarity index 96% rename from tests/fortran/semantic_pyi_format/end_to_end/test_contract_package_runtime.py rename to tests/fortran/infrastructure/semantic_pyi/end_to_end/test_contract_package_runtime.py index 704a31412..eb0f7a231 100644 --- a/tests/fortran/semantic_pyi_format/end_to_end/test_contract_package_runtime.py +++ b/tests/fortran/infrastructure/semantic_pyi/end_to_end/test_contract_package_runtime.py @@ -14,7 +14,7 @@ from tests.fortran._support.pyi_fixtures import assert_generated_pyi_package_matches_fixture from tests.fortran._support.wrapper_build import REPO_ROOT -SEMANTIC_PYI_FIXTURES = REPO_ROOT / "tests" / "fortran" / "semantic_pyi_format" / "pipeline" / "fixtures" +SEMANTIC_PYI_FIXTURES = REPO_ROOT / "tests" / "fortran" / "infrastructure" / "semantic_pyi" / "pipeline" / "fixtures" NATIVE_FIXTURES = SEMANTIC_PYI_FIXTURES / "native" CONTRACT_FIXTURES = SEMANTIC_PYI_FIXTURES / "contracts" STANDALONE_ONLY = NATIVE_FIXTURES / "contract_standalone_only.f90" diff --git a/tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py b/tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py similarity index 100% rename from tests/fortran/semantic_pyi_format/parsing/test_python_ast_contracts.py rename to tests/fortran/infrastructure/semantic_pyi/parsing/test_python_ast_contracts.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_import_graph/generated/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_import_graph/generated/__init__.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_import_graph/generated/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_import_graph/generated/__init__.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_import_graph/generated/deep.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_import_graph/generated/deep.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_import_graph/generated/deep.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_import_graph/generated/deep.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_import_graph/generated/m1.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_import_graph/generated/m1.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_import_graph/generated/m1.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_import_graph/generated/m1.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_mixed_module_external/generated/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_mixed_module_external/generated/__init__.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_mixed_module_external/generated/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_mixed_module_external/generated/__init__.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_mixed_module_external/generated/contract_math_mod.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_mixed_module_external/generated/contract_math_mod.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_mixed_module_external/generated/contract_math_mod.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_mixed_module_external/generated/contract_math_mod.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_same_name/generated/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_same_name/generated/__init__.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_same_name/generated/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_same_name/generated/__init__.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_same_name/generated/contract_same_name.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_same_name/generated/contract_same_name.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_same_name/generated/contract_same_name.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_same_name/generated/contract_same_name.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_standalone_only/generated/__init__.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_standalone_only/generated/__init__.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/contracts/contract_standalone_only/generated/__init__.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/contracts/contract_standalone_only/generated/__init__.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/invalid/projection_metadata/incomplete_native_call.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/invalid/projection_metadata/incomplete_native_call.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/invalid/projection_metadata/incomplete_native_call.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/invalid/projection_metadata/incomplete_native_call.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/modern_math_physics.pyi b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/modern_math_physics.pyi similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/modern_math_physics.pyi rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/modern_math_physics.pyi diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_import_graph.f90 b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_import_graph.f90 similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_import_graph.f90 rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_import_graph.f90 diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_mixed_module_external.f90 b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_mixed_module_external.f90 similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_mixed_module_external.f90 rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_mixed_module_external.f90 diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_multi_module.f90 b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_multi_module.f90 similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_multi_module.f90 rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_multi_module.f90 diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_same_name.f90 b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_same_name.f90 similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_same_name.f90 rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_same_name.f90 diff --git a/tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_standalone_only.f90 b/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_standalone_only.f90 similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/fixtures/native/contract_standalone_only.f90 rename to tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/native/contract_standalone_only.f90 diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_calls_and_policy_metadata.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_calls_and_policy_metadata.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_calls_and_policy_metadata.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_classes_and_methods.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_classes_and_methods.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_classes_and_methods.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_contract_loading.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_contract_package_generation.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_package_generation.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_modern_example.py similarity index 88% rename from tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_modern_example.py index 1b8219b59..fdc029f4a 100644 --- a/tests/fortran/semantic_pyi_format/pipeline/test_modern_example.py +++ b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_modern_example.py @@ -6,14 +6,7 @@ def test_modern_fortran_example_pyi_snapshot(): - fixture = ( - Path(__file__).resolve().parents[2] - / "source_parsing" - / "parsing" - / "fixtures" - / "general" - / "modern_pyi_example.f90" - ) + fixture = Path(__file__).resolve().parents[2] / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" expected_fixture = Path(__file__).parent / "fixtures" / "modern_math_physics.pyi" source = fixture.read_text(encoding="utf-8") diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_native_abi_source_round_trip.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_native_abi_source_round_trip.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_native_abi_source_round_trip.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_native_abi_source_round_trip.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_conversion_smoke.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_conversion_smoke.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_conversion_smoke.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_conversion_smoke.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_imports_and_packages.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_pyi_printer_imports_and_packages.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_imports_and_packages.py diff --git a/tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_types_and_declarations.py similarity index 100% rename from tests/fortran/semantic_pyi_format/pipeline/test_types_and_declarations.py rename to tests/fortran/infrastructure/semantic_pyi/pipeline/test_types_and_declarations.py diff --git a/tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py b/tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py similarity index 100% rename from tests/fortran/semantic_pyi_format/semantics/test_calls_and_projections.py rename to tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py diff --git a/tests/fortran/semantic_pyi_format/semantics/test_classes_and_overloads.py b/tests/fortran/infrastructure/semantic_pyi/semantics/test_classes_and_overloads.py similarity index 100% rename from tests/fortran/semantic_pyi_format/semantics/test_classes_and_overloads.py rename to tests/fortran/infrastructure/semantic_pyi/semantics/test_classes_and_overloads.py diff --git a/tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py b/tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py similarity index 100% rename from tests/fortran/semantic_pyi_format/semantics/test_imports_and_packages.py rename to tests/fortran/infrastructure/semantic_pyi/semantics/test_imports_and_packages.py diff --git a/tests/fortran/semantic_pyi_format/semantics/test_native_abi.py b/tests/fortran/infrastructure/semantic_pyi/semantics/test_native_abi.py similarity index 100% rename from tests/fortran/semantic_pyi_format/semantics/test_native_abi.py rename to tests/fortran/infrastructure/semantic_pyi/semantics/test_native_abi.py diff --git a/tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py b/tests/fortran/infrastructure/semantic_pyi/semantics/test_round_trip_properties.py similarity index 100% rename from tests/fortran/semantic_pyi_format/semantics/test_round_trip_properties.py rename to tests/fortran/infrastructure/semantic_pyi/semantics/test_round_trip_properties.py diff --git a/tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py b/tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py similarity index 100% rename from tests/fortran/semantic_pyi_format/semantics/test_types_and_values.py rename to tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py diff --git a/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py b/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py index 7b726b902..4f42ac022 100644 --- a/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py +++ b/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py @@ -15,7 +15,9 @@ NATIVE_CALL_EXAMPLES_F90_SOURCE = ( Path(__file__).parents[2] - / "pyi_contracts" + / "infrastructure" + / "semantic_pyi" + / "contracts" / "calls_and_results" / "end_to_end" / "fixtures" diff --git a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py index dbda093eb..fd43d806f 100644 --- a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py +++ b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py @@ -17,7 +17,16 @@ FMATH_CONTRACT = Path("tests/fortran/data_types/end_to_end/fixtures/baseline/contracts/fmath/__init__.pyi") -CALLS_NATIVE = Path(__file__).parents[2] / "pyi_contracts" / "calls_and_results" / "end_to_end" / "fixtures" / "native" +CALLS_NATIVE = ( + Path(__file__).parents[2] + / "infrastructure" + / "semantic_pyi" + / "contracts" + / "calls_and_results" + / "end_to_end" + / "fixtures" + / "native" +) def _source_semantic_module(filename: str, *, module_name: str): diff --git a/tools/run_fortran_toolchain_lane.py b/tools/run_fortran_toolchain_lane.py index 77cd8f203..b7b213ee1 100644 --- a/tools/run_fortran_toolchain_lane.py +++ b/tools/run_fortran_toolchain_lane.py @@ -14,11 +14,11 @@ REPO_ROOT = Path(__file__).resolve().parents[1] PROFILE_TEST_PATHS = ( - "tests/fortran/building_shared_library/compiling/test_compiler_verbose.py", - "tests/fortran/source_preprocessing/preprocessing/test_configuration_and_adapters.py", + "tests/fortran/infrastructure/building/compiling/test_compiler_verbose.py", + "tests/fortran/infrastructure/preprocessing/test_configuration_and_adapters.py", ) FOCUSED_FORTRAN_CLI_NODES = ( - "tests/fortran/source_preprocessing/preprocessing/test_cli.py::" + "tests/fortran/infrastructure/preprocessing/test_cli.py::" "test_cli_fortran_compiler_mode_runs_exact_compiler_and_parses_stdout", ) From 9e3db6f9484111d1945d5d1489d780be1d87ad4b Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 19:24:08 +0100 Subject: [PATCH 19/26] codex: Repair the references the test reorganisation left behind Moving the C and Fortran suites to `/` updated everything inside `tests/`, but several references outside it still named the old paths. The tracked pre-push hook was the worst: it pointed at a wrapper smoke node that no longer collects, so pytest exited 4 and every push from a clone with `core.hooksPath` enabled was blocked. Repoint the hook, the two published feature-matrix evidence links, the golden-regeneration command in the C parser fixture README, and three package-level pointers. Reconcile both owner tables with the tree: drop the `infrastructure/types/` row for a directory that does not exist, add `printers/`, and record the C `execution_examples/` owner. Vulture matches `fnmatch` against the resolved absolute path, so every repo-relative pattern in its exclude list had silently matched nothing since it was written. Rewrite them with a leading `*/`, which also restores coverage of the relocated build fixtures. Finally, replace the depth-coupled `Path(__file__).parents[N]` arithmetic that reached across owners -- three sites had grown to `parents[5]` -- with anchors in `tests//_support/paths.py`. Each root was previously defined in four places at four different depths; a later move would have resolved them to the wrong directory instead of failing. `_visit_FortranModule` also crossed the staged complexity limit when abstract types landed, which blocks the same pre-push hook; extract `_record_abstract_type_names` to bring it back under. Co-Authored-By: Claude Opus 5 --- .githooks/pre-push | 2 +- docs/user/language-support/feature-matrix.md | 4 ++-- prik/parsers/c/README.md | 2 +- prik/parsers/c/parser.py | 2 +- prik/preprocessing/README.md | 4 ++-- prik/semantics/fortran2ir.py | 14 +++++++++----- pyproject.toml | 16 ++++++++++------ tests/README.md | 15 +++++++++------ tests/c/README.md | 1 + tests/c/_support/fixture_outputs.py | 3 +-- tests/c/_support/paths.py | 13 +++++++++++++ tests/c/fixtures/parser/README.md | 2 +- tests/c/infrastructure/parsing/test_c_corpus.py | 4 ++-- .../parsing/test_c_error_fixture_suite.py | 3 ++- .../parsing/test_c_fixture_suite.py | 3 ++- .../infrastructure/parsing/test_c_json_sanity.py | 4 ++-- tests/fortran/README.md | 4 ++-- tests/fortran/_support/fixture_outputs.py | 7 ++++--- tests/fortran/_support/paths.py | 13 +++++++++++++ tests/fortran/_support/printer_models.py | 8 ++------ tests/fortran/_support/wrapper_build.py | 2 +- tests/fortran/conftest.py | 3 ++- .../end_to_end/test_verified_baseline.py | 3 ++- .../test_scalar_generated_pyi_contracts.py | 3 ++- .../test_derived_runtime_mechanisms.py | 3 ++- .../test_scalar_actual_dummy_matrix.py | 3 ++- .../end_to_end/real_libraries/_support.py | 3 ++- .../end_to_end/test_source_build_modes.py | 3 ++- .../infrastructure/cli/pipeline/_support.py | 4 ++-- .../cli/pipeline/test_output_contract.py | 3 ++- .../cli/pipeline/test_stage_dispatch.py | 3 ++- .../parsing/test_parser_benchmarks.py | 4 ++-- .../runtime/test_native_support.py | 7 +++---- .../semantics/test_semantic_conversion_smoke.py | 6 ++---- .../end_to_end/test_package_exports.py | 3 ++- .../test_visibility_and_initialization.py | 3 ++- .../end_to_end/test_edited_class_surfaces.py | 5 +++-- .../semantic_pyi/pipeline/test_modern_example.py | 3 ++- .../test_pyi_printer_conversion_smoke.py | 6 ++---- .../end_to_end/test_explicit_borrowed_owner.py | 5 ++--- .../end_to_end/test_raw_fixed_string_arrays.py | 3 ++- .../end_to_end/test_raw_native_addresses.py | 3 ++- .../policy/test_subroutine_output_policy.py | 3 ++- 43 files changed, 127 insertions(+), 81 deletions(-) create mode 100644 tests/c/_support/paths.py create mode 100644 tests/fortran/_support/paths.py diff --git a/.githooks/pre-push b/.githooks/pre-push index 6763c652b..3a57292f1 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -12,7 +12,7 @@ DOCUMENTATION_SMOKE_TESTS = ( "tests/docs/test_user_content.py", ) WRAPPER_SMOKE_TEST = ( - "tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::" + "tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py::" "test_fortran_wrapper_default_module_name_does_not_collide_with_root_function" ) REQUIRED_TESTS = ("tests/tools", "tests/workflows") diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index 4cfbcf71a..0ec59ec1b 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -95,7 +95,7 @@ PRIK_C_DOCS_END --> | Generated reference pages for modules, functions, and classes | Partially supported | [Reference index](../reference/index.md) | [Codebase map](../../developer/codebase-map.md) | [Documentation reference checks](../../../tests/docs/test_reference_and_codebase_map.py), [semantic contract tests](../../../tests/fortran/infrastructure/semantic_pyi/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. | @@ -117,7 +117,7 @@ memory, or outlive its native storage. | Quad-precision real and complex storage | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | `real(16)` and `complex(16)` have no portable NumPy dtype, so prik blocks them rather than silently narrowing to 64-bit. Narrower real, complex, integer, and all logical kinds are supported. | diff --git a/prik/parsers/c/README.md b/prik/parsers/c/README.md index 991010222..0e08639b3 100644 --- a/prik/parsers/c/README.md +++ b/prik/parsers/c/README.md @@ -28,7 +28,7 @@ not own preprocessing. - User recipe: `docs/user/examples/recipes/inspect-c-api.md` - Source navigation: `docs/developer/codebase-map.md`, `docs/developer/feature-to-code-map.md` - Parser tests: `tests/c/fixtures/parser/` -- Semantic handoff tests: `tests/c/semantics/conversion/` +- Semantic handoff tests: `tests/c/infrastructure/semantic_ir/semantics/` Runtime C-input wrapping is future backend work. Keep C docs clear about the current boundary: parse, semantic IR, and `.pyi` are implemented; diff --git a/prik/parsers/c/parser.py b/prik/parsers/c/parser.py index c20e92840..2eae363d8 100644 --- a/prik/parsers/c/parser.py +++ b/prik/parsers/c/parser.py @@ -61,7 +61,7 @@ parser inputs. Executable walkthroughs live in -``tests/c/parsing/test_c_parser_developer_tutorial.py``. +``tests/c/infrastructure/execution_examples/test_c_parser_developer_tutorial.py``. """ from __future__ import annotations diff --git a/prik/preprocessing/README.md b/prik/preprocessing/README.md index 6f8302250..686d91394 100644 --- a/prik/preprocessing/README.md +++ b/prik/preprocessing/README.md @@ -35,8 +35,8 @@ extension. `prik.compiler` supplies reusable compiler mechanisms; ## Tests And Docs -- `tests/c/preprocessing/` -- `tests/c/probes/` +- `tests/c/infrastructure/preprocessing/` +- `tests/c/data_types/probes/` - `tests/fortran/infrastructure/preprocessing/` - `tests/fortran/data_types/probes/` - `docs/developer/packages/preprocessing.md` diff --git a/prik/semantics/fortran2ir.py b/prik/semantics/fortran2ir.py index 54393b28d..e74126876 100644 --- a/prik/semantics/fortran2ir.py +++ b/prik/semantics/fortran2ir.py @@ -1067,6 +1067,14 @@ def _derived_type_component_fact(field: FortranArgument) -> dict[str, object]: "target": field.target, } + def _record_abstract_type_names(self, module: FortranModule) -> None: + """Remember which of the module's derived types are declared abstract.""" + self._abstract_type_names |= { + str(dtype.name).casefold() + for dtype in module.derived_types + if any(str(attribute).casefold() == "abstract" for attribute in dtype.attributes) + } + def _visit_FortranModule( self, module: FortranModule, @@ -1081,11 +1089,7 @@ def _visit_FortranModule( later policy completion owns wrapper behavior decisions. """ context = self._module_derived_type_context(module) - self._abstract_type_names |= { - str(dtype.name).casefold() - for dtype in module.derived_types - if any(str(attribute).casefold() == "abstract" for attribute in dtype.attributes) - } + self._record_abstract_type_names(module) callback_interfaces = { **(callback_interfaces or {}), **self._callback_interface_lookup(module), diff --git a/pyproject.toml b/pyproject.toml index 2e16608fd..70e9a6c88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,6 +126,8 @@ extend-exclude = [ "tests/c/fixtures/pyi", "tests/fortran/*/end_to_end/fixtures", "tests/fortran/*/pipeline/fixtures", + "tests/fortran/infrastructure/building/end_to_end/fixtures", + "tests/fortran/infrastructure/building/pipeline/fixtures", "tests/fortran/infrastructure/semantic_pyi/contracts/*/end_to_end/fixtures", "tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures", "prik.egg-info", @@ -165,12 +167,14 @@ exclude_dirs = ["tests", "docs", "prik.egg-info"] [tool.vulture] paths = ["prik", "tests"] exclude = [ - "tests/c/fixtures/pyi/", - "tests/fortran/*/end_to_end/fixtures/", - "tests/fortran/*/pipeline/fixtures/", - "tests/fortran/infrastructure/semantic_pyi/contracts/*/end_to_end/fixtures/", - "tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/", - "prik.egg-info/", + "*/tests/c/fixtures/pyi/*", + "*/tests/fortran/*/end_to_end/fixtures/*", + "*/tests/fortran/*/pipeline/fixtures/*", + "*/tests/fortran/infrastructure/building/end_to_end/fixtures/*", + "*/tests/fortran/infrastructure/building/pipeline/fixtures/*", + "*/tests/fortran/infrastructure/semantic_pyi/contracts/*/end_to_end/fixtures/*", + "*/tests/fortran/infrastructure/semantic_pyi/pipeline/fixtures/*", + "*/prik.egg-info/*", ] min_confidence = 80 sort_by_size = true diff --git a/tests/README.md b/tests/README.md index 8e134323f..bd91c2748 100644 --- a/tests/README.md +++ b/tests/README.md @@ -78,12 +78,15 @@ reductions, conditionals, powers, and logical-kind arrays. Contract-batch reconciliation belongs with `tests/fortran/infrastructure/semantic_pyi/`, where editable `.pyi` imports and prototypes are exercised. -Cross-feature mechanisms have explicit infrastructure owners: `parsing/`, -`preprocessing/`, `cli/`, `semantic_ir/`, `semantic_pyi/`, `building/`, and -`policy/`. A user-visible language behavior stays with its feature even when -its test crosses several pipeline stages. Minimized real-world parser -interactions belong under `infrastructure/parsing/`; full third-party snapshots -are temporary analysis inputs, not permanent fixtures. +Cross-feature mechanisms have explicit infrastructure owners. `parsing/`, +`preprocessing/`, `cli/`, `semantic_ir/`, `semantic_pyi/`, and `building/` own +shared pipeline behavior; the remaining owners mirror their production package +(`policy/`, `codegen/`, `printers/`, `naming/`, `pipeline/`, `runtime/`, +`utilities/`). `tests/fortran/README.md` and `tests/c/README.md` carry the +complete per-language tables. A user-visible language behavior stays with its +feature even when its test crosses several pipeline stages. Minimized +real-world parser interactions belong under `infrastructure/parsing/`; full +third-party snapshots are temporary analysis inputs, not permanent fixtures. ## Independent suite gates diff --git a/tests/c/README.md b/tests/c/README.md index 39d7aab77..bcfdb7d16 100644 --- a/tests/c/README.md +++ b/tests/c/README.md @@ -28,6 +28,7 @@ The quarantined owners are: | `infrastructure/preprocessing/` | C recipes, dependencies, mappings, execution, and diagnostics | | `infrastructure/semantic_ir/` | C parser-model conversion to semantic IR | | `infrastructure/semantic_pyi/` | C semantic `.pyi` conversion and source/generated-contract parity | +| `infrastructure/execution_examples/` | Executable C parser walkthroughs kept runnable as documentation | | `fixtures/native/` | C source and include inputs | | `fixtures/parser/` | C parser snapshots and update commands | | `fixtures/pyi/` | checked C generated-contract packages | diff --git a/tests/c/_support/fixture_outputs.py b/tests/c/_support/fixture_outputs.py index 652cebb05..3e9187648 100644 --- a/tests/c/_support/fixture_outputs.py +++ b/tests/c/_support/fixture_outputs.py @@ -11,10 +11,9 @@ from prik.preprocessing import PreprocessingConfig, preprocess_source from prik.semantics.c2ir import c_project_to_semantic_module from prik.printers import emit_module +from tests.c._support.paths import C_DATA_DIR, C_ROOT -C_ROOT = Path(__file__).resolve().parents[1] -C_DATA_DIR = C_ROOT / "fixtures" / "native" GENERAL_C_DIR = C_DATA_DIR / "general" C_PYI_FIXTURE_DIR = C_ROOT / "fixtures" / "pyi" / "general" C_SOURCE_SUFFIXES = {".c", ".h", ".i"} diff --git a/tests/c/_support/paths.py b/tests/c/_support/paths.py new file mode 100644 index 000000000..15bbefb40 --- /dev/null +++ b/tests/c/_support/paths.py @@ -0,0 +1,13 @@ +"""Directory anchors for tests that read a file owned by another directory. + +Computing `Path(__file__).parents[N]` couples a test to its own depth in the +tree, so moving it silently resolves the path to the wrong directory instead of +failing. Import the anchor that names what is wanted. +""" + +from pathlib import Path + +C_ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = C_ROOT.parents[1] +C_DATA_DIR = C_ROOT / "fixtures" / "native" +PARSER_FIXTURE_ROOT = C_ROOT / "fixtures" / "parser" diff --git a/tests/c/fixtures/parser/README.md b/tests/c/fixtures/parser/README.md index d82391464..df84635db 100644 --- a/tests/c/fixtures/parser/README.md +++ b/tests/c/fixtures/parser/README.md @@ -46,7 +46,7 @@ Fatal diagnostic fixtures live in `tests/c/fixtures/native/errors/parser/` and t expected metadata lives in `fixtures/errors/`. Regenerate them with: ```bash -C_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/c/parsing/test_c_error_fixture_suite.py +C_PARSER_UPDATE_GOLDENS=1 PYTHONPATH=. pytest -q tests/c/infrastructure/parsing/test_c_error_fixture_suite.py ``` The standalone error generator remains available for targeted refreshes, and diff --git a/tests/c/infrastructure/parsing/test_c_corpus.py b/tests/c/infrastructure/parsing/test_c_corpus.py index d8b0d5126..1ebe04adf 100644 --- a/tests/c/infrastructure/parsing/test_c_corpus.py +++ b/tests/c/infrastructure/parsing/test_c_corpus.py @@ -5,12 +5,12 @@ constants, and callback hook fields without requiring a large build system. """ -from pathlib import Path import shutil import pytest +from tests.c._support.paths import C_DATA_DIR -_CJSON_DIR = Path(__file__).resolve().parents[2] / "fixtures" / "native" / "json" +_CJSON_DIR = C_DATA_DIR / "json" def _preprocessed_cjson_source(filename: str) -> str: diff --git a/tests/c/infrastructure/parsing/test_c_error_fixture_suite.py b/tests/c/infrastructure/parsing/test_c_error_fixture_suite.py index 555f4d6d0..24c4207a9 100644 --- a/tests/c/infrastructure/parsing/test_c_error_fixture_suite.py +++ b/tests/c/infrastructure/parsing/test_c_error_fixture_suite.py @@ -5,9 +5,10 @@ from pathlib import Path import pytest +from tests.c._support.paths import C_ROOT -_C_ROOT = Path(__file__).resolve().parents[2] +_C_ROOT = C_ROOT _ERRORS_DIR = _C_ROOT / "fixtures" / "native" / "errors" / "parser" _EXPECTED_ERRORS_DIR = _C_ROOT / "fixtures" / "parser" / "fixtures" / "errors" _SOURCE_SUFFIXES = {".c", ".h", ".i"} diff --git a/tests/c/infrastructure/parsing/test_c_fixture_suite.py b/tests/c/infrastructure/parsing/test_c_fixture_suite.py index 0f0932475..c232a0b49 100644 --- a/tests/c/infrastructure/parsing/test_c_fixture_suite.py +++ b/tests/c/infrastructure/parsing/test_c_fixture_suite.py @@ -7,8 +7,9 @@ from pathlib import Path import pytest +from tests.c._support.paths import C_ROOT -_C_ROOT = Path(__file__).resolve().parents[2] +_C_ROOT = C_ROOT _DATA_DIR = _C_ROOT / "fixtures" / "native" _SOURCE_SUFFIXES = {".c", ".h", ".i"} _SOURCE_ORDER = {".c": 0, ".h": 1, ".i": 2} diff --git a/tests/c/infrastructure/parsing/test_c_json_sanity.py b/tests/c/infrastructure/parsing/test_c_json_sanity.py index 2f28dd0a4..57c322e13 100644 --- a/tests/c/infrastructure/parsing/test_c_json_sanity.py +++ b/tests/c/infrastructure/parsing/test_c_json_sanity.py @@ -1,9 +1,9 @@ """JSON schema sanity tests for legacy C parser project snapshots.""" import json -from pathlib import Path +from tests.c._support.paths import PARSER_FIXTURE_ROOT -_FIXTURES_DIR = Path(__file__).resolve().parents[2] / "fixtures" / "parser" / "fixtures" +_FIXTURES_DIR = PARSER_FIXTURE_ROOT / "fixtures" _PARSER_FIXTURE_GROUPS = ("general", "json", "tinyexpr", "linmath", "nanosvg", "stb") diff --git a/tests/fortran/README.md b/tests/fortran/README.md index fcf6ce586..a82dea9f7 100644 --- a/tests/fortran/README.md +++ b/tests/fortran/README.md @@ -84,10 +84,10 @@ representation is supporting evidence, not the ownership rule. | `infrastructure/semantic_pyi/` | Semantic `.pyi` parsing, conversion, contracts, and loading | | `infrastructure/building/` | Shared native build modes, compiler integration, and runtime ABI behavior | | `infrastructure/policy/` | Internal ownership, policy completion, and completed wrapper-policy mechanics | -| `infrastructure/codegen/` | Internal plan, planner, generator, binding, bridge, printer, docstring, advisory review, and visitor mechanics | +| `infrastructure/codegen/` | Internal plan, planner, generator, binding, bridge, docstring, advisory review, and visitor mechanics | | `infrastructure/naming/` | Internal generated-name and public-name policy owned by `prik/naming/` | | `infrastructure/pipeline/` | Generated-wrapper orchestration and transport owned by `prik/pipeline/` | -| `infrastructure/types/` | Internal NumPy type mapping and target mapping-report mechanics | +| `infrastructure/printers/` | Internal C and Fortran source serialization owned by `prik/printers/` | | `infrastructure/utilities/` | Internal string and class-visitor helpers owned by `prik/utilities/` | Each infrastructure test module has an explicit production owner. New internal diff --git a/tests/fortran/_support/fixture_outputs.py b/tests/fortran/_support/fixture_outputs.py index 5a944e212..e2d2d45b8 100644 --- a/tests/fortran/_support/fixture_outputs.py +++ b/tests/fortran/_support/fixture_outputs.py @@ -4,10 +4,11 @@ from prik.parsers.fortran import parse_fortran_file from prik.semantics.fortran2ir import fortran_module_to_semantic_module +from tests.fortran._support.paths import ( + FORTRAN_ROOT, + GENERAL_FORTRAN_DIR, +) -FORTRAN_ROOT = Path(__file__).resolve().parents[1] -PARSER_FIXTURE_ROOT = FORTRAN_ROOT / "infrastructure" / "parsing" / "fixtures" -GENERAL_FORTRAN_DIR = PARSER_FIXTURE_ROOT / "general" SEMANTICS_FIXTURE_DIR = ( FORTRAN_ROOT / "infrastructure" / "semantic_ir" / "semantics" / "fixtures" / "general" / "expected" ) diff --git a/tests/fortran/_support/paths.py b/tests/fortran/_support/paths.py new file mode 100644 index 000000000..7a3fec891 --- /dev/null +++ b/tests/fortran/_support/paths.py @@ -0,0 +1,13 @@ +"""Directory anchors for tests that read a file owned by another directory. + +Computing `Path(__file__).parents[N]` couples a test to its own depth in the +tree, so moving it silently resolves the path to the wrong directory instead of +failing. Import the anchor that names what is wanted. +""" + +from pathlib import Path + +FORTRAN_ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = FORTRAN_ROOT.parents[1] +PARSER_FIXTURE_ROOT = FORTRAN_ROOT / "infrastructure" / "parsing" / "fixtures" +GENERAL_FORTRAN_DIR = PARSER_FIXTURE_ROOT / "general" diff --git a/tests/fortran/_support/printer_models.py b/tests/fortran/_support/printer_models.py index 5d8157068..e3fe498f4 100644 --- a/tests/fortran/_support/printer_models.py +++ b/tests/fortran/_support/printer_models.py @@ -1,6 +1,3 @@ -from pathlib import Path - - from prik.contracts import CONTRACT_SYMBOLS from prik.parsers.fortran import parse_fortran_file as parse_fortran_source @@ -23,10 +20,9 @@ ) from prik.policy.completion import complete_semantic_policies +from tests.fortran._support.paths import FORTRAN_ROOT -OPERATOR_F90_SOURCE = ( - Path(__file__).parents[1] / "generic_interfaces" / "end_to_end" / "fixtures" / "foperators_f90.f90" -) +OPERATOR_F90_SOURCE = FORTRAN_ROOT / "generic_interfaces" / "end_to_end" / "fixtures" / "foperators_f90.f90" CONTRACT_IMPORT = f"from prik.contracts import {', '.join(sorted(CONTRACT_SYMBOLS))}\n" diff --git a/tests/fortran/_support/wrapper_build.py b/tests/fortran/_support/wrapper_build.py index fb4bdd764..d69118c02 100644 --- a/tests/fortran/_support/wrapper_build.py +++ b/tests/fortran/_support/wrapper_build.py @@ -17,6 +17,7 @@ import numpy as np import pytest +from tests.fortran._support.paths import REPO_ROOT 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 @@ -38,7 +39,6 @@ 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 WRAPPER_SOURCE_PATHS = { "c_order_flat_buffer.f90": REPO_ROOT diff --git a/tests/fortran/conftest.py b/tests/fortran/conftest.py index d050ba5f0..622e0f7b2 100644 --- a/tests/fortran/conftest.py +++ b/tests/fortran/conftest.py @@ -10,7 +10,8 @@ import pytest -REPO_ROOT = Path(__file__).resolve().parents[2] +from tests.fortran._support.paths import REPO_ROOT + COMPILER_ENV = "PRIK_TEST_FORTRAN_COMPILER" COMPILER_OPTION = "--prik-fortran-compiler" diff --git a/tests/fortran/data_types/end_to_end/test_verified_baseline.py b/tests/fortran/data_types/end_to_end/test_verified_baseline.py index f9e098165..d3ad50c12 100644 --- a/tests/fortran/data_types/end_to_end/test_verified_baseline.py +++ b/tests/fortran/data_types/end_to_end/test_verified_baseline.py @@ -19,9 +19,10 @@ ) from prik import build_pyi_extension from prik.runtime.handles import _NativeArrayHandoff, AllocatableArray, PointerArray +from tests.fortran._support.paths import FORTRAN_ROOT DATA_TYPE_CONTRACTS = Path(__file__).parent / "fixtures" / "baseline" / "contracts" -ARRAY_CONTRACTS = Path(__file__).parents[2] / "arrays" / "end_to_end" / "fixtures" / "baseline" / "contracts" +ARRAY_CONTRACTS = FORTRAN_ROOT / "arrays" / "end_to_end" / "fixtures" / "baseline" / "contracts" SCALAR_FIXED_SOURCE = wrapper_source("fmath.f") ARRAY_FIXED_SOURCE = wrapper_source("fmath_arrays.f") SCALAR_F90_SOURCE = wrapper_source("fmath_f90.f90") diff --git a/tests/fortran/data_types/pipeline/test_scalar_generated_pyi_contracts.py b/tests/fortran/data_types/pipeline/test_scalar_generated_pyi_contracts.py index 9d23b1caa..75b5d9f5b 100644 --- a/tests/fortran/data_types/pipeline/test_scalar_generated_pyi_contracts.py +++ b/tests/fortran/data_types/pipeline/test_scalar_generated_pyi_contracts.py @@ -12,9 +12,10 @@ contract_case_id, source_contract_case, ) +from tests.fortran._support.paths import FORTRAN_ROOT DATA_TYPE_CONTRACTS = Path(__file__).parents[1] / "end_to_end" / "fixtures" / "baseline" / "contracts" -ARRAY_CONTRACTS = Path(__file__).parents[2] / "arrays" / "end_to_end" / "fixtures" / "baseline" / "contracts" +ARRAY_CONTRACTS = FORTRAN_ROOT / "arrays" / "end_to_end" / "fixtures" / "baseline" / "contracts" CASES = ( source_contract_case(DATA_TYPE_CONTRACTS, "fbind_value_f90.f90"), source_contract_case(DATA_TYPE_CONTRACTS, "fmath.f"), diff --git a/tests/fortran/derived_types/end_to_end/test_derived_runtime_mechanisms.py b/tests/fortran/derived_types/end_to_end/test_derived_runtime_mechanisms.py index 6f71a5715..439d93946 100644 --- a/tests/fortran/derived_types/end_to_end/test_derived_runtime_mechanisms.py +++ b/tests/fortran/derived_types/end_to_end/test_derived_runtime_mechanisms.py @@ -16,6 +16,7 @@ ) from prik import build_pyi_extension from prik.runtime.handles import AllocatableArray +from tests.fortran._support.paths import FORTRAN_ROOT FIXTURES = Path(__file__).parent / "fixtures" EDITED_CONTRACTS = FIXTURES / "edited_contracts" @@ -25,7 +26,7 @@ PLAIN_MODULE_CONTRACT = EDITED_CONTRACTS / "module_live_proxy" / "__init__.pyi" ALIASED_MODULE_SOURCE = FIXTURES / "fmodule_derived_alias_f90.f90" ALIASED_MODULE_CONTRACT = EDITED_CONTRACTS / "module_aliased_proxy" / "__init__.pyi" -DERIVED_CONSTANT_SOURCE = Path(__file__).parents[2] / "modules" / "end_to_end" / "fixtures" / "fmodule_vars_f90.f90" +DERIVED_CONSTANT_SOURCE = FORTRAN_ROOT / "modules" / "end_to_end" / "fixtures" / "fmodule_vars_f90.f90" pytestmark = pytest.mark.fortran_end_to_end DERIVED_CONSTANT_CONTRACT = """\ from prik.contracts import Final, Int32 diff --git a/tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py b/tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py index a68c9a4ca..4f670ac19 100644 --- a/tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py +++ b/tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py @@ -13,6 +13,7 @@ import numpy as np import pytest +from tests.fortran._support.paths import REPO_ROOT from tests.fortran._support.wrapper_build import _import_from_build_dir from prik import build_pyi_extension @@ -595,7 +596,7 @@ def test_injected_restoration_failure_poison_isolated_origin_and_continues_clean argument, poisoned_reader, ], - cwd=Path(__file__).parents[4], + cwd=REPO_ROOT, env=environment, check=False, capture_output=True, diff --git a/tests/fortran/infrastructure/building/end_to_end/real_libraries/_support.py b/tests/fortran/infrastructure/building/end_to_end/real_libraries/_support.py index 480089724..227291b52 100644 --- a/tests/fortran/infrastructure/building/end_to_end/real_libraries/_support.py +++ b/tests/fortran/infrastructure/building/end_to_end/real_libraries/_support.py @@ -10,9 +10,10 @@ from prik import build_fortran_extension from tests.fortran._support.wrapper_build import _import_from_build_dir +from tests.fortran._support.paths import REPO_ROOT -REPOSITORY_ROOT = Path(__file__).resolve().parents[5] +REPOSITORY_ROOT = REPO_ROOT def real_library_source_dir(library: str) -> Path: diff --git a/tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py b/tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py index f9c3db031..a58b8a98d 100644 --- a/tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py +++ b/tests/fortran/infrastructure/building/end_to_end/test_source_build_modes.py @@ -14,6 +14,7 @@ from tests.fortran._support.wrapper_build import _sole_native_module from prik.preprocessing import PreprocessingConfig from prik.pipeline.build import NativeBuildPlan, NativeLinkItem, build_fortran_extension +from tests.fortran._support.paths import REPO_ROOT NATIVE_FIXTURES = Path(__file__).parent / "fixtures" / "native" VERBOSE_SOURCE = NATIVE_FIXTURES / "verbose_api.f90" @@ -21,7 +22,7 @@ SCALE_SOURCE = NATIVE_FIXTURES / "scale.f90" SCALAR_SOURCE = SCALE_SOURCE HOME_POINTS_SOURCE = NATIVE_FIXTURES / "home_points.f90" -BUILD_MODULE = Path(__file__).resolve().parents[4] / "prik" / "pipeline" / "build.py" +BUILD_MODULE = REPO_ROOT / "prik" / "pipeline" / "build.py" pytestmark = pytest.mark.fortran_end_to_end diff --git a/tests/fortran/infrastructure/cli/pipeline/_support.py b/tests/fortran/infrastructure/cli/pipeline/_support.py index cf224bce9..1f91fbd93 100644 --- a/tests/fortran/infrastructure/cli/pipeline/_support.py +++ b/tests/fortran/infrastructure/cli/pipeline/_support.py @@ -1,9 +1,9 @@ import types -from pathlib import Path import prik.cli as prik_cli +from tests.fortran._support.paths import GENERAL_FORTRAN_DIR -TEST_FILE = Path(__file__).parents[2] / "parsing" / "fixtures" / "general" / "basic_subroutine.f90" +TEST_FILE = GENERAL_FORTRAN_DIR / "basic_subroutine.f90" class _MainParserError(Exception): diff --git a/tests/fortran/infrastructure/cli/pipeline/test_output_contract.py b/tests/fortran/infrastructure/cli/pipeline/test_output_contract.py index 9337c9c21..fe7a447ba 100644 --- a/tests/fortran/infrastructure/cli/pipeline/test_output_contract.py +++ b/tests/fortran/infrastructure/cli/pipeline/test_output_contract.py @@ -21,6 +21,7 @@ PreprocessingDiagnostic, PreprocessingError, ) +from tests.fortran._support.paths import GENERAL_FORTRAN_DIR from tests.fortran.infrastructure.cli.pipeline._support import ( TEST_FILE, _MainParserError, @@ -650,7 +651,7 @@ def test_subcommand_help_tailors_shared_compiler_options(command, expected, excl def test_cli_parse_shows_module_derived_types_and_derived_arg_kinds(): - fixture = Path(__file__).parents[2] / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" + fixture = GENERAL_FORTRAN_DIR / "modern_pyi_example.f90" cmd = [sys.executable, "-m", "prik", "parse", str(fixture)] res = subprocess.run(cmd, capture_output=True, text=True, check=True) diff --git a/tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py b/tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py index 66ec5c829..869cee1e7 100644 --- a/tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py +++ b/tests/fortran/infrastructure/cli/pipeline/test_stage_dispatch.py @@ -19,6 +19,7 @@ PreprocessingError, ) from prik.semantics.fortran2ir import collect_semantic_compile_time_requirements +from tests.fortran._support.paths import GENERAL_FORTRAN_DIR from tests.fortran.infrastructure.cli.pipeline._support import ( TEST_FILE, _install_main_parser, @@ -572,7 +573,7 @@ def fail_parse(_paths, _preprocessing): def test_cli_parse_modern_fixture_prints_derived_block_verbatim(): - fixture = Path(__file__).parents[2] / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" + fixture = GENERAL_FORTRAN_DIR / "modern_pyi_example.f90" cmd = [sys.executable, "-m", "prik", "parse", str(fixture)] res = subprocess.run(cmd, capture_output=True, text=True, check=True) diff --git a/tests/fortran/infrastructure/parsing/test_parser_benchmarks.py b/tests/fortran/infrastructure/parsing/test_parser_benchmarks.py index 734cfd0bd..05f754996 100644 --- a/tests/fortran/infrastructure/parsing/test_parser_benchmarks.py +++ b/tests/fortran/infrastructure/parsing/test_parser_benchmarks.py @@ -2,13 +2,13 @@ from __future__ import annotations -from pathlib import Path import pytest from prik.semantics.fortran2ir import fortran_file_to_semantic_modules from prik.pipeline.pyi import emit_module_stubs from prik.parsers.fortran import parse_fortran_file +from tests.fortran._support.paths import REPO_ROOT pytestmark = pytest.mark.skip(reason="Benchmarks are parked until benchmark adoption resumes.") @@ -37,7 +37,7 @@ def test_parse_convert_emit_representative_fortran_module(benchmark): @pytest.mark.benchmark def test_parse_real_lapack_dgesv(benchmark): - source = (Path(__file__).resolve().parents[4] / "examples" / "lapack" / "native" / "dgesv.f").read_text( + source = (REPO_ROOT / "examples" / "lapack" / "native" / "dgesv.f").read_text( encoding="utf-8", ) parsed = benchmark(parse_fortran_file, source, filename="lapack/dgesv.f") diff --git a/tests/fortran/infrastructure/runtime/test_native_support.py b/tests/fortran/infrastructure/runtime/test_native_support.py index 105385826..2fb7fdbb7 100644 --- a/tests/fortran/infrastructure/runtime/test_native_support.py +++ b/tests/fortran/infrastructure/runtime/test_native_support.py @@ -1,11 +1,10 @@ """Public native-binding support surface checks.""" -from pathlib import Path +from tests.fortran._support.paths import REPO_ROOT -ROOT = Path(__file__).resolve().parents[4] -SUPPORT_HEADER = ROOT / "prik" / "runtime" / "native_support" / "prik_binding.h" -SUPPORT_SOURCE = ROOT / "prik" / "runtime" / "native_support" / "prik_binding.c" +SUPPORT_HEADER = REPO_ROOT / "prik" / "runtime" / "native_support" / "prik_binding.h" +SUPPORT_SOURCE = REPO_ROOT / "prik" / "runtime" / "native_support" / "prik_binding.c" def test_native_binding_support_is_header_only_and_exposes_the_small_prik_api(): diff --git a/tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_conversion_smoke.py b/tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_conversion_smoke.py index 97c805ac8..9c2843871 100644 --- a/tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_conversion_smoke.py +++ b/tests/fortran/infrastructure/semantic_ir/semantics/test_semantic_conversion_smoke.py @@ -3,10 +3,8 @@ import pytest -from tests.fortran._support.fixture_outputs import ( - PARSER_FIXTURE_ROOT as TESTS_DIR, - parse_fixture, -) +from tests.fortran._support.fixture_outputs import parse_fixture +from tests.fortran._support.paths import PARSER_FIXTURE_ROOT as TESTS_DIR from tests.fortran._support.fixture_conversion import FORTRAN_FIXTURES from tests.fortran._support.fixture_outputs import ( SEMANTICS_FIXTURE_DIR, diff --git a/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py index 965ec5504..5a3dfa0bd 100644 --- a/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py @@ -13,8 +13,9 @@ _import_from_build_dir, ) from prik import build_pyi_extension +from tests.fortran._support.paths import FORTRAN_ROOT -MODULE_FIXTURES = Path(__file__).parents[5] / "modules" / "end_to_end" / "fixtures" +MODULE_FIXTURES = FORTRAN_ROOT / "modules" / "end_to_end" / "fixtures" EDITED_ENTRIES = Path(__file__).parent / "fixtures" / "edited_contracts" / "module_exports" SOURCE = MODULE_FIXTURES / "module_exports.f90" BASE_CONTRACT = MODULE_FIXTURES / "contracts" / "module_exports" diff --git a/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py index f20eff1e3..318017e92 100644 --- a/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py @@ -11,8 +11,9 @@ _sole_native_module, ) from prik import build_pyi_extension +from tests.fortran._support.paths import FORTRAN_ROOT -MODULE_FIXTURES = Path(__file__).parents[5] / "modules" / "end_to_end" / "fixtures" +MODULE_FIXTURES = FORTRAN_ROOT / "modules" / "end_to_end" / "fixtures" FEATURE_FIXTURES = Path(__file__).parent / "fixtures" MODULE_VARIABLE_SOURCE = MODULE_FIXTURES / "fmodule_vars_f90.f90" MODIFIED_CONTRACT = FEATURE_FIXTURES / "edited_contracts" / "module_variables_visibility" / "__init__.pyi" diff --git a/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py index 4e89160f3..a8fa3cb10 100644 --- a/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py +++ b/tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py @@ -11,10 +11,11 @@ _sole_native_module, ) from prik import build_pyi_extension +from tests.fortran._support.paths import FORTRAN_ROOT FEATURE_ROOT = Path(__file__).parent / "fixtures" / "edited_contracts" -DERIVED_FIXTURES = Path(__file__).parents[5] / "derived_types" / "end_to_end" / "fixtures" -GENERIC_FIXTURES = Path(__file__).parents[5] / "generic_interfaces" / "end_to_end" / "fixtures" +DERIVED_FIXTURES = FORTRAN_ROOT / "derived_types" / "end_to_end" / "fixtures" +GENERIC_FIXTURES = FORTRAN_ROOT / "generic_interfaces" / "end_to_end" / "fixtures" CLASS_SOURCE = DERIVED_FIXTURES / "fclasses_f90.f90" OVERLOAD_SOURCE = GENERIC_FIXTURES / "foverloads_f90.f90" pytestmark = pytest.mark.fortran_end_to_end diff --git a/tests/fortran/infrastructure/semantic_pyi/pipeline/test_modern_example.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_modern_example.py index fdc029f4a..f8e12f44d 100644 --- a/tests/fortran/infrastructure/semantic_pyi/pipeline/test_modern_example.py +++ b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_modern_example.py @@ -3,10 +3,11 @@ from prik.parsers.fortran import parse_fortran_file from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik.printers import emit_module +from tests.fortran._support.paths import GENERAL_FORTRAN_DIR def test_modern_fortran_example_pyi_snapshot(): - fixture = Path(__file__).resolve().parents[2] / "parsing" / "fixtures" / "general" / "modern_pyi_example.f90" + fixture = GENERAL_FORTRAN_DIR / "modern_pyi_example.f90" expected_fixture = Path(__file__).parent / "fixtures" / "modern_math_physics.pyi" source = fixture.read_text(encoding="utf-8") diff --git a/tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_conversion_smoke.py b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_conversion_smoke.py index 63df217ea..adc55e585 100644 --- a/tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_conversion_smoke.py +++ b/tests/fortran/infrastructure/semantic_pyi/pipeline/test_pyi_printer_conversion_smoke.py @@ -5,10 +5,8 @@ from prik.semantics.fortran2ir import fortran_module_to_semantic_module from prik.printers import emit_module -from tests.fortran._support.fixture_outputs import ( - PARSER_FIXTURE_ROOT as TESTS_DIR, - parse_fixture, -) +from tests.fortran._support.fixture_outputs import parse_fixture +from tests.fortran._support.paths import PARSER_FIXTURE_ROOT as TESTS_DIR from tests.fortran._support.fixture_conversion import FORTRAN_FIXTURES diff --git a/tests/fortran/memory_management/end_to_end/test_explicit_borrowed_owner.py b/tests/fortran/memory_management/end_to_end/test_explicit_borrowed_owner.py index 4a2d33591..5fced8f67 100644 --- a/tests/fortran/memory_management/end_to_end/test_explicit_borrowed_owner.py +++ b/tests/fortran/memory_management/end_to_end/test_explicit_borrowed_owner.py @@ -12,10 +12,9 @@ _sole_native_module, ) from prik import build_pyi_extension +from tests.fortran._support.paths import FORTRAN_ROOT -FINALIZER_SOURCE = ( - Path(__file__).parents[2] / "derived_types" / "end_to_end" / "fixtures" / "fborrowed_finalizer_f90.f90" -) +FINALIZER_SOURCE = FORTRAN_ROOT / "derived_types" / "end_to_end" / "fixtures" / "fborrowed_finalizer_f90.f90" FINALIZER_CONTRACT = Path(__file__).parent / "fixtures" / "edited_contracts" / "borrowed_owner" / "__init__.pyi" pytestmark = pytest.mark.fortran_end_to_end diff --git a/tests/fortran/raw_addresses/end_to_end/test_raw_fixed_string_arrays.py b/tests/fortran/raw_addresses/end_to_end/test_raw_fixed_string_arrays.py index 9a0706844..6be8eda7b 100644 --- a/tests/fortran/raw_addresses/end_to_end/test_raw_fixed_string_arrays.py +++ b/tests/fortran/raw_addresses/end_to_end/test_raw_fixed_string_arrays.py @@ -11,8 +11,9 @@ _sole_native_module, ) from prik import build_pyi_extension +from tests.fortran._support.paths import FORTRAN_ROOT -STRING_FIXTURES = Path(__file__).resolve().parents[2] / "strings" / "end_to_end" / "fixtures" +STRING_FIXTURES = FORTRAN_ROOT / "strings" / "end_to_end" / "fixtures" STRING_F90_SOURCE = STRING_FIXTURES / "fstrings_f90.f90" RAW_CONTRACT = Path(__file__).parent / "fixtures" / "edited_contracts" / "raw_string_array" / "__init__.pyi" pytestmark = pytest.mark.fortran_end_to_end diff --git a/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py b/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py index 4f42ac022..94bab6849 100644 --- a/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py +++ b/tests/fortran/raw_addresses/end_to_end/test_raw_native_addresses.py @@ -12,9 +12,10 @@ _sole_native_module, ) from prik import build_pyi_extension +from tests.fortran._support.paths import FORTRAN_ROOT NATIVE_CALL_EXAMPLES_F90_SOURCE = ( - Path(__file__).parents[2] + FORTRAN_ROOT / "infrastructure" / "semantic_pyi" / "contracts" diff --git a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py index fd43d806f..3ace8b682 100644 --- a/tests/fortran/subroutines/policy/test_subroutine_output_policy.py +++ b/tests/fortran/subroutines/policy/test_subroutine_output_policy.py @@ -2,6 +2,7 @@ from tests.fortran._support.ownership_policy import parse_pyi_text +from tests.fortran._support.paths import FORTRAN_ROOT 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.preprocessing import PreprocessingConfig @@ -18,7 +19,7 @@ CALLS_NATIVE = ( - Path(__file__).parents[2] + FORTRAN_ROOT / "infrastructure" / "semantic_pyi" / "contracts" From 715f14f3dda7c2e53afe030e685231478589fffd Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 19:24:42 +0100 Subject: [PATCH 20/26] codex: Correct the constructor and abstract-type limitations Wrapping abstract types and generic constructors made three published claims false, and nothing caught it because no test reads these files. The generic-interfaces guide still said source generic interfaces are never inferred as constructors; an interface named for a derived type has been that type's constructor since generic constructors landed. The README still listed abstract types and deferred bindings among the forms PRIK rejects, and described the real constructor diagnostics as "ambiguous or incomplete" candidates -- vague enough to be unactionable. The actual rejections are a shared runtime signature between overload candidates, and edited `.pyi` constructors that omit `@bind` or sit alongside the generated field constructor; name those instead. In the coverage table, the generic-interface limitations row claimed Blocked status and cited two tests deleted with the behavior they pinned. Restate it as partially supported, point it at the constructor inference and keyword-field evidence, and repoint the inheritance and semantic `.pyi` rows at live negative evidence. Four node IDs left over from earlier commits stay untouched: their tests were renamed alongside a behavior change from blocked to supported, so substituting the new names would record a claim their owners never made. Co-Authored-By: Claude Opus 5 --- README.md | 8 +++++--- docs/user/guide/generic-interfaces.md | 7 +++++-- tests/fortran/CONTRACT_COVERAGE.md | 8 ++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9e835a670..c809a8154 100644 --- a/README.md +++ b/README.md @@ -225,9 +225,11 @@ code generation with a diagnostic naming the boundary and the reason. - procedure-pointer module variables, and callbacks retained after the wrapped call returns; -- polymorphic outputs, mutable polymorphic arguments, - unlimited polymorphism (`class(*)`), abstract types, and deferred bindings; -- constructor overload sets whose candidates are ambiguous or incomplete. +- polymorphic outputs, mutable polymorphic arguments, polymorphic + `allocatable` and `pointer` scalars, and unlimited polymorphism (`class(*)`); +- overload sets whose candidates share one runtime signature, and hand-edited + `.pyi` constructors that omit `@bind` or contradict the generated field + constructor. The [language feature matrix](https://pynumlab.github.io/prik/user/language-support/feature-matrix/) records the full support status of every feature with its evidence. diff --git a/docs/user/guide/generic-interfaces.md b/docs/user/guide/generic-interfaces.md index 07b6b7efb..0bc286650 100644 --- a/docs/user/guide/generic-interfaces.md +++ b/docs/user/guide/generic-interfaces.md @@ -226,8 +226,11 @@ in Wrapping Derived Types. ## Limitations -- Source generic interfaces are not inferred as constructors automatically. - Edited exact constructor overload sets are supported. +- Only an interface named for a derived type becomes that type's constructor. + Any other generic interface stays an overloaded module function. +- Contradictory edited `.pyi` constructors are rejected before the build: a + hand-written `__init__` must carry `@bind`, and a bound `__init__` replaces + the generated field constructor rather than joining it. - Polymorphic (`class(*)`) arguments and results are blocked. - Arrays of derived types and complex polymorphic cases are not supported yet. diff --git a/tests/fortran/CONTRACT_COVERAGE.md b/tests/fortran/CONTRACT_COVERAGE.md index 9aec3a78b..f33c60bfd 100644 --- a/tests/fortran/CONTRACT_COVERAGE.md +++ b/tests/fortran/CONTRACT_COVERAGE.md @@ -26,7 +26,7 @@ Authoritative sources: - Use `—` only when that evidence kind is not required. - Record every documented unsafe or unsupported behavior in Negative evidence as an exact node followed by its terminal stage, for example - `` `tests/fortran/arrays/policy/test_contracts.py::test_rank_limit` + `` `tests/fortran/allocatables/policy/test_allocatable_result_policy.py::test_direct_allocatable_scalar_function_result_is_blocked_before_codegen` (`policy`) ``. - Record source, generated-`.pyi` replay, edited-`.pyi`, and source-free native artifact routes separately when the documentation claims each route. @@ -90,7 +90,7 @@ Authoritative sources: | [Generic Interfaces: Inspect the Overloads](../../docs/user/guide/generic-interfaces.md#inspect-the-overloads) | Supported | one public callable; all accepted signatures; hidden concrete procedures and internal names | — | `tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py::test_fortran_generic_interfaces_dispatch_in_generated_c_extension[source]` | — | canonical | | [Generic Interfaces: Extend an Overload Set](../../docs/user/guide/generic-interfaces.md#extend-an-overload-set) | Supported | edited `.pyi`; renamed public binding; added overload group; private-specific routing through public generic; absent candidate rejection | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_module_specifics_without_bind-missing_targets0]` (`compiling`) | canonical | | [Generic Interfaces: Key Rules](../../docs/user/guide/generic-interfaces.md#key-rules) | Supported | exact dtype/rank/class match; no-match `TypeError`; ambiguous signature rejection; exact-once specific links; `@bind`; private visibility; type-bound generics; defined operators; defined assignment | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_module_and_type_bound_generic_overload_sets`
`tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators`
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_plan_records_one_exact_numpy_scalar_predicate_per_candidate` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generator_rejects_ambiguous_edited_overload_plan_before_emission` (`codegen`)
`tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_rejects_invalid_prik_overload_links[@overload("missing")\ndef convert(value: Int32) -> Int32: ...\n-missing specific procedure 'missing']` (`semantics`) | canonical | -| [Generic Interfaces: Limitations](../../docs/user/guide/generic-interfaces.md#limitations) | Blocked | source generic constructor inference; assumed-type `class(*)`; arrays of derived values | — | — | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_rejects_generic_constructor_interfaces_during_semantic_conversion` (`semantics`)
`tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py::test_assumed_type_generic_candidate_is_rejected_at_parsing` (`parsing`)
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generic_candidate_with_array_of_derived_values_is_blocked_before_lowering` (`codegen`) | canonical | +| [Generic Interfaces: Limitations](../../docs/user/guide/generic-interfaces.md#limitations) | Partially supported | only a type-named interface is a constructor; contradictory edited constructors; assumed-type `class(*)`; arrays of derived values | — | `tests/fortran/derived_types/end_to_end/test_generic_constructor.py::test_constructor_interface_overloads_init_from_its_specifics`
`tests/fortran/derived_types/end_to_end/test_generic_constructor.py::test_type_without_a_constructor_interface_keeps_keyword_fields` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected` (`semantics`)
`tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py::test_assumed_type_generic_candidate_is_rejected_at_parsing` (`parsing`)
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generic_candidate_with_array_of_derived_values_is_blocked_before_lowering` (`codegen`) | canonical | | [Wrapping Derived Types: Complete Example](../../docs/user/guide/wrapping-derived-types.md#complete-example) | Supported | derived declarations; public and nested fields; source generation; reviewed generated `.pyi`; source build; generated-`.pyi` replay | `tests/fortran/derived_types/parsing/test_derived_type_declarations.py::test_derived_type_fields_and_methods_detection`
`tests/fortran/derived_types/pipeline/test_generated_derived_contracts.py::test_generated_derived_contract_matches_fixture[fderived_boundary_f90]` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[source]`
`tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[generated-pyi]` | — | canonical | | [Wrapping Derived Types: Usage in Python](../../docs/user/guide/wrapping-derived-types.md#usage-in-python) | Supported | keyword construction; public field get/set; `intent(inout)` identity; owned result; nested borrowed component | `tests/fortran/derived_types/policy/test_derived_policy_defaults.py::test_recursive_module_policy_map_includes_nested_fields_and_functions` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[source]` | — | canonical | | [Wrapping Derived Types: Inspect the Class](../../docs/user/guide/wrapping-derived-types.md#inspect-the-class) | Supported | class, constructor, field, method, parameter, return, and overload docstrings; no native implementation names | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_and_method_reuse_completed_direct_function_plans`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | @@ -101,7 +101,7 @@ Authoritative sources: | [Wrapping Derived Types: Type-Bound Generics](../../docs/user/guide/wrapping-derived-types.md#type-bound-generics) | Supported | private specifics; public generic bind; exact `Int32`/`Float64` dispatch; wrapped receiver fixed by class; no trial calls | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` (`runtime`) | canonical | | [Wrapping Derived Types: Defined Operators](../../docs/user/guide/wrapping-derived-types.md#defined-operators) | Supported | direct/reflected binary; unary; comparison; logical; named operators; defined assignment; exact wrapped/scalar dispatch; operator docstrings | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` (`runtime`) | canonical | | [Fortran Wrapper: Derived Types Across Procedure Boundaries](../../docs/user/reference/fortran-wrapper.md#derived-types-across-procedure-boundaries) | Supported | complete scalar actual/dummy matrix; module and nonmodule storage; ordinary, target, allocatable, allocatable-target, pointer; six dummy forms; identity, writeback, empty states, rollback, lifetime, and deliberate blockers | `tests/fortran/derived_types/codegen/test_scalar_actual_dummy_plan.py::test_every_dummy_form_has_one_exhaustive_completed_matrix[object_dummy-object]` | `tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_all_sixty_actual_dummy_cells[A-module_object]`
`tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_one_call_uses_all_six_dummy_forms_and_optional_arguments_stay_linear`
`tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_later_acquisition_failure_rolls_back_earlier_origins` | `tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_reassociable_pointer_dummy_requires_pointer_storage[module_object]` (`runtime`)
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_unsupported_derived_shapes_fail_on_exact_completed_policy_blockers[\nfrom prik.contracts import Float64\n\nclass point:\n x: Float64\n\ndef consume(value: point[:]) -> None: ...\n-unsupported array of derived values]` (`codegen`) | canonical | -| [Fortran Wrapper: Inheritance And Polymorphism](../../docs/user/reference/fortran-wrapper.md#inheritance-and-polymorphism) | Partially supported | scalar extension inheritance; closed `class(base), intent(in)` dispatch; exact extension classes; unsupported polymorphic results, mutation, arrays, descriptor scalars, and assumed type | `tests/fortran/derived_types/codegen/test_class_surfaces.py::test_inheritance_and_polymorphism_are_completed_before_planning` | `tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py::test_fortran_extension_types_generate_python_inheritance[source]`
`tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py::test_fortran_extension_types_generate_python_inheritance[generated-pyi]` | `tests/fortran/derived_types/codegen/test_class_surfaces.py::test_invalid_class_graph_fails_before_emission` (`codegen`)
`tests/fortran/derived_types/policy/test_derived_accessor_policy.py::test_abstract_type_and_deferred_binding_fail_in_completed_derived_policy` (`policy`) | canonical | +| [Fortran Wrapper: Inheritance And Polymorphism](../../docs/user/reference/fortran-wrapper.md#inheritance-and-polymorphism) | Partially supported | scalar extension inheritance; closed `class(base), intent(in)` dispatch; exact extension classes; unsupported polymorphic results, mutation, arrays, descriptor scalars, and assumed type | `tests/fortran/derived_types/codegen/test_class_surfaces.py::test_inheritance_and_polymorphism_are_completed_before_planning` | `tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py::test_fortran_extension_types_generate_python_inheritance[source]`
`tests/fortran/derived_types/end_to_end/test_inheritance_and_polymorphism.py::test_fortran_extension_types_generate_python_inheritance[generated-pyi]` | `tests/fortran/derived_types/codegen/test_class_surfaces.py::test_invalid_class_graph_fails_before_emission` (`codegen`)
`tests/fortran/derived_types/policy/test_derived_accessor_policy.py::test_deferred_binding_without_an_abstract_type_is_refused` (`policy`) | canonical | | [Fortran Wrapper: Constructors, Initialization, And Finalizers](../../docs/user/reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | Supported | generated keyword constructor; default field values; custom direct constructor; overloaded constructors; commit-on-success; exact finalization; borrowed non-finalization | `tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py::test_derived_type_initializers_and_finalizers_reach_semantic_ir`
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_owned_derived_result_has_explicit_failure_and_release_lifecycle` | `tests/fortran/derived_types/end_to_end/test_default_constructors_and_finalizers.py::test_fortran_default_constructor_keywords_and_finalization[source]`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract`
`tests/fortran/derived_types/end_to_end/test_borrowed_components.py::test_borrowed_child_wrapper_never_finalizes_native_component[source]` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected[\nclass state:\n def __init__(self, seed: Int32) -> None: ...\n-Non-generated __init__ declarations must use @bind("specific_name")]` (`semantics`) | canonical | | [Fortran Wrapper: Derived-Type Layout And Interoperability](../../docs/user/reference/fortran-wrapper.md#derived-type-layout-and-interoperability) | Supported | opaque accessor storage for ordinary, `bind(C)`, and `sequence`; field get/set; by-value copy; no direct C aggregate access | `tests/fortran/derived_types/semantics/test_fortran_derived_semantics.py::test_bind_c_and_sequence_types_preserve_accessor_layout_metadata`
`tests/fortran/derived_types/codegen/test_derived_lowering.py::test_exact_typed_value_lowering_uses_fortran_value_semantics_and_opaque_binding` | `tests/fortran/derived_types/end_to_end/test_opaque_layout.py::test_bind_c_derived_types_use_accessors_and_fortran_value_copy[source]`
`tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py::test_sequence_derived_value_uses_the_same_typed_opaque_call_path` | — | canonical | | [Allocatables: Key Concepts](../../docs/user/guide/allocatables.md#key-concepts) | Supported | scalar value versus array handle; allocated, unallocated, and zero-sized states; live views; module, field, result, and caller-created descriptor origins | `tests/fortran/allocatables/semantics/test_pyi_allocatable_semantics.py::test_persistent_allocatable_descriptors_preserve_scalar_and_array_kinds`
`tests/fortran/allocatables/policy/test_allocatable_handle_policy.py::test_allocatable_array_field_is_wrapper_owned_borrowed_view` | `tests/fortran/allocatables/end_to_end/test_allocatable_handles.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles[source]` | — | canonical | @@ -214,7 +214,7 @@ Authoritative sources: | [Semantic `.pyi`: Projection Metadata](../../docs/user/reference/semantic-pyi-format.md#projection-metadata) | Supported | ordered `Arg`, `Addr`, `Value`, `Return`, descriptor, length, shape, presence, literal, pass, and workspace entries | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_native_call_accepts_hidden_native_values`
`tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_emit_native_call_hidden_native_values` | — | `tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_pyi_python_api_rejects_invalid_projection_before_codegen` (`pipeline`) | canonical | | [Semantic `.pyi`: Current Generated Coverage](../../docs/user/reference/semantic-pyi-format.md#current-generated-coverage) | Partially supported | canonical parser/printer round trip; reviewed package layout; authoritative runtime input; documented generated and loaded subsets | `tests/fortran/infrastructure/semantic_pyi/semantics/test_round_trip_properties.py::test_generated_semantic_ir_round_trips_through_pyi`
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_checked_contract_package_has_reviewed_files` | `tests/fortran/infrastructure/semantic_pyi/end_to_end/test_authoritative_contract_runtime.py::test_generated_contract_rebuilds_without_native_source_fallback` | — | canonical | | [Semantic `.pyi`: Rejected Or Not Yet Supported](../../docs/user/reference/semantic-pyi-format.md#rejected-or-not-yet-supported) | Blocked | unknown types; invalid subscriptions, depth, callable shapes, decorators, bodies, arguments, and overload/projection combinations | — | — | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_convert_pyi_to_ir_rejects_invalid_projection_and_type_forms[value: Unknown\n-Unknown semantic type is not allowed in .pyi annotations]` (`semantics`)
`tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_rejects_additional_invalid_storage_forms[value: Float64[ORDER_F]\n-Non-dimensional type subscriptions are not supported; use Final[...] for constants and Annotated[...] for constraints or array metadata]` (`semantics`) | canonical | -| [Semantic `.pyi`: Remaining Format And Runtime Work](../../docs/user/reference/semantic-pyi-format.md#remaining-format-and-runtime-work) | Partially supported | implemented ordered projection and policy dispatch; broader polymorphism, pointer lifetimes, and IDE-only stub separation remain limited | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_fortran_to_pyi_and_back_preserves_mixed_input_output_projection` | — | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_rejects_generic_constructor_interfaces_during_semantic_conversion` (`semantics`)
`tests/fortran/allocatables/policy/test_allocatable_result_policy.py::test_direct_allocatable_scalar_function_result_is_blocked_before_codegen` (`policy`) | canonical | +| [Semantic `.pyi`: Remaining Format And Runtime Work](../../docs/user/reference/semantic-pyi-format.md#remaining-format-and-runtime-work) | Partially supported | implemented ordered projection and policy dispatch; broader polymorphism, pointer lifetimes, and IDE-only stub separation remain limited | `tests/fortran/infrastructure/semantic_pyi/semantics/test_calls_and_projections.py::test_fortran_to_pyi_and_back_preserves_mixed_input_output_projection` | — | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected` (`semantics`)
`tests/fortran/allocatables/policy/test_allocatable_result_policy.py::test_direct_allocatable_scalar_function_result_is_blocked_before_codegen` (`policy`) | canonical | | [`.pyi` Exports And Modules: Choose The Package Shape](../../docs/user/reference/pyi-contracts/exports-and-modules.md#choose-the-package-shape) | Supported | child namespaces; wildcard flattening; selective imports; symbol and module aliases; nested aliases; support-import exclusion; reachable declarations only | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering`
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_checked_entry_discovers_its_complete_contract_package[contract_import_graph]` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_rejects_colliding_wildcard_exports` (`pipeline`)
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_recursive_graph_reports_missing_relative_contract_before_native_validation` (`pipeline`)
`tests/fortran/infrastructure/semantic_pyi/pipeline/test_contract_loading.py::test_recursive_graph_reports_cycles_before_codegen` (`pipeline`) | canonical | | [`.pyi` Exports And Modules: Remove Or Hide A Declaration](../../docs/user/reference/pyi-contracts/exports-and-modules.md#remove-or-hide-a-declaration) | Supported | deleted function and variable; `@private`; `private[...]`; class constructor suppression; later class/member/overload runtime owner retained | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/policy/test_export_and_initializer_policy.py::test_policy_completion_prunes_unexported_entry_declarations_before_lowering`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_removing_constructor_suppresses_generated_keyword_initialization` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_and_initialization.py::test_editable_contract_removes_hides_and_initializes_module_declarations` | — | canonical | | [`.pyi` Exports And Modules: Add Or Rename A Native Procedure](../../docs/user/reference/pyi-contracts/exports-and-modules.md#add-or-rename-a-native-procedure) | Supported | added module-leaf declaration; `@bind`; renamed standalone `@standalone`; unchanged native targets; no invented implementation | `tests/fortran/infrastructure/semantic_pyi/semantics/test_types_and_values.py::test_convert_pyi_to_ir_preserves_user_private_bound_function_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_package_exports.py::test_entry_contract_selects_child_flattened_aliased_and_bound_exports` | — | canonical | From ac5ba6609e55e09913a57face0e6541e2b8e3777 Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 19:31:15 +0100 Subject: [PATCH 21/26] codex: Stop listing rejected contracts as language limitations "Current limitations" documents Fortran forms PRIK will not wrap. A hand-edited `.pyi` whose constructor declarations contradict each other is not such a form -- it is a malformed contract, and the diagnostic naming it is the tool working. Overload candidates that share one runtime signature are likewise already stated as a rule in the generic-interfaces Key Rules, not a boundary on what can be wrapped. Drop both from the README and the generic-interfaces limitations, and narrow the coverage row to the dimensions that remain documented limitations. The contradictory-constructor test keeps its four citations on the `.pyi` contract-format rows, where the diagnostic belongs. Co-Authored-By: Claude Opus 5 --- README.md | 5 +---- docs/user/guide/generic-interfaces.md | 3 --- tests/fortran/CONTRACT_COVERAGE.md | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c809a8154..751fb72ec 100644 --- a/README.md +++ b/README.md @@ -226,10 +226,7 @@ code generation with a diagnostic naming the boundary and the reason. - procedure-pointer module variables, and callbacks retained after the wrapped call returns; - polymorphic outputs, mutable polymorphic arguments, polymorphic - `allocatable` and `pointer` scalars, and unlimited polymorphism (`class(*)`); -- overload sets whose candidates share one runtime signature, and hand-edited - `.pyi` constructors that omit `@bind` or contradict the generated field - constructor. + `allocatable` and `pointer` scalars, and unlimited polymorphism (`class(*)`). The [language feature matrix](https://pynumlab.github.io/prik/user/language-support/feature-matrix/) records the full support status of every feature with its evidence. diff --git a/docs/user/guide/generic-interfaces.md b/docs/user/guide/generic-interfaces.md index 0bc286650..9095d698a 100644 --- a/docs/user/guide/generic-interfaces.md +++ b/docs/user/guide/generic-interfaces.md @@ -228,9 +228,6 @@ in Wrapping Derived Types. - Only an interface named for a derived type becomes that type's constructor. Any other generic interface stays an overloaded module function. -- Contradictory edited `.pyi` constructors are rejected before the build: a - hand-written `__init__` must carry `@bind`, and a bound `__init__` replaces - the generated field constructor rather than joining it. - Polymorphic (`class(*)`) arguments and results are blocked. - Arrays of derived types and complex polymorphic cases are not supported yet. diff --git a/tests/fortran/CONTRACT_COVERAGE.md b/tests/fortran/CONTRACT_COVERAGE.md index f33c60bfd..0318fc9b7 100644 --- a/tests/fortran/CONTRACT_COVERAGE.md +++ b/tests/fortran/CONTRACT_COVERAGE.md @@ -90,7 +90,7 @@ Authoritative sources: | [Generic Interfaces: Inspect the Overloads](../../docs/user/guide/generic-interfaces.md#inspect-the-overloads) | Supported | one public callable; all accepted signatures; hidden concrete procedures and internal names | — | `tests/fortran/generic_interfaces/end_to_end/test_generic_interfaces.py::test_fortran_generic_interfaces_dispatch_in_generated_c_extension[source]` | — | canonical | | [Generic Interfaces: Extend an Overload Set](../../docs/user/guide/generic-interfaces.md#extend-an-overload-set) | Supported | edited `.pyi`; renamed public binding; added overload group; private-specific routing through public generic; absent candidate rejection | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_bind_selects_module_method_and_constructor_overload_targets` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_method_and_constructor_overloads_share_one_edited_contract` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_private_native_specific_without_overload_bind_fails_at_build[private_module_specifics_without_bind-missing_targets0]` (`compiling`) | canonical | | [Generic Interfaces: Key Rules](../../docs/user/guide/generic-interfaces.md#key-rules) | Supported | exact dtype/rank/class match; no-match `TypeError`; ambiguous signature rejection; exact-once specific links; `@bind`; private visibility; type-bound generics; defined operators; defined assignment | `tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_module_and_type_bound_generic_overload_sets`
`tests/fortran/generic_interfaces/semantics/test_fortran_generic_semantics.py::test_converter_preserves_defined_operators_assignment_and_type_bound_operators`
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_plan_records_one_exact_numpy_scalar_predicate_per_candidate` | `tests/fortran/generic_interfaces/end_to_end/test_defined_operators.py::test_fortran_defined_operators_and_assignment_dispatch_in_generated_c_extension[source]` | `tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generator_rejects_ambiguous_edited_overload_plan_before_emission` (`codegen`)
`tests/fortran/generic_interfaces/semantics/test_pyi_overload_semantics.py::test_convert_pyi_to_ir_rejects_invalid_prik_overload_links[@overload("missing")\ndef convert(value: Int32) -> Int32: ...\n-missing specific procedure 'missing']` (`semantics`) | canonical | -| [Generic Interfaces: Limitations](../../docs/user/guide/generic-interfaces.md#limitations) | Partially supported | only a type-named interface is a constructor; contradictory edited constructors; assumed-type `class(*)`; arrays of derived values | — | `tests/fortran/derived_types/end_to_end/test_generic_constructor.py::test_constructor_interface_overloads_init_from_its_specifics`
`tests/fortran/derived_types/end_to_end/test_generic_constructor.py::test_type_without_a_constructor_interface_keeps_keyword_fields` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py::test_contradictory_constructor_declarations_are_rejected` (`semantics`)
`tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py::test_assumed_type_generic_candidate_is_rejected_at_parsing` (`parsing`)
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generic_candidate_with_array_of_derived_values_is_blocked_before_lowering` (`codegen`) | canonical | +| [Generic Interfaces: Limitations](../../docs/user/guide/generic-interfaces.md#limitations) | Partially supported | only a type-named interface is a constructor; assumed-type `class(*)`; arrays of derived values | — | `tests/fortran/derived_types/end_to_end/test_generic_constructor.py::test_constructor_interface_overloads_init_from_its_specifics`
`tests/fortran/derived_types/end_to_end/test_generic_constructor.py::test_type_without_a_constructor_interface_keeps_keyword_fields` | `tests/fortran/generic_interfaces/parsing/test_generic_interface_syntax.py::test_assumed_type_generic_candidate_is_rejected_at_parsing` (`parsing`)
`tests/fortran/generic_interfaces/codegen/test_overload_dispatch_plan.py::test_generic_candidate_with_array_of_derived_values_is_blocked_before_lowering` (`codegen`) | canonical | | [Wrapping Derived Types: Complete Example](../../docs/user/guide/wrapping-derived-types.md#complete-example) | Supported | derived declarations; public and nested fields; source generation; reviewed generated `.pyi`; source build; generated-`.pyi` replay | `tests/fortran/derived_types/parsing/test_derived_type_declarations.py::test_derived_type_fields_and_methods_detection`
`tests/fortran/derived_types/pipeline/test_generated_derived_contracts.py::test_generated_derived_contract_matches_fixture[fderived_boundary_f90]` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[source]`
`tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[generated-pyi]` | — | canonical | | [Wrapping Derived Types: Usage in Python](../../docs/user/guide/wrapping-derived-types.md#usage-in-python) | Supported | keyword construction; public field get/set; `intent(inout)` identity; owned result; nested borrowed component | `tests/fortran/derived_types/policy/test_derived_policy_defaults.py::test_recursive_module_policy_map_includes_nested_fields_and_functions` | `tests/fortran/derived_types/end_to_end/test_derived_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries[source]` | — | canonical | | [Wrapping Derived Types: Inspect the Class](../../docs/user/guide/wrapping-derived-types.md#inspect-the-class) | Supported | class, constructor, field, method, parameter, return, and overload docstrings; no native implementation names | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_bound_constructor_and_method_reuse_completed_direct_function_plans`
`tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py::test_edited_overloads_complete_exact_dispatch_and_reject_ambiguous_plan` | `tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/end_to_end/test_edited_class_surfaces.py::test_module_procedure_is_reused_by_bound_constructor_method_and_public_function` | — | canonical | From 056b3e14f71856d62f1a423dc6772a9c1cf6def6 Mon Sep 17 00:00:00 2001 From: said Date: Thu, 20 Aug 2026 22:37:39 +0100 Subject: [PATCH 22/26] fix bugs and add real(10) and complex(10) --- .github/workflows/merge-validation.yml | 9 +- CHANGELOG.md | 17 ++ README.md | 7 +- docs/user/guide/data-types.md | 70 ++++- docs/user/language-support/feature-matrix.md | 4 +- prik/codegen/fortran/bridge.py | 110 +++++++- prik/codegen/primitive_scalar_types.py | 66 +++++ prik/planning/planner.py | 6 + prik/policy/construction.py | 68 +++++ prik/policy/ownership.py | 2 + prik/preprocessing/probes/c_types.py | 16 +- prik/preprocessing/probes/fortran_types.py | 23 +- prik/runtime/native_support/prik_binding.h | 255 ++++++++++++++++++ prik/semantics/fortran2ir.py | 21 ++ tests/c/data_types/probes/test_c_types.py | 2 +- .../codegen/test_raw_array_lowering.py | 5 +- 16 files changed, 649 insertions(+), 32 deletions(-) diff --git a/.github/workflows/merge-validation.yml b/.github/workflows/merge-validation.yml index 854f1236c..6feac8121 100644 --- a/.github/workflows/merge-validation.yml +++ b/.github/workflows/merge-validation.yml @@ -440,7 +440,7 @@ jobs: done native-libraries: - name: BLAS + LAPACK + FFTPACK + MINPACK · Ubuntu 24.04 · Python 3.12 + name: BLAS + LAPACK + FFTPACK + MINPACK + BSPLINE-FORTRAN · Ubuntu 24.04 · Python 3.12 needs: [unit-tests, unit-tests-macos] if: >- ${{ !contains(github.event.pull_request.labels.*.name, 'ignore-real-library-wrappers') }} @@ -537,6 +537,13 @@ jobs: run: | source examples/minpack/build_all.sh python -m pytest -q examples/minpack/tests + - name: Run BSPLINE-FORTRAN full-surface audit + env: + PYTHONPATH: . + HYPOTHESIS_PROFILE: ci + run: | + source examples/bspline/build_all.sh + python -m pytest -q examples/bspline/tests documentation-benchmark: name: Documentation performance benchmark · Ubuntu 24.04 ARM64 · Python 3.12 diff --git a/CHANGELOG.md b/CHANGELOG.md index db618acad..04e7a874d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,14 @@ release tags add a leading `v` to the package version. checks use analytic values and `scipy.interpolate` as independent oracles. It is the first example project written in modern Fortran rather than FORTRAN 77. +- BSPLINE-FORTRAN now follows the maintained real-library example workflow: + its checked-in build instructions are verified with the documentation suite, + its full procedural and derived-type surface is exercised in the native + library CI job, and its inventory fails closed if generated exports or named + numerical tests drift. The example now calls all one- through six-dimensional + procedural setup and evaluation routines and constructs every concrete spline + class against an independent affine interpolation result. + - Abstract Fortran derived types are now wrapped. A `type, abstract ::` declaration becomes a Python class with no constructor — instantiating it raises `TypeError` naming the concrete extensions to use instead — while its @@ -65,6 +73,15 @@ release tags add a leading `v` to the package version. ### Fixed +- A `bind(C)` character dummy that is a pointer now declares deferred length, + as the Fortran standard requires. GNU Fortran 13 and newer reject the + declared-length spelling earlier releases emitted, so wrapping a + `character(len=N), pointer` module array failed to compile there. Pointer + assignment takes the length from its target, so the associated width is + unchanged. The matching allocatable descriptor consumer travels as an + assumed-length assumed-shape dummy, whose descriptor still carries the + element length. + - A generic interface whose specifics project an `intent(out)` argument into a result now reloads from its generated contract. The declaration states the public signature, so an output the projection turned into a result is not one diff --git a/README.md b/README.md index 751fb72ec..660069469 100644 --- a/README.md +++ b/README.md @@ -218,8 +218,11 @@ code generation with a diagnostic naming the boundary and the reason. - arrays of derived types, and assumed-type `type(*)` arrays; - character arrays that cannot be represented as a fixed-width NumPy bytes dtype, and `allocatable` and `pointer` character *fields*. -- quad precision — `real(16)` and `complex(16)` — which has no portable NumPy - dtype. Everything narrower is supported. +- real and complex storage wider than the target's `long double`. NumPy's + `longdouble` is whatever the target C compiler provides, so `real(10)` and C + `long double` are supported while IEEE quad `real(16)` is refused on a target + whose `long double` is x87 extended precision. The diagnostic names the + measured mantissa width on both sides. **Procedures and polymorphism** diff --git a/docs/user/guide/data-types.md b/docs/user/guide/data-types.md index 8aea24663..11c710470 100644 --- a/docs/user/guide/data-types.md +++ b/docs/user/guide/data-types.md @@ -36,6 +36,7 @@ Create `numeric_types.f90`: ```fortran module numeric_types + use iso_c_binding, only: c_long_double, c_long_double_complex use iso_fortran_env, only: int32, real64 implicit none contains @@ -50,11 +51,21 @@ contains output = 2.0_real64 * value end function double + real(c_long_double) function double_extended(value) result(output) + real(c_long_double), intent(in) :: value + output = 2.0_c_long_double * value + end function double_extended + complex(real64) function conjugate_value(value) result(output) complex(real64), intent(in) :: value output = conjg(value) end function conjugate_value + complex(c_long_double_complex) function conjugate_extended(value) result(output) + complex(c_long_double_complex), intent(in) :: value + output = conjg(value) + end function conjugate_extended + logical(kind=1) function invert(flag) result(output) logical(kind=1), intent(in) :: flag output = .not. flag @@ -78,7 +89,7 @@ python3 -m prik numeric_types.f90 --out-dir build/numeric-types The generated `numeric_types.pyi` is: ```python -from prik.contracts import Addr, Arg, Bool8, Complex128, Float64, Int32, native_call +from prik.contracts import Addr, Arg, Bool8, Complex128, Complex256, Float128, Float64, Int32, native_call @native_call([Addr(Arg(0))]) def add_one( @@ -90,11 +101,21 @@ def double( value: Float64 ) -> Float64: ... +@native_call([Addr(Arg(0))]) +def double_extended( + value: Float128 +) -> Float128: ... + @native_call([Addr(Arg(0))]) def conjugate_value( value: Complex128 ) -> Complex128: ... +@native_call([Addr(Arg(0))]) +def conjugate_extended( + value: Complex256 +) -> Complex256: ... + @native_call([Addr(Arg(0))]) def invert( flag: Bool8 @@ -121,12 +142,22 @@ import sys import numpy as np sys.path.insert(0, "build/numeric-types") -from numeric_types.numeric_types import add_one, conjugate_value, double, invert - -print(add_one(np.int32(4))) # 5 -print(double(np.float64(1.5))) # 3.0 -print(conjugate_value(np.complex128(1.0 + 2.0j))) # (1-2j) -print(invert(True)) # False +from numeric_types.numeric_types import ( + add_one, + conjugate_extended, + conjugate_value, + double, + double_extended, + invert, +) + +print(add_one(np.int32(4))) # 5 +print(double(np.float64(1.5))) # 3.0 +# np.float64 cannot hold this value; np.longdouble keeps it. +print(double_extended(np.longdouble("1.0000000000000000001"))) +print(conjugate_value(np.complex128(1.0 + 2.0j))) # (1-2j) +print(conjugate_extended(np.clongdouble(1.0 + 2.0j))) # (1-2j) +print(invert(True)) # False ``` @@ -137,6 +168,8 @@ Result: ```text 5 3.0 +2.0000000000000000002 +(1-2j) (1-2j) False ``` @@ -151,12 +184,21 @@ False | `integer(8)` / `int64` | `Int64` | `np.int64` | `np.int64` | | `real(4)` | `Float32` | `np.float32` | `np.float32` | | `real(8)` / `real64` | `Float64` | `np.float64` | `np.float64` | +| `real(c_long_double)` — `real(10)` on x86-64 | `Float128` | `np.longdouble` | `np.longdouble` | | `complex(4)` | `Complex64` | `np.complex64` | `np.complex64` | | `complex(8)` | `Complex128` | `np.complex128` | `np.complex128` | +| `complex(c_long_double_complex)` — `complex(10)` on x86-64 | `Complex256` | `np.clongdouble` | `np.clongdouble` | | `logical` | `Bool8`-`Bool64` | `bool` or `np.bool_` | `bool` | | `character` | `String` / `String[n]` | Depends on the string boundary | Depends on the string boundary | | Derived Type | Generated Class | Instance of that class | Instance of that class | +`Float128` and `Complex256` mean the target's `long double`, not a fixed +128-bit format. On x86-64 that is x87 extended precision, so `real(10)` and +`complex(10)` map to it and `real(16)` does not; on a target whose `long +double` is IEEE quad, `real(16)` maps to it instead. prik decides from the +mantissa width the compiler reports, never from storage size — see +[Unsupported Widths And Forms](#unsupported-widths-and-forms). + Boolean contract names describe native storage, not different Python dtypes: | Semantic Contract | Native Logical Storage Represented | Scalar Input | Direct Result | Array Storage | @@ -229,9 +271,17 @@ NumPy scalar listed in the mapping table; Boolean scalar results are Python ## Unsupported Widths And Forms -The semantic format can represent wider types such as `Float128` and -`Complex256`, but the current Fortran wrapper blocks real storage wider than 64 -bits and complex storage wider than 128 total bits instead of narrowing it. +`Float128` and `Complex256` name the target's `long double`, which NumPy +exposes as `longdouble` and `clongdouble`. Storage size alone cannot identify +that format: on x86-64 both x87 extended precision and IEEE binary128 occupy +128 bits and differ only in mantissa width. + +prik therefore compares the compiler-measured mantissa against the target's +`long double` rather than trusting the declaration. On a target whose `long +double` is x87 extended precision this accepts C `long double` and Fortran +`real(10)`, and refuses `real(16)` with a diagnostic naming both widths -- +rather than narrowing it silently. On a target whose `long double` is IEEE +quad, the same rule accepts `real(16)`. --- diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index 0ec59ec1b..8385aaa55 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -71,7 +71,7 @@ limitation for each feature. | Module variables, constants, saved state, and common-block procedure state | Supported | [Wrapping modules](../guide/wrapping-modules.md) | [Module state route](../../developer/feature-to-code-map.md#feature-routes) | [Module state tests](../../../tests/fortran/modules/end_to_end/test_module_variables_and_state.py), [scalar-derived matrix tests](../../../tests/fortran/derived_types/end_to_end/test_scalar_actual_dummy_matrix.py), [common-block tests](../../../tests/fortran/modules/end_to_end/test_common_blocks.py) | Common-block storage is not exported as Python variables. Rank-zero derived module objects use direct, scoped, allocation-transaction, or pointer-transaction handoff selected before lowering. `character` module state is supported in every form: a declared-length scalar reads and writes as `str` at exactly its declared byte width, an `allocatable` or `pointer` scalar reads as a detached `str` or `None`, and arrays reach Python as fixed-width bytes. Only declared-length non-descriptor scalars are writable by assignment; descriptor scalars are read-only snapshots for numeric and `character` state alike, and arrays are mutated in place through their view or handle rather than rebound. | | Fortran enum constants | Supported | [Enumerations](../guide/enumerations.md) | [Semantic constants route](../../developer/codebase-map.md#cross-stage-hotspots) | [Enum runtime tests](../../../tests/fortran/enumerations/end_to_end/test_enum_runtime.py), [enum semantic tests](../../../tests/fortran/enumerations/semantics/test_enum_semantics.py), [enum diagnostics](../../../tests/fortran/enumerations/parsing/test_enum_diagnostics.py) | No Python `Enum` or `IntEnum` classes are generated. | | Scalar character arguments, results, and fields | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character argument tests](../../../tests/fortran/strings/end_to_end/test_character_boundaries.py), [edge-case tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype. Scalar `character` `allocatable` and `pointer` values are supported for `intent(in)`, `intent(out)`, `intent(inout)`, and function results, at deferred (`len=:`) and declared (`len=n`) length; a mutable dummy returns the value the procedure left behind, or `None`. prik copies out of native pointer storage and never frees it, so a procedure that allocates a fresh target per call leaks unless it frees its own. | -| Scalar kind coverage | Supported | [Data types](../guide/data-types.md) | [Fortran type probe](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py) | Quad precision (`real(16)`, `complex(16)`) is blocked because it has no portable NumPy dtype. All `logical` kinds are supported and adapt to one-byte NumPy Booleans at the boundary. | +| Scalar kind coverage | Supported | [Data types](../guide/data-types.md) | [Fortran type probe](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/end_to_end/test_primitive_scalar_runtime.py) | Real and complex storage wider than the target's `long double` is blocked; `real(10)` and C `long double` map to NumPy `longdouble`. All `logical` kinds are supported and adapt to one-byte NumPy Booleans at the boundary. | | Caller-ordered multi-source builds, Makefiles, verbose mode, and output placement | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/codebase-map.md#cross-stage-hotspots) | [Multi-source tests](../../../tests/fortran/infrastructure/building/end_to_end/test_multi_source_builds.py), [compiler verbose tests](../../../tests/fortran/infrastructure/building/compiling/test_compiler_verbose.py) | prik does not discover, reorder, or resolve all external source dependencies. | | Visibility, naming, keyword escaping, and collision policy | Supported | [Visibility and naming](../reference/fortran-wrapper.md#visibility-naming-and-the-python-surface) | [Naming policy](../../developer/codebase-map.md#cross-stage-hotspots) | [Visibility/naming tests](../../../tests/fortran/infrastructure/semantic_pyi/contracts/exports_and_modules/end_to_end/test_visibility_naming.py) | Strict mode rejects names that default mode can normalize. | | Immediate call-scoped Python callbacks | Supported | [Callbacks](../guide/callbacks.md) | [Callback bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Callback plan tests](../../../tests/fortran/callbacks/codegen/test_callback_planning.py), [scalar callback tests](../../../tests/fortran/callbacks/end_to_end/test_scalar_callbacks.py), [array callback tests](../../../tests/fortran/callbacks/end_to_end/test_array_callbacks.py), [combined shape tests](../../../tests/fortran/callbacks/end_to_end/test_supported_callback_shapes.py) | Direct wrapper-plan generation supports entering-thread callbacks only. Stored, optional, asynchronous, or cross-thread callbacks are unsupported. | @@ -114,7 +114,7 @@ memory, or outlive its native storage. | Unsupported polymorphic forms | Unsupported | [Inheritance limits](../reference/fortran-wrapper.md#inheritance-and-polymorphism) | [Class policy route](../../developer/codebase-map.md#cross-stage-hotspots) | [Inheritance tests](../../../tests/fortran/derived_types/codegen/test_class_surfaces.py) | Results, mutable dummies, arrays, polymorphic allocatable/pointer scalars, and `class(*)` are blocked. Abstract types and deferred bindings are supported. | | Ambiguous or incomplete constructor overload sets | Unsupported | [Constructor limitations](../reference/fortran-wrapper.md#constructors-initialization-and-finalizers) | [Constructor route](../../developer/codebase-map.md#cross-stage-hotspots) | [Constructor semantic tests](../../../tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/semantics/test_method_and_constructor_contracts.py), [class-plan validation tests](../../../tests/fortran/infrastructure/semantic_pyi/contracts/functions_and_classes/policy/test_class_surface_policy.py) | Candidates must have distinguishable exact runtime signatures and compatible native-owner lifecycles. A Fortran `interface ` is wrapped as the type's overloaded constructor. | | Character arrays and caller-supplied deferred-length character storage | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/codebase-map.md#cross-stage-hotspots) | [Character edge tests](../../../tests/fortran/strings/end_to_end/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype, whose width each accessor reports from the Fortran declaration; Unicode/object arrays are unsupported. Scalar `character` `allocatable` and `pointer` values work for every intent and as function results. A mutable `pointer` dummy that the native procedure reassociates without deallocating orphans the target the adapter allocated for that call. A deferred-length `character(len=:), allocatable` module array does not build under GNU Fortran 11.4, which raises an internal compiler error on that declaration. | -| Quad-precision real and complex storage | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | `real(16)` and `complex(16)` have no portable NumPy dtype, so prik blocks them rather than silently narrowing to 64-bit. Narrower real, complex, integer, and all logical kinds are supported. | +| Real and complex storage wider than the target `long double` | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | prik compares the compiler-measured mantissa against the target's `long double` instead of trusting storage size, which alone cannot separate x87 extended precision from IEEE binary128. `real(16)` is blocked on an x87 target; `real(10)` and C `long double` are supported. | | Generated reference pages for modules, functions, and classes | Partially supported | [Reference index](../reference/index.md) | [Codebase map](../../developer/codebase-map.md) | [Documentation reference checks](../../../tests/docs/test_reference_and_codebase_map.py), [semantic contract tests](../../../tests/fortran/infrastructure/semantic_pyi/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. | @@ -117,7 +118,7 @@ memory, or outlive its native storage. | Real and complex storage wider than the target `long double` | Unsupported | [Datatype limits](../guide/data-types.md#unsupported-widths-and-forms) | [Type probing](../../developer/codebase-map.md#cross-stage-hotspots) | [Scalar kind tests](../../../tests/fortran/data_types/semantics/test_fortran_scalar_semantics.py) | prik compares the compiler-measured mantissa against the target's `long double` instead of trusting storage size, which alone cannot separate x87 extended precision from IEEE binary128. `real(16)` is blocked on an x87 target; `real(10)` and C `long double` are supported. | diff --git a/docs/user/reference/cli-commands.md b/docs/user/reference/cli-commands.md index 804541365..9dd894bd9 100644 --- a/docs/user/reference/cli-commands.md +++ b/docs/user/reference/cli-commands.md @@ -49,15 +49,15 @@ selects it explicitly. ## Input selection -The default build accepts either one or more Fortran source `INPUT` values, or -exactly one semantic `.pyi` entry contract — never both. With +The default build accepts either one or more Fortran or supported C source +`INPUT` values, or exactly one semantic `.pyi` entry contract — never both. With `--build-manifest PATH`, omit positional input entirely. | Option | Purpose | | --- | --- | | `paths` | Source files, `.pyi` files, or directories. Omit only with `--build-manifest`. | | `--version` | Prints the installed PRIK version and exits. | -| `--language fortran` | Selects the frontend explicitly when suffix inference is unavailable. | +| `--language {fortran,c}` | Selects the source or source-free contract language explicitly. C builds require `c`. | | `--build-manifest PATH` | Replays a saved `prik-build.json`. It does not generate one. | | `--jobs N` | Limits concurrent compiler processes. The default uses available CPUs. | @@ -65,9 +65,9 @@ exactly one semantic `.pyi` entry contract — never both. With | `--language {fortran,c}` | Selects the frontend. Required for C inputs, directories, and unknown suffixes. | PRIK_C_DOCS_END --> -Compiled wrapper builds are Fortran-only, so the default build advertises -`--language {fortran}`. The `parse`, `semantics`, `generate --pyi`, and `probe` -paths advertise `--language {fortran,c}` because they support both frontends. +Compiled wrapper builds support Fortran and the documented direct-only C +primitive lane. C paths require `--language c`; the parser also accepts more C +forms than that runtime lane, which fail before wrapper planning. Directories are expanded recursively in deterministic path order. @@ -78,22 +78,24 @@ PRIK_C_DOCS_END --> ## Wrapper builds -A positional Fortran source is both a semantic input and a native +A positional Fortran or C source is both a semantic input and a native implementation source. A `.pyi` is only the semantic contract, so it needs at -least one explicit native input: `--native-fortran-sources`, `--native-objects`, +least one explicit native input: `--native-fortran-sources`, `--native-c-sources`, `--native-objects`, `--native-library`, or `--native-link-item`. | Option | Purpose | | --- | --- | | `--out NAME` | Python module name, `PyInit_` symbol, and stable `NAME.so` alias. Accepts `NAME` or `NAME.so`, and requires a value. | | `--out-dir DIR` | Where generated artifacts and the ABI-suffixed extension are built. Default `./__prik__`. | -| `--compiler COMPILER` | The input-language compiler used for the whole build: preprocessing, datatype measurement, native and bridge compilation, and linking. Default `gfortran`. | +| `--compiler COMPILER` | The input-language compiler used for preprocessing, datatype measurement, native compilation, and linking. Defaults to `gfortran` for Fortran and `cc` for C. | | `-I DIR`, `--include-dir DIR` | Build-wide include directory. Repeat to preserve search order. | | `--strict-wrapper-names` | Rejects Python names that would need escaping or a collision suffix. | | `--assume-intent-in-scalars` | Treats a primitive scalar dummy that declares no `intent` as `intent(in)`, so its value is not returned. A declared `intent` always wins; arrays, derived-type objects, and `character` values are unaffected. Also accepted by `generate --pyi`, where it removes the same results from the generated contract, and by `semantics`. | | `--no-compile-input-sources` | Treats positional sources as semantic inputs only. Requires an explicit native input. | | `--native-fortran-sources PATH ...` | Compiles extra native sources without exposing them as public API. | +| `--native-c-sources PATH ...` | Compiles extra C sources without exposing them as public API. | | `--native-compile-flags FLAG ...` | Flags for native implementation compilation. | +| `--native-c-compile-flags FLAG ...` | Flags for extra C implementation compilation. | | `--native-objects PATH ...` | Links object files, static archives, or shared libraries. | | `--native-library NAME ...` | Links system libraries by name — `--native-library openblas` passes `-lopenblas`. | | `--native-link-item KIND:VALUE ...` | Ordered link items. `KIND` is `object`, `archive`, `shared-library`, `library`, or `arg`. | @@ -121,10 +123,9 @@ Build rules worth knowing: behavior, native inputs, and link plan, so other flags are rejected rather than silently ignored. - +- A source-free C `.pyi` contract is C-native only when `--language c` is + supplied. PRIK does not infer that identity from the contract filename, + compiler, native source list, or `@native_abi("c")`. ## Parse and semantics @@ -276,8 +277,10 @@ for semantic `.pyi` builds the normalized replay `manifest`. | Print semantic IR | `python3 -m prik semantics path/to/file.f90` | | Emit a semantic `.pyi` contract directory | `python3 -m prik generate --pyi path/to/file.f90 --out contracts` | | Build a Fortran wrapper | `python3 -m prik path/to/file.f` | +| Build a direct-only primitive C wrapper | `python3 -m prik --language c path/to/file.c --compiler cc` | | Build with native compiler and link flags | `python3 -m prik path/to/file.f90 --native-compile-flags="-O3 -fopenmp" --wrapper-c-flags=-fopenmp` | | Build from a semantic contract and native object | `python3 -m prik contracts/module.pyi --native-objects build/module.o -I build` | +| Build a C-native semantic contract | `python3 -m prik --language c contracts/module.pyi --native-c-sources native/module.c --compiler cc` | | Build with an explicit module and `.so` name | `python3 -m prik path/to/file.f90 --out my_extension` | | Generate wrapper sources only | `python3 -m prik generate --sources dependency.f90 api.f90 --out-dir build` | | Generate an editable Makefile | `python3 -m prik generate --makefile dependency.f90 api.f90 --out-dir build` | diff --git a/docs/user/reference/fortran-wrapper.md b/docs/user/reference/fortran-wrapper.md index a385d52b6..62bc015cd 100644 --- a/docs/user/reference/fortran-wrapper.md +++ b/docs/user/reference/fortran-wrapper.md @@ -34,12 +34,12 @@ is validated. PRIK_C_DOCS_END --> ## Contents diff --git a/docs/user/reference/python-api.md b/docs/user/reference/python-api.md index 05a33fd26..1c4796b15 100644 --- a/docs/user/reference/python-api.md +++ b/docs/user/reference/python-api.md @@ -10,7 +10,7 @@ publication: draft # Python API Reference `prik` is a small facade. The root package exposes the installed version and -the three ways to build a wrapper — nothing else. Parser models, semantic +the four ways to build a wrapper — nothing else. Parser models, semantic conversion, compiler probes, runtime handles, and plans are imported from the package that owns them. @@ -23,7 +23,7 @@ print(sorted(prik.__all__)) ```text -['__version__', 'build_fortran_extension', 'build_pyi_extension', 'build_pyi_extension_from_manifest'] +['__version__', 'build_c_extension', 'build_fortran_extension', 'build_pyi_extension', 'build_pyi_extension_from_manifest'] ``` ## Root API @@ -31,6 +31,7 @@ print(sorted(prik.__all__)) | Symbol | Use it for | | --- | --- | | `__version__` | The installed PRIK distribution version. | +| `build_c_extension` | Build the documented direct-only primitive C source lane. | | `build_fortran_extension` | Build from Fortran source, plus optional native-only inputs. | | `build_pyi_extension` | Build from semantic `.pyi` contracts, plus explicit native implementation inputs. | | `build_pyi_extension_from_manifest` | Replay a saved `.pyi` build manifest, or generate its Makefile. | @@ -86,7 +87,9 @@ Reach past the root facade when you need a single stage rather than a build. - A parser success is only a source fact. Semantic conversion, policy completion, planning, and generation are separate stages that can each reject input the parser accepted. -- The C frontend is inspection-only and is not part of the root API. +- C source builds are limited to the documented direct-only primitive lane. + Other parser-accepted C forms fail before wrapper planning rather than using + a generated adapter. ## Related pages diff --git a/docs/user/reference/semantic-ir.md b/docs/user/reference/semantic-ir.md index 878841c4f..bebbcce48 100644 --- a/docs/user/reference/semantic-ir.md +++ b/docs/user/reference/semantic-ir.md @@ -18,9 +18,10 @@ PRIK_C_DOCS_END --> @@ -30,7 +31,7 @@ PRIK_C_DOCS_END --> This document records the shared scalar datatype policy used when C and Fortran parser facts are converted to semantic IR. The semantic names are the stable bridge between parser-native type spellings, `.pyi` output, policy completion, -the implemented Fortran wrapper, and a future C-input wrapper backend. +the implemented Fortran wrapper, and the direct-only primitive C backend. PRIK_C_DOCS_END --> ### Semantic Names @@ -502,9 +503,10 @@ work includes: PRIK_C_DOCS_END --> PRIK_C_DOCS_END -->