diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..a54524e05 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,81 @@ +name: Documentation + +on: + push: + branches: [main] + paths: + - ".github/workflows/docs.yml" + - "README.md" + - "docs/**" + - "mkdocs.yml" + - "pyproject.toml" + - "tests/docs/**" + - "tools/mkdocs_publication.py" + pull_request: + paths: + - ".github/workflows/docs.yml" + - "README.md" + - "docs/**" + - "mkdocs.yml" + - "pyproject.toml" + - "tests/docs/**" + - "tools/mkdocs_publication.py" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml + + - name: Install documentation dependencies + run: python -m pip install -e ".[docs,qa]" + + - name: Run documentation tests + run: python -m pytest -q tests/docs + + - name: Build reviewed documentation + run: python -m mkdocs build --strict + + - name: Configure GitHub Pages + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + uses: actions/configure-pages@v5 + + - name: Upload GitHub Pages artifact + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@v4 + with: + path: site + + deploy: + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + permissions: + pages: write + id-token: write + steps: + - name: Deploy GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 87a5d500f..1d057da14 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ mutants/ .ruff_cache/ .benchmarks/ htmlcov/ +site/ *.pyc *.pyo diff --git a/AGENTS.md b/AGENTS.md index 21ae28980..3720a9e82 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,6 +17,12 @@ When updating tests, remove obsolete tests that only assert removed/old implemen Before `x2py/semantics/ir2ast.py` runs, the post-IR policy stage must have completed every semantic decision needed by wrapper generation, including object kind, ownership, transfer, destruction, mutability/writeback, nullability, output projection, release responsibility, contract-value storage mode (`stack`, `heap`, or `alias`), getter behavior, native setter assignment, and Python setter exposure. Bridge and binding generators may only dispatch from those completed decisions into small named implementation methods. They must not infer or override semantic policy from datatype, `intent`, dotted-variable shape, `is_alias`, or local memory checks, and they must not contain a fallback that silently chooses a different behavior. When such a decision is found in bridge or binding code, remove it there and move it into post-IR policy completion. Backend-local helper temporaries may still be created inside the selected implementation method because they are emitted-code details, not semantic policy. +For behavior changes, first try to express the change in completed semantic +policy or the shared wrapper plan. Change binding or bridge lowering only when +the selected plan requires a genuinely new emitted-code mechanism; those +generators should otherwise keep reusing and dispatching existing planned +paths. + 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, IR-to-AST/lowering, binding diff --git a/README.md b/README.md index e216c8f63..9d8a9aa0f 100644 --- a/README.md +++ b/README.md @@ -344,7 +344,7 @@ X2PY_C_DOCS_END --> For diagnostic and inspection commands beyond the main build path, start with `python3 -m x2py --help`, then continue to the -[Fortran wrapper guide](docs/user/guide/fortran-wrapper.md). +[CLI command reference](docs/user/reference/cli-commands.md). @@ -54,7 +56,7 @@ For example, a new CLI stage option normally requires: 1. A focused contract test in `tests/cli/`. 2. Dispatch or output routing in `x2py/cli.py`. 3. Preprocessing tests if the option changes source loading. -4. A copy-paste command in [Verified examples cookbook](../user/examples/verified-cookbook.md). +4. A copy-paste command in the relevant user guide or checked example. 5. A tutorial update only when the main user workflow changes. ## Support Evidence Rule @@ -72,9 +74,9 @@ Use these documentation roles consistently: | Document | Role | | --- | --- | -| [Basic wrapper tutorial](../user/tutorials/basic-wrapper.md) | Main supported user workflow and boundaries | -| [Verified examples cookbook](../user/examples/verified-cookbook.md) | Copy-paste commands and Python API recipes | -| [Fortran wrapper guide](../user/guide/fortran-wrapper.md) | Implemented Fortran runtime contract, mechanism, ownership, and build modes | +| [Getting Started](../user/getting-started/index.md) | Main supported user workflow and boundaries | +| [Examples Gallery](../user/examples/index.md) | Checked commands and Python API recipes | +| [Fortran wrapper reference](../user/reference/fortran-wrapper.md) | Implemented Fortran runtime contract, mechanism, ownership, and build modes | | [Fortran parser reference](fortran-parser-reference.md) | Developer inventory for the Fortran frontend | | [Semantic IR reference](../user/reference/semantic-ir.md) | Accepted semantic IR and datatype contract | | [Semantic .pyi format](../user/reference/semantic-pyi-format.md) | User-visible semantic `.pyi` syntax and roadmap | @@ -172,9 +174,10 @@ PYTHONPATH=. python3 -m pytest -q tests/docs/test_examples.py ## References -- [Tutorial](../user/tutorials/basic-wrapper.md): supported end-to-end user workflow and current - boundaries. -- [Verified examples cookbook](../user/examples/verified-cookbook.md): CLI and Python API recipes. +- [Getting Started](../user/getting-started/index.md): supported end-to-end user + workflow and current boundaries. +- [Examples Gallery](../user/examples/index.md): checked CLI and Python API + recipes. - [Fortran parser reference](fortran-parser-reference.md): Fortran frontend scope, recursive parser organization, API/CLI behavior, diagnostics, fixture workflow, semantic handoff, and tests. @@ -319,8 +322,9 @@ When changing `.pyi` syntax: 1. Add or update parser tests in `tests/parsing/pyi/`. 2. Add or update printer tests in `tests/wrapper_codegen/printers/`. 3. Update fixture tests only if the public generated contract changes. -4. Update [Basic wrapper tutorial](../user/tutorials/basic-wrapper.md) or [Verified examples cookbook](../user/examples/verified-cookbook.md) if users - need to write or read the new syntax. +4. Update the relevant [User Guide](../user/guide/index.md) or checked + [example](../user/examples/index.md) if users need to write or read the new + syntax. 5. Update [Semantic .pyi format](../user/reference/semantic-pyi-format.md) for the full user-facing reference. 6. Update [Semantic IR reference](../user/reference/semantic-ir.md) if the underlying semantic IR contract changes. @@ -347,9 +351,10 @@ When changing datatype mapping: 2. Add `.pyi` printer/loader coverage if the emitted syntax changes. 3. Update semantic fixtures only when serialized semantic IR intentionally changes. -4. Update [Semantic IR reference](../user/reference/semantic-ir.md), plus - [Basic wrapper tutorial](../user/tutorials/basic-wrapper.md) or [Verified examples cookbook](../user/examples/verified-cookbook.md) when the visible - user workflow or examples change. +4. Update [Semantic IR reference](../user/reference/semantic-ir.md), plus the + relevant [User Guide](../user/guide/index.md) or checked + [example](../user/examples/index.md) when the visible user workflow or + examples change. 5. Regenerate and update the exact target mapping snapshots in [Semantic IR reference](../user/reference/semantic-ir.md). The executable documentation test must match the complete output of: @@ -1110,8 +1115,10 @@ X2PY_C_DOCS_END --> `x2py/semantics/c2ir.py` and add coverage in `tests/semantics/conversion/c/`. 7. If the generated `.pyi` changes, update `tests/wrapper_codegen/printers/` or `tests/pipeline/pyi_builds/test_contract_fixtures.py`. -8. Update [C parser reference](c-parser-reference.md), [Basic wrapper tutorial](../user/tutorials/basic-wrapper.md), - [Verified examples cookbook](../user/examples/verified-cookbook.md), or [Semantic IR reference](../user/reference/semantic-ir.md) if users or +8. Update [C parser reference](c-parser-reference.md), the relevant + [User Guide](../user/guide/index.md), checked + [example](../user/examples/index.md), or + [Semantic IR reference](../user/reference/semantic-ir.md) if users or developers need to know the new behavior. X2PY_C_DOCS_END --> @@ -1155,8 +1162,10 @@ metadata item. and `tests/semantics/conversion/fortran/`. 7. If generated `.pyi` changes, update `tests/wrapper_codegen/printers/` and the relevant fixture tests. -8. Update [Fortran parser reference](fortran-parser-reference.md), [Basic wrapper tutorial](../user/tutorials/basic-wrapper.md), - [Verified examples cookbook](../user/examples/verified-cookbook.md), or [Semantic IR reference](../user/reference/semantic-ir.md) as needed. +8. Update [Fortran parser reference](fortran-parser-reference.md), the relevant + [User Guide](../user/guide/index.md), checked + [example](../user/examples/index.md), or + [Semantic IR reference](../user/reference/semantic-ir.md) as needed. Focused verification: @@ -1180,9 +1189,10 @@ X2PY_C_DOCS_END --> there is a deliberate schema decision. 4. If the emitted `.pyi` annotation changes, update `tests/wrapper_codegen/printers/` and `tests/parsing/pyi/`. -5. Update the datatype tables in [Semantic IR reference](../user/reference/semantic-ir.md), and update - [Basic wrapper tutorial](../user/tutorials/basic-wrapper.md) or [Verified examples cookbook](../user/examples/verified-cookbook.md) when a visible - example changes. +5. Update the datatype tables in + [Semantic IR reference](../user/reference/semantic-ir.md), and update the + relevant [User Guide](../user/guide/index.md) or checked + [example](../user/examples/index.md) when a visible example changes. diff --git a/docs/developer/fortran-parser-reference.md b/docs/developer/fortran-parser-reference.md index 7a0eced22..d2c93a2f7 100644 --- a/docs/developer/fortran-parser-reference.md +++ b/docs/developer/fortran-parser-reference.md @@ -4,6 +4,7 @@ audience: developers prerequisites: repository structure, parser architecture related: adding-a-fortran-construct.md, repository-structure.md status: maintained +publication: draft --- # Fortran parser reference (wrapper-focused subset) @@ -616,8 +617,6 @@ error was raised internally: python -m x2py bad.f90 --debug ``` -`--debug-traceback` remains accepted as a compatibility alias. - The same developer mode can be enabled with the environment variable `FORTRAN_PARSER_DEBUG=1`: @@ -813,8 +812,7 @@ disable ANSI output. On Windows, ANSI console compatibility is enabled through For parser development, `format_diagnostic(debug=True)` appends a note with the internal parser file, line, and function that raised the error. The CLI exposes -this through `--debug`, its compatibility alias `--debug-traceback`, or -`FORTRAN_PARSER_DEBUG=1`; normal CLI parse errors intentionally hide Python +this through `--debug` or `FORTRAN_PARSER_DEBUG=1`; normal CLI parse errors intentionally hide Python tracebacks. The sections below list each error category, the triggering condition, and the diff --git a/docs/developer/index.md b/docs/developer/index.md index 91b69afb2..cd20ed006 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -4,6 +4,7 @@ audience: developers, contributors prerequisites: repository checkout related: development-workflow.md, source-map.md, contributing/index.md status: maintained +publication: draft --- # Developer Documentation diff --git a/docs/developer/quality-assurance.md b/docs/developer/quality-assurance.md index a875c41b9..14408215f 100644 --- a/docs/developer/quality-assurance.md +++ b/docs/developer/quality-assurance.md @@ -4,6 +4,7 @@ audience: developers, contributors prerequisites: repository checkout, QA dependencies related: testing-strategy.md, development-workflow.md status: maintained +publication: draft --- # Quality Assurance diff --git a/docs/developer/repository-structure.md b/docs/developer/repository-structure.md index 845400ef1..4c0f640f5 100644 --- a/docs/developer/repository-structure.md +++ b/docs/developer/repository-structure.md @@ -4,6 +4,7 @@ audience: contributors prerequisites: repository checkout related: source-map.md, feature-to-code-map.md, build-system.md, testing-strategy.md status: maintained +publication: draft --- # Repository Structure @@ -51,7 +52,7 @@ through `x2py/__init__.py`. | `tests/wrapper_codegen/` | Typed planning, direct bridge/binding generation, and source-printer tests. | | `tests/utilities/` | Shared Python utility tests. | | `tests/wrapper/fortran/` | Runtime wrapper tests that compile, import, call, and check failure paths. | -| `tests/docs/` | Documentation example and structure checks. | +| `tests/docs/` | Documentation example, structure, and website-publication checks. | | `tests/tools/` | Repository tooling tests. | ## Package Map @@ -80,7 +81,7 @@ X2PY_C_DOCS_END --> | `x2py/parsers/c/` | C lexer, parser, models, preprocessing metadata, and C parser CLI helpers | `parser.py`, `lexer.py`, `models.py`, `preprocessor.py`, `type_resolver.py`, `cli.py` | `tests/parser/c/`, `docs/developer/c-parser-reference.md` | | `x2py/parsers/pyi/` | Semantic `.pyi` text/file parsing to Python AST. | `parser.py` | `tests/parsing/pyi/`, `docs/user/reference/semantic-pyi-format.md` | | `x2py/semantics/` | Language-neutral semantic IR, source-to-IR conversion, `.pyi` AST conversion, and policy completion | `models.py`, `fortran2ir.py`, `c2ir.py`, `pyi2ir.py`, `policy_completion.py` | `tests/semantics/`, `tests/pyi/`, `docs/user/reference/semantic-ir.md`, `docs/user/reference/semantic-pyi-format.md` | -| `x2py/wrapper_codegen/` | Canonical wrapper planning, C/Fortran generation, source printing, and semantic `.pyi` printing | `plan.py`, `planner.py`, `generator.py`, `printers/` | `tests/wrapper_codegen/`, `tests/wrapper/`, `docs/user/guide/fortran-wrapper.md` | +| `x2py/wrapper_codegen/` | Canonical wrapper planning, C/Fortran generation, source printing, and semantic `.pyi` printing | `plan.py`, `planner.py`, `generator.py`, `printers/` | `tests/wrapper_codegen/`, `tests/wrapper/`, `docs/user/reference/fortran-wrapper.md` | | `x2py/naming/` | Unified public-name and generated-symbol policy for Python, C, and Fortran targets | `policy.py` | naming, visibility, and wrapper runtime tests | X2PY_C_DOCS_END --> diff --git a/docs/developer/testing-strategy.md b/docs/developer/testing-strategy.md index 737a1dd16..c01bd332b 100644 --- a/docs/developer/testing-strategy.md +++ b/docs/developer/testing-strategy.md @@ -4,6 +4,7 @@ audience: developers, contributors prerequisites: repository structure related: quality-assurance.md, development-workflow.md status: maintained +publication: draft --- # Testing Strategy diff --git a/docs/index.md b/docs/index.md index aac35597e..5a3dc88ed 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,24 +1,104 @@ --- -title: x2py Documentation -audience: users, developers +title: x2py +description: Turn Fortran into importable Python extensions with zero boilerplate +audience: users prerequisites: none -related: user/index.md, developer/index.md +related: user/getting-started/index.md, user/getting-started/installation.md status: maintained +publication: reviewed --- -# x2py Documentation +# x2py -x2py website documentation is divided into User and Developer lanes. Choose -the lane that matches whether you are using x2py or changing its code. +**x2py turns supported Fortran source into fast, importable Python extensions.** -## User Documentation +It also generates a language-neutral semantic IR and editable `.pyi` +contracts, so unsupported boundaries are reported before wrapper compilation. -[User documentation](user/index.md) explains how to install x2py, build and use -wrappers, understand generated contracts, diagnose failures, and distribute -artifacts. Start here when x2py is a tool you are using. +--- + +## Try it in 30 seconds {#try-x2py} + +Create a file `scale.f90`: + + +```fortran +real(8) function scale(value, factor) result(output) + real(8), intent(in) :: value + real(8), intent(in) :: factor + output = value * factor +end function scale +``` + +Build the Python extension: + +```bash +python3 -m x2py scale.f90 +``` + +Use it from Python: + +```python +import numpy as np +import scale + +result = scale.scale(np.float64(3.0), np.float64(2.5)) +print(result) # 7.5 +``` + +Inspect the generated contract: + +```python +print(scale.scale.__doc__) +``` + +```text +scale(value, factor) -> float64 + +Parameters +---------- +value : float64 +factor : float64 + +Returns +------- +result : float64 -## Developer Documentation +Raises +------ +TypeError + If an argument has an incompatible Python type or dtype. +``` + +--- + +## How it works + +1. You write standard Fortran +2. `x2py` parses the interface and generates a compact native wrapper +3. It produces a Python extension module and editable semantic `.pyi` contracts +4. You get full NumPy scalar dtype safety and clear error messages + +No manual `f2py` signatures. No low-level boilerplate. + +## Next steps + +[Getting Started](user/getting-started/index.md){ .md-button .md-button--primary } + +This guide walks you through installation, compiler setup, and a deeper look at the generated artifacts. + +--- + +## Features + +- Automatic generation of Python extensions from Fortran +- Language-neutral semantic IR +- Editable `.pyi` type stubs +- Strict NumPy dtype checking at call time +- Clean, readable `__doc__` strings +- Build artifacts isolated in `__x2py__/` + +--- -[Developer documentation](developer/index.md) explains how to change x2py, -locate implementation ownership, add features, run focused tests, and prepare a -contribution. Start here when you are modifying the codebase. +**Ready to wrap your Fortran code?** +Start with the [Getting Started](user/getting-started/index.md) guide. diff --git a/docs/javascripts/code-copy.js b/docs/javascripts/code-copy.js new file mode 100644 index 000000000..f592a606a --- /dev/null +++ b/docs/javascripts/code-copy.js @@ -0,0 +1,98 @@ +(function () { + "use strict"; + + const copyIcon = ` + `; + const copiedIcon = ` + `; + + function fallbackCopy(text) { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", ""); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + + try { + if (!document.execCommand("copy")) { + throw new Error("The browser rejected the copy command."); + } + } finally { + textarea.remove(); + } + } + + async function copyText(text) { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text); + return; + } + fallbackCopy(text); + } + + function addCopyButton(code) { + const pre = code.closest("pre"); + if (!pre) { + return; + } + + const parent = pre.parentElement; + const host = + parent && (parent.classList.contains("highlight") || parent.classList.contains("codehilite")) + ? parent + : pre; + if (host.classList.contains("x2py-copy-host")) { + return; + } + + host.classList.add("x2py-copy-host"); + const button = document.createElement("button"); + button.type = "button"; + button.className = "x2py-code-copy"; + button.setAttribute("aria-label", "Copy code to clipboard"); + button.title = "Copy"; + button.innerHTML = copyIcon + copiedIcon; + + let resetTimer; + button.addEventListener("click", async function () { + window.clearTimeout(resetTimer); + button.disabled = true; + try { + await copyText(code.textContent); + button.classList.add("is-copied"); + button.setAttribute("aria-label", "Copied to clipboard"); + button.title = "Copied"; + } catch (_error) { + button.classList.add("is-error"); + button.setAttribute("aria-label", "Could not copy to clipboard"); + button.title = "Copy failed"; + } finally { + button.disabled = false; + resetTimer = window.setTimeout(function () { + button.classList.remove("is-copied", "is-error"); + button.setAttribute("aria-label", "Copy code to clipboard"); + button.title = "Copy"; + }, 2000); + } + }); + + host.appendChild(button); + } + + function addCopyButtons() { + document.querySelectorAll("pre code").forEach(addCopyButton); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", addCopyButtons); + } else { + addCopyButtons(); + } +})(); diff --git a/docs/maintainer/README.md b/docs/maintainer/README.md index 76fe2bd38..6ec1c8a33 100644 --- a/docs/maintainer/README.md +++ b/docs/maintainer/README.md @@ -4,6 +4,7 @@ audience: maintainers prerequisites: developer documentation related: documentation-architecture.md, internal-architecture/index.md, roadmap/index.md status: maintained +publication: draft --- # Maintainer Documentation @@ -34,5 +35,5 @@ Implementation orientation, source maps, feature workflows, tests, and contribution requirements remain in the separate [Developer documentation](../developer/index.md) lane. -The historical [old documentation archive](../old_docs) is retained for -comparison only and is excluded from active navigation. +The historical `docs/old_docs/` archive is retained for comparison only and is +excluded from the website and active navigation. diff --git a/docs/maintainer/ci-cd.md b/docs/maintainer/ci-cd.md index bd578f7a5..99983c8a0 100644 --- a/docs/maintainer/ci-cd.md +++ b/docs/maintainer/ci-cd.md @@ -4,15 +4,37 @@ audience: maintainers prerequisites: testing strategy related: ../developer/testing-strategy.md, release-process.md status: planned-documentation +publication: draft --- # CI/CD -Reserved contributor page for GitHub Actions, quality gates, coverage, -scheduled fuzzing, and documentation publication. +GitHub Actions owns repository quality checks and the reviewed-documentation +deployment. The documentation workflow builds the same filtered MkDocs site +that maintainers can preview locally, uploads the generated `site/` directory, +and deploys it through GitHub Pages. + +## Documentation Publication + +The `Documentation` workflow runs for relevant pull requests, pushes to +`main`, and manual dispatches. Pull requests run the documentation tests and a +strict production build without deploying. A push to `main` runs those checks +and deploys the reviewed site when GitHub Pages is configured to use GitHub +Actions. + +Enable the repository once through **Settings > Pages > Build and deployment > +Source > GitHub Actions**. Then open **Actions > Documentation > Run workflow**, +select `main`, and run it. Later documentation changes deploy automatically +after they are merged or pushed to `main`; maintainers do not build or upload +`site/` themselves. + +Before changing a page to `publication: reviewed`, preview the production view +with `python3 -m mkdocs serve`. Use +`X2PY_DOCS_INCLUDE_DRAFTS=1 python3 -m mkdocs serve` to review unpublished +pages with their draft warning. The lane index must also be reviewed before a +page in that lane can enter the deployed artifact. ## TODO -- TODO: Document the current CI quality gates and the future documentation - website preview/publish flow. +- TODO: Document the complete current CI quality gates and scheduled jobs. - TODO: Link coverage troubleshooting to the maintained quality page. diff --git a/docs/maintainer/design/code-generation.md b/docs/maintainer/design/code-generation.md index c35a5e838..2fd6b74ee 100644 --- a/docs/maintainer/design/code-generation.md +++ b/docs/maintainer/design/code-generation.md @@ -4,6 +4,7 @@ audience: maintainers prerequisites: semantic analysis related: cpython-integration.md, runtime-model.md status: planned-documentation +publication: draft --- # Code Generation diff --git a/docs/maintainer/design/cpython-integration.md b/docs/maintainer/design/cpython-integration.md index b3305f5ba..283864015 100644 --- a/docs/maintainer/design/cpython-integration.md +++ b/docs/maintainer/design/cpython-integration.md @@ -5,6 +5,7 @@ audience: maintainers prerequisites: code generation related: runtime-model.md, error-propagation-model.md status: planned-documentation +publication: draft --- Reference details live in: - `docs/developer/fortran-parser-reference.md` -- `docs/user/guide/fortran-wrapper.md` +- `docs/user/reference/fortran-wrapper.md` - `docs/user/reference/semantic-ir.md` | Source loading and preprocessing | `x2py/pipeline/preprocessing.py` | `docs/developer/source-map.md`, parser references | | Editable semantic contracts | `x2py/parsers/pyi/parser.py`, `x2py/pipeline/pyi.py`, `x2py/semantics/pyi2ir.py`, `x2py/wrapper_codegen/printers/pyi_printer.py` | `docs/user/reference/semantic-pyi-format.md` | | Semantic and wrapper-planning errors | `x2py/semantics/fortran2ir.py`, `x2py/semantics/policy_completion.py`, `x2py/semantics/wrapper_policy.py`, `x2py/wrapper_codegen/planner.py` | `docs/user/guide/error-handling.md` | -| Wrapper policy and lowering | `x2py/semantics/policy_completion.py`, `x2py/semantics/ownership.py`, `x2py/wrapper_codegen/planner.py`, `x2py/wrapper_codegen/generator.py` | `docs/user/guide/fortran-wrapper.md`, ownership docs | +| Wrapper policy and lowering | `x2py/semantics/policy_completion.py`, `x2py/semantics/ownership.py`, `x2py/wrapper_codegen/planner.py`, `x2py/wrapper_codegen/generator.py` | `docs/user/reference/fortran-wrapper.md`, ownership docs | | Native build | `x2py/pipeline/build.py`, `x2py/compiling/compilers.py`, `x2py/compiling/native_support.py` | compiling package README and build-system docs | and future packaging boundaries. - [ ] `docs/developer/coding-standards.md`: document Python style, documentation front matter, no-compatibility-layer rule, parser/codegen - organization, and review expectations. + organization, public contributor rules, TODO markers, support-claim + discipline, and review expectations. - [ ] `docs/maintainer/ci-cd.md`: document current GitHub Actions gates, coverage policy, static-analysis policy, docs checks, and local caveats for CI-only environment values. @@ -115,9 +117,6 @@ X2PY_C_DOCS_END --> - [ ] `docs/developer/contributing/review-process.md`: document review focus, support claims, docs completeness, fixture quality, and blocking versus advisory comments. -- [ ] `docs/developer/contributing/coding-standards.md`: document public contributor style - rules, docs metadata, TODO markers, and support-claim discipline. - ### Design And Internal Architecture - [ ] `docs/maintainer/design/overall-architecture.md`: document system components, @@ -223,6 +222,9 @@ X2PY_C_DOCS_END --> - [ ] Public documentation site publication gate: deploy the existing MkDocs documentation as the project website only after all of the following are true; do not create a separate marketing-content system for this milestone. + - [x] Material for MkDocs, fail-closed `publication` metadata filtering, + local draft preview, strict production builds, and the GitHub Pages Actions + workflow are configured. - [ ] The landing page states the current project promise, supported workflow, and limitations without relying on planned behavior. - [ ] Installation and the first-wrapper workflow are complete and verified @@ -233,10 +235,14 @@ X2PY_C_DOCS_END --> management have maintained user-facing explanations. - [ ] The architecture overview explains the parser, semantic-policy, lowering, bridge, and binding boundaries. - - [ ] Empty, placeholder-only, and TODO-only pages are removed from public - navigation until their content is ready. - - [ ] An unlisted development preview has validated navigation, links, search, - rendering, and the static site build before public deployment. + - [ ] Each page has been reviewed explicitly; change `publication: draft` to + `publication: reviewed` only after that review. + - [ ] Each lane index is reviewed last, after the lane pages intended for its + initial publication are ready. A draft lane index keeps the complete lane + out of production. + - [ ] A local draft preview and the Pages workflow artifact have validated + navigation, links, search, rendering, and the static site build before + enabling GitHub Pages. ## Completed Content Evidence @@ -244,13 +250,13 @@ These pages already carry maintained content or active implementation roadmap evidence. Keep them current as behavior changes, but do not treat them as the primary placeholder queue. -- [x] `docs/index.md`: maintained website entry point for User and Developer - documentation. +- [x] `docs/index.md`: maintained website entry point for all reviewed + documentation lanes. - [x] `docs/user/index.md`: maintained User documentation lane entry point. - [x] `docs/developer/index.md`: maintained Developer documentation lane entry point. -- [x] `docs/maintainer/README.md`: maintained GitHub-only Maintainer - documentation entry point. +- [x] `docs/maintainer/README.md`: maintained Maintainer documentation entry + point, publication-gated like the User and Developer indexes. - [x] `docs/maintainer/documentation-architecture.md`: maintained three-lane documentation and publication contract. - [x] `docs/user/getting-started/index.md`: maintained beginner route from @@ -268,6 +274,11 @@ primary placeholder queue. - [x] `docs/user/reference/semantic-ir.md`: maintained Semantic IR contract. - [x] `docs/user/reference/semantic-pyi-format.md`: maintained semantic `.pyi` contract. +- [x] `docs/user/reference/pyi-contracts/`: maintained editable `.pyi` + contract reference, organized by exports, callable surfaces, and argument + and result projection. +- [x] `docs/user/reference/fortran-wrapper.md`: maintained Fortran wrapper + contract reference. - [x] `docs/user/reference/cli-commands.md`: maintained CLI reference. - [x] `docs/user/reference/python-api.md`: maintained Python API reference. - [x] `docs/user/reference/diagnostic-codes.md`: maintained diagnostic registry. @@ -284,24 +295,27 @@ primary placeholder queue. mapping through calls, storage, runtime behavior, and deployment. - [x] `docs/user/guide/data-types.md`: maintained Fortran storage, semantic `.pyi`, Python value, and NumPy dtype mapping with compiler-probed limits. +- [x] `docs/user/guide/arrays.md`: maintained dtype, rank, shape, layout, + C-order zero-copy and `COPY_F`, stride-aware view, lower-bound, assumed-rank, + zero-size, result, and validation guide. +- [x] `docs/user/guide/strings.md`: maintained immutable value, replacement, + mutable storage, fixed-width array, length, and encoding guide. - [x] `docs/user/guide/wrapping-functions.md`: maintained scalar, array-result, mixed-output, signature, native-call-limit, and evidence guide. - [x] `docs/user/guide/wrapping-subroutines.md`: maintained input, output, inout, hidden/visible storage, tuple-order, and scalar-replacement guide. - [x] `docs/user/guide/wrapping-modules.md`: maintained module namespace, procedure, constant, variable, saved-state, module-array, and common-block guide. -- [x] `docs/user/guide/arrays.md`: maintained dtype, rank, shape, layout, - stride, lower-bound, assumed-rank, zero-size, result, and validation guide. - [x] `docs/user/guide/optional-arguments.md`: maintained omission, `None`, keyword, input/output, default, limitation, and diagnostic guide. - [x] `docs/user/guide/generic-interfaces.md`: maintained named, type-bound, operator, assignment, exact-dispatch, ambiguity, and overload guide. +- [x] `docs/user/guide/wrapping-derived-types.md`: maintained class, field, + method, constructor, finalizer, nested borrow, layout, and polymorphism guide. - [x] `docs/user/guide/allocatables.md`: maintained scalar projection, copy, replacement, borrowed module/component view, unallocated, lifetime, and limitation guide. - [x] `docs/user/guide/pointers.md`: maintained scalar projection, call-local input, detached result, nullability, target policy, and blocked-reassociation guide. -- [x] `docs/user/guide/wrapping-derived-types.md`: maintained class, field, - method, constructor, finalizer, nested borrow, layout, and polymorphism guide. - [x] `docs/user/guide/memory-management.md`: maintained ownership, transfer, destruction, mutability, release, borrowing, and policy-completion guide. - [x] `docs/user/guide/callbacks.md`: maintained immediate callback contract, @@ -310,16 +324,10 @@ primary placeholder queue. value, typing, naming, and unsupported-form guide. - [x] `docs/user/guide/error-handling.md`: maintained failure-layer, Python exception, native status projection, callback, diagnostic, and cleanup guide. -- [x] `docs/user/guide/packaging.md`: maintained local project integration, - artifact, Makefile, rebuild, import, and packaging-limit guide. -- [x] `docs/user/guide/distribution.md`: maintained source-rebuild, prebuilt - compatibility, native dependency, wheel-limit, and release-checklist guide. -- [x] `docs/user/guide/fortran-wrapper.md`: maintained Fortran wrapper contract. -- [x] `docs/user/guide/editing-semantic-pyi-contracts.md`: maintained editable - `.pyi` contract guide. -- [x] `docs/user/tutorials/basic-wrapper.md`: maintained basic wrapper workflow. -- [x] `docs/user/examples/verified-cookbook.md`: maintained verified example - cookbook. +- [x] `docs/user/guide/building-shared-library.md`: maintained build, import, + multi-source, compatibility, and editable-Makefile guide. +- [x] `docs/user/guide/raw-addresses.md`: maintained primitive, array, + fixed-string, lifetime, validation, and address-safety guide. - [x] `docs/user/examples/recipes/`: maintained recipe lane for checked command and API examples. - [x] `docs/user/language-support/feature-matrix.md`: maintained support matrix. diff --git a/docs/maintainer/roadmap/index.md b/docs/maintainer/roadmap/index.md index 40f84e712..0062e42a6 100644 --- a/docs/maintainer/roadmap/index.md +++ b/docs/maintainer/roadmap/index.md @@ -2,8 +2,9 @@ title: Roadmap audience: maintainers prerequisites: user language support, developer documentation -related: ../../user/language-support/planned-features.md, wrapper-plan-migration-checklist.md, semantic-pyi-wrapper-checklist.md, native-array-handle-checklist.md, documentation-content-checklist.md +related: ../../user/language-support/feature-matrix.md, wrapper-plan-migration-checklist.md, semantic-pyi-wrapper-checklist.md, native-array-handle-checklist.md, documentation-content-checklist.md status: active-roadmap +publication: draft --- # Roadmap diff --git a/docs/maintainer/roadmap/native-array-handle-checklist.md b/docs/maintainer/roadmap/native-array-handle-checklist.md index 4e9e43793..6432f984a 100644 --- a/docs/maintainer/roadmap/native-array-handle-checklist.md +++ b/docs/maintainer/roadmap/native-array-handle-checklist.md @@ -4,6 +4,7 @@ audience: maintainers prerequisites: semantic .pyi format, ownership policy, allocatables, pointers related: index.md, ../../user/reference/semantic-pyi-format.md, ../../user/guide/allocatables.md, ../../user/guide/pointers.md status: active-roadmap +publication: draft --- # Native Array Handle Checklist @@ -648,16 +649,14 @@ specialize operation bodies by descriptor kind. stable owner storage. - [x] Use wrapper-owned standard C descriptor storage for allocatable results: allocate persistent rank-specific `CFI_CDESC_T(rank)` storage and establish - it with allocatable attribute. Numeric function results populate local - allocatable storage whose allocation is transferred with `move_alloc`; - generated shape-changing operations use `CFI_allocate`. -- [x] Assign a numeric direct allocatable function result once into a + it with allocatable attribute. Numeric function results populate a local + allocatable once, then transfer that allocation with `move_alloc`; generated + shape-changing operations use `CFI_allocate`. +- [x] Assign a supported numeric direct allocatable function result once into a bridge-local allocatable, then `move_alloc` that allocation into the allocatable `intent(out)` dummy backed by persistent CFI storage. Do not - generate a collector, an `allocated(...)` guard, or a second intrinsic - assignment. The native function must return an allocated, defined result; an - unallocated nonpointer result is a nonconforming native procedure and remains - the user's responsibility. + generate a collector or a second intrinsic assignment. Rank-one, matrix, and + higher-rank results preserve allocated, zero-sized, and unallocated state. - [x] Return a native pointer to owner storage for owned allocatable handles. - [x] Generate destroy routines called by the Python handle finalizer for owned allocatable handles. diff --git a/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md b/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md index b26474ca9..937ea6030 100644 --- a/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md +++ b/docs/maintainer/roadmap/semantic-pyi-wrapper-checklist.md @@ -1,9 +1,10 @@ --- title: Semantic .pyi Wrapper Checklist audience: maintainers -prerequisites: semantic .pyi format, Fortran wrapper guide +prerequisites: semantic .pyi format, Fortran wrapper reference related: ../../user/reference/semantic-pyi-format.md, index.md status: active-roadmap +publication: draft --- # Semantic `.pyi` Wrapper Checklist @@ -452,8 +453,8 @@ X2PY_C_DOCS_END --> immutable, and declare as ownership/lifetime policy. It separates editable wrapper policy from native ABI facts and records the failure layers for edited contracts. Evidence: - `docs/user/guide/editing-semantic-pyi-contracts.md`, - `docs/user/guide/fortran-wrapper.md`, and + `docs/user/reference/pyi-contracts/`, + `docs/user/reference/fortran-wrapper.md`, and `tests/docs/test_structure.py`. - [x] Edited contracts can remove a class, method, generated constructor, class member, and individual overload candidate from the Python API. They can also @@ -464,10 +465,10 @@ X2PY_C_DOCS_END --> `tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_without_constructor_member/`, and `tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_added_bindings/`. -- [x] Module overload groups can be renamed while preserving the native generic - name with `@overload("specific", generic="native_generic")`, and the printer - round-trips that metadata. Evidence: - `tests/semantics/conversion/pyi/test_classes_and_overloads.py::test_convert_pyi_to_ir_renames_module_generic_and_round_trips_native_name` +- [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/semantics/conversion/pyi/test_classes_and_overloads.py::test_convert_pyi_to_ir_applies_module_overload_bind_and_round_trips_native_name` 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 @@ -504,15 +505,20 @@ X2PY_C_DOCS_END --> - [x] Generic `Annotated` constraints and semantic coercions are not silently accepted as runtime validation. Fortran wrapper planning reports direct blockers until named validators or conversion actions exist. Evidence: - wrapper-plan tests that prove generic constraints without a runtime validator fail - and `docs/user/reference/semantic-pyi-format.md`. + wrapper-plan tests that prove generic constraints without a runtime validator + fail. +- [ ] Implement runtime validators for `Bounded(...)` and `Finite`, connect + them to completed wrapper policy and generated calls, and document them for + users only after runtime enforcement is tested. +- [ ] Implement explicit runtime dtype-conversion actions before documenting + contract-controlled coercion as a user feature. - [x] The currently documented editable-contract surface has direct modified runtime evidence or focused semantic/planning evidence: removal and hiding, added and renamed bindings, overload pruning and renamed overload groups, native-order identity calls without `@native_call`, immutable replacement, - ownership triples, pointer-policy blockers, runtime constraints, `@raises`, + ownership triples, pointer-policy blockers, `@raises`, `@hold_gil`, and native-artifact failures. Evidence: - `docs/user/guide/editing-semantic-pyi-contracts.md`, + `docs/user/reference/pyi-contracts/`, `tests/wrapper/fortran/edit_pyi_contracts/`, `tests/semantics/policy/`, `tests/wrapper/fortran/runtime_behavior/test_runtime_policy_decorators.py`, diff --git a/docs/maintainer/roadmap/test-suite-organization-checklist.md b/docs/maintainer/roadmap/test-suite-organization-checklist.md index 9499df682..2e803522d 100644 --- a/docs/maintainer/roadmap/test-suite-organization-checklist.md +++ b/docs/maintainer/roadmap/test-suite-organization-checklist.md @@ -4,6 +4,7 @@ audience: maintainers prerequisites: testing strategy, repository structure related: ../../../developer/testing-strategy.md, ../../../../tests/README.md status: active-roadmap +publication: draft --- # Test Suite Organization Checklist diff --git a/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md b/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md index c5a05fc52..f50ee2518 100644 --- a/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md +++ b/docs/maintainer/roadmap/wrapper-plan-migration-checklist.md @@ -4,6 +4,7 @@ audience: maintainers prerequisites: pipeline map, semantic IR, ownership policy related: ../internal-architecture/pipeline-map.md, ../../user/reference/semantic-ir.md, semantic-pyi-wrapper-checklist.md, index.md status: active-roadmap +publication: draft --- # Wrapper Plan Migration Checklist @@ -593,7 +594,7 @@ summary, the exhaustive matrix, and the test tree disagree. | Status | Collected nodes | | --- | ---: | -| `wrapper-plan` | 353 | +| `wrapper-plan` | 366 | | `dual-route` | 0 | | `legacy` | 0 | | `not-applicable` | 76 | @@ -650,6 +651,7 @@ already covered by the new generator. | `tests/wrapper/fortran/arrays/test_array_contracts.py::*` | source/generated-.pyi parity or parametrized route | ordinary arrays | `wrapper-plan` | | `tests/wrapper/fortran/arrays/test_array_generated_pyi_contracts.py::*` | non-generating: generated semantic .pyi fixture parity | semantic .pyi generation/parsing | `not-applicable` | | `tests/wrapper/fortran/arrays/test_array_results.py::test_array_results_follow_data_buffer_and_descriptor_handle_contracts[*]` | production plan route in source/generated-.pyi parity modes | fixed/runtime-shape ordinary array results; owned allocatable descriptor results; namespace preservation | `wrapper-plan` | +| `tests/wrapper/fortran/arrays/test_array_results.py::test_maybe_unallocated_allocatable_result_preserves_absent_state` | edited semantic `.pyi` contract over the existing array-result native unit | `MaybeUnallocated` direct allocatable vector/matrix result annotations preserve allocated and unallocated result states without changing default always-allocated result handling | `wrapper-plan` | | `tests/wrapper/fortran/arrays/test_array_results.py::test_ordinary_array_results_use_canonical_plan` | canonical production output-only plan route | fixed/runtime-shape ordinary array results; ranks one through fifteen; Fortran order; zero-sized results; allocation/copy/release failure paths | `wrapper-plan` | | `tests/wrapper/fortran/arrays/test_array_results.py::test_owned_allocatable_results_preserve_handle_state` | canonical reduced owned-result contract | allocated and zero-sized wrapper-owned `CFI_CDESC_T` function-result handles; extraction and release | `wrapper-plan` | | `tests/wrapper/fortran/arrays/test_assumed_rank_arrays.py::test_assumed_rank_arguments_dispatch_to_runtime_rank[*]` | source/generated-.pyi parity | ordinary arrays; native-handle actuals deferred to Phase 7 | `wrapper-plan` | @@ -724,9 +726,11 @@ already covered by the new generator. | `tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py::*` | reduced passing legacy/source artifacts compared with direct typed-plan generation; plain non-target module objects intentionally use the safer member-proxy correction described in Phase 8 | scalar derived arguments/results; optional and by-value inputs; projected identity; owned/borrowed lifecycle; plain/`Aliased` module objects; scalar/string/array/nested/native-handle fields; production routing | `wrapper-plan` | | `tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py::*` | reduced direct-plan bound-constructor runtime and artifact proof | explicit bound construction; shared method plan; allocation and owner commit | `wrapper-plan` | | `tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py::*` | complete source/generated-contract and direct-plan proof over the canonical scalar-derived matrix fixture; replaces the former isolated descriptor rejection unit; final Phase 8H cross-suite verification remains a separate closure gate | all five actual declarations from module and wrapper origins; all six dummy forms; exact action/error selection; holder, scoped-address, allocation and pointer transactions; mixed multi-argument acquisition and reverse cleanup; distinct module-origin callbacks for qualified types from separate Fortran modules | `wrapper-plan` | +| `tests/wrapper/fortran/derived_types/test_pointers.py::test_caller_created_pointer_crosses_separately_built_extensions` | two independently built semantic-contract extensions | caller-created pointer descriptor identity; cross-extension validation and association | `wrapper-plan` | +| `tests/wrapper/fortran/derived_types/test_pointers.py::test_caller_created_pointer_handle_tracks_native_output_association` | direct semantic-contract wrapper/build route | caller-created pointer storage attachment; output association and descriptor operations | `wrapper-plan` | | `tests/wrapper/fortran/derived_types/test_pointers.py::test_module_and_derived_pointer_handles_track_native_association[*]` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes; native handles/descriptors | `wrapper-plan` | | `tests/wrapper/fortran/derived_types/test_pointers.py::test_module_native_array_handles_use_canonical_plan` | canonical reduced module-only contract | borrowed pointer/allocatable handles; descriptor calls; strided extraction; ordinary array actuals; operation permissions | `wrapper-plan` | -| `tests/wrapper/fortran/derived_types/test_pointers.py::test_pointer_array_handles_block_on_unsupported_result_owner_policy[*]` | source/generated-.pyi parity or parametrized route | derived types/object lifetimes; native handles/descriptors | `wrapper-plan` | +| `tests/wrapper/fortran/derived_types/test_pointers.py::test_pointer_array_results_use_owned_descriptors_without_owning_targets[*]` | source/generated-.pyi parity or parametrized route | owned pointer result descriptors; associated and unassociated state; borrowed target lifetime | `wrapper-plan` | | `tests/wrapper/fortran/derived_types/test_pointers.py::test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime` | direct wrapper/build route | derived types/object lifetimes; native handles/descriptors | `wrapper-plan` | | `tests/wrapper/fortran/edit_pyi_contracts/test_native_order_contracts.py::test_editable_contract_can_use_native_order_arguments_without_native_call` | direct wrapper/build route | semantic .pyi generation/parsing; raw array addresses completed by Phase 6G; derived result remains Phase 8 | `wrapper-plan` | | `tests/wrapper/fortran/edit_pyi_contracts/test_native_order_contracts.py::test_raw_array_addresses_use_canonical_plan` | reduced edited semantic `.pyi` entries over existing vector/matrix native routines | raw numeric addresses; visible scalar-storage extents; rank one/two; default C and explicit Fortran orientation; mutation; integer-only conversion | `wrapper-plan` | @@ -759,13 +763,16 @@ already covered by the new generator. | `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_optional_arguments_drive_fortran_present_behavior[*]` | source/generated-.pyi parity or parametrized route | optional/presence/writeback | `wrapper-plan` | | `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_optional_array_buffers_preserve_omission_and_identity` | reduced semantic `.pyi` entry over the existing optional native unit | omitted/`None`/present ordinary array storage; mutation; projected identity; native-handle actuals deferred to Phase 7 | `wrapper-plan` | | `tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py::test_scalar_copy_in_out_returns_replacement` | canonical production plan route | scalar copy-in/native mutation/copy-out/cleanup; build/artifact integration | `wrapper-plan` | +| `tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py::test_source_generated_scalar_inout_contract_returns_replacement_and_keeps_namespace` | source/generated-.pyi parity | scalar replacement projection; namespace preservation; semantic .pyi generation/parsing | `wrapper-plan` | | `tests/wrapper/fortran/function_calls/test_output_arguments.py::test_output_arguments_and_multiple_results_follow_python_projection_rules[*]` | source/generated-.pyi parity | mixed-type multiple-result aggregation; ordinary arrays; strings; derived types/object lifetimes; native handles/descriptors | `wrapper-plan` | | `tests/wrapper/fortran/function_calls/test_output_arguments.py::test_hidden_ordinary_array_output_uses_canonical_plan` | canonical production output-only plan route | fixed/runtime-shape hidden ordinary array output; zero-sized output; allocation/copy failure paths | `wrapper-plan` | | `tests/wrapper/fortran/layout_rules/test_wrapper_guide_layout.py::*` | non-generating: wrapper docs/test layout | test/docs layout | `not-applicable` | | `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_allocatable_inout_arrays_mutate_and_return_the_same_handle[*]` | source/generated-.pyi parity route | module variables/state; native handles/descriptors | `wrapper-plan` | | `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_allocatable_replacement_has_no_native_memory_errors[*]` | source/generated-.pyi parity route | module variables/state; native handles/descriptors | `wrapper-plan` | +| `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_caller_created_allocatable_crosses_separately_built_extensions` | two independently built semantic-contract extensions | caller-created allocatable descriptor identity; cross-extension validation and mutation | `wrapper-plan` | | `tests/wrapper/fortran/module_state/test_allocatable_replacement.py::test_projected_allocatable_descriptor_preserves_same_handle_identity` | canonical reduced owned-result plus projected-descriptor contract | direct persistent descriptor mutation; allocation/reallocation/deallocation; same-handle result identity | `wrapper-plan` | | `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles[*]` | source/generated-.pyi parity with one mixed generation unit | derived class/field handles and parent retention remain Phase 8/9 blockers | `wrapper-plan` | +| `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_maybe_unallocated_direct_allocatable_results_preserve_unallocated_state` | edited semantic `.pyi` contract over the existing allocatable module unit | `MaybeUnallocated` direct allocatable result annotation preserves the unallocated result state without changing default always-allocated result handling | `wrapper-plan` | | `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_scalar_descriptor_module_variables_return_copied_optional_values[*]` | production plan route in source/generated-.pyi parity modes | rank-zero allocatable/pointer arguments, writeback, results, and copied nullable module values | `wrapper-plan` | | `tests/wrapper/fortran/module_state/test_allocatable_views.py::test_plain_allocatable_module_array_exposes_current_live_view[*]` | production plan route after the Phase 7 contract correction | plain and `Aliased` module handles return a current live view or `None`; explicit `.copy()` is independent and a fresh extraction follows current native state | `wrapper-plan` | | `tests/wrapper/fortran/module_state/test_common_blocks.py::*` | source/generated-.pyi parity or parametrized route | scalar calls with internal common-block storage | `wrapper-plan` | @@ -776,6 +783,7 @@ already covered by the new generator. | `tests/wrapper/fortran/multiple_files/test_multi_source_builds.py::test_makefile_mode_reproduces_multi_source_build` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state | `wrapper-plan` | | `tests/wrapper/fortran/multiple_files/test_multi_source_builds.py::test_multi_file_modules_build_one_merged_extension` | direct wrapper/build route | scalar multi-source build/link orchestration; module variables/state | `wrapper-plan` | | `tests/wrapper/fortran/multiple_files/test_multi_source_builds.py::test_multi_file_standalone_procedures_build_one_merged_extension` | direct wrapper/build route | scalar multi-source external symbols and link orchestration | `wrapper-plan` | +| `tests/wrapper/fortran/multiple_files/test_multi_source_builds.py::test_generated_child_modules_are_importable_submodules` | direct wrapper/build route | generated child-module imports and namespace preservation | `wrapper-plan` | | `tests/wrapper/fortran/multiple_files/test_multi_source_builds.py::test_multi_source_generated_contract_build_matches_source_runtime_and_link_order` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state; semantic .pyi generation/parsing | `wrapper-plan` | | `tests/wrapper/fortran/multiple_files/test_multi_source_builds.py::test_multi_source_modified_entry_preserves_modules_and_adds_documented_alias` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state | `wrapper-plan` | | `tests/wrapper/fortran/multiple_files/test_multi_source_builds.py::test_multi_source_pyi_out_writes_one_flat_combined_package` | direct wrapper/build route | build/compile/link orchestration; external symbols/native linkage; module variables/state; semantic .pyi generation/parsing | `wrapper-plan` | @@ -1358,7 +1366,7 @@ validation, allocation, and writeback behavior, while `_build_string_storage_argument()`, `_convert_raw_string_argument()`, and `_convert_string_result()` define the bridge representation. The public contract and observable oracle are -`docs/user/guide/fortran-wrapper.md`, `docs/user/guide/data-types.md`, +`docs/user/reference/fortran-wrapper.md`, `docs/user/guide/data-types.md`, `docs/user/reference/semantic-pyi-format.md`, `tests/wrapper/fortran/strings/test_character_arguments.py`, and `tests/wrapper/fortran/strings/test_character_edge_cases.py`. Direct-plan @@ -1718,7 +1726,7 @@ with the native-handle caller contract recorded as their sole Phase 7 blocker; the direct route is forced only by the internal parity harness. The public behavior is defined by the NumPy array contract in -`docs/user/guide/fortran-wrapper.md`, the array spelling and metadata rules in +`docs/user/reference/fortran-wrapper.md`, the array spelling and metadata rules in `docs/user/reference/semantic-pyi-format.md`, and the existing array wrapper tests. The legacy binding validates exact dtype, rank, every expressible extent, native byte order, alignment, layout/stride requirements, and @@ -2609,30 +2617,27 @@ Legacy oracle: ### Phase 7E — Owned Allocatable Results And Hidden Outputs -Included: rank-positive allocatable direct function results and hidden output -descriptors whose completed policy selects `owned_result_descriptor`. A valid -allocatable function result is allocated when returned; an unallocated -nonpointer function result is a nonconforming native procedure and the wrapper -does not compensate for it. An allocatable output dummy may validly remain -unallocated and still returns a present `AllocatableArray` handle whose state -lives inside that handle. Pointer handle results remain blocked until stable -owner storage and target lifetime are explicit. - -For a numeric direct allocatable function result, the bridge assigns the native -function expression once into a procedure-local allocatable and then uses -`move_alloc` to transfer that allocation into the allocatable `intent(out)` -dummy backed by persistent wrapper-owned `CFI_CDESC_T(rank)` storage. The move -does not copy the array payload. Do not insert a collector helper, an -`allocated(...)` guard, or a second intrinsic assignment. The native function -must return an allocated, defined result; an unallocated result is a -nonconforming native procedure and remains the user's responsibility rather -than a wrapper fallback. Other procedure-local storage remains permitted only -when representation conversion genuinely requires it, such as -deferred-character byte materialization. The binding constructs the complete -generated operation table and Python handle only after owner storage is valid. -Ownership transfers to the handle exactly once; every earlier failure path -releases the persistent allocation and any genuinely required bridge-local -allocation. +Included: allocatable array direct function results and hidden output +descriptors whose completed policy selects `owned_result_descriptor`. +Direct array results preserve allocated, zero-sized, and unallocated state, +including matrices and higher-rank arrays. An allocatable output dummy may +validly remain unallocated and still returns a present `AllocatableArray` handle +whose state lives inside that handle. Pointer handle results remain blocked +until stable owner storage and target lifetime are +explicit. + +For a supported numeric direct allocatable function result, the bridge assigns +the native function expression once into a procedure-local allocatable and then +uses `move_alloc` to transfer its state into the allocatable `intent(out)` dummy +backed by persistent wrapper-owned `CFI_CDESC_T(rank)` storage. The move does +not copy the array payload and preserves an unallocated rank-one result. Do not +insert a collector helper or a second intrinsic assignment. Other +procedure-local storage remains permitted only when representation conversion +genuinely requires it, such as deferred-character byte materialization. The +binding constructs the complete generated operation table and Python handle +only after owner storage is valid. Ownership transfers to the handle exactly +once; every earlier failure path releases persistent storage and any genuinely +required bridge-local allocation. Character-element handles carry runtime `elem_len` and declared element-length policy in the same descriptor record. Because a deferred character width is @@ -2863,7 +2868,7 @@ Phase 7 rows were split, proved through both routes, and then recorded as | `build_from_pyi/test_pyi_wrapper_builds.py::test_pyi_manifest_records_pointer_descriptor_interop_requirements` | non-generating manifest policy; `not-applicable` | Phase 7G plan/header union is covered by direct generated-artifact tests | | `derived_types/test_pointers.py::test_module_and_derived_pointer_handles_track_native_association[*]` | module, normal-array actual, and field mix; `legacy` | split Phase 7A/7F module subsets; field subset remains Phase 8/9 | | `derived_types/test_pointers.py::test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime` | module/field descriptor views; `legacy` | Phase 7B/7G module subset; field owner remains Phase 8/9 | -| `derived_types/test_pointers.py::test_pointer_array_handles_block_on_unsupported_result_owner_policy[*]` | explicit supported blocker; `legacy` | remain blocker until owner/lifetime policy changes; never auto-promote | +| `derived_types/test_pointers.py::test_pointer_array_results_use_owned_descriptors_without_owning_targets[*]` | owned pointer-result descriptor support; `wrapper-plan` | replaces the former owner-policy blocker after descriptor ownership and target lifetime became explicit | | `edit_pyi_contracts/test_ownership_contracts.py::*` | module, field, result lifetime mix; `legacy` | Phase 7E/7F subsets; field/finalizer owners remain Phase 8/9 | | `function_calls/test_optional_arguments.py::test_optional_allocatable_scalar_descriptor_distinguishes_omitted_none_and_value` | scalar baseline; `wrapper-plan` | reuse Phase 3 behavior; no status change | | `function_calls/test_output_arguments.py::test_output_arguments_and_multiple_results_follow_python_projection_rules[*]` | mixed scalar/array/string/derived/allocatable outputs; `legacy` | Phase 7E reduced allocatable result; retain mixed row | @@ -4430,8 +4435,8 @@ existing source/generated-`.pyi` runtime assertions are the behavioral oracle. | `callbacks/test_array_callbacks.py::test_immediate_dummy_procedure_converts_array_arguments_and_results[*]` | writable array view, shaped array result, outer-output identity, and reference writeback | array argument/result slice | | `callbacks/test_all_callback_shapes.py::test_immediate_callbacks_cover_all_supported_argument_shapes[*]` | scalar values, fixed strings, arrays, derived values, non-scalar reference writeback, and one combined call envelope | cross-kind closure slice | | `callbacks/test_derived_callbacks.py::test_immediate_dummy_procedure_converts_derived_arguments_and_results[*]` | callback-local borrowed derived input plus wrapper-owned derived result conversion | derived slice after Phase 9 construction | -| `callbacks/test_callback_generated_pyi_contracts.py` | named prototypes, reference-default and `Value(T)` transport, shape, character storage, cross-module identity, and result annotations round-trip exactly | semantic-contract parity slice | -| `semantics/conversion/pyi/test_types_and_values.py` callback cases | prototype declarations and references, `Value(T)`, exact argument names used by shapes, and unnecessary `Addr(...)` prototype forms | policy completion before planner work | +| `callbacks/test_callback_generated_pyi_contracts.py` | named prototypes, primitive value defaults, explicit primitive `Addr(T)` references, non-primitive `Value(T)` transport, shape, character storage, cross-module identity, and result annotations round-trip exactly | semantic-contract parity slice | +| `semantics/conversion/pyi/test_types_and_values.py` callback cases | prototype declarations and references, primitive `Addr(T)`, non-primitive `Value(T)`, exact argument names used by shapes, and invalid prototype transport forms | policy completion before planner work | ### Phase 10 Plan Shape And Action Vocabulary @@ -4503,12 +4508,13 @@ For every dependency-closed sub-lane: call scope, context lifetime, same-thread rule, GIL rule, cleanup, and fatal-error behavior. - [x] Preserve generated and edited named prototypes exactly. Reject an - incomplete prototype reference, unnecessary `Addr`, optional procedure, + incomplete prototype reference, invalid prototype `Addr`, optional procedure, stored/procedure-pointer lifetime, unavailable mandatory native interface, or unsupported result with the owner path and one exact reason. - [x] Complete callback signature/result/ownership policy before wrapper planning; lowering may only project the completed callback record. -- [x] Add policy/planning tests for reference-default transport, `Value(T)`, +- [x] Add policy/planning tests for primitive value defaults, explicit + primitive `Addr(T)` references, non-primitive `Value(T)`, and retained unsupported forms before planner changes. ### Phase 10B — Typed Callback Plan And Validation diff --git a/docs/old_docs/examples.md b/docs/old_docs/examples.md index e2a02ff1d..4787a77d5 100644 --- a/docs/old_docs/examples.md +++ b/docs/old_docs/examples.md @@ -2,7 +2,7 @@ title: Verified Examples Cookbook audience: users prerequisites: installation, first wrapped function -related: tutorials/basic-wrapper.md, examples-gallery/index.md +related: examples-gallery/index.md status: maintained --- diff --git a/docs/old_docs/fortran_parser.md b/docs/old_docs/fortran_parser.md index 5bb29ef2b..bd0ca2d00 100644 --- a/docs/old_docs/fortran_parser.md +++ b/docs/old_docs/fortran_parser.md @@ -594,8 +594,6 @@ error was raised internally: python -m x2py bad.f90 --debug ``` -`--debug-traceback` remains accepted as a compatibility alias. - The same developer mode can be enabled with the environment variable `FORTRAN_PARSER_DEBUG=1`: @@ -793,8 +791,7 @@ disable ANSI output. On Windows, ANSI console compatibility is enabled through For parser development, `format_diagnostic(debug=True)` appends a note with the internal parser file, line, and function that raised the error. The CLI exposes -this through `--debug`, its compatibility alias `--debug-traceback`, or -`FORTRAN_PARSER_DEBUG=1`; normal CLI parse errors intentionally hide Python +this through `--debug` or `FORTRAN_PARSER_DEBUG=1`; normal CLI parse errors intentionally hide Python tracebacks. The sections below list each error category, the triggering condition, and the diff --git a/docs/old_docs/tutorial.md b/docs/old_docs/tutorial.md index eb0abb56a..2252f0798 100644 --- a/docs/old_docs/tutorial.md +++ b/docs/old_docs/tutorial.md @@ -2,7 +2,7 @@ title: Tutorial audience: users prerequisites: installation, supported compiler toolchain -related: getting-started/index.md, tutorials/basic-wrapper.md +related: getting-started/index.md status: maintained --- diff --git a/docs/stylesheets/code-copy.css b/docs/stylesheets/code-copy.css new file mode 100644 index 000000000..d7e00bad1 --- /dev/null +++ b/docs/stylesheets/code-copy.css @@ -0,0 +1,78 @@ +.x2py-copy-host { + position: relative; +} + +.x2py-code-copy { + position: absolute; + z-index: 2; + top: 0.45rem; + right: 0.45rem; + display: inline-flex; + width: 2rem; + height: 2rem; + align-items: center; + justify-content: center; + padding: 0; + color: #404040; + background: rgb(255 255 255 / 92%); + border: 1px solid #d6d6d6; + border-radius: 0.2rem; + cursor: pointer; + opacity: 0.35; + transition: color 0.15s ease, background-color 0.15s ease, opacity 0.15s ease; +} + +.x2py-copy-host:hover > .x2py-code-copy, +.x2py-code-copy:focus-visible, +.x2py-code-copy.is-copied, +.x2py-code-copy.is-error { + opacity: 1; +} + +.x2py-code-copy:hover { + color: #ffffff; + background: #2980b9; + border-color: #2980b9; +} + +.x2py-code-copy:focus-visible { + outline: 3px solid #f1c40f; + outline-offset: 2px; +} + +.x2py-code-copy.is-copied { + color: #ffffff; + background: #27ae60; + border-color: #27ae60; +} + +.x2py-code-copy.is-error { + color: #ffffff; + background: #c0392b; + border-color: #c0392b; +} + +.x2py-code-copy svg { + width: 1.1rem; + height: 1.1rem; + fill: none; + stroke: currentcolor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + +.x2py-copied-icon, +.x2py-code-copy.is-copied .x2py-copy-icon { + display: none; +} + +.x2py-code-copy.is-copied .x2py-copied-icon { + display: block; +} + +@media (hover: none) { + .x2py-code-copy { + opacity: 0.8; + } +} diff --git a/docs/stylesheets/site.css b/docs/stylesheets/site.css new file mode 100644 index 000000000..e88637b3d --- /dev/null +++ b/docs/stylesheets/site.css @@ -0,0 +1,43 @@ +.wy-nav-content { + max-width: 1200px; + margin: 0; +} + +.wy-nav-side { + padding-bottom: 0; +} + +.wy-side-scroll { + width: 100%; + overflow-y: auto; + scrollbar-color: #9b9b9b #343131; + scrollbar-width: thin; +} + +.wy-side-scroll::-webkit-scrollbar { + width: 0.65rem; +} + +.wy-side-scroll::-webkit-scrollbar-track { + background: #343131; +} + +.wy-side-scroll::-webkit-scrollbar-thumb { + background: #9b9b9b; + border: 2px solid #343131; + border-radius: 0.65rem; +} + +.wy-side-scroll::-webkit-scrollbar-thumb:hover { + background: #c2c2c2; +} + +.rst-versions { + display: none; +} + +.rst-content pre { + width: 100%; + max-width: 56rem; + padding-right: 3.25rem; +} diff --git a/docs/user/changelog/index.md b/docs/user/changelog/index.md index 5e09befba..8debcaa4f 100644 --- a/docs/user/changelog/index.md +++ b/docs/user/changelog/index.md @@ -2,8 +2,9 @@ title: Changelog audience: users, developers prerequisites: none -related: ../language-support/index.md, ../guide/distribution.md +related: ../language-support/index.md, ../guide/building-shared-library.md status: planned-documentation +publication: draft --- # Changelog diff --git a/docs/user/examples/blas-wrapper.md b/docs/user/examples/blas-wrapper.md index bccaf63ef..a9220969e 100644 --- a/docs/user/examples/blas-wrapper.md +++ b/docs/user/examples/blas-wrapper.md @@ -4,6 +4,7 @@ audience: users, advanced users prerequisites: arrays, packaging related: lapack-wrapper.md, ../guide/arrays.md status: planned-documentation +publication: draft --- # BLAS Wrapper Example diff --git a/docs/user/examples/cfd-mini-example.md b/docs/user/examples/cfd-mini-example.md index 983db008b..b8d6bf871 100644 --- a/docs/user/examples/cfd-mini-example.md +++ b/docs/user/examples/cfd-mini-example.md @@ -4,6 +4,7 @@ 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 diff --git a/docs/user/examples/index.md b/docs/user/examples/index.md index 6a3dd04af..d5f3231ee 100644 --- a/docs/user/examples/index.md +++ b/docs/user/examples/index.md @@ -2,36 +2,37 @@ title: Examples Gallery audience: users prerequisites: getting started -related: ../tutorials/index.md, verified-cookbook.md +related: ../tutorials/index.md, ../guide/building-shared-library.md status: planned-documentation +publication: draft --- # Examples Gallery -The maintained part of this section is the checked recipe cookbook. Use it when -you need a copy-paste command, a short Python API pattern, or the current -boundary between inspection and runtime wrapper support. +The maintained part of this section is the checked recipes. Use them when you +need a copy-paste command, a short Python API pattern, or the current boundary +between inspection and runtime wrapper support. 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. -## Maintained Recipes - -- [Verified examples cookbook](verified-cookbook.md) -- [Build and import with the CLI](recipes/build-and-import-cli.md) -- [Build and import with the Python API](recipes/build-and-import-python-api.md) -- [Generate an editable Makefile](recipes/generate-editable-makefile.md) -- [Build multiple Fortran sources](recipes/build-multiple-fortran-sources.md) -- [Inspect a Fortran API](recipes/inspect-fortran-api.md) -- [Work with semantic .pyi contracts](recipes/semantic-pyi-contracts.md) -- [Control CLI output](recipes/control-cli-output.md) -- [Use Python inspection APIs](recipes/use-python-inspection-apis.md) -- [Use compiler preprocessing options](recipes/compiler-preprocessing.md) +## Choose A Page +| Goal | Page | +| --- | --- | +| Build and import a first extension | [First Wrapped Function](../getting-started/first-wrapped-function.md) | +| Build from several ordered sources | [Building the Shared Library](../guide/building-shared-library.md#multiple-source-files) | +| Generate and edit `Makefile.x2py` | [Building the Shared Library](../guide/building-shared-library.md#use-a-makefile) | +| Build through Python code | [Build and import with the Python API](recipes/build-and-import-python-api.md) | +| Inspect a Fortran API | [Inspect a Fortran API](recipes/inspect-fortran-api.md) | +| Work with semantic `.pyi` contracts | [Work with semantic `.pyi` contracts](recipes/semantic-pyi-contracts.md) | +| Control command output | [Control CLI output](recipes/control-cli-output.md) | +| Use inspection APIs from Python | [Use Python inspection APIs](recipes/use-python-inspection-apis.md) | +| Pass compiler and preprocessing options | [Use compiler preprocessing options](recipes/compiler-preprocessing.md) | ## Planned Project Examples diff --git a/docs/user/examples/lapack-wrapper.md b/docs/user/examples/lapack-wrapper.md index 829935808..66c76fe6e 100644 --- a/docs/user/examples/lapack-wrapper.md +++ b/docs/user/examples/lapack-wrapper.md @@ -4,6 +4,7 @@ audience: users, advanced users prerequisites: arrays, BLAS wrapper example related: blas-wrapper.md, ../guide/error-handling.md status: planned-documentation +publication: draft --- # LAPACK Wrapper Example diff --git a/docs/user/examples/mpi-example.md b/docs/user/examples/mpi-example.md index 36f14d94f..b4fc6eb5e 100644 --- a/docs/user/examples/mpi-example.md +++ b/docs/user/examples/mpi-example.md @@ -4,6 +4,7 @@ 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 diff --git a/docs/user/examples/object-oriented-fortran.md b/docs/user/examples/object-oriented-fortran.md index 29daa3955..01cace948 100644 --- a/docs/user/examples/object-oriented-fortran.md +++ b/docs/user/examples/object-oriented-fortran.md @@ -4,6 +4,7 @@ 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 diff --git a/docs/user/examples/ode-solver.md b/docs/user/examples/ode-solver.md index df33a1fbe..1947e2470 100644 --- a/docs/user/examples/ode-solver.md +++ b/docs/user/examples/ode-solver.md @@ -4,6 +4,7 @@ audience: users, advanced users prerequisites: callbacks, arrays related: ../tutorials/numerical-solver.md, ../guide/callbacks.md status: planned-documentation +publication: draft --- # ODE Solver Example diff --git a/docs/user/examples/openmp-example.md b/docs/user/examples/openmp-example.md index fb0bc0538..75084bb49 100644 --- a/docs/user/examples/openmp-example.md +++ b/docs/user/examples/openmp-example.md @@ -4,6 +4,7 @@ audience: advanced users prerequisites: runtime troubleshooting, platform-specific troubleshooting related: mpi-example.md, ../guide/error-handling.md status: planned-documentation +publication: draft --- # OpenMP Example diff --git a/docs/user/examples/recipes/build-and-import-cli.md b/docs/user/examples/recipes/build-and-import-cli.md deleted file mode 100644 index c1f5cd927..000000000 --- a/docs/user/examples/recipes/build-and-import-cli.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Build And Import With The CLI -audience: users -prerequisites: basic wrapper tutorial, supported compiler toolchain -related: ../verified-cookbook.md, ../../guide/fortran-wrapper.md -status: maintained ---- - -# Build And Import With The CLI - -Use this recipe when you want x2py to compile a Fortran source file into an -importable Python extension from the command line. - -## Input - - -```fortran -module fruntime_abi_f90 -contains - real(8) function scale(value, factor) result(output) - real(8), intent(in) :: value - real(8), intent(in) :: factor - output = value * factor - end function scale -end module fruntime_abi_f90 -``` - -## Build - -```bash -python3 -m x2py tests/data/fortran/wrapper/fruntime_abi_f90.f90 \ - --out-dir build/fruntime_abi \ - --json -``` - -Recognizable Fortran sources select the wrapper build when no inspection stage -is selected, so this is equivalent: - -```bash -python3 -m x2py tests/data/fortran/wrapper/fruntime_abi_f90.f90 \ - --out-dir build/fruntime_abi \ - --json -``` - -## Import - -```python -import sys - -import numpy as np - -sys.path.insert(0, "build/fruntime_abi") -import fruntime_abi_f90 - -result = fruntime_abi_f90.scale(np.float64(3.0), np.float64(2.5)) -print(result) # 7.5 -``` - -## Notes - -- Use `--out-dir` to keep generated sources and build artifacts in one place. -- Use `--verbose` to print compiler and linker commands. -- Exact NumPy scalar dtypes are part of the native ABI contract. - - 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 ac758e63d..dec997116 100644 --- a/docs/user/examples/recipes/build-and-import-python-api.md +++ b/docs/user/examples/recipes/build-and-import-python-api.md @@ -2,8 +2,9 @@ title: Build And Import With The Python API audience: users, developers prerequisites: basic wrapper tutorial, supported compiler toolchain -related: ../verified-cookbook.md, ../../reference/python-api.md +related: ../../reference/python-api.md status: maintained +publication: draft --- # Build And Import With The Python API diff --git a/docs/user/examples/recipes/build-multiple-fortran-sources.md b/docs/user/examples/recipes/build-multiple-fortran-sources.md deleted file mode 100644 index 0e0cf3ede..000000000 --- a/docs/user/examples/recipes/build-multiple-fortran-sources.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Build Multiple Fortran Sources -audience: users, developers -prerequisites: basic wrapper tutorial, supported compiler toolchain -related: ../verified-cookbook.md, ../../guide/fortran-wrapper.md -status: maintained ---- - -# Build Multiple Fortran Sources - -Use this recipe when one Python extension needs declarations or implementations -from more than one Fortran source file. - -## Build - -Pass every source in compiler-valid order. The first semantic module names the -merged extension: - -```bash -python3 -m x2py \ - tests/data/fortran/wrapper/first_api.f90 \ - tests/data/fortran/wrapper/second_api.f90 \ - --out-dir build/multi_api \ - --json -``` - -## Import - -```python -import sys - -import numpy as np - -sys.path.insert(0, "build/multi_api") -import first_api - -assert first_api.first_api.add_one(np.int32(4)) == np.int32(5) -assert first_api.second_api.double_value(np.int32(4)) == np.int32(10) -``` - -## Ordering Rules - -x2py does not discover missing sources and does not reorder dependencies. Put -module providers before module consumers, matching the order your compiler -expects for a direct native build. - -## Generate One Contract Package - -The same ordered source list can generate one combined semantic `.pyi` package: - -```bash -python3 -m x2py generate --pyi \ - tests/data/fortran/wrapper/first_api.f90 \ - tests/data/fortran/wrapper/second_api.f90 \ - --out contracts/multi_api -``` - -`contracts/multi_api/__init__.pyi` is the only semantic wrapper input. Native -module leaves are written directly under `contracts/multi_api/`; x2py does not -create per-source subdirectories. - -## Notes - -- The output is one Python extension, not one extension per source file. -- Use `--out-dir` to keep all generated and native artifacts together. -- Use [Generate an editable Makefile](generate-editable-makefile.md) when you - need your build system to run the compile/link step later. diff --git a/docs/user/examples/recipes/compiler-preprocessing.md b/docs/user/examples/recipes/compiler-preprocessing.md index c291cd52b..1940cccfd 100644 --- a/docs/user/examples/recipes/compiler-preprocessing.md +++ b/docs/user/examples/recipes/compiler-preprocessing.md @@ -2,8 +2,9 @@ title: Use Compiler Preprocessing Options audience: users, developers prerequisites: installation, native project compiler flags -related: ../verified-cookbook.md, ../../../developer/c-parser-reference.md, ../../../developer/fortran-parser-reference.md +related: ../../../developer/c-parser-reference.md, ../../../developer/fortran-parser-reference.md status: maintained +publication: draft --- # Use Compiler Preprocessing Options diff --git a/docs/user/examples/recipes/control-cli-output.md b/docs/user/examples/recipes/control-cli-output.md index 72aeb6e10..57ae0eb60 100644 --- a/docs/user/examples/recipes/control-cli-output.md +++ b/docs/user/examples/recipes/control-cli-output.md @@ -2,8 +2,9 @@ title: Control CLI Output audience: users, developers prerequisites: installation -related: ../verified-cookbook.md, ../../reference/cli-commands.md +related: ../../reference/cli-commands.md status: maintained +publication: draft --- # Control CLI Output diff --git a/docs/user/examples/recipes/generate-editable-makefile.md b/docs/user/examples/recipes/generate-editable-makefile.md deleted file mode 100644 index 22ca9d482..000000000 --- a/docs/user/examples/recipes/generate-editable-makefile.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: Generate An Editable Makefile -audience: users, developers -prerequisites: basic wrapper tutorial, GNU Make, supported compiler toolchain -related: ../verified-cookbook.md, ../../guide/fortran-wrapper.md -status: maintained ---- - -# Generate An Editable Makefile - -Use this recipe when you want x2py to generate wrapper sources and -`Makefile.x2py`, then let your build environment run the compile and link -steps. For semantic `.pyi` builds, x2py also writes `x2py-build.json`; that -manifest is the source of truth used to generate the Makefile. - -## Generate The Build Files - -```bash -python3 -m x2py generate --makefile tests/data/fortran/wrapper/fruntime_abi_f90.f90 \ - --out-dir build/fruntime_abi \ - --json -``` - -This writes generated wrapper sources, the header-only native binding support, dependency files, and -`build/fruntime_abi/Makefile.x2py`. - -For a semantic `.pyi` contract with native implementation sources, use the same -mode with explicit native inputs: - -```bash -python3 -m x2py generate --makefile contracts/fruntime_abi_f90.pyi \ - --native-fortran-sources native/fruntime_abi_f90.f90 \ - --native-compile-flags="-O3 -fopenmp" \ - --out-dir build/fruntime_abi \ - --json -``` - -This writes `build/fruntime_abi/x2py-build.json` first and then projects -`build/fruntime_abi/Makefile.x2py` from that manifest. - -## Build With GNU Make - -```bash -make -f build/fruntime_abi/Makefile.x2py -j4 \ - X2PY_FFLAGS=-O3 \ - X2PY_CFLAGS=-O3 \ - X2PY_LDFLAGS=-O3 -``` - -The generated Makefile exposes these variables for local override: - -| Variable | Meaning | -| --- | --- | -| `FC` | Fortran compiler | -| `X2PY_LD` | Link command | -| `X2PY_FFLAGS` | Extra Fortran compiler flags | -| `X2PY_LDFLAGS` | Extra linker flags | - - - -## Notes - -- `--makefile` generates the build plan without compiling immediately. -- `--makefile` selects the editable wrapper-build mode directly. -- `--makefile` and `--verbose` are mutually exclusive. -- `.pyi` Makefile generation is replayable through - `python3 -m x2py generate --makefile --build-manifest build/fruntime_abi/x2py-build.json` - or buildable through - `python3 -m x2py --build-manifest build/fruntime_abi/x2py-build.json`. -- User Fortran sources remain in caller-provided order. Generated independent - objects may be built in parallel by Make. diff --git a/docs/user/examples/recipes/inspect-c-api.md b/docs/user/examples/recipes/inspect-c-api.md index d01afb9b9..0a0e331aa 100644 --- a/docs/user/examples/recipes/inspect-c-api.md +++ b/docs/user/examples/recipes/inspect-c-api.md @@ -3,8 +3,9 @@ title: Deferred Native API Inspection audience: users, developers prerequisites: installation -related: ../verified-cookbook.md, ../../../developer/c-parser-reference.md +related: ../../../developer/c-parser-reference.md status: maintained +publication: draft --- - -## Fixture Inputs - -The recipes reuse these checked fixtures: - -| Purpose | Repository fixture | -| --- | --- | -| Compiled Fortran wrapper and scalar call | `tests/data/fortran/wrapper/fruntime_abi_f90.f90` | -| Basic Fortran procedure | `tests/data/fortran/general/basic_subroutine.f90` | -| Rich Fortran module, types, arrays, and visibility | `tests/data/fortran/general/modern_pyi_example.f90` | -| Generated Fortran semantic interface | `tests/pyi/fixtures/general/modern_pyi_example/modern_pyi_example.pyi` | - - - -## Current Boundary - - - -## Related Documentation - -- [Basic wrapper tutorial](../tutorials/basic-wrapper.md) -- [Fortran wrapper guide](../guide/fortran-wrapper.md) -- [Semantic .pyi Format](../reference/semantic-pyi-format.md) -- [Semantic IR Reference](../reference/semantic-ir.md) -- [Diagnostic Codes](../reference/diagnostic-codes.md) diff --git a/docs/user/faq/index.md b/docs/user/faq/index.md index 8721c3c42..597422dbe 100644 --- a/docs/user/faq/index.md +++ b/docs/user/faq/index.md @@ -4,6 +4,7 @@ audience: users prerequisites: getting started related: ../troubleshooting/index.md, ../guide/index.md status: planned-documentation +publication: draft --- # FAQ diff --git a/docs/user/getting-started/beginner-workflow.md b/docs/user/getting-started/beginner-workflow.md index 1b8a23353..6002d167c 100644 --- a/docs/user/getting-started/beginner-workflow.md +++ b/docs/user/getting-started/beginner-workflow.md @@ -1,87 +1,65 @@ --- title: Common Beginner Workflow +description: Recommended development loop — edit, review contract, build, test, and rebuild audience: users prerequisites: first wrapped module -related: ../tutorials/basic-wrapper.md, ../examples/verified-cookbook.md, ../reference/cli-commands.md +related: ../guide/index.md status: maintained +publication: reviewed --- # Common Beginner Workflow -You have already built and called the `scale.f90` example. This page turns that -same file into a repeatable project workflow: keep source under `src/`, build -into `build/`, run a small Python check, and cleanly rebuild when the native -contract changes. +Now that you have built a function and a module, use this loop for your own +project: edit the source, review its Python interface, build, and test. -Use the `scale.f90` input from the -[README Quick Start](../../../README.md#quick-start). Keep the same filename when -you move it into a project layout. - -## 1. Create A Small Project Layout +--- -Keep native sources under `src/` and Python tests under `tests/`. Treat every -file under `build/` as generated output that the next build may replace. +## Recommended Project Layout -Start with this layout: +This layout continues with the `scale.f90` example: -```text -scale-project/ - src/ - scale.f90 - build/ - tests/ - test_scale.py +``` +my-project/ +├── src/ +│ └── scale.f90 +├── build/ # ← Generated, do not commit +├── tests/ +│ └── test_scale.py +└── contracts/ # Optional edited semantic contracts ``` -Run the remaining commands from `scale-project/`. Keep `src/` and `tests/` -under version control. Do not commit `build/`. +Keep `src/` and `tests/` under version control. Never commit the `build/` folder. - +--- -## 2. Review The Contract Before Compiling +## 1. Edit and Review -Before building, print the semantic `.pyi` contract for the same source: +Edit the Fortran source, then preview the generated Python interface: ```bash python3 -m x2py generate --pyi src/scale.f90 ``` -The output should match the wrapper contract from the -[First Wrapped Function](first-wrapped-function.md) page: - -```python -from x2py.contracts import Addr, Arg, Float64, external, native_call - -@external -@native_call([Addr(Arg(0)), Addr(Arg(1))]) -def scale( - value: Float64, - factor: Float64 -) -> Float64: ... -``` +Check the function names, arguments, result types, and required NumPy dtypes. +This review is especially useful after changing a public Fortran declaration. -This confirms the Python-facing dtype contract and the native scalar-address -projection that code generation will follow. It does not prove that the -compiler, linker, native dependency set, or runtime environment is valid; the -build and smoke test still need to run. +--- -## 3. Build Into An Explicit Directory +## 2. Build the Extension ```bash -python3 -m x2py src/scale.f90 \ - --out-dir build/scale +python3 -m x2py src/scale.f90 --out-dir build/scale ``` -Build output goes under `build/scale`, leaving `src/scale.f90` untouched. Use -`--verbose` when you need exact compiler and linker commands in build logs. +Rerun the same command after source changes. Add `--verbose` only when you need +the compiler and linker details. + +--- -## 4. Run A Python Smoke Test +## 3. Write a Small Test -Put this in `tests/test_scale.py`, or run it directly while learning the flow: +Create `tests/test_scale.py`: ```python import sys @@ -91,86 +69,57 @@ import numpy as np sys.path.insert(0, "build/scale") import scale -result = scale.scale(np.float64(3.0), np.float64(2.5)) -assert result == np.float64(7.5) +def test_scale_function(): + result = scale.scale(np.float64(3.0), np.float64(2.5)) + assert result == 7.5 ``` -Do not stop at “the extension imports.” For each wrapped routine, keep at least -one asserted result. For real projects, also add failure checks that matter to -the contract: wrong dtype, wrong rank or shape, non-writable outputs, or -unsupported optional arguments. The generated `.pyi` defines the current call -contract; the language feature matrix later collects support boundaries and -their focused evidence. - -## 5. Review Generated Artifacts +Run it with: -You normally do not need to open generated files. When debugging, expect -`build/scale` to contain: +```bash +python3 -m pytest tests/test_scale.py -q +``` -| Artifact | Purpose | -| --- | --- | -| `binding_support/` | header-only native binding support | -| `.o` and `.mod` files | native intermediates | -| `.` | importable extension | +--- - +## 4. Optionally Edit the Contract -Treat these as diagnostic evidence, not editable API definitions. Change the -native source or an intentional semantic `.pyi` contract instead. +Save a contract package when you want to change the Python interface: -## 6. Rebuild Cleanly When The Contract Changes +```bash +python3 -m x2py generate --pyi src/scale.f90 --out contracts/scale +``` -For a normal rerun, execute the same build command. After changing source order, -compiler flags, native dependencies, or the wrapper contract, remove the -selected output directory first: +Edit `contracts/scale/scale.pyi`, then build through its package entry: ```bash -rm -rf build/scale -python3 -m x2py src/scale.f90 --out-dir build/scale +python3 -m x2py contracts/scale/__init__.pyi \ + --native-fortran-sources src/scale.f90 \ + --out-dir build/scale-edited ``` -The advanced Makefile workflow is available when you intentionally want -inspectable commands and manual rebuild control. Makefile generation and -`--verbose` are separate modes and cannot be combined. +Use this form instead of the source build in step 2 when the edited contract +should control the wrapper. The `.pyi` controls the Python surface; the Fortran +source still supplies the native implementation. Keep its native symbol names, +types, rank, and argument order accurate. -## Advanced Next Step: Edit The Semantic Contract +The User Guide introduces small edits next to the feature they affect, such as +renaming a function, changing array layout, adding an overload, or exposing a +module procedure as a method. -Stay with source-driven builds until the normal loop is clear. When you need to -review or intentionally edit the semantic `.pyi` contract, generate it -separately: +Use [Editing `.pyi` Contracts](../reference/pyi-contracts/index.md) to find +every supported edit and its complete rules. -```bash -python3 -m x2py generate --pyi src/scale.f90 --out contracts -``` +--- + +## 5. Diagnose a Failure + +If a build fails, rerun it with `--verbose`. If a Python call fails, compare +the arguments with the generated contract. Use a clean output directory only +when you need to rule out stale build files. + +--- + +## Next -Do not treat this as the beginner default. A runtime build from an edited `.pyi` -must also receive the native implementation explicitly through options such as -`--native-fortran-sources`, `--native-objects`, or native libraries. Editing -Semantic `.pyi` Contracts later provides the complete workflow; do not use that -path until its ownership and native-artifact requirements are understood. - -## Failure Routing - -1. If `.pyi` generation fails, fix preprocessing, parsing, or semantic diagnostics. -2. If compilation or linking fails, rebuild with `--verbose` and inspect - the emitted native commands. Build Issues is covered later. -3. If import or runtime behavior fails, compare the artifact and call with the - generated contract. Runtime Issues is covered later. -4. If a documented supported behavior fails, reproduce it with the focused - wrapper test linked by the feature matrix before escalating to full CI. - -Support boundaries are collected later in the language feature matrix. Platform -and toolchain requirements are established in [Installation](installation.md), -and Distribution later explains artifact portability. - -## Evidence - -CLI build modes, output placement, and clean artifact expectations are checked -by [`test_build_modes.py`](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py). -The source-driven runtime call is checked by -[`test_runtime_abi.py`](../../../tests/wrapper/fortran/build_from_source/test_runtime_abi.py), -and semantic `.pyi` build requirements by -[`test_pyi_wrapper_builds.py`](../../../tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py). +- Continue with the [User Guide](../guide/index.md). diff --git a/docs/user/getting-started/first-wrapped-function.md b/docs/user/getting-started/first-wrapped-function.md index c1e86827f..5de39674b 100644 --- a/docs/user/getting-started/first-wrapped-function.md +++ b/docs/user/getting-started/first-wrapped-function.md @@ -1,115 +1,143 @@ --- title: First Wrapped Function +description: Build and call your first Fortran function as a Python extension audience: users prerequisites: installation, verification -related: first-wrapped-module.md, ../guide/wrapping-functions.md, ../reference/semantic-pyi-format.md +related: first-wrapped-module.md, ../guide/wrapping-functions.md status: maintained +publication: reviewed --- # First Wrapped Function -This example builds one checked scalar function and calls it with the exact -NumPy dtypes required by its native contract. +This example shows how to build a simple scalar Fortran function and call it from Python using the exact NumPy dtypes required by its contract. -## Source +--- + +## Source Code -Reuse the same `scale.f90` input from the -[README Quick Start](../../../README.md#quick-start). +Use the same `scale.f90` from the homepage: -The generated Python call accepts two `numpy.float64` values and returns a -Python `float` result. +```fortran +real(8) function scale(value, factor) result(output) + real(8), intent(in) :: value + real(8), intent(in) :: factor + output = value * factor +end function scale +``` -## Build +--- -From the directory containing `scale.f90`: +## Inspect the Generated Contract + +Preview the Python interface before building: ```bash -python3 -m x2py scale.f90 \ - --out-dir build/first-function +python3 -m x2py generate --pyi scale.f90 ``` -The extension is named after the source stem: `scale`. The standalone native -function is exposed directly at that extension's root. - -## Import And Call +The generated semantic `.pyi` contains: ```python -import sys - -import numpy as np +from x2py.contracts import Addr, Arg, Float64, external, native_call -sys.path.insert(0, "build/first-function") -import scale +@external +@native_call([Addr(Arg(0)), Addr(Arg(1))]) +def scale( + value: Float64, + factor: Float64 +) -> Float64: ... +``` -result = scale.scale(np.float64(3.0), np.float64(2.5)) +`Float64` means the function requires `numpy.float64` scalar arguments and +returns the same scalar type. `@external` identifies a procedure outside a +Fortran module. `@native_call(...)` maps the two Python arguments to the native +call and passes each scalar by address. -assert isinstance(result, float) -assert result == 7.5 -``` +This file is both the wrapper contract and an editable description of the +Python interface. You can leave it unchanged for this example; later pages +show useful edits in context. -The checked call returns the Python value `7.5`. +--- -## Inspect The Generated Signature +## Build the Extension -Before compiling, print the semantic contract: +From the directory containing `scale.f90`, run: ```bash -python3 -m x2py generate --pyi scale.f90 +python3 -m x2py scale.f90 --out-dir build/first-function ``` -The generated declaration is: +This creates an importable `scale` extension module in the `build/first-function` directory. + +--- + +## Inspect the Generated Docstring + +x2py creates NumPy-style docstrings from the same contract. Import the built +extension and inspect the function: ```python -from x2py.contracts import Addr, Arg, Float64, external, native_call +import sys -@external -@native_call([Addr(Arg(0)), Addr(Arg(1))]) -def scale( - value: Float64, - factor: Float64 -) -> Float64: ... +sys.path.insert(0, "build/first-function") +import scale + +print(scale.scale.__doc__) ``` -The contract describes `value` and `factor` as read-only `Float64` values at -the Python boundary. The `@native_call` decorator records that the native call -receives the address of each converted native scalar slot. It does not mean the -caller passes references. The semantic `.pyi` is a native contract, not an -ordinary pure-Python type stub. Do not edit it during this first workflow; the -Semantic `.pyi` Format reference explains the complete grammar later. +```text +scale(value, factor) -> float64 + +Parameters +---------- +value : float64 +factor : float64 + +Returns +------- +result : float64 +``` -Fortran `intent` is not printed into the semantic `.pyi`. It helps generate the -initial Python argument/result projection, but the visible signature, -`Returns[...]`, and ordered `@native_call` list are the wrapper authority after -the contract is loaded. The compiled Fortran procedure retains its own `intent`. +`help(scale.scale)` shows the same signature, parameter types, result, and +documented exceptions. Generated modules, classes, methods, and properties +also provide docstrings. -## Failure Mode: Wrong Scalar Type +--- -Native scalar arguments use exact NumPy dtypes. A plain Python `float` is not a -replacement for `numpy.float64` at this boundary: +## Call the Function ```python -scale.scale(3.0, 2.5) # raises TypeError +import numpy as np + +result = scale.scale(np.float64(3.0), np.float64(2.5)) +print(result) # 7.5 +assert result == 7.5 ``` -Do not fix this by adding an implicit conversion inside generated code. Convert -at the Python call site so the selected ABI is explicit: +--- + +## Common Pitfall: Wrong Scalar Type + +You **must** pass the exact NumPy scalar types: ```python +# This will raise TypeError +scale.scale(3.0, 2.5) + +# Correct way scale.scale(np.float64(3.0), np.float64(2.5)) ``` -For array functions, rank, dtype, shape, order, contiguity, and allowed stride -patterns can also be contract requirements. Wrapping Functions and Arrays -expand those rules later in the User Guide. The language feature matrix later -records supported, partial, and unsupported wrapper forms. +Always convert at the call site for scalar arguments. -Build Issues and Runtime Issues are covered later in Troubleshooting. For now, -rerun failed builds with `--verbose`, and compare rejected calls with the -generated `.pyi` contract. +--- + +If the build fails, rerun it with `--verbose`. + +--- -## Evidence +## Next -The linked `scale.f90` input is checked against the repository fixture by -[`test_examples.py`](../../../tests/docs/test_examples.py). -The default extension name and `7.5` runtime result are checked by -[`test_build_modes.py`](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py). +- Continue with [Your First Wrapped Module](first-wrapped-module.md). +- For more function behavior, see [Wrapping Functions](../guide/wrapping-functions.md). diff --git a/docs/user/getting-started/first-wrapped-module.md b/docs/user/getting-started/first-wrapped-module.md index cb4d0b123..e1404ca9a 100644 --- a/docs/user/getting-started/first-wrapped-module.md +++ b/docs/user/getting-started/first-wrapped-module.md @@ -1,25 +1,28 @@ --- title: First Wrapped Module +description: Wrap a Fortran module with public procedures and state variables audience: users prerequisites: first wrapped function -related: beginner-workflow.md, ../guide/wrapping-modules.md, ../language-support/feature-matrix.md +related: beginner-workflow.md, ../guide/wrapping-modules.md status: maintained +publication: reviewed --- # First Wrapped Module -A Fortran module becomes a child Python module inside the extension. Public -procedures and supported public state appear on that child; private native -names and internal getter/setter hooks do not. +A Fortran `module` becomes a **child namespace** inside the generated Python extension. Public procedures and supported public variables are exposed under that namespace. -## Source +--- + +## Source Code -Create `module_state.f90` with this module: +Create a file named `module_state.f90`: ```fortran module module_state implicit none private + public :: nmax, counter, scale, saved_counter public :: summarize, scaled_counter, next_local @@ -30,6 +33,7 @@ module module_state integer(4) :: hidden_counter = 17 contains + integer(4) function summarize() result(value) value = counter + nmax end function summarize @@ -40,135 +44,119 @@ contains integer(4) function next_local() result(value) integer(4), save :: local_counter = 0 - local_counter = local_counter + 1 value = local_counter end function next_local + end module module_state ``` -## Build +--- + +## Build the Extension -From the directory containing `module_state.f90`: +Run the following command: ```bash -python3 -m x2py module_state.f90 \ - --out-dir build/first-module +python3 -m x2py module_state.f90 --out-dir build/first-module ``` -The source stem creates extension `module_state`. Its contained module is -available as `module_state.module_state`. +The extension will be named `module_state`, and the Fortran module will be available as `module_state.module_state`. + +--- + +## Inspect the Generated Docstring -## Read Procedures And State +Import the built module and print its generated docstring: ```python import sys -import numpy as np - sys.path.insert(0, "build/first-module") -import module_state +import module_state.module_state as mod -module = module_state.module_state +print(mod.__doc__) +``` -assert module.nmax == np.int32(12) -assert module.counter == np.int32(3) -assert module.scale == np.float64(1.5) -assert module.summarize() == np.int32(15) +```text +module_state + +Module Attributes +----------------- +nmax : int32 + Read-only constant. +counter : int32 +scale : float64 +saved_counter : int32 + +Functions +--------- +summarize() -> int32 +scaled_counter() -> float64 +next_local() -> int32 ``` -The generated surface exposes public variable names directly. Internal native -helpers such as `get_counter` and `set_counter`, and private names such as -`hidden_counter`, are not part of the Python API. +`help(mod)` shows the same index. Individual functions have their own detailed +docstrings. -## Mutate Module State +--- -Writable state is assigned through the public attribute with its exact NumPy -dtype: +## Usage Example ```python -module.counter = np.int32(9) -assert module.counter == np.int32(9) -assert module.summarize() == np.int32(21) - -module.scale = np.float64(2.0) -assert module.scaled_counter() == np.float64(18.0) -``` - -Supported saved state is native process state. Importing a second extension -module object does not create a second copy of the underlying writable Fortran -state; updates are visible through both wrappers. Python-side values that are -not backed by a native setter can differ between module objects, so do not infer -native mutability from assignment success alone. +import numpy as np -Procedure-local saved state also persists across calls: +print(mod.nmax) # 12 +print(mod.counter) # 3 +print(mod.scale) # 1.5 -```python -assert module.next_local() == np.int32(1) -assert module.next_local() == np.int32(2) +print(mod.summarize()) # 15 +print(mod.scaled_counter()) # 4.5 ``` -## Public Surface Rules +--- -- The extension name comes from the first source filename. -- Each contained Fortran module is a Python child namespace. -- Public procedures use their generated Python names under that namespace. -- Supported writable module variables use direct attributes; generated native - accessors stay hidden. -- Constants and parameters may be readable without a native setter. -- Private Fortran declarations remain absent from the public wrapper. +## Mutating Module State -Use `--pyi` to inspect names and types before building: +```python +mod.counter = np.int32(9) +print(mod.summarize()) # 21 -```bash -python3 -m x2py generate --pyi module_state.f90 +mod.scale = np.float64(2.0) +print(mod.scaled_counter()) # 18.0 ``` -The generated package entry preserves the module namespace: +Procedure-local `save` variables also persist across calls: ```python -from . import module_state +print(mod.next_local()) # 1 +print(mod.next_local()) # 2 ``` -The generated module leaf remains the native contract: - -```python -from x2py.contracts import Final, Float64, Int32 - -nmax: Final[Int32] = 12 - -counter: Int32 - -scale: Float64 - -saved_counter: Int32 +--- -def summarize() -> Int32: ... +## Inspect the Contract -def scaled_counter() -> Float64: ... +Preview the generated interface without building: -def next_local() -> Int32: ... +```bash +python3 -m x2py generate --pyi module_state.f90 ``` -The entry file is Python export policy. Advanced contract editing can flatten -the child module, select only some declarations, or expose one native declaration -under multiple Python names. Those edits reshape Python exports only; they are -not native ABI changes. Keep the leaf contract as the source of native facts and -wait for Editing Semantic `.pyi` Contracts later in the User Guide before -changing the generated contract package. +--- + +## Key Rules -The language feature matrix later collects support boundaries for module state -and other Fortran constructs. +- The extension name is derived from the source filename. +- Each Fortran `module` becomes a child Python namespace. +- Only **public** entities are exposed. +- Private variables (like `hidden_counter`) are hidden. +- Assign module variables with the matching NumPy scalar dtype. -If the extension imports but a name is absent, inspect the generated `.pyi`, -then check native visibility. Runtime Issues later expands this diagnosis. +--- -## Evidence +## Next -The same module-state behavior, hidden accessors, mutation, saved state, and -repeated import behavior are checked by the internal fixture tests in -[`test_module_state.py`](../../../tests/wrapper/fortran/module_state/test_module_state.py). -Generated module contracts are checked by -[`test_module_state_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/module_state/test_module_state_generated_pyi_contracts.py). -The advanced entry export policy linked from this page is checked by -[`test_pyi_wrapper_builds.py`](../../../tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py). +- Continue with the [Beginner Workflow](beginner-workflow.md) to turn these + steps into a repeatable development loop. +- For module details, see [Wrapping Modules](../guide/wrapping-modules.md). diff --git a/docs/user/getting-started/index.md b/docs/user/getting-started/index.md index 93853ad65..bf513e6d2 100644 --- a/docs/user/getting-started/index.md +++ b/docs/user/getting-started/index.md @@ -1,42 +1,36 @@ --- title: Getting Started +description: Install x2py, set up compilers, and build your first Fortran-to-Python extension audience: users prerequisites: repository checkout related: installation.md, verification.md status: maintained +publication: reviewed --- # Getting Started -This section takes you from a source checkout to an imported Python extension. -The supported beginner path wraps Fortran source with the GNU native toolchain. +This guide takes you from a fresh clone to your first working Python extension built from Fortran code. - +The recommended beginner path uses the **GNU toolchain**, which offers the best compatibility right now. + +--- ## Beginner Path Follow these pages in order: -1. [Install x2py and its native prerequisites](installation.md). -2. [Verify Python, NumPy, the CLI, and the compilers](verification.md). -3. [Build and call a scalar function](first-wrapped-function.md). -4. [Work with a Fortran module and its saved state](first-wrapped-module.md). -5. [Use the normal edit, inspect, build, test, and rebuild loop](beginner-workflow.md). +1. **[Installation](installation.md)** — Install x2py and the required native compilers. +2. **[Verification](verification.md)** — Check the package, headers, and compiler. +3. **[Your First Function](first-wrapped-function.md)** — Wrap a simple scalar Fortran function. +4. **[Your First Module](first-wrapped-module.md)** — Work with Fortran modules and saved state. +5. **[Development Workflow](beginner-workflow.md)** — Learn the edit → review → build → test loop. -## What You Will Build +--- -The checked beginner example exposes a Fortran function through an importable -Python extension: +## What You Will Build - +By the end of this section you will be able to write Fortran and call it cleanly from Python: ```python import numpy as np @@ -44,21 +38,14 @@ import numpy as np import scale result = scale.scale(np.float64(3.0), np.float64(2.5)) -assert result == np.float64(7.5) +print(result) # 7.5 ``` -The first example is a standalone procedure exposed directly at the extension -root. The next module example introduces contained Fortran modules as Python -child namespaces. +The first example exposes a standalone Fortran function directly on the extension. +Later examples show how Fortran modules become Python namespaces. -Check the [language feature matrix](../language-support/feature-matrix.md) before -depending on an advanced construct. Installation, compiler, build, and import -failures are routed through [Troubleshooting](../troubleshooting/index.md). +--- -## Evidence +## Next -The standalone example used throughout this section is checked against its -fixture by -[`test_examples.py`](../../../tests/docs/test_examples.py), -and its `7.5` runtime result is checked by -[`test_build_modes.py`](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py). +- Start with [Installation](installation.md). diff --git a/docs/user/getting-started/installation.md b/docs/user/getting-started/installation.md index edf4d3799..a55cea84a 100644 --- a/docs/user/getting-started/installation.md +++ b/docs/user/getting-started/installation.md @@ -1,67 +1,54 @@ --- title: Installation +description: Install x2py from source and set up the native GNU toolchain audience: users, contributors prerequisites: Python 3.10 or newer, repository checkout -related: verification.md, ../troubleshooting/installation-issues.md, ../../developer/quality-assurance.md +related: verification.md status: maintained +publication: reviewed --- # Installation -x2py is currently installed from a source checkout. A runtime wrapper build -needs both the Python package and a native GNU toolchain. +x2py is currently installed from a local source checkout. Building Python +extensions also requires GNU Fortran and standard build tools. + +--- ## Supported Python Versions -The package metadata requires Python 3.10 or newer. GitHub Actions currently -tests Python 3.10, 3.11, and 3.12 on Ubuntu 24.04. A newer Python may satisfy -the package constraint but is not part of the current CI matrix. +x2py requires **Python 3.10 or newer**. +The project is regularly tested on Python 3.10, 3.11, and 3.12. -Check the interpreter before creating the environment: +Check your Python version first: ```bash python3 --version ``` -## Native Prerequisites - -Install these before attempting a wrapper build: +--- -- GNU Fortran (`gfortran`) for preprocessing, type probes, and native builds; -- Python development headers matching the active interpreter; -- NumPy, whose installed package supplies the required development files; -- a native linker supplied by the compiler toolchain. +## Native Prerequisites - +Install these packages before building wrappers: -GNU Make is optional. Direct builds do not require it. The generated Makefile -workflow is an advanced build mode that expects GNU Make and a POSIX-style -shell. +- `gfortran` (GNU Fortran compiler) +- `python3-dev` (Python development headers) +- NumPy (includes required development files) +- `build-essential` (linker and build tools) -On Ubuntu or Debian, the prerequisite packages normally come from: +On **Ubuntu / Debian**: ```bash sudo apt-get update sudo apt-get install build-essential gfortran python3-dev ``` - - -The checked CI target uses Ubuntu 24.04 and `gfortran-13`. Package names and -compiler locations differ on other Linux distributions. +--- ## User Installation -Create an isolated environment from the repository root and install the -checkout in editable mode: +From the root of the cloned repository, run: ```bash python3 -m venv .venv @@ -70,65 +57,31 @@ python3 -m pip install --upgrade pip python3 -m pip install -e . ``` -The installation pulls the runtime Python dependencies declared by the -project, including NumPy, `filelock`, and `immutabledict`. +This installs x2py in editable mode along with its runtime dependencies (including NumPy). + +--- ## Contributor Installation -Contributors should install the optional QA dependencies as well: +If you are contributing code or running tests, also install the QA tools: ```bash python3 -m pip install -e ".[qa]" ``` -The `qa` extra includes pytest, coverage, Hypothesis, Ruff, Bandit, Vulture, -and Radon. These tools are not required merely to import x2py or build a wrapper. - -## Header And Compiler Checks - -Verify that the active environment can locate its development headers: - -```bash -python3 -c "import sysconfig; print(sysconfig.get_path('include'))" -python3 -c "import numpy; print(numpy.get_include())" -``` - -Verify the compiler executables independently: - -```bash -gfortran --version -``` - - - -Continue with the Verification page only after these commands succeed and the -printed header directories exist. - -## Platform Caveats +--- -| Platform | Current status | -| --- | --- | -| Ubuntu Linux | CI-verified with Ubuntu 24.04, Python 3.10-3.12, and `gfortran-13`. | -| Other Linux distributions | Expected to require equivalent GNU compilers and development headers; package names and ABI details are not CI-verified. | -| macOS | Not in the current wrapper CI matrix. Compiler discovery, extension suffixes, linker flags, and runtime library paths need platform validation. | -| Windows | Not in the current wrapper CI matrix. The direct GNU/POSIX build assumptions and generated Makefile workflow are not established as supported. | +## Platform Support -Do not interpret successful contract-generation or diagnostic commands as proof -that the native wrapper toolchain works on an unverified platform. +| Platform | Current Status | +|--------------------|-----------------------------------------| +| Ubuntu Linux | CI-verified (Ubuntu 24.04 + gfortran-13) | +| Other Linux | Expected to work with GNU tools | +| macOS | Not yet in CI matrix | +| Windows | Not yet supported | -## Evidence And Troubleshooting +--- -Dependency and version declarations live in -[`pyproject.toml`](../../../pyproject.toml). The current CI environment is defined -in [`.github/workflows/quality.yml`](../../../.github/workflows/quality.yml), and -the compiler/header configuration is exercised by -[`test_runtime_abi.py`](../../../tests/wrapper/fortran/build_from_source/test_runtime_abi.py). +## Next -For missing packages, headers, or virtual-environment problems, the later -Installation Issues page provides focused checks. Compiler discovery and -linking problems are covered later under Compiler Issues. +- Go to [Verification](verification.md) to check the installation and compiler. diff --git a/docs/user/getting-started/verification.md b/docs/user/getting-started/verification.md index 3e0b9ac59..9eb9c14d4 100644 --- a/docs/user/getting-started/verification.md +++ b/docs/user/getting-started/verification.md @@ -1,146 +1,69 @@ --- title: Verification +description: Verify that x2py, NumPy, and the native toolchain are working correctly audience: users, contributors prerequisites: installation -related: first-wrapped-function.md, ../troubleshooting/index.md, ../reference/cli-commands.md +related: first-wrapped-function.md status: maintained +publication: reviewed --- # Verification -Verify the Python environment, contract-generation path, and native build path -separately. This makes a failure easier to route. +After installation, check the Python package, required headers, and compiler. +The next page uses them together to build a complete extension. -## 1. Verify The Installed Package +--- + +## 1. Verify the Installed Package -Run these commands from the activated environment: +Run these commands in your activated virtual environment: ```bash -python3 -c "from importlib.metadata import version; import x2py; print(version('x2py'))" -python3 -c "import numpy; print(numpy.__version__)" +# Check x2py and NumPy +python3 -c "from importlib.metadata import version; import x2py, numpy; print('x2py:', version('x2py')); print('NumPy:', numpy.__version__)" + +# Check the command-line interface python3 -m x2py --help ``` -The first two commands prove that x2py and NumPy import from the selected -interpreter. The third proves that the module entrypoint is installed. - -## 2. Verify The Contract Path +--- -Use the `scale.f90` input created in the -[README Quick Start](../../../README.md#quick-start). +## 2. Verify the Required Headers -From the directory containing `scale.f90`, print the semantic `.pyi` contract -without compiling a wrapper: +Print the Python and NumPy header directories: ```bash -python3 -m x2py generate --pyi scale.f90 +python3 -c "import sysconfig; print(sysconfig.get_path('include'))" +python3 -c "import numpy; print(numpy.get_include())" ``` -The generated declaration should look like: - -```python -from x2py.contracts import Addr, Arg, Float64, external, native_call - -@external -@native_call([Addr(Arg(0)), Addr(Arg(1))]) -def scale( - value: Float64, - factor: Float64 -) -> Float64: ... -``` +Both commands should print existing directories. -This verifies preprocessing, parsing, semantic lowering, type probing, and -contract printing. It does not compile or import an extension. - -## 3. Verify The Native Toolchain +--- -Check compiler discovery before running a build: +## 3. Verify the Compiler ```bash gfortran --version ``` - +The output should identify GNU Fortran. If the command is missing, install +`gfortran` or add it to `PATH`. -From the same directory, build `scale.f90` into a dedicated directory: - -```bash -python3 -m x2py scale.f90 \ - --out-dir build/verify -``` - -The command must create: - -- an importable `scale` extension under `build/verify`; and -- generated native bridge, object, native-support, and extension files. - - - -Import the extension from that build directory: - -```python -import sys - -import numpy as np - -sys.path.insert(0, "build/verify") -import scale - -assert scale.scale(np.float64(3.0), np.float64(2.5)) == np.float64(7.5) -``` - -## 4. Inspect Generated Files - -`WrapperBuildResult` is the stable way to inspect a Python API build: +--- -```python -from pathlib import Path +## Troubleshooting Guide -from x2py import build_fortran_extension +| Failure Type | Recommended Action | +|--------------------------------|---------------------------------------------| +| Cannot import x2py / NumPy | Check active virtual environment | +| A header directory is missing | Reinstall Python development files or NumPy | +| Compiler not found | Fix `PATH` or reinstall gfortran | -build = build_fortran_extension( - "scale.f90", - output_dir="build/verify", -) +--- -assert build.compiled -assert build.shared_library.is_file() -assert all(Path(path).exists() for path in build.generated_files) -print(build.output_dir) -print(build.shared_library) -``` +## Next -For CLI builds, add `--verbose` when a compiler or linker command fails; it -prints the exact native commands and stage timings. - -## Escalation Path - -| Failure | Next action | -| --- | --- | -| `import x2py` or `import numpy` fails | Recheck the active interpreter; Installation Issues is covered later. | -| `--help` works but `.pyi` generation fails | Read the diagnostic; diagnostic codes are catalogued later in the reference section. | -| Compiler executable is missing | Recheck `PATH` and the compiler command; Compiler Issues is covered later. | -| Native compilation or linking fails | Rebuild with `--verbose`; Build Issues is covered later. | -| Build succeeds but import or call fails | Compare the call with the generated contract; Runtime Issues is covered later. | -| The checked smoke test passes but an advanced construct fails | The language feature matrix later records its support status and focused evidence. | - -Contributors changing documentation should run -`python3 -m pytest -q tests/docs/test_examples.py tests/docs/test_structure.py`. -Wrapper behavior changes require the focused `tests/wrapper/fortran/...` path; -the full GitHub Actions matrix is the final cross-version evidence. - -## Evidence - -The linked `scale.f90` input is checked against the repository fixture by -[`test_examples.py`](../../../tests/docs/test_examples.py). -Native artifact placement and runtime calls are checked by -[`test_build_modes.py`](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py) -and -[`test_runtime_abi.py`](../../../tests/wrapper/fortran/build_from_source/test_runtime_abi.py). +- Build and call [Your First Wrapped Function](first-wrapped-function.md). That + example is the end-to-end verification. diff --git a/docs/user/guide/allocatables.md b/docs/user/guide/allocatables.md index bf6b482d7..836e46d15 100644 --- a/docs/user/guide/allocatables.md +++ b/docs/user/guide/allocatables.md @@ -1,40 +1,40 @@ --- title: Allocatables +description: How x2py handles Fortran `allocatable` variables, arrays, and descriptors audience: users, advanced users prerequisites: arrays related: arrays.md, pointers.md, memory-management.md status: maintained +publication: reviewed --- # Allocatables -Allocatable behavior depends on whether the contract describes a scalar -descriptor projection or an array descriptor handle. Scalar allocatables cross -procedure boundaries as ordinary nullable Python values. Array allocatables use -`Allocatable[T[...]]`, which is a Python handle to a native allocatable -descriptor, not a NumPy array. +A Fortran allocatable descriptor records whether storage is allocated and, for +arrays, its address, shape, and strides. The descriptor controls the allocation, +and an x2py handle gives Python access to that descriptor. + +## Key Concepts + +- Scalar allocatables appear as `T | None`; array allocatables use + `Allocatable[T[...]]` handles. +- An array handle exposes allocation state and descriptor operations; it is not + itself a NumPy array. +- `allocated` reports whether storage exists; `to_numpy()` returns a live view + of that storage. +- Reallocation or deallocation invalidates existing views. +- Module and derived-field handles expose storage that belongs to their module + or parent object. Returned and caller-created handles have their own + descriptor storage. +- When available, `deallocate()` releases the current allocation but keeps the + handle open. `close()` permanently ends a returned or caller-created handle. -| Case | Python sees | Owner and lifetime | -| --- | --- | --- | -| Scalar allocatable projection | `T | None` | a call-local native descriptor is created or read back by the bridge | -| Allocatable descriptor argument | `Allocatable[T[...]]` handle | the handle passes the native allocatable descriptor | -| Module allocatable array | `Allocatable[T[...]]` handle | the Fortran module owns allocation and release | -| Derived allocatable field | `Allocatable[T[...]]` handle | the containing generated wrapper owns the native instance | -| Owned allocatable result | `Allocatable[T[...]]` handle | x2py-owned descriptor storage releases the native allocation with the handle | - -`Allocatable` is the dynamic-storage fact shared by all rows. It does not by -itself choose copy, replacement, borrowed-view, or owned-handle behavior. The -declaration context and completed ownership policy choose that behavior before -wrapper lowering. - -At runtime, every allocatable array handle described below is an -`AllocatableArray`. Scalar allocatables never produce an `AllocatableArray`; -they remain ordinary `T | None` values at the Python boundary. +--- -## Array Handles +## When To Use An Allocatable Handle -`Allocatable[T[...]]` is the active allocatable-array spelling in semantic -`.pyi` contracts: +Use `Allocatable[T[...]]` when the native callable needs the allocatable +descriptor and may inspect or change its allocation state: ```python from x2py.contracts import Allocatable, Float64, Int32 @@ -42,479 +42,293 @@ from x2py.contracts import Allocatable, Float64, Int32 values: Allocatable[Float64[:]] def resize(values: Allocatable[Float64[:]], n: Int32) -> None: ... -def scale(values: Float64[:]) -> None: ... ``` -The handle owns the allocation state, so an unallocated descriptor is still a -present handle: `h.allocated is False`, `h.shape is None`, and -`h.to_numpy() is None`. `| None` means the handle object itself may be absent -for an optional native dummy, making native `present(values)` false: +Use ordinary `T[...]` when the callable needs only array data: ```python -def maybe_resize(values: Allocatable[Float64[:]] | None = ...) -> None: ... +def sum_values(values: Float64[:]) -> Float64: ... ``` -That spelling is valid only for optional callable arguments. Do not use -`Allocatable[T[...]] | None` for module variables, derived-type fields, or -function results; those surfaces return a present handle. Module variables, -fields, allocatable output dummies, function results, and handles changed by -later operations may be unallocated. The handle then reports -`allocated is False` and `to_numpy() is None`; this is descriptor state, not an -absent optional argument. - -Passing a handle to `Allocatable[T[...]]` passes the native descriptor. Passing -the same allocated handle to a normal `T[...]` argument uses ordinary Fortran -array-actual semantics by handing off the handle's native array data facet. It -is not an implicit call to `.to_numpy()`. A normal `T[...]` argument rejects an -unallocated handle because there is no valid array actual to pass; an allocated -zero-length array remains valid. - -Plain NumPy arrays are accepted by normal `T[...]` array parameters. They are -rejected for `Allocatable[T[...]]` descriptor parameters because a NumPy array -does not carry a native allocatable descriptor. - -`h.to_numpy()` is the explicit extraction operation. It returns `None` when the -handle is unallocated. Otherwise, it returns a live mutable NumPy view of the -current native allocation. It never creates an automatic detached snapshot or -copy. Users who need independent storage must explicitly call `.copy()` on the -returned array. - -A borrowed view is a NumPy array that points at storage Python does not own. -Mutating the view mutates the owner. Deallocating or reallocating the owner can -make an existing view stale. Accessing a stale view is unsupported and may -crash the process; discard it and call `to_numpy()` again after the native state -changes. Each fresh extraction inspects the current descriptor and starts at -the current native lower bounds. Changing lower bounds during native -reallocation must not offset the first element exposed to NumPy. - -An allocatable array returned by a function or hidden output is different from -a borrowed module or field handle. x2py transfers the result into persistent -descriptor storage owned by the returned handle. The handle remains -usable after the native call returns, and `close()` or finalization releases the -native allocation. A NumPy view extracted from that handle retains the handle; -as with every live view, explicitly closing or resizing the handle makes older -views stale. - -## Scalar Allocatable Projections - -Scalar allocatables cross a procedure boundary as ordinary nullable Python -values. The semantic `.pyi` keeps the Python annotation as `T | None` and uses -`Allocatable(...)` inside `@native_call` to describe native descriptor -construction and readback. - -For example: +A plain NumPy array cannot satisfy an `Allocatable[T[...]]` parameter because +it does not carry native allocation state. Use `to_numpy()` when Python needs +the current array data held by an allocatable handle. -```fortran -function maybe_scale(enabled) result(scale) - integer(4), intent(in) :: enabled - real(8), allocatable :: scale - - if (enabled /= 0) then - allocate(scale) - scale = 2.5_8 - end if -end function maybe_scale - -subroutine update_scale(scale) - real(8), allocatable, intent(inout) :: scale - - if (allocated(scale)) then - scale = scale + 1.0_8 - else - allocate(scale) - scale = 1.0_8 - end if -end subroutine update_scale -``` +--- + +## Allocatable Array Handle API -The corresponding semantic contract is: +`Allocatable[T[...]]` is the type annotation. At runtime, generated Python APIs +use an `AllocatableArray`. You can also create an unallocated handle when a +routine needs a present descriptor that it will allocate: ```python -from x2py.contracts import Addr, Allocatable, Arg, Float64, Int32, Return, Returns, native_call +import x2py.contracts as xc -@native_call([Addr(Arg(0))], result=Allocatable(Return(0))) -def maybe_scale(enabled: Int32) -> Float64 | None: ... +values = xc.Allocatable[xc.Float64[:]]() +assert values.allocated is False -@native_call([Allocatable(Arg(0))]) -def update_scale( - scale: Float64 | None, -) -> Returns["scale", Float64] | None: ... +api.fill_values(values) +assert values.allocated is True ``` -Passing `None` creates a present but unallocated call-local descriptor. Omitting -a defaulted scalar descriptor argument creates native optional absence, so -`present(scale)` is false. Passing a value creates a present allocated -call-local descriptor. A projected output becomes `None` when its descriptor is -unallocated, including an allocatable scalar function result. Ordinary scalar -projection rules still apply: -`intent(out)` uses `Allocatable(Return("name", j))`, while `intent(inout)` uses -`Allocatable(Arg(i))` plus a matching `Returns["name", T] | None` readback. The -singular `result=Allocatable(Return(j))` mapping describes the native function -result and places it among any other Python results. +The annotation supplies the element dtype and rank. The handle creates its +native descriptor storage when first passed to a matching writable argument. +It stays the same Python object after the call. +`Allocatable[Float64]()` is not supported because scalar allocatables cross the +Python boundary as values rather than array handles. -Use a default only when the native scalar dummy is optional: +A returned or attribute array handle remains present even when its descriptor +is unallocated. Reading the Python attribute +returns an `Allocatable[T[...]]` handle, not `ndarray | None`. + +A NumPy view reflects current native storage. Access it only while the +allocation is present: ```python -@native_call([Allocatable(Arg(0))]) -def update_scale(scale: Float64 | None = ...) -> None: ... +h = api.some_allocatable +if h.allocated: + view = h.to_numpy() # live view + view[0] = 42.0 +else: + print("Not allocated") ``` -This scalar rule is separate from array allocatable handles. Array arguments use -`Allocatable[T[...]] | None` only for an optional absent handle; unallocated array -state stays inside a present handle. - -## Complete Allocatable Example +| Member | Type | Behavior | +| --- | --- | --- | +| `allocated` | `bool` | Whether native storage is currently allocated. | +| `shape` | `tuple[int, ...] \| None` | Current dimensions, or `None` when unallocated. | +| `dtype` | `numpy.dtype` | Declared array element type. | +| `rank` | `int` | Declared number of dimensions. | +| `to_numpy()` | `numpy.ndarray \| None` | A live view of current storage, or `None` when unallocated. It never creates an automatic detached snapshot. | +| `deallocate()` | `() -> None` | Deallocates current storage when this operation is available for the handle. | +| `resize(shape)` | `(int \| Sequence[int]) -> None` | Allocates or resizes storage to `shape` when this operation is available for the handle. | +| `close()` | `() -> None` | Permanently releases a returned or caller-created descriptor and any remaining allocation. It does nothing on a module or field handle. | +| `closed` | `bool` | Whether a closable handle has been closed. | + +Calling `deallocate()` or `resize(shape)` when the operation is unavailable +raises `NotImplementedError`. -Create `allocations.f90`: +--- -```fortran -module storage - implicit none - real(8), allocatable, target :: shared_values(:) - real(8), allocatable :: plain_values(:) -contains - function make_values(count) result(values) - integer(4), intent(in) :: count - real(8), allocatable :: values(:) - integer(4) :: index - - if (count <= 0) return - allocate(values(count)) - values = [(2.0_8 * index, index = 1, count)] - end function make_values +## Deallocate Versus Close - subroutine replace_values(values) - real(8), allocatable, intent(inout) :: values(:) +| Operation | What it releases | Handle afterward | +| --- | --- | --- | +| `deallocate()` | The current array allocation. | Open and usable, with `allocated == False`. | +| `close()` | This handle's descriptor and any allocation it still contains. | Permanently closed and unusable. | - if (allocated(values)) deallocate(values) - allocate(values(2)) - values = [10.0_8, 20.0_8] - end subroutine replace_values +Returned and caller-created handles close automatically when Python no longer +uses them. Call `close()` explicitly only when immediate release matters, such +as after using a large allocation. - subroutine allocate_shared(count) - integer(4), intent(in) :: count - integer(4) :: index +Calling `close()` on a module or field handle does nothing: it leaves the +handle and the module's or parent object's storage unchanged. `deallocate()` +changes that allocation when the operation is available. - if (allocated(shared_values)) deallocate(shared_values) - allocate(shared_values(count)) - shared_values = [(1.0_8 * index, index = 1, count)] - end subroutine allocate_shared +--- - subroutine allocate_plain(count) - integer(4), intent(in) :: count - integer(4) :: index +## Where Handles Come From - if (allocated(plain_values)) deallocate(plain_values) - allocate(plain_values(count)) - plain_values = [(3.0_8 * index, index = 1, count)] - end subroutine allocate_plain +### Module Variables And Derived Fields - subroutine release_shared() - if (allocated(shared_values)) deallocate(shared_values) - end subroutine release_shared +A module handle observes the live module allocatable descriptor. A derived-field +handle retains its parent wrapper and observes the live allocatable component +inside it. Native allocation changes are visible through the same Python +handle: - subroutine scale_plain(scale) - real(8), intent(in) :: scale - plain_values = scale * plain_values - end subroutine scale_plain +```python +h = api.values +assert not h.allocated - subroutine release_plain() - if (allocated(plain_values)) deallocate(plain_values) - end subroutine release_plain +api.allocate_values(3) +assert h.allocated +assert h.shape == (3,) - real(8) function shared_sum() result(total) - total = sum(shared_values) - end function shared_sum -end module storage +api.resize_values(5) +assert h.shape == (5,) ``` -Inspecting `allocations.f90` prints allocatable array handles for module -storage, descriptor results, and descriptor arguments. `Aliased` remains a -language-neutral fact that native storage may be externally aliased or -addressed. It does not change `to_numpy()` extraction semantics: +### Function Results + +An allocatable-array function result becomes an `AllocatableArray` with its own +descriptor storage, which x2py releases automatically: ```python -from x2py.contracts import Addr, Aliased, Allocatable, Annotated, Arg, Float64, Int32, Returns, native_call +values = api.make_values(3) +print(values.to_numpy()) +``` -shared_values: Annotated[Allocatable[Float64[:]], Aliased] -plain_values: Allocatable[Float64[:]] +A direct allocatable-array function result is expected to be allocated. Use a +zero-sized allocation to represent an empty result. If the native function may +instead return an unallocated result, declare that possibility explicitly: -@native_call([Addr(Arg(0))]) -def make_values( - count: Int32 -) -> Allocatable[Float64[:]]: ... +```python +from x2py.contracts import Allocatable, Annotated, Float64, Int32, MaybeUnallocated + +def make_values(n: Int32) -> Allocatable[Float64[:]]: ... -def replace_values( - values: Allocatable[Float64[:]] -) -> Returns["values", Allocatable[Float64[:]]]: ... +def maybe_values( + n: Int32, +) -> Annotated[Allocatable[Float64[:]], MaybeUnallocated]: ... +``` -@native_call([Addr(Arg(0))]) -def allocate_shared( - count: Int32 -) -> None: ... +The second function still returns a present handle. That handle may have +`allocated == False`, in which case `to_numpy()` returns `None`. Returning an +unallocated direct result without `MaybeUnallocated` violates the wrapper +contract. -@native_call([Addr(Arg(0))]) -def allocate_plain( - count: Int32 -) -> None: ... +### Output And Inout Arguments -def release_shared() -> None: ... +A nonoptional allocatable-array `intent(out)` does not consume incoming +allocation state, so it is hidden and returned as a new handle. A hidden +output may remain unallocated. -@native_call([Addr(Arg(0))]) -def scale_plain( - scale: Float64 -) -> None: ... +An optional `intent(out)` remains visible so omission preserves native +`present(...)` behavior. An `intent(inout)` descriptor also remains visible +because native code reads and changes its current allocation. When the semantic +contract projects that argument as a result, Python receives the same handle +object: -def release_plain() -> None: ... +```python +values = api.make_values(2) +returned = api.replace_values(values) -def shared_sum() -> Float64: ... +assert returned is values +print(values.to_numpy()) ``` -`plain_values` and `shared_values` have the same extraction behavior: a fresh -`to_numpy()` call returns a live view of the current allocation or `None`. -`Aliased` remains present on `shared_values` because the native declaration -supplies the corresponding addressability fact. It does not change the -allocatable-array extraction mode. +--- + +## Complete Example + +Create `storage.f90`: + +```fortran +module storage + implicit none + real(8), allocatable :: values(:) +contains + + function make_values(n) result(arr) + integer(4), intent(in) :: n + integer(4) :: i + real(8), allocatable :: arr(:) + allocate(arr(max(n, 0))) + if (n > 0) then + arr = [(real(i, 8)*2, i = 1, n)] + end if + end function make_values + + subroutine replace_values(arr) + real(8), allocatable, intent(inout) :: arr(:) + if (allocated(arr)) deallocate(arr) + allocate(arr(2)) + arr = [10.0_8, 20.0_8] + end subroutine replace_values + +end module storage +``` Build it: ```bash -python3 -m x2py allocations.f90 --out-dir build/allocations +python3 -m x2py storage.f90 --out-dir build/storage ``` -Then exercise owned-result, descriptor-argument, and module-handle behavior: +Use the generated module: ```python import sys -import numpy as np -sys.path.insert(0, "build/allocations") -import allocations +import numpy as np -api = allocations.storage +sys.path.insert(0, "build/storage") +from storage.storage import make_values, replace_values -values = api.make_values(np.int32(3)) -np.testing.assert_array_equal(values.to_numpy(), np.array([2.0, 4.0, 6.0], dtype=np.float64)) -assert api.make_values(np.int32(0)).allocated is False +values = make_values(np.int32(3)) +print(values.to_numpy()) # [2. 4. 6.] -returned = api.replace_values(values) -assert returned is values -np.testing.assert_array_equal(values.to_numpy(), np.array([10.0, 20.0], dtype=np.float64)) - -api.allocate_shared(np.int32(3)) -shared = api.shared_values -view = shared.to_numpy() -view[0] = np.float64(10.0) -assert api.shared_sum() == np.float64(15.0) - -api.allocate_plain(np.int32(3)) -plain_view = api.plain_values.to_numpy() -plain_copy = plain_view.copy() -plain_view[0] = np.float64(12.0) - -api.scale_plain(np.float64(2.0)) -np.testing.assert_array_equal(plain_copy, np.array([3.0, 6.0, 9.0], dtype=np.float64)) -np.testing.assert_array_equal( - api.plain_values.to_numpy(), - np.array([24.0, 12.0, 18.0], dtype=np.float64), -) +returned = replace_values(values) +assert returned is values # same handle +print(values.to_numpy()) # [10. 20.] ``` -Do not access `view` after `api.release_shared()`, or `plain_view` after -`api.release_plain()` or another reallocation. Native storage changes make the -previous views stale, and accessing a stale view is unsupported and may crash. - -## Output And Function Results - -Allocated top-level results and non-optional hidden allocatable outputs return -wrapper-owned `AllocatableArray` objects. The generated binding transfers the -result into persistent descriptor storage; the handle releases that storage on -`close()` or finalization. Unallocated storage is represented by a present -handle whose `allocated` property is false and whose `to_numpy()` result is -`None`. Optional allocatable outputs remain visible so the caller can omit them -and make native `present(...)` false. +--- -A NumPy view returned by `to_numpy()` retains its handle owner. Changing that -view changes the handle's current allocation, but does not affect later, -independent result handles. +## Safety Checklist -## Inout Replacement +Keep these rules in mind when allocation state can change. -An allocatable `intent(inout)` descriptor argument accepts an -`AllocatableArray`, not a plain NumPy array. A matching `Returns[...]` -projection records that the same caller handle is the Python result. Policy -completion marks that descriptor boundary read-write before lowering; generated -binding code does not manufacture a replacement ndarray or a second handle. +### Check Allocation Before Use ```python -assert api.replace_values(values) is values +view = h.to_numpy() +view[0] = 1.0 # NOT OK: view may be None ``` -The source for this call is already shown in the complete example above. - -## Character Array Replacement +```python +if h.allocated: + view = h.to_numpy() + if view is not None: + view[0] = 1.0 +``` -Allocatable character arrays use fixed-width NumPy bytes storage. Create -`character_allocatables.f90`: +This check is required whenever an allocatable may be unallocated, including a +result declared with `MaybeUnallocated`. -```fortran -module character_names - implicit none -contains - function make_names() result(names) - character(len=:), allocatable :: names(:) +### Copy Or Discard Views Before Storage Changes - allocate(character(len=3) :: names(2)) - names = [character(len=3) :: "red", "sky"] - end function make_names +```python +view = h.to_numpy() +saved = None if view is None else view.copy() +h.resize(8) +``` - subroutine replace_names(names) - character(len=:), allocatable, intent(inout) :: names(:) - integer :: count +After `resize()`, `deallocate()`, or a native call that may reallocate the +descriptor, discard `view` and call `to_numpy()` again. The independent +`saved` copy remains safe. - if (allocated(names)) then - count = size(names) - else - count = 2 - end if +### Do Not Keep Using A Closed Result - if (allocated(names)) deallocate(names) - allocate(character(len=5) :: names(count)) - names = " " - if (count >= 1) names(1) = "red" - if (count >= 2) names(2) = "blue" - end subroutine replace_names -end module character_names +```python +view = result.to_numpy() +result.close() +result.shape # NOT OK: the descriptor has been released +view[0] # NOT OK: close() released the allocation ``` -The generated `.pyi` represents a fixed-length rank-one character array as -`String[n][::]`, where `n` is its fixed element length. A deferred-length -allocatable rank-one array uses the two-axis -handle spelling `Allocatable[String[:][:]]`, so the element width can come from -the native allocation at runtime: - -```python -from x2py.contracts import Allocatable, Returns, String +A view normally retains its handle, but an explicit `close()` releases a +returned allocatable result immediately. Finish using or copy all views before +closing the handle. -def make_names() -> Allocatable[String[:][:]]: ... +### Release Only Through The Owner -def replace_names( - names: Allocatable[String[:][:]] -) -> Returns[ - "names", Allocatable[String[:][:]] -]: ... +```python +h.deallocate() # may be unavailable for module or field storage ``` -Build the example: +Not every module or field handle lets Python resize or deallocate its storage. +An unavailable operation raises `NotImplementedError`. Use the module's or +parent object's functions to change that storage instead. -```bash -python3 -m x2py character_allocatables.f90 --out-dir build/character_allocatables -``` +--- -Pass an existing compatible allocatable character handle. The projected result -is that same handle, and extraction remains explicit: +## Scalar Allocatables -```python -import sys -sys.path.insert(0, "build/character_allocatables") -import character_allocatables - -api = character_allocatables.character_names -names = api.make_names() -assert api.replace_names(names) is names -assert names.to_numpy().dtype.itemsize == 5 -assert names.to_numpy().tolist() == [b"red ", b"blue "] -``` +Scalar allocatables appear as `T | None` values at the Python boundary rather +than `AllocatableArray` handles. An unallocated projected scalar result becomes +`None`. Scalar values do not expose persistent allocation state, `to_numpy()`, +or descriptor operations. -The `S5` itemsize comes from `allocate(character(len=5) :: names(count))`. -Plain NumPy arrays are not allocatable descriptors and are rejected for this -handle-typed parameter. When extracting character storage, x2py uses NumPy -bytes dtype `S`; Unicode (`U`) and object (`O`) arrays are not descriptor-handle -substitutes. - -Projected writable descriptor mutation requires a handle with persistent -wrapper-owned standard-descriptor storage, such as the owned result returned by -`make_names()`. A borrowed module handle can be passed to a read-only descriptor -argument through descriptor facts, but it cannot be passed to a projected -writable descriptor argument: native mutation of a call-local reconstructed -descriptor would not update the module handle reliably. - -## Module Handles And Views - -An allocatable module array is native-owned. Reading the Python attribute -returns an `Allocatable[T[...]]` handle, not `ndarray | None`. The module's -allocation routines create and release the storage. `h.to_numpy()` returns the -current live view or `None` according to the current allocation state. When the -Fortran declaration has `target`, the generated `.pyi` marks the handle with -`Aliased`. `Aliased` is not an ownership mode or an extraction selector; it -records native addressability for pointer association, raw-address, foreign-pointer, -and related policy. - -A plain allocatable module array has the same extraction contract as an -`Aliased` one. The wrapper uses the completed descriptor mechanism to inspect -the current allocation without copying. If the backend cannot expose a live -view through a supported mechanism, wrapper planning stops with a clear -diagnostic. Call `.copy()` explicitly when independent Python-owned storage is -required. - -A supported allocatable component belongs to its containing native derived-type -instance. The generated wrapper owns that native instance. The field exposes an -`Allocatable[T[...]]` handle that retains the parent wrapper. Any borrowed NumPy -view produced by `to_numpy()` retains the field handle, and the field handle -retains the parent wrapper. Assigning a replacement array directly to such a -field is rejected when native reallocation must go through an explicit method. - -Neither owner model can invalidate an already-created NumPy object safely after -native reallocation. Copy before any operation that may reallocate or -deallocate: +For an optional scalar allocatable argument, omission makes the argument absent. +Passing `None` makes it present but unallocated, while passing a value makes it +present with that value. See [Optional Arguments](optional-arguments.md). -```python -independent = view.copy() -``` +--- + +## Next -## Limitations - -- A wrapper-owned allocatable scalar derived result can be passed to a - compatible ordinary, target, allocatable, allocatable-target, input-only - pointer, or value dummy. The generated typed holder preserves the same Python - object and writes allocation changes back to that holder. -- An allocatable scalar derived module variable is a live nullable field proxy, - and it can satisfy a compatible allocatable dummy through a scoped - `move_alloc` transaction. The allocation is moved into an exact typed local - holder, passed to the native procedure, and restored exactly once; no object - address substitutes for the module descriptor and no descriptor crosses the - interoperable boundary. -- The complete ordinary, `TARGET`, `ALLOCATABLE`, `ALLOCATABLE,TARGET`, - `POINTER`, and `VALUE` compatibility rules—including empty state, - multi-argument cleanup, and deliberate errors—are in the later Wrapping - Derived Types guide under “Scalar Actuals And Native Dummies.” -- Mutable scalar deferred-length character storage is blocked. -- Plain derived module objects use typed module-specific member access; - `Aliased` is needed only for policies that require a direct native address. - Allocatable module-array handles use their standard descriptor path and have - the same live-view extraction contract with or without `Aliased`. -- Borrowed module handles do not provide the persistent direct descriptor - handoff required by projected writable descriptor arguments; use an owned - result handle for that operation. -- An edited `.pyi` cannot relabel a native-owned descriptor as Python-owned. - Use an implemented owned-result handle or copy an extracted NumPy value when - Python needs independent storage. -- `Annotated[T[...], Allocatable]` is no longer the active public spelling for - allocatable array descriptors; use `Allocatable[T[...]]`. - -## Evidence And Troubleshooting - -Owned results, module and component handles, unallocated state, extraction, and -owner retention are exercised by -[`test_allocatable_views.py`](../../../tests/wrapper/fortran/module_state/test_allocatable_views.py). -Allocatable descriptor `intent(inout)` mutation and same-handle projection are -exercised by -[`test_allocatable_replacement.py`](../../../tests/wrapper/fortran/module_state/test_allocatable_replacement.py). -Character descriptor generation in source and generated-`.pyi` modes is -exercised by -[`test_character_arguments.py`](../../../tests/wrapper/fortran/strings/test_character_arguments.py). - -A borrowed view can become stale after its native owner reallocates or -deallocates storage, so copy any data that needs an independent lifetime. -Memory Management expands this rule later, and Runtime Issues later covers -dtype, rank, and stale-storage symptoms. +- Continue with [Pointers](pointers.md) for association and target lifetime. +- Then read [Memory Management](memory-management.md) for the rules shared by + both kinds of handle. diff --git a/docs/user/guide/arrays.md b/docs/user/guide/arrays.md index 2e31a941f..29bab497e 100644 --- a/docs/user/guide/arrays.md +++ b/docs/user/guide/arrays.md @@ -1,21 +1,40 @@ --- title: Arrays +description: NumPy array shape, layout, strides, and validation in x2py audience: users -prerequisites: data types, wrapping functions -related: allocatables.md, pointers.md, wrapping-subroutines.md +prerequisites: data types +related: strings.md, allocatables.md, pointers.md, wrapping-subroutines.md status: maintained +publication: reviewed --- # Arrays -Ordinary numeric Fortran arrays cross the Python boundary as NumPy arrays. -Native allocatable and pointer array descriptors instead cross as -`Allocatable[T[...]]` and `Pointer[T[...]]` handles. In both cases, the -semantic contract records element dtype, rank, known extents, layout, allowed -strides, mutability, and storage category. The wrapper validates these facts -before the native call and does not silently repair an incompatible value. +x2py exposes Fortran arrays as **NumPy arrays**. +Each generated contract defines the accepted dtype, shape, layout, +writeability, and strides. x2py validates these rules before native code runs. -## Complete Array Example +This page starts with normal Fortran-order arrays. It then covers C-order +arrays, `COPY_F`, `Flat` storage, and strided views. + +Small `intent` note for this page: `intent(in)` reads an array, +`intent(inout)` mutates it, and `intent(out)` fills caller-provided storage. +Without `intent`, x2py conservatively uses the `intent(inout)` rule. The +subroutines page covers the full return rules. + +--- + +## Complete Example + +The page uses one module throughout. Its routines cover: + +- `scale_matrix`: a 2D array mutated in place +- `shift`: lower bounds with normal Python indexing +- `sum_columns`: the effect of storage order +- `sum_flat`: `values(*)` assumed-size storage +- `sum_flat_columns`: a checked prefix and flat final axis +- `scale_visible_rows`: stride-aware assumed-shape arrays +- `automatic_vector`: an array function result Create `arrays.f90`: @@ -23,6 +42,7 @@ Create `arrays.f90`: module array_ops implicit none contains + subroutine scale_matrix(rows, columns, values) integer(4), intent(in) :: rows, columns real(8), intent(inout) :: values(rows, columns) @@ -35,213 +55,412 @@ contains values = values + 1.0_8 end subroutine shift + subroutine sum_columns(size, values, result) + integer(4), intent(in) :: size + real(8), intent(in) :: values(size, size) + real(8), intent(out) :: result(size) + integer(4) :: column + + do column = 1, size + result(column) = sum(values(:, column)) + end do + end subroutine sum_columns + + function sum_flat(count, values) result(total) + integer(4), intent(in) :: count + real(8), intent(in) :: values(*) + real(8) :: total + integer(4) :: index + + total = 0.0_8 + do index = 1, count + total = total + values(index) + end do + end function sum_flat + + function sum_flat_columns(rows, columns, values) result(total) + integer(4), intent(in) :: rows, columns + real(8), intent(in) :: values(rows, *) + real(8) :: total + integer(4) :: row, column + + total = 0.0_8 + do column = 1, columns + do row = 1, rows + total = total + values(row, column) + end do + end do + end function sum_flat_columns + + subroutine scale_visible_rows(values, out) + real(8), intent(in) :: values(:, :) + real(8), intent(out) :: out(:, :) + + out = 3.0_8 * values + end subroutine scale_visible_rows + function automatic_vector(count) result(values) integer(4), intent(in) :: count real(8) :: values(count) - integer(4) :: index + integer(4) :: i - values = [(2.0_8 * index, index = 1, count)] + values = [(2.0_8 * i, i = 1, count)] end function automatic_vector + end module array_ops ``` -Inspecting `arrays.f90` prints these array contracts: +Build: + +```bash +python3 -m x2py arrays.f90 --out-dir build/arrays +``` + +--- + +## Python Usage ```python -from x2py.contracts import Addr, Arg, Float64, Int32, native_call +import sys +import numpy as np -@native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2)]) -def scale_matrix( - rows: Int32, - columns: Int32, - values: Float64[rows, columns] +sys.path.insert(0, "build/arrays") +from arrays.array_ops import * + +# Fortran-order matrix, mutated in place. +matrix = np.ones((2, 3), dtype=np.float64, order="F") +scale_matrix(np.int32(2), np.int32(3), matrix) +print(matrix) +# [[2. 2. 2.] +# [2. 2. 2.]] + +# The Fortran routine uses a lower bound, but Python sees an ordinary ndarray. +shifted = np.zeros(4, dtype=np.float64) +shift(np.int32(4), shifted) +print(shifted) # [1. 1. 1. 1.] + +# A flat argument can read any contiguous rank. +flat_matrix = np.array( + [ + [1.0, 2.0, 3.0], + [10.0, 20.0, 30.0], + ], + dtype=np.float64, + order="F", +) +total = sum_flat(np.int32(flat_matrix.size), flat_matrix) +print(total) # 66.0 + +# A flat final axis keeps the prefix and flattens the rest. +panels = np.asfortranarray( + np.arange(1, 25, dtype=np.float64).reshape((2, 3, 4), order="F") +) +panel_total = sum_flat_columns(np.int32(2), np.int32(12), panels) +print(panel_total) # 300.0 + +# Array function results come back as NumPy arrays. +vec = automatic_vector(np.int32(4)) +print(vec) # [2. 4. 6. 8.] +``` + +--- + +## What x2py Validates + +- Exact NumPy dtype (`np.float64`, `np.int32`, ...) +- Correct rank and shape (including expressions such as `rows, columns`) +- Required layout and contiguity +- Writeability for `intent(out)` or `intent(inout)` arrays +- Declared stride pattern for stride-aware contracts + +**x2py does not silently cast, copy, transpose, or convert layouts.** +A mismatch raises `TypeError` before native code runs. + +Contiguous elements have no gaps between them in the required layout. +Two arrays can print the same values but use different memory orders. + +--- + +## Layout: Fortran First + +Use Fortran (column-major) order for normal multidimensional arrays: + +```python +values = np.asfortranarray(data, dtype=np.float64) +# or +values = np.ones(shape, dtype=np.float64, order="F") +``` + +x2py rejects C-contiguous matrices for a Fortran-contiguous contract. +This gives the native routine the layout it expects. + +--- + +## C-order Arrays + +Start with the generated Fortran-oriented contract for `sum_columns`: + +```python +from x2py.contracts import Float64, Int32 + +def sum_columns( + size: Int32, + values: Float64[size, size], + result: Float64[size], ) -> None: ... +``` + +With that contract, pass a Fortran-order matrix. The routine sums columns: + +```python +values = np.array( + [ + [1.0, 2.0, 3.0], + [10.0, 20.0, 30.0], + [100.0, 200.0, 300.0], + ], + dtype=np.float64, + order="F", +) +result = np.empty(values.shape[0], dtype=np.float64) + +sum_columns(np.int32(values.shape[0]), values, result) +print(result) # [111. 222. 333.] +``` -@native_call([Addr(Arg(0)), Arg(1)]) -def shift( +### Option 1: Accept C-order Without Copy + +Edit the semantic `.pyi` and add `ORDER_C`: + +```python +from x2py.contracts import Annotated, Float64, Int32, ORDER_C + +def sum_columns( size: Int32, - values: Float64[size] + values: Annotated[Float64[size, size], ORDER_C], + result: Float64[size], ) -> None: ... +``` + +For the complete dtype, shape, layout, and optionality rules, see +[Edit Types, Shapes, Layout, and Optionality](../reference/pyi-contracts/calls-and-results.md#edit-types-shapes-layout-and-optionality). + +The call does not change. Pass a C-order array instead. +The same values now produce row sums: + +```python +values = np.array( + [ + [1.0, 2.0, 3.0], + [10.0, 20.0, 30.0], + [100.0, 200.0, 300.0], + ], + dtype=np.float64, + order="C", +) +result = np.empty(values.shape[0], dtype=np.float64) -@native_call([Addr(Arg(0))]) -def automatic_vector( - count: Int32 -) -> Float64[count]: ... +sum_columns(np.int32(values.shape[0]), values, result) +print(result) # [ 6. 60. 600.] ``` -Build it: +No transposition happens. Native code reads the existing storage directly. -```bash -python3 -m x2py arrays.f90 --out-dir build/arrays +| Python layout | Native grouping | Result | +|---------------|-----------------|--------| +| Fortran order | Python columns | `[111.0, 222.0, 333.0]` | +| C-order | Python rows | `[6.0, 60.0, 600.0]` | + +### Option 2: Copy C-order to Fortran Order + +Keep `ORDER_C` and add `COPY_F`: + +```python +from x2py.contracts import Annotated, COPY_F, Float64, Int32, ORDER_C + +def sum_columns( + size: Int32, + values: Annotated[Float64[size, size], ORDER_C, COPY_F], + result: Float64[size], +) -> None: ... ``` -Then assert in-place mutation, lower-bound handling, and an array result: +x2py copies the input to Fortran order before the native call. +The routine returns the original column sums: ```python -import sys +values = np.array( + [ + [1.0, 2.0, 3.0], + [10.0, 20.0, 30.0], + [100.0, 200.0, 300.0], + ], + dtype=np.float64, + order="C", +) +result = np.empty(values.shape[0], dtype=np.float64) -import numpy as np +sum_columns(np.int32(values.shape[0]), values, result) +print(result) # [111. 222. 333.] +``` -sys.path.insert(0, "build/arrays") -import arrays +`ORDER_C` validates the caller's layout. +`COPY_F` creates the Fortran-order temporary while preserving logical axes. +For output arrays, x2py copies the result back to the caller's C-order storage. -api = arrays.array_ops -matrix = np.ones((2, 3), dtype=np.float64, order="F") -api.scale_matrix(np.int32(2), np.int32(3), matrix) -np.testing.assert_array_equal(matrix, np.full((2, 3), 2.0, order="F")) +--- -shifted = np.zeros(4, dtype=np.float64) -api.shift(np.int32(4), shifted) -np.testing.assert_array_equal(shifted, np.ones(4, dtype=np.float64)) +## Flat Storage -result = api.automatic_vector(np.int32(4)) -np.testing.assert_array_equal( - result, - np.array([2.0, 4.0, 6.0, 8.0], dtype=np.float64), +An assumed-size dummy such as `values(*)` does not declare its extent. +A companion argument such as `count` tells the routine how much storage to read. + +The generated contract for `sum_flat` uses `Flat`: + +```python +from x2py.contracts import Flat, Float64, Int32 + +def sum_flat( + count: Int32, + values: Float64[Flat], +) -> Float64: ... +``` + +`Float64[Flat]` accepts a contiguous array with rank 1-15. +Native code sees its storage as rank one: + +```python +values = np.array( + [ + [1.0, 2.0, 3.0], + [10.0, 20.0, 30.0], + ], + dtype=np.float64, ) +total = sum_flat(np.int32(values.size), values) +print(total) # 66.0 ``` -## Read The Contract +Storage order controls flattening: -For the complete example, generated annotations record a rank-two matrix whose -extents depend on `rows` and `columns`, a rank-one lower-bound-aware array, and -an automatic rank-one result. Other supported contracts can use `Float64[:]`, -`Float64[3]`, `Float64[::]`, `Float64[Flat]`, or `Float64[...]`. +- Fortran-contiguous: column-major order +- C-contiguous: row-major order -The element name maps to an exact NumPy dtype; see [Data Types](data-types.md). -Dimension expressions constrain extents, not source lower/upper-bound -spellings. The native dimension `0:size-1` therefore becomes the public extent -`size`. Python remains zero-indexed even when the native declaration has -non-default lower bounds. +`Flat` rejects strided slices; dtype and contiguity rules still apply. -## Validation +`Flat` can appear at one edge of a multidimensional contract. +Other axes remain visible. x2py collapses the remaining Python axes into one +native extent. -Before entering native code, x2py checks: +For `real(8) :: values(rows, *)`, the generated contract is: -- exact NumPy dtype without implicit casts; -- native byte order and alignment; -- required rank and every expressible extent; -- contract-required contiguity, orientation, and stride pattern; and -- writeability for output and inout storage. +```python +from x2py.contracts import Flat, Float64, Int32 -Read-only arrays are valid for input-only arguments. x2py does not byte-swap, -realign, de-alias overlapping arrays, or make a hidden contiguous copy for an -ordinary in-place contract. A violation raises `TypeError` before native code -runs. +def sum_flat_columns( + rows: Int32, + columns: Int32, + values: Float64[rows, Flat], +) -> Float64: ... +``` + +This accepts a Fortran-contiguous array of rank 2 or higher. +Shape `(2, 3, 4)` becomes the native shape `(2, 12)`: + +```python +panels = np.asfortranarray( + np.arange(1, 25, dtype=np.float64).reshape((2, 3, 4), order="F") +) + +total = sum_flat_columns(np.int32(2), np.int32(12), panels) +print(total) # 300.0 +``` -## Layout And Strides +`Float64[:, Flat]` reads the leading extent from the array itself. -Use `numpy.asfortranarray` or `order="F"` for a multidimensional contract that -requires Fortran orientation, as shown by `matrix` in the complete example. +For C-order buffers, put `Flat` first: +`Annotated[Float64[Flat, columns], ORDER_C]`. +This checks the final Python axis and flattens the leading axes. -Layout annotations describe a deliberate non-default storage representation; -they do not request an automatic conversion. Plain multidimensional -Fortran-facing arrays already pass Fortran-contiguous storage with their logical -axes unchanged. `ORDER_C` passes the same C-contiguous data address without -copying and constructs the Fortran bridge view with reversed axes. For example, -a C-order Python shape `(2, 3)` is a Fortran bridge shape `(3, 2)` over the -same six elements. Use `ORDER_C` only when the native operation intentionally -accepts that transposed storage view. +--- + +## Strided Views -Add `COPY_F` when Python should accept C-contiguous storage but native Fortran -must observe the same logical axes in Fortran order: +Use `::` for an assumed-shape axis that supports positive strides: ```python -values: Annotated[Float64[:, :], ORDER_C, COPY_F] -``` - -The binding owns this complete representation lifecycle. It creates an -F-contiguous NumPy temporary before the call, passes that ordinary F-order -buffer through the unchanged bridge path, copies values back into the original -C-order array after the call, and releases the temporary. Projected results -return the original C-order object. Native `intent(in)` remains a property of -the native procedure call; neither the semantic `.pyi` nor the bridge temporary -needs a separate direction annotation for `COPY_F`. The bridge performs neither -half of this argument conversion. - -Rank-one contiguous arrays can satisfy their documented contiguous contract -without a meaningful row/column distinction. Legacy fixed-form array contracts -are contiguous-only. A modern Fortran dummy is stride-aware only when its -generated contract explicitly permits strides. Inspect `.pyi` output instead -of assuming every slice is accepted. - -Zero-sized dimensions are supported when dtype, rank, writeability, and known -extent rules still match. Degenerate strides on axes with no addressable -movement do not by themselves make the layout invalid. - -## Inputs, Outputs, And Inout Arrays - -- Input arrays remain caller-owned and may be read-only. -- Ordinary output arrays remain visible; the caller allocates writable storage. -- Inout arrays remain visible and mutate in place. -- Ordinary array function results are Python-owned NumPy copies. -- Non-optional hidden allocatable outputs are wrapper-owned - `Allocatable[T[...]]` handles. Unallocated state remains inside the present - handle. -- Optional allocatable outputs remain visible so the caller controls native - `present(...)`. -- Pointer-array handle results remain blocked until owner storage, target - lifetime, descriptor extraction, and destroy behavior are implemented. -- Allocatable and pointer module variables and supported components are handle - objects. NumPy views are obtained explicitly with `to_numpy()` and require - lifetime care after native descriptor changes. - -Caller-provided output storage is demonstrated with complete source in -[Wrapping Subroutines](wrapping-subroutines.md#complete-output-example). - -## Assumed Size And Lower Bounds - -`Float64[Flat]` records supported flat assumed-size storage. Python supplies -the actual allocation, and the caller must ensure it is large enough for the -native routine. x2py validates explicit dimensions it can express but cannot -infer an omitted final extent from an unrelated argument. - -Non-default native lower bounds change how extents are computed internally, -but they do not alter Python indexing. Even if a Fortran argument is declared -with custom bounds like values(3:size+2), -the wrapped NumPy array in Python remains strictly zero-indexed. - -## Assumed Rank - -Supported numeric assumed-rank arguments accept NumPy ranks 1 through 15 through -a generated native rank dispatcher. Each assumed-rank argument dispatches at -its own runtime rank. Rank-zero values and ranks above 15 are rejected. - -## Array Results - -Supported ordinary numeric and fixed-width character array results preserve -dtype, rank, and Fortran-oriented multidimensional data as NumPy arrays. -Character arrays use NumPy bytes dtypes such as `S5`, where the dtype itemsize -is the Fortran element length. Ordinary zero-sized results remain zero-sized -arrays. - -An allocatable array result instead returns an `Allocatable[T[...]]` handle. -An allocated zero-sized result is a handle whose shape contains a zero extent; -an unallocated result is a present handle with `allocated is False` and -`to_numpy() is None`. Pointer-array results remain blocked until their owner -and target lifetime can be represented safely. - -## Unsupported Forms - -- assumed type `type(*)`; -- character arrays that cannot be represented as fixed-width NumPy bytes - storage; -- arrays of derived types; -- pointer-array results and reassociation without completed owner, lifetime, - and operation policy; and -- any kind or rank whose portable NumPy storage contract cannot be proved. - -## Evidence And Troubleshooting - -Validation and layout behavior are exercised by -[`test_array_contracts.py`](../../../tests/wrapper/fortran/arrays/test_array_contracts.py), -assumed-rank behavior by -[`test_assumed_rank_arrays.py`](../../../tests/wrapper/fortran/arrays/test_assumed_rank_arrays.py), -multidimensional behavior by -[`test_multidimensional_arrays.py`](../../../tests/wrapper/fortran/arrays/test_multidimensional_arrays.py), -and results by -[`test_array_results.py`](../../../tests/wrapper/fortran/arrays/test_array_results.py). - -When a call fails, compare `value.dtype`, `value.shape`, `value.strides`, -`value.flags`, and writeability with the generated annotation. Runtime Issues -later covers calls that still fail when those properties match. +from x2py.contracts import Float64, Returns + +def scale_visible_rows( + values: Float64[::, ::], + out: Float64[::, ::], +) -> Returns["out", Float64[::, ::]]: ... +``` + +Here, only the first Python axis is sliced: + +```python +base = np.asfortranarray( + np.arange(1, 25, dtype=np.float64).reshape((8, 3), order="F") +) + +visible_rows = base[::2, :] # shape (4, 3) +out_storage = np.zeros((8, 3), dtype=np.float64, order="F") +out = out_storage[::2, :] # matching strided output + +scale_visible_rows(visible_rows, out) +print(out) +# [[ 3. 27. 51.] +# [ 9. 33. 57.] +# [15. 39. 63.] +# [21. 45. 69.]] +``` + +x2py passes the base address, extents, and positive element strides. Reversed +slices, broadcasted views, and C-order strided matrices are rejected for this +Fortran-oriented contract. Strides are not an order workaround. + +--- + +## Mutation and Results + +| Fortran intent / result | Python behavior | +|-----------------------------|----------------------------------------------| +| `intent(in)` array | NumPy array read by native code | +| `intent(inout)` array | Mutated in place; no Python return | +| `intent(out)` array | Filled in place; no Python return | +| Array without `intent` | Mutated in place; no Python return | +| Function returning array | New NumPy array result | + +--- + +## Common Array Contracts + +`T` means a concrete primitive contract such as `Float64`, `Int32`, or +`Complex128`. The examples above use `Float64` because `arrays.f90` declares +`real(8)`. + +Use this list when reading or editing a generated `.pyi` contract: + +- `T[:]`: 1D contiguous +- `T[:, :]`: 2D Fortran-contiguous +- `Annotated[T[:, :], ORDER_C]`: 2D C-contiguous +- `Annotated[T[:, :], ORDER_C, COPY_F]`: C-order input, Fortran temporary +- `T[::]`: 1D strided +- `T[::, ::]`: 2D stride-aware +- `T[rows, columns]`: shape depends on other arguments +- `T[Flat]`: any contiguous rank, flattened to native rank one +- `T[rows, Flat]`: Fortran-contiguous; checked prefix, remaining axes flattened +- `Annotated[T[Flat, columns], ORDER_C]`: C-contiguous; checked suffix, + leading axes flattened +- `T[...]`: assumed-rank, currently rank 1-15 + +--- + +## Next + +- Continue with [Strings](strings.md) for fixed-width NumPy byte arrays. +- Then read [Wrapping Functions](wrapping-functions.md) and + [Wrapping Subroutines](wrapping-subroutines.md). +- [Allocatables](allocatables.md) and [Pointers](pointers.md) for native + allocation control. diff --git a/docs/user/guide/building-shared-library.md b/docs/user/guide/building-shared-library.md new file mode 100644 index 000000000..a7d065646 --- /dev/null +++ b/docs/user/guide/building-shared-library.md @@ -0,0 +1,95 @@ +--- +title: Building the Shared Library +description: How to build and import a Python extension shared library with x2py +audience: users +prerequisites: common beginner workflow +related: error-handling.md +status: maintained +publication: reviewed +--- + +# Building the Shared Library + +x2py turns Fortran source into a Python extension module. The final module is a +native shared library that Python imports directly. + +## Build + +Run x2py on the source file and choose a build directory: + +```bash +python3 -m x2py src/scale.f90 --out-dir build/scale +``` + +The shared library and the generated build files are written to +`build/scale`. By default, the module name comes from the source filename. Use +`--out` to choose it explicitly: + +```bash +python3 -m x2py src/scale.f90 --out scale_api --out-dir build/scale_api +``` + +## Import + +Add the build directory to Python's search path, then import the module by its +name: + +```python +import sys + +sys.path.insert(0, "build/scale_api") + +import scale_api +``` + +The shared-library filename includes a platform- and Python-specific suffix, +but the import uses only the module name. + +## Multiple Source Files + +Pass source files in the order required by the compiler. Choosing the module +name explicitly keeps the result clear: + +```bash +python3 -m x2py src/types.f90 src/solver.f90 \ + --out solver \ + --out-dir build/solver +``` + +x2py preserves the given order. It does not discover source dependencies or +external libraries automatically. + +## Use a Makefile + +To inspect or customize the build commands, generate a Makefile without +compiling: + +```bash +python3 -m x2py generate --makefile src/scale.f90 --out-dir build/scale +``` + +Edit `Makefile.x2py` before running `make` when customization is needed. Its +most useful settings are near the top: + +| Setting | What it changes | +| --- | --- | +| `FC` | Fortran compiler | +| `X2PY_LD` | Command that creates the shared library | +| `X2PY_FFLAGS` | Extra Fortran compiler flags | +| `X2PY_LDFLAGS` | Extra linker flags | + +The build targets and commands follow these settings and normally do not need +editing. Then build the shared library: + +```bash +make -f build/scale/Makefile.x2py +``` + +You can pass the same ordered list of source files used in the previous +example. This workflow requires GNU Make. + +## Compatibility + +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. diff --git a/docs/user/guide/callbacks.md b/docs/user/guide/callbacks.md index e0ea7b655..0e262dcca 100644 --- a/docs/user/guide/callbacks.md +++ b/docs/user/guide/callbacks.md @@ -1,43 +1,107 @@ --- title: Callbacks +description: How to pass Python callables to Fortran as callbacks with x2py audience: advanced users -prerequisites: wrapping functions, error handling, data types -related: error-handling.md, memory-management.md, ../reference/semantic-pyi-format.md +prerequisites: wrapping functions, data types +related: error-handling.md, memory-management.md status: maintained +publication: reviewed --- # Callbacks -x2py supports Python callbacks invoked immediately during one wrapped native -call. A semantic `.pyi` declares each native callback shape once as a named -prototype, then callback-taking procedures refer to that prototype by name. +Callbacks let wrapped Fortran call a Python function while an x2py call is +running. They are useful for objective functions, progress hooks, custom +transforms, and small pieces of user-defined numerical logic. -This is the opposite direction from a normal `.pyi` function signature. A -normal function signature describes how Python calls the wrapper; x2py may -lower that call into any compatible native bridge shape. A `@prototype` -declaration describes how native code calls the callback adapter, so argument -order, value/reference passing, rank, shape, character length, and result shape -are part of the callback contract. Native callback direction is deliberately -not repeated in semantic `.pyi`. +Declare the callback shape once with `@prototype`, then use that prototype name +as the type of the procedure argument that accepts the callback. -## Complete Callback Example +--- + +## The Short Version + +| Native callback argument | Prototype spelling | Python callable receives | +| --- | --- | --- | +| Primitive scalar dummy declared with Fortran `value` | `value: Float64` | Independent `np.float64` scalar | +| Primitive scalar reference dummy | `value: Addr(Float64)` | Independent `np.float64` scalar | +| Array reference dummy | `values: Float64[n]` | NumPy array view | +| Fixed-length string reference dummy | `label: String[8]` | Writable rank-zero bytes storage | +| Derived-type reference dummy | `point: point_t` | Generated wrapper object | + +!!! tip "Rule of thumb" + Bare primitive callback arguments are native values: + `value: Float64`, `count: Int32`, and so on. + + Use `Addr(T)` only when a primitive callback dummy is passed by reference. + +Arrays, strings, and derived-type callback arguments already use native storage +or wrapper objects, so they do not need `Addr(...)` for ordinary reference +dummies. Use `Value(point_t)` only for a supported derived-type callback dummy +declared with the Fortran `value` attribute. + +--- + +## What The Callable Sees + +Two declarations can appear around callbacks, and they control different calls: + +| Declaration | Controls | +| --- | --- | +| `@prototype` | How Fortran calls the callback adapter. | +| `@native_call(...)` | How Python arguments are passed into the outer wrapped function. | + +For example, the wrapped function may need `@native_call([Addr(Arg(1))])` +because its `value` argument is passed to Fortran by reference: + +```python +from x2py.contracts import Addr, Arg, Float64, native_call, prototype + +@prototype +def scalar_callback(value: Addr(Float64)) -> Float64: ... + +@native_call([Arg(0), Addr(Arg(1))]) +def apply(callback: scalar_callback, value: Float64) -> Float64: ... +``` + +The two `Addr(...)` markers belong to different boundaries. The one inside +`@prototype` describes how Fortran calls the callback. The one inside +`@native_call(...)` describes how Python calls the wrapped function. + +At runtime, pass an ordinary Python callable: + +```python +import numpy as np + +api.apply(lambda value: np.float64(3.0 * value), np.float64(2.5)) +``` + +The lambda receives converted Python objects, not `Addr(...)` markers. + +--- + +## Small Example Create `callbacks.f90`: ```fortran module callbacks_api implicit none + abstract interface real(8) function scalar_callback(value) result(output) real(8), intent(in) :: value end function scalar_callback end interface + contains + real(8) function apply(callback, value) result(output) procedure(scalar_callback) :: callback real(8), intent(in) :: value output = callback(value) end function apply + end module callbacks_api ``` @@ -47,173 +111,107 @@ Build it: python3 -m x2py callbacks.f90 --out-dir build/callbacks ``` -Then pass a Python callable and assert the converted result: +**Python usage:** ```python import sys + import numpy as np sys.path.insert(0, "build/callbacks") -import callbacks +from callbacks.callbacks_api import apply -api = callbacks.callbacks_api -result = api.apply(lambda value: np.float64(3.0 * value), np.float64(2.5)) -assert result == np.float64(7.5) +result = apply( + lambda value: np.float64(3.0 * value), + np.float64(2.5) +) +print(result) # 7.5 ``` -## Lifetime - -The generated wrapper keeps a strong reference to the Python callable only -until the wrapped call returns. Native code must not store the callback or call -it later. Nested callback-taking calls on the same entering Python thread are -supported. +--- -Primitive scalar callback arguments are materialized as independent NumPy -scalar values, so retaining them is safe. Temporary NumPy array views and -borrowed derived wrappers are valid only for that callback invocation. -Retaining either afterward is unsupported unless the value is explicitly -copied. +## Choosing The Prototype Spelling -## Callback Values +Prototype declarations describe the **native callback signature**. They are not +Python runtime functions and they are not exported from the generated module. -Callback arguments use ordinary semantic types. Native code passes them by -reference unless the prototype applies the one ABI override, `Value(T)`: +For ordinary scalar and array callback arguments, use the same contract spellings +you use elsewhere: ```python -from x2py.contracts import Float64, Int32, Value, prototype +from x2py.contracts import Addr, Float64, Int32, prototype @prototype def update_values( - count: Int32, - scale: Value(Float64), - values: Float64[count], + count: Addr(Int32), + scale: Float64, + values: Float64[count] ) -> None: ... - -def apply_update(callback: update_values, count: Int32) -> None: ... ``` -The spellings mean: +Here `count` is a primitive reference dummy, while `scale` is a primitive value +dummy. Python receives both as NumPy scalar values. -| Callback spelling | Fortran callback dummy | Python callback object | -| --- | --- | --- | -| `Int32` | scalar reference dummy | owned `np.int32` scalar value | -| `Value(Float64)` | scalar `value` dummy | owned `np.float64` scalar value | -| `Float64[n]` | array reference dummy | writable NumPy array view | -| `point_t` | derived reference dummy | generated wrapper object | -| `Value(point_t)` | derived `value` dummy | generated wrapper over the call-local value copy | - -Array callback arguments require exact dtype, rank, declared shape, alignment, -and required Fortran contiguity. Derived values use the generated wrapper class. -Reference arrays, characters, and derived objects are exposed permissively and -written back before the callback adapter returns. Primitive scalar arguments -are immutable NumPy scalar values even when their native ABI uses a reference. -Scalar reference writeback is unsupported: model a scalar value delivered back -to native code as the declared callback result. A `Value(...)` argument changes -only the native ABI to Fortran `value`; it is also received as an independent -NumPy scalar. - -## Character Callback Arguments - -Fixed-length character callback arguments use their ordinary semantic spelling: +For scalar arguments, choose the spelling from the Fortran callback dummy: -```python -from x2py.contracts import String, prototype +| Fortran callback dummy | Matching prototype | +| --- | --- | +| `real(8), intent(in) :: value` | `value: Addr(Float64)` | +| `real(8), value :: value` | `value: Float64` | -@prototype -def label_callback(label: String[8]) -> None: ... -``` +Both forms call Python with an independent `np.float64` scalar. The difference +is the native calling convention x2py must match. -Because reference callbacks are permissive, the callback receives mutable -rank-zero NumPy bytes storage with shape `()` and dtype `S8`. The Python -callback reads or writes through that storage: +`Value(T)` is only for supported non-primitive scalar value dummies, such as a +derived-type callback dummy declared with the Fortran `value` attribute. -```python -def rewrite_label(label): - label[...] = b"done " -``` +--- + +## Key Rules + +- The callback is only valid **during** the wrapped native call. +- Native code must not store the callback for later use. +- Return the exact NumPy scalar type when x2py expects a scalar callback result. +- Primitive scalar callback arguments arrive as independent NumPy scalar values, + whether the native dummy is `value` or reference. +- Primitive scalar reference writeback is unsupported; return a scalar result + instead. +- Arrays and derived-type arguments can expose live native state; copy data you + need after the wrapped call returns. + +--- + +## Important Limitations + +Supported callbacks are immediate, same-thread adapters. The native routine may +call the Python callable while the wrapped call is active, and x2py tears down +the callback context when that wrapped call returns. + +The current callback contract does not support: + +- Stored callbacks, persistent callbacks, procedure pointers, or callbacks + invoked after the wrapped call returns. Pass the callable into each wrapped + call that needs it. +- Optional callback procedure arguments. Expose a separate native entry point + for the no-callback path, or require the callback argument. +- Optional arguments inside a `@prototype`. Pass an explicit value, sentinel, or + presence flag instead. +- Allocatable, pointer, polymorphic, or assumed-type callback arguments and + results. Use plain scalars, fixed-shape primitive arrays, fixed-length strings, + or supported scalar derived types. +- Arrays passed by Fortran `value`, arrays of derived values, and array callback + results without a complete fixed shape. Pass arrays by reference and give array + results an exact primitive shape. +- Variable-length callback strings. Use a fixed positive `String[n]` length. +- Callback execution on a different Python thread. The callback must run on the + same thread that entered the wrapper. + +Callback exceptions and invalid return conversions are fatal at the callback +boundary: x2py prints the Python traceback and aborts the host process. + +--- + +## Next -The semantic callback annotation remains `String[n]`; the callback adapter -chooses mutable scalar storage without encoding native direction in `.pyi`. - -A non-callable argument raises `TypeError` before native execution. - -## Threads And The GIL - -The callback trampoline acquires the Python GIL for the callback and releases -the matching state afterward. The callback must execute on the same Python -thread that entered the wrapped routine. Cross-thread native invocation is not -supported. - -Callback-taking calls keep the GIL policy required by the callback bridge. -Do not use callback execution as synchronization for unrelated native state. - -## Callback Failures - -A callback exception, invalid callback result, or cross-thread invocation -cannot be safely unwound through arbitrary native frames. The wrapper prints -the Python traceback and aborts the host process. It never invents a fallback -return value and continues native execution. - -Run untrusted callback behavior in a subprocess if the host application must -survive such failures. - -## Unsupported Forms - -- stored callback registration and unregistration; -- callbacks invoked after the wrapped call; -- optional dummy procedure arguments; -- procedure pointers and null procedure pointers; -- asynchronous or cross-thread callback invocation; and -- persistent callback ownership during object or library teardown. - -## Adapter Policy - -The post-IR policy stage completes how the adapter crosses the -Fortran-to-Python-to-Fortran boundary before wrapper generation begins. It -records value versus reference ABI, permissive reference writeback, array shape, -fixed character length, exact derived type identity, call-scoped context -lifetime, entering-thread enforcement, GIL entry, cleanup, and the fatal error -action. The Python binding and Fortran bridge only lower those completed actions. - -A `@prototype` declaration is a compile-time semantic declaration, not a -Python runtime export. Its declaration order, value/reference transport, -storage shape, character length, and result type define the callback transport -contract. The Python callable may adapt argument names itself, so prototypes do -not use normal wrapper projection such as `@native_call`. - -Post-IR policy selects the weakest correct native declaration from the complete -prototype. Classic scalar, explicit-shape, and assumed-size signatures use an -implicit external declaration. Signatures with optional or descriptor -arguments, polymorphism, or non-scalar/descriptor results use the -named native prototype through an explicit declaration. That path imports the -real interface, including direction facts deliberately omitted from semantic -`.pyi`, so its compiler module file must be available. `Value(...)` alone does -not force the explicit path when a typed external declaration is sufficient. - -Future work may add user-selectable policy for: - -- borrowed-view, detached-copy, and zero-copy choices; -- dtype conversion, overflow checks, and result coercion; -- fixed-length character encoding, padding, truncation, and writeback rules; -- ownership and lifetime rules for arrays, scalar storage, derived wrappers, - and temporary callback values; -- writeback protocols for values that cannot be mutated - directly by the Python object currently passed to the callback; and -- callback-specific error/result policy beyond the current fatal native - callback boundary. - -These choices are not currently user-selectable; unsupported forms remain -blocked instead of selecting a different backend behavior. - -## Evidence And Troubleshooting - -Scalar lifetime, nesting, GIL behavior, invalid callbacks, and fatal exception -behavior are exercised by -[`test_scalar_callbacks.py`](../../../tests/wrapper/fortran/callbacks/test_scalar_callbacks.py). -Array conversion is exercised by -[`test_array_callbacks.py`](../../../tests/wrapper/fortran/callbacks/test_array_callbacks.py) -and derived values by -[`test_derived_callbacks.py`](../../../tests/wrapper/fortran/callbacks/test_derived_callbacks.py). - -Error Handling later distinguishes ordinary wrapper exceptions from fatal -callback-boundary failures. +- Continue with [Enumerations](enumerations.md). +- Review [Error Handling](error-handling.md) when callback failure behavior matters. diff --git a/docs/user/guide/data-types.md b/docs/user/guide/data-types.md index c56e59d71..61cbe4e5d 100644 --- a/docs/user/guide/data-types.md +++ b/docs/user/guide/data-types.md @@ -1,27 +1,31 @@ --- title: Data Types +description: How x2py maps Fortran types to Python, NumPy dtypes, and semantic contracts audience: users prerequisites: common beginner workflow -related: arrays.md, wrapping-derived-types.md, ../reference/semantic-pyi-format.md +related: arrays.md, strings.md, wrapping-derived-types.md status: maintained +publication: reviewed --- # Data Types -x2py resolves native Fortran types into explicit semantic types before wrapper -generation. The generated `.pyi` contract shows the resolved type, and Python -callers use the matching NumPy dtype or generated wrapper class. Do not infer a -mapping from a Fortran kind number alone: kind values are compiler-dependent, -so wrapper builds probe the selected compiler. +x2py resolves Fortran types using the selected compiler, then generates an +explicit semantic contract (`.pyi`). Inspect that contract before calling the +wrapper because kind numbers are compiler-dependent. -The first example uses a small file named `numeric_types.f90`. Create it with -the complete source below: +--- + +## Example + +Create `numeric_types.f90`: ```fortran module numeric_types use iso_fortran_env, only: int32, real64 implicit none contains + integer(int32) function add_one(value) result(output) integer(int32), intent(in) :: value output = value + 1 @@ -41,227 +45,117 @@ contains logical(kind=1), intent(in) :: flag output = .not. flag end function invert + end module numeric_types ``` -It is highly recommended to generate the type contract first for inspection: +Generate the contract: ```bash python3 -m x2py generate --pyi numeric_types.f90 ``` -Build the wrapper with: +Build it: ```bash python3 -m x2py numeric_types.f90 --out-dir build/numeric-types ``` -Here is how to call the generated module from Python: +--- + +## Calling from Python ```python import sys + import numpy as np sys.path.insert(0, "build/numeric-types") -import 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(bool(invert(True))) # False +``` + +--- + +## Scalar Type Mapping + +| Fortran Type | Semantic Type | Preferred Python / NumPy Type | +|-------------------------------|-----------------|------------------------------------| +| `integer(1)` | `Int8` | `np.int8` | +| `integer(2)` | `Int16` | `np.int16` | +| `integer(4)` / `int32` | `Int32` | `np.int32` | +| `integer(8)` / `int64` | `Int64` | `np.int64` | +| `real(4)` | `Float32` | `np.float32` | +| `real(8)` / `real64` | `Float64` | `np.float64` | +| `complex(4)` | `Complex64` | `np.complex64` | +| `complex(8)` | `Complex128` | `np.complex128` | +| `logical` | `Bool` | `bool` or `np.bool_` | +| `character` | `String` / `String[n]` | `str` or fixed `np.bytes_` | +| Derived Type | Generated Class | Instance of that class | + +--- + +## Runtime Default Constructors -api = numeric_types.numeric_types +Concrete primitive contracts can create their matching NumPy scalar with its +zero value: -assert api.add_one(np.int32(4)) == np.int32(5) -assert api.double(np.float64(1.5)) == np.float64(3.0) -assert api.conjugate_value(np.complex128(1.0 + 2.0j)) == np.complex128(1.0 - 2.0j) -assert bool(api.invert(True)) is False +```python +import x2py.contracts as xc + +count = xc.Int32() # np.int32(0) +weight = xc.Float64() # np.float64(0.0) +flag = xc.Bool() # np.bool_(False) ``` -The tables below summarize the currently verified Fortran-to-Python type mappings. - -## Scalar Mapping - -| Fortran storage resolved by the compiler | Semantic `.pyi` type | Python input to prefer | NumPy array dtype | -| --- | --- | --- | --- | -| signed integer, 8 bits | `Int8` | `numpy.int8` | `numpy.int8` | -| signed integer, 16 bits | `Int16` | `numpy.int16` | `numpy.int16` | -| signed integer, 32 bits | `Int32` | `numpy.int32` | `numpy.int32` | -| signed integer, 64 bits | `Int64` | `numpy.int64` | `numpy.int64` | -| real, 32 bits | `Float32` | `numpy.float32` | `numpy.float32` | -| real, 64 bits | `Float64` | `numpy.float64` | `numpy.float64` | -| complex, 64 total bits | `Complex64` | `numpy.complex64` | `numpy.complex64` | -| complex, 128 total bits | `Complex128` | `numpy.complex128` | `numpy.complex128` | -| supported logical storage | `Bool` | `bool` or `numpy.bool_` as documented by the generated contract | `numpy.bool_` | -| scalar character | `String` or `String[n]` | `str` | fixed-width NumPy bytes, such as `S8` | -| derived type | generated class name | instance of that generated class | arrays of derived types are unsupported | -| dummy procedure | named `@prototype` reference | Python callable matching the named prototype | not applicable | - -## Source Kind Names - -Source spellings such as default `integer`, `integer(8)`, -`integer(kind=int64)`, or a selected-kind expression do not define a portable -NumPy dtype by themselves. x2py resolves the expression with the selected -compiler and then emits `Int8`, `Int16`, `Int32`, or `Int64`. Real, complex, -and logical kinds follow the same rule. - -Common `iso_fortran_env` and compiler-supported kind expressions are resolved -during the build. Inspect `--pyi` output whenever compiler flags, the compiler, -or target architecture changes. x2py blocks a mapping that cannot preserve the -native storage instead of silently narrowing it. - -## Scalar Values And Native Storage - -A bare numeric semantic type is a Python-visible value. `T` is a value -contract, not a copy of Fortran `intent`. Its default native boundary is -pass-by-value. -`Addr(Arg(...))` in `@native_call` means x2py converts that Python-visible value -to call-local native scalar storage and passes the storage address to native -code. - -Use `T[()]` when the Python API itself exposes safe caller-provided scalar -storage as a rank-0 NumPy array. Use `Addr(T)` only when the caller passes a raw -address such as `array.ctypes.data`. For raw array addresses such as -`Addr(Float64[n])`, every extent must be a fixed literal or a visible argument; -the address value itself does not carry shape. x2py does not reject zero or -negative integer addresses or validate that raw-address extents are positive. -It reports integer-to-pointer overflow, but otherwise the caller is responsible -for supplying a live address and a valid pointee shape before native code uses -either value. - -Arrays and strings are storage-like at the native boundary. `Float64[n]` already -passes the NumPy data address, and `String[8]` already passes the address of -x2py's temporary fixed-width character storage. Do not add `Addr(Arg(...))` for -those arguments. - -These annotations describe the native contract, not implicit Python -conversions. Use exact NumPy scalar types where the generated call requires -them. Scalar `intent(out)` values are normally hidden and returned as values; -caller-provided writable scalar storage is an explicit advanced `.pyi` -contract, not the default source-generated interface. - -Internally x2py treats the Python-extension extraction step and the native handoff step -as two different completed policies: - -| `.pyi` contract shape | Python barrier | Native barrier | -| --- | --- | --- | -| `T` | read a Python scalar value | pass the value or call-local scalar storage selected by policy | -| `@native_call([Addr(Arg(i))])` on bare `T` | read a Python scalar value | pass the address of call-local scalar storage | -| `T[()]` | validate rank-0 NumPy scalar storage | pass the caller-provided storage address | -| `T[:]`, `T[n]`, `T[:, :]` | validate NumPy array storage | pass the packed array descriptor/data contract | -| `String[n]` | read a Python `str` | pass x2py's call-local fixed-width character storage | -| `String[n][:]`, `String[:][:]` | validate NumPy bytes array storage | pass the character array descriptor/data contract | -| `Addr(T)` or `Addr(T[n])` | read a raw address value | pass that raw address unchanged | - -Those barrier actions are completed after semantic IR is loaded and before -wrapper lowering. Generated bridges and bindings dispatch from the completed -actions; they do not reinterpret datatype, `intent`, addressability, or local -memory details to choose a different behavior. - -## Arrays - -Array annotations combine an element dtype with rank and shape: - -| Semantic type | Python value | -| --- | --- | -| `Float64[:]` | rank-one dense array with `dtype=numpy.float64` | -| `Float64[:, :]` | rank-two dense array with `dtype=numpy.float64`; the wrapper requires the documented contiguous layout | -| `Float64[::]` | rank-one array whose axis may be a strided NumPy view | -| `Float64[::, ::]` | rank-two array whose axes may be strided NumPy views | -| `Float64[3, 4]` | exact shape `(3, 4)` | -| `Float64[n, :]` | first extent constrained by semantic constant or argument `n` | -| `Float64[Flat]` | flat contiguous storage for a supported assumed-size contract | -| `Float64[...]` | supported assumed-rank numeric storage, ranks 1 through 15 | - -Plain `:` records a dense axis. `::` records an axis where the contract allows -runtime strides; x2py reads that exact slice spelling from the `.pyi` source -before Python AST normalization. Multidimensional dense arrays in a -Fortran-facing contract use Fortran orientation by default; generated contracts -omit `ORDER_F`. Explicit order metadata appears only when a contract -deliberately requests a non-default representation. - -The wrapper validates exact dtype, native byte order, rank, known extents, -alignment, layout, and writeability before entering native code. It does not -silently cast, byte-swap, realign, or repair an incompatible array. Arrays later -expands layout, stride, output-storage, and zero-size rules. - -## Strings - -Scalar Fortran character values use Python `str`. `String[8]` records fixed -native length eight; plain `String` records assumed, deferred, or otherwise -non-fixed scalar length. The first `String[...]` subscription is character -length, not array shape. Bare `String[:]` is invalid; write `String` for a -scalar non-fixed string or add a second shape axis for an array. - -| Contract | Meaning | -| --- | --- | -| `String` | scalar string with unknown, assumed, deferred, or otherwise non-fixed length | -| `String[8]` | scalar fixed-length string with length 8 | -| `String[:][:]` | rank-one array of strings whose element length is not fixed in the public contract | -| `String[8][:]` | rank-one array of fixed-length strings | -| `String[8][()]` | mutable scalar fixed-length string storage | - -For `String[8]`, the encoded Python string length must be exactly eight; pass -`"aa "` when the native argument is an eight-character value. - -Returned strings are Python-owned copies. Fixed-length results retain trailing -Fortran blanks. For mutable scalar character input/output, x2py creates -call-local character storage, passes its address to native code, and returns a -replacement only when the `.pyi` signature includes `Returns["name", String[n]]`. -Without that return contract, native mutation is discarded because the original -Python `str` is immutable. - -Mutable scalar character storage uses a rank-zero fixed-width NumPy bytes array: +This applies to Boolean, fixed-width numeric, and `SizeT` contracts. It does +not apply to target-resolved `Int`, `UInt`, or `CEnum`, or to `Byte`, `Char`, +`String`, and `Void`, because those names do not define one portable NumPy +scalar representation by themselves. A scalar being constructible does not +mean every wrapper backend supports that native type; the generated contract +and feature matrix remain authoritative. + +Array annotations are not array factories: `Float64[:]()` is invalid. Create +ordinary arrays with NumPy. Allocatable and pointer descriptor handles have +their own default constructors, described in their later user-guide pages. + +## Important Rules + +- Always use **exact NumPy scalar dtypes** (`np.float64`, `np.int32`, etc.). +- Plain Python `float` / `int` will raise `TypeError` for scalar arguments. +- x2py resolves kinds using the selected compiler (`gfortran` by default). +- Inspect the contract with `generate --pyi` whenever you change compiler flags or architecture. + +--- + +## Values And Native Storage + +A bare primitive type represents a Python-visible scalar: ```python -label = np.array("abcdefgh", dtype="S8") +def double(value: Float64) -> Float64: ... ``` -The matching `.pyi` annotation is `String[8][()]`. Native mutation writes back -into the NumPy array, and Python reads the scalar storage as bytes, for example -`label[()]`. - -Character arrays use fixed-width NumPy bytes dtypes. `String[8][:]` is a rank-one -array of eight-character elements. `String[:][:]` is a rank-one array whose -element length is not fixed in the public contract. For `character(len=5) :: -names(:)`, Python passes and receives arrays with `dtype="S5"`. For an -allocatable deferred-length character array, the runtime allocation length -becomes the returned dtype itemsize. x2py treats these arrays as raw fixed-width -bytes storage; Python Unicode arrays, object arrays, and mutable scalar -deferred-length character storage remain blocked. - -## Derived Types - -A supported Fortran derived type becomes a generated Python extension class. -Scalar inputs accept that exact generated class or a supported descendant where -polymorphic dispatch is documented. Scalar outputs and function results become -wrapper-owned instances. Nested derived components are borrowed child wrappers -whose parent remains their owner. Do not treat a nested child as independently -owned native storage. Wrapping Derived Types later expands nested-object and -finalization behavior. - -## Unsupported Widths And Forms - -The semantic format can represent names such as `Float128` and `Complex256`, -but representation in a `.pyi` file is not a native binding support claim. Current -Fortran wrapper generation blocks: - -- real storage wider than 64 bits; -- complex storage wider than 128 total bits; -- wider explicit logical storage without a portable NumPy round trip; -- unsigned Fortran integer assumptions without a proved native mapping; -- character arrays that cannot be represented as fixed-width NumPy bytes - storage; and -- arrays of derived types. - -The language feature matrix later records the support status and evidence for a -wrapper-planning error. - -## Evidence And Troubleshooting - -Scalar integer, logical, real, and complex mappings are exercised by -[`test_scalar_kinds.py`](../../../tests/wrapper/fortran/scalars/test_scalar_kinds.py). -String behavior is exercised by -[`test_character_arguments.py`](../../../tests/wrapper/fortran/strings/test_character_arguments.py), -and array dtype validation by -[`test_array_contracts.py`](../../../tests/wrapper/fortran/arrays/test_array_contracts.py). - -For a wrong scalar or array dtype, compare the value with `--pyi` output and -convert explicitly at the Python call site. Runtime Issues later covers a -successful build that rejects a call, while Compiler Issues covers kind probing -and compiler-selection failures. +The wrapper handles the native call details. Python still passes and receives +`numpy.float64` values. + +`T[()]` represents rank-zero NumPy storage: arguments accept a 0-D NumPy +array, and results return a 0-D NumPy array. Raw integer addresses are an +advanced boundary covered later in the guide. + +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. + +--- + +## Next + +- Continue with [Arrays](arrays.md) for rank, shape, strides, and contiguity. +- Then read [Strings](strings.md) for immutable values and mutable character + storage. +- [Wrapping Derived Types](wrapping-derived-types.md) diff --git a/docs/user/guide/distribution.md b/docs/user/guide/distribution.md deleted file mode 100644 index 731f409ea..000000000 --- a/docs/user/guide/distribution.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -title: Distribution -audience: users, packagers -prerequisites: packaging -related: packaging.md, ../troubleshooting/platform-specific-issues.md, ../getting-started/installation.md -status: maintained ---- - -# Distribution - -The portable distribution unit today is the project source plus a reproducible -native build recipe, not a universal prebuilt wheel. A generated extension may -be shared only with environments that match its Python, NumPy, operating-system, -architecture, compiler ABI, and native-library assumptions. - -## Source Distribution Workflow - -Reuse the `scale-project` and `scale.f90` source first presented in -[Packaging](packaging.md#complete-local-project-example). Distribute these -inputs: - -```text -scale-project/ - src/ - scale.f90 - python/ - check_scale.py - requirements.txt - BUILDING.md -``` - -`BUILDING.md` should record the exact supported build command: - -```bash -python3 -m x2py src/scale.f90 --out-dir build/scale -python3 python/check_scale.py -``` - -The asserted result remains the Python value `7.5`, as shown with the original -source in the packaging example. - -Record the required Python and NumPy versions, compiler family, compiler flags, -native libraries, library search paths, source order, and platform assumptions. -The receiving environment rebuilds the extension and runs the same smoke test. - -## Sharing A Prebuilt Extension - -A prebuilt extension is a platform-specific artifact. Before sharing it, the -producer and consumer must match at least: - -- operating system and architecture; -- Python implementation, major/minor version, and extension suffix; -- compatible NumPy runtime ABI; -- native compiler ABI and runtime libraries; -- linked native library versions and load paths; and -- extension module name and expected package namespace. - -Use the produced extension file as-is; do not rename it without also preserving -its Python initialization symbol. - -Even when these facts appear to match, import and runtime smoke tests on the -target environment are required. Current CI evidence does not establish a -general portability matrix. - -## Native Dependencies - -x2py can link caller-supplied objects, archives, shared libraries, named -libraries, and library directories for supported builds. It does not bundle, -relocate, or discover those dependencies for distribution. The application or -platform packaging system remains responsible for: - -- shipping redistributable native libraries; -- setting runtime loader paths; -- preserving compiler runtime dependencies; -- respecting library licenses; and -- validating symbols and ABI on the target platform. - -## Wheels And Source Archives - -x2py does not currently claim a stable automated wheel workflow, manylinux or -equivalent compliance, macOS universal binaries, Windows wheel support, or -automatic source-archive build hooks. A project may build custom packaging -around x2py, but that project owns the resulting portability and installation -contract. - -Do not label a wheel or source archive as generally supported merely because it -worked on the machine that produced it. - -## Platform Boundaries - -The verified wrapper path uses a GNU native toolchain on the tested Linux -environment. Other platforms and compilers require their own build, ABI, -import, runtime, ownership, and cleanup evidence. See -[Installation](../getting-started/installation.md) for current prerequisites. - -## Release Checklist - -Before distributing a wrapper project: - -1. Generate and review semantic `.pyi` output. -2. Build from a clean output directory with recorded source order and flags; - resolve any wrapper-plan errors reported by the default build. -3. Preserve the exact build command, source order, flags, and Makefile manifest - when one is generated. -4. Run asserted calls for every public routine used by the application. -5. Test expected invalid dtype, rank, shape, and ownership cases. -6. Rebuild and rerun on every claimed target environment. -7. State unsupported platforms and external dependencies explicitly. - -## Evidence And Troubleshooting - -Local output placement and importable artifact creation are exercised by -[`test_build_modes.py`](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py). -Caller-ordered multi-source and external-library builds are exercised by the -focused wrapper suites recorded later in the language feature matrix. - -No repository evidence currently proves universal wheel portability. -Platform-Specific Issues and Build Issues later cover target-environment -limitations and failures while rebuilding from source. diff --git a/docs/user/guide/editing-semantic-pyi-contracts.md b/docs/user/guide/editing-semantic-pyi-contracts.md deleted file mode 100644 index 843d3feb8..000000000 --- a/docs/user/guide/editing-semantic-pyi-contracts.md +++ /dev/null @@ -1,735 +0,0 @@ ---- -title: Editing Semantic .pyi Contracts -audience: users, advanced users -prerequisites: Fortran wrapper guide, semantic .pyi format -related: fortran-wrapper.md, ../reference/semantic-pyi-format.md -status: maintained ---- - -# Editing Semantic `.pyi` Contracts - -This guide is the user-facing contract for changing a generated semantic -`.pyi` before building a wrapper. It covers the edits x2py handles, the native -facts an edit must preserve, the runtime effect of each supported edit, and the -errors raised for unsafe combinations. - -The Semantic `.pyi` Format reference later provides the complete grammar. Use -this guide to decide whether a proposed edit is a supported wrapper operation. - -## The Editing Workflow - -Generate a starter contract package from the native sources: - -```bash -python3 -m x2py generate --pyi native/solver.f90 --out contracts/solver -``` - -Keep the generated package as a baseline, copy it, and edit the copy: - -```text -contracts/ -├── generated_solver/ -│ ├── __init__.pyi -│ └── solver.pyi -└── edited_solver/ - ├── __init__.pyi - └── solver.pyi -``` - -Build the edited entry contract with the same native implementation artifacts: - -```bash -python3 -m x2py contracts/edited_solver/__init__.pyi \ - --native-objects build/solver.o \ - -I build/mod \ - --out-dir build/edited-solver -``` - -The semantic `.pyi` entry contract selects the wrapper build automatically; -the native artifact options provide the implementation to compile or link. - -The entry `.pyi` is the sole semantic input to wrapper generation. x2py does -not reparse the native source to restore a removed declaration, projection, or -policy. Objects, archives, shared libraries, module files, and optional native -sources supplied to the build are implementation inputs, not hidden semantic -inputs. - -## What May And May Not Change - -An edited contract contains two kinds of information: - -1. **Native facts** describe the implementation that already exists: native - module and symbol identity, procedure kind, native argument order, ABI type - and kind, rank, storage category, callback signature, and required native - imports. -2. **Wrapper policy** describes the Python surface x2py should generate: - exports, visibility, Python names, overload grouping, result projection, - validation, mutation, ownership, lifetime, destruction, error translation, - and GIL behavior. - -Wrapper policy is editable. Native facts may be rewritten only when the new -facts still describe the supplied native artifacts. x2py validates structural -consistency, but it cannot inspect an arbitrary object or shared library and -prove its ABI. A structurally valid lie about an opaque native binary can still -fail at compile, link, import, or call time. - -The supported edit surface is: - -| Edit | Supported effect | -| --- | --- | -| Remove a declaration or entry import | Remove that function, method, variable, class, constructor, class member, or overload candidate from the Python API. | -| Add `@private` or `private[...]` | Retain a declaration as an internal contract input while hiding it from Python. | -| Add a declaration for an existing native symbol | Wrap that symbol when the declaration supplies all required native facts and the artifact implements them. | -| Change the Python export name or namespace | Edit the entry-package import/export tree; use `@bind(...)` when the Python declaration name differs from its native target. | -| Change overload grouping | Add or remove `@overload("specific")` candidates with distinct supported dtype/rank signatures. | -| Change Python/native argument projection | Add or edit `@native_call(...)` and `Returns[...]`, or remove `@native_call` and expose the complete native argument list in native order. | -| Change visible mutation | Use caller-owned writable storage, or `Immutable` plus an explicit replacement result. | -| Change supported ownership/lifetime policy | Supply a valid `Ownership(...)`, `Transfer(...)`, and `Destruction(...)` triple for the declared storage and context. | -| Translate native status to exceptions | Add `@raises(...)` with valid projected status/message values. | -| Keep the GIL | Add `@hold_gil` for a call that must execute while holding the Python GIL. | - - - -The following are not supported edits: - -- changing ABI dtype, kind, rank, calling convention, native argument order, or - native symbol without supplying a matching implementation; -- declaring that arbitrary native storage is wrapper-owned without a generated - wrapper instance or an implemented native release path; -- requesting a borrowed pointer view without owner retention and stale-view - invalidation; -- using generic `Annotated` helpers such as `Bounded(...)` or `Finite` as if - they already generated runtime checks; they currently round-trip as semantic - constraints only; -- requesting general implicit dtype coercion; wrapper arguments currently use - the exact documented NumPy dtype unless a specific supported path says - otherwise; or -- relying on omitted metadata to select a risky copy, borrow, reassociation, or - destruction policy. - -Unsupported policy is a blocker, not a request for x2py to guess. - -## Removing And Hiding API Members - -### Remove a declaration - -Delete a public declaration from the leaf `.pyi` to remove it from the -generated Python API. For example, deleting `next_local` removes the function -without affecting the remaining module variables and functions: - -```python -from x2py.contracts import Int32 - -counter: Int32 - -def summarize() -> Int32: ... -``` - -This rule applies to top-level functions, module variables, classes, methods, -fields, constructors, and individual overload declarations. x2py does not -recreate a deleted declaration from native source. - -Generated derived types use `__init__(self, *, ...)` when they have eligible -scalar field keywords and `__init__(self)` when they support only native -default construction. Removing either generated declaration suppresses public -construction. Native allocation may still exist internally, but the deleted -public constructor is not regenerated. - -### Hide a declaration but keep it available internally - -Use `@private` for functions, methods, and classes: - -```python -from x2py.contracts import Float64, private - -@private -def scaled_counter() -> Float64: ... -``` - -Use `private[...]` for data declarations or arguments: - -```python -from x2py.contracts import Float64, private - -scale: private[Float64] -``` - -Private declarations can still supply native types, bindings, or helper facts -needed by other public declarations. User-private declarations remain -printable and reloadable so an edited contract round-trip does not expose them. -Ordinary declarations that were private only in the native source remain -omitted from newly generated starter contracts. - -### Remove an overload candidate - -Each candidate is an independent declaration. Removing one candidate narrows -runtime dispatch without removing the generic name: - -```python -from x2py.contracts import Float64, Int32, overload, private - -@private -def convert_integer(value: Int32) -> Int32: ... - -@overload("convert_integer") -def convert(value: Int32) -> Int32: ... - -# The generated Float64 candidate was removed intentionally. -``` - -Calls that no longer match a remaining candidate raise `TypeError`. Do not keep -an empty overload declaration as an absence marker; remove it. - -## Editing Module Variable Initializers - -A mutable scalar module variable may include a literal default: - -```python -from x2py.contracts import Int32 - -counter: Int32 = 41 -``` - -For wrapper builds, that value is applied to native module storage during -extension import by calling the generated native setter. The variable remains -writable after import; later reads and writes still use the current native -storage. This form accepts literal values only. Calls, names, and expressions -such as `f(42)`, `x + 1`, or `SOME_NAME` are rejected for mutable module -variables. The declaration must also have a completed write-through native -setter; unsupported module-variable defaults are reported during wrapper planning -instead of being treated as copied Python values. - -Use `Final[...]` for true constants: - -```python -from x2py.contracts import Final, Int32 - -nmax: Final[Int32] = 12 -``` - -## Adding And Renaming Declarations - -### Add a contained procedure already present in a native module - -Add the complete callable declaration to the module leaf: - -```python -from x2py.contracts import Float64 - -def norm2(values: Float64[:]) -> Float64: ... -``` - -The leaf filename identifies the native module. The Python name is also the -native procedure name unless `@bind(...)` says otherwise. - -### Add or rename a native target - -Use `@bind(...)` when the declaration's Python name differs from the native -specific procedure: - -```python -from x2py.contracts import Float64, Int32, bind - -@bind("solver_step") -def step(values: Float64[:]) -> Int32: ... -``` - -For a standalone external symbol, also use `@external`: - -```python -from x2py.contracts import Float64, bind, external - -@external -@bind("vendor_norm2") -def norm2(values: Float64[:]) -> Float64: ... -``` - -`@bind(...)` changes name resolution. It does not adapt an incompatible ABI. - -### Add an overload candidate - -Link every Python overload to one concrete native specific: - -```python -from x2py.contracts import Float64, Int32, overload, private - -@private -def scale_integer(value: Int32) -> Int32: ... - -@private -def scale_real(value: Float64) -> Float64: ... - -@overload("scale_integer") -def scale(value: Int32) -> Int32: ... - -@overload("scale_real") -def scale(value: Float64) -> Float64: ... -``` - -To rename the Python overload group while calling an existing native generic, -preserve the native generic explicitly: - -```python -from x2py.contracts import Int32, overload, private - -@private -def convert_integer(value: Int32) -> Int32: ... - -@overload("convert_integer", generic="convert") -def convert_number(value: Int32) -> Int32: ... -``` - -Candidates must be distinguishable by the implemented runtime dispatcher. -Duplicate dtype/rank signatures are rejected because declaration order must not -silently choose a native procedure. - -### Replace the generated constructor - -An edited class may bind `__init__` to one concrete native initializer: - -```python -from x2py.contracts import Addr, Arg, Int32, Pass, bind, native_call, private - -class state: - @private - @native_call([Pass(), Addr(Arg(0))]) - def init_state(self, size: Int32) -> None: ... - - @bind("init_state") - @native_call([Pass(), Addr(Arg(0))]) - def __init__(self, size: Int32) -> None: ... -``` - -The generated field-keyword constructor and a bound native initializer are -different contracts. Remove the old constructor declaration when replacing it. - -## Editing The Call Shape - -### Remove `@native_call` and expose native order - -When every native dummy remains visible in native order, the edited declaration -does not need `@native_call`: - -```python -from x2py.contracts import Int32 - -def scalar_status( - base: Int32[()], - status: Int32[()], -) -> None: ... -``` - -The caller supplies scalar storage objects for the visible native scalar slots: - -```python -base = np.array(4, dtype=np.int32) -status = np.empty((), dtype=np.int32) -assert module.scalar_status(base, status) is None -assert status[()] == np.int32(15) -``` - -This exact edit is compiled and exercised by -[`test_native_order_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_native_order_contracts.py). -It covers scalar, array, matrix, string, mixed-result, and derived-type native -order calls. An ordinary Python `str` cannot observe mutation of the temporary -native character buffer; use a projected replacement when Python must see the -new string. - -For a native-order scalar character dummy, keep the Python boundary as -`String[n]`: - -```python -from x2py.contracts import String - -def fixed_inout(label: String[8]) -> None: ... -``` - -The caller passes `str`. x2py creates fixed-width character storage, passes its -address to the native call, and discards native mutation because the signature -returns `None`. Add a replacement return when Python should receive the mutated -value: - -```python -from x2py.contracts import Returns, String - -def fixed_inout(label: String[8]) -> Returns["label", String[8]]: ... -``` - -### Project native arguments into Python returns - -Use `Returns[...]` for the Python result contract and `@native_call(...)` when -the native call needs hidden output storage, reordered arguments, constants, -lengths, presence flags, shapes, or work buffers: - -```python -from x2py.contracts import Addr, Arg, Int32, Return, Returns, native_call - -@native_call([Addr(Arg(0)), Return("status", 0)]) -def scalar_status( - base: Int32, -) -> Returns["status", Int32]: ... -``` - -Removing the explicit `status` parameter and adding the result projection are -one edit. A projection must map every required native argument exactly once; -incomplete, duplicate, or out-of-range mappings are contract errors. -The semantic `.pyi` intentionally has no `intent` annotation. For a generated -starter contract, source `intent` only helps select the default visible -arguments and projected results. After loading, the signature, `Returns[...]`, -and exhaustive `@native_call` list are authoritative. An output dummy may -remain caller-supplied storage, while a projected output exists only when the -contract explicitly requests that projection. The bridge may use permissive -writable local storage; the called native procedure retains and enforces its -own direction. - -### Make mutation replacement-only - -`Immutable` says the original Python-visible object must not be mutated. A -writable native argument therefore needs either an explicit replacement result -or an explicit call-local discarded-mutation policy: - -```python -from x2py.contracts import Annotated, Float64, Immutable, Int32, Returns - -def scale_with_status( - values: Annotated[Float64[:], Immutable], - status: Int32[()], -) -> Returns["values", Float64[:]]: ... -``` - -At runtime, x2py copies `values` into mutable native storage, calls native code, -and returns a different NumPy array. The original remains unchanged. The -compiled evidence is -[`test_policy_dispatch_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_policy_dispatch_contracts.py). - -Mutability is a property of this argument boundary, not of `Float64`, -`String[n]`, or another datatype in isolation. The same datatype may be an -input value, caller-owned writable storage, or a replacement-only value in -different procedures, so datatype spelling cannot replace `Immutable`. - -`Immutable` plus `Transfer("borrowed_view")` on a writable value is -contradictory: one requests replacement-only semantics and the other requests a -writable shared view. The contract fails instead of selecting one silently. - -## Editing Types, Shapes, Layout, And Optionality - -The annotation is runtime policy, not merely an IDE hint. Supported edits can -tighten or broaden validation without changing the native ABI: - -```python -from x2py.contracts import Float64 - -def solve( - matrix: Float64[3, 3], - rhs: Float64[3], -) -> Float64[3]: ... -``` - -Plain multidimensional arrays in a Fortran semantic `.pyi` use `ORDER_F` by -default. Generated contracts omit that default order. Use explicit layout -metadata only to request a non-default Python storage representation. - -The wrapper validates exact dtype, rank, shape, layout, writeability, byte -order, alignment, and zero-sized-array rules required by the selected backend -path. Examples of supported changes include: - -- `Float64[:, :]` to `Float64[3, 3]` to require one shape; -- `ORDER_ANY` when the native path is implemented for either - contiguous orientation; -- `T | None` or a default `= ...` for a genuinely optional native argument; -- `Allocatable`, `Pointer`, `Aliased`, or `PointerPolicy(...)` when those - facts match the native declaration and the selected policy path; and -- `Immutable` for a supported replacement or call-local mutation policy. - -Changing `Float64[:]` to `Int32[:]`, changing rank, or inventing optionality is -not a Python-only conversion. It changes the declared native ABI and is valid -only when the linked implementation has that ABI. - -Generic constraints such as `Bounded(1, 8)` and `Finite` currently survive -parse/print round-trips but do not generate runtime validation. Wrapper -wrapper planning reports `fortran_runtime_constraints_unsupported` instead of building -a wrapper that silently ignores them. General semantic coercions are handled -the same way through `fortran_runtime_coercions_unsupported`. - -## Editing Errors And GIL Behavior - -Use `@raises(...)` to turn a projected native status into a Python exception: - -```python -from x2py.contracts import Float64, Int32, Returns, String, raises - -@raises(status="status", message="message", success=0) -def solve(values: Float64[:]) -> tuple[ - Returns["result", Float64[:]], - Returns["status", Int32], - Returns["message", String], -]: ... -``` - -The named status and optional message must exist in the function's projected -results. Successful calls omit status-only implementation results from the -Python value according to the documented projection. Non-success status raises -the generated Python exception before returning an ordinary result. - -Wrappers release the GIL around ordinary native calls when the call contract -allows it. Add `@hold_gil` when native code must call Python synchronously or -otherwise requires the current Python thread to retain the GIL: - -```python -from x2py.contracts import Float64, hold_gil, prototype - -@prototype -def scalar_callback(value: Float64) -> Float64: ... - -@hold_gil -def invoke_callback(callback: scalar_callback) -> Float64: ... -``` - -These decorators change wrapper runtime policy; they do not change the native -procedure ABI. - -## Ownership, Transfer, And Destruction - -Ownership edits use a complete policy triple: - -```python -from x2py.contracts import Annotated, Destruction, Float64, Ownership, Transfer - -values: Annotated[ - Float64[:], - Ownership("native"), - Transfer("borrowed_view"), - Destruction("native_owner"), -] -``` - -The three values answer different questions: - -- `Ownership(...)`: who owns the authoritative storage? -- `Transfer(...)`: does Python receive a value, temporary, in-place object, - copy, view, or wrapper instance? -- `Destruction(...)`: which runtime releases owned storage, and at what - lifetime boundary? - -They are not three independent switches. x2py validates the combination -against object kind, native storage category, call position, mutability, -nullability, projection, and available release mechanism. An edit can choose -between implemented boundary behaviors; it cannot retroactively change where a -native allocation came from. - -### One `values` example in three descriptor contexts - -The following variants all expose a rank-one `Float64` allocatable handle named -`values`, but their descriptor contexts make ownership and lifetime different. - -#### Fortran-owned module storage - -```python -from x2py.contracts import Aliased, Allocatable, Annotated, Destruction, Float64, Ownership, Transfer - -module_values: Annotated[ - Allocatable[Float64[:]], - Aliased, - Ownership("native"), - Transfer("borrowed_view"), - Destruction("native_owner"), -] -``` - -Python receives a persistent `AllocatableArray` for the module descriptor. -`handle.to_numpy()` exposes a current live view for both plain and `Aliased` -module allocatables. `Aliased` preserves native addressability for other policy; -it is not the extraction switch. NumPy must not free the data. A native -allocate/deallocate routine controls the allocation, and a later native -deallocation or reallocation makes previous views stale. Accessing stale views -is unsupported and may crash. The same handle then reports `allocated is -False`; the module attribute itself does not become `None`. - -Lifecycle: - -```text -Fortran owns descriptor -> handle borrows descriptor -> view borrows allocation - \-> Fortran deallocates authoritative storage -``` - -#### Wrapper-owned component storage - -```python -from x2py.contracts import Allocatable, Annotated, Destruction, Float64, Ownership, Transfer - -class buffer: - values: Annotated[ - Allocatable[Float64[:]], - Ownership("wrapper"), - Transfer("borrowed_view"), - Destruction("wrapper_dealloc"), - ] -``` - -The containing Python extension object owns the native derived-type instance; -the allocatable component belongs to that instance. Access returns an -`AllocatableArray` that retains the wrapper. A NumPy view returned by -`handle.to_numpy()` retains the handle, so the owner chain remains live. The -generated wrapper deallocator finalizes/releases the native instance after the -last owning reference is gone. An explicit native component-deallocation method -may make an existing view stale sooner, so callers must not retain views across -such calls. - -Lifecycle: - -```text -wrapper allocates instance -> field handle retains wrapper -> view retains handle - \-> wrapper finalizes instance -``` - -#### Wrapper-owned result descriptor - -```python -from x2py.contracts import Addr, Allocatable, Annotated, Arg, Destruction, Float64, Int32, Ownership, Return, Transfer, native_call - -@native_call([Addr(Arg(0)), Return("values", 0)]) -def build_values( - n: Int32, -) -> Annotated[ - Allocatable[Float64[:]], - Ownership("wrapper"), - Transfer("wrapper_instance"), - Destruction("wrapper_dealloc"), -]: ... -``` - -Native code produces allocatable output storage. The generated binding -transfers its values into persistent descriptor storage and returns an owned -`AllocatableArray`. The handle remains valid after the native call; -`handle.close()` or finalization releases its allocation. A NumPy view extracted -with `to_numpy()` retains the handle and is not a detached copy. - -Lifecycle: - -```text -Fortran allocates output -> wrapper-owned descriptor receives values - -> result handle releases descriptor on finalization -``` - -These are supported contexts for the same handle concept, not permission to -relabel one allocation arbitrarily. In particular, changing the module-storage -example to `Destruction("wrapper_dealloc")` would be unsafe: the generated -module wrapper has no right to finalize storage owned by Fortran module state. -Call `.copy()` on an extracted view when Python needs an independent NumPy -lifetime. - -### Supported transfer modes - -| Transfer | Supported use | Destruction | -| --- | --- | --- | -| `by_value` | Scalar values returned to Python. | `python_refcount` | -| `call_local` | Converted scalar/string/array inputs, pointer inputs associated only for one call, and explicitly discarded immutable mutation. | `none` or `call_local` | -| `in_place` | Caller-supplied writable scalar storage, NumPy arrays, and existing wrapper instances. | `caller` or the existing wrapper's `wrapper_dealloc` | -| `copy_return` | Strings, ordinary array results, and immutable replacement results copied to Python. | `python_refcount` | -| `snapshot_copy` | Detached copies for explicitly supported projections. Pointer-array handle results remain blocked until owner storage, target lifetime, descriptor extraction, and destroy behavior are implemented. | `python_refcount` | -| `borrowed_view` | Target-backed module allocatables and supported fields/components whose owner remains identifiable. | `native_owner` or `wrapper_dealloc` | -| `wrapper_instance` | Derived-type output or owned allocatable result represented by a Python object controlling native storage. | `wrapper_dealloc` | -| `blocked` | Intentional declaration that no safe implemented transfer exists. | `blocked` | - -### Destruction responsibilities - -| Destruction | Runtime responsibility | -| --- | --- | -| `python_refcount` | Python, NumPy, or a generated base capsule releases Python-owned storage after references are gone. | -| `wrapper_dealloc` | The generated extension object's deallocator finalizes/releases its native instance. | -| `native_owner` | Fortran module state or an external native owner releases storage; Python only borrows. | -| `caller` | The caller retains and releases the object supplied to x2py. | -| `call_local` | Generated bridge cleanup releases the temporary before the wrapper call returns. | -| `none` | x2py created no persistent owned storage for this boundary value. | -| `blocked` | Release responsibility is unknown, contradictory, or not implemented; generation stops. | - -`Ownership("unknown")`, `Transfer("blocked")`, and -`Destruction("blocked")` are useful for making an unresolved contract fail -closed. They are not runtime ownership modes. - -### Ownership combinations that fail - -Examples include: - -- `Immutable` writable storage with `Transfer("borrowed_view")`; -- `Transfer("copy_return")` on an argument with no projected replacement; -- pointer-array results without stable owner storage and target lifetime; -- pointer reassociation or ownership-changing operations without completed - owner, shape, lifetime, and release behavior; -- borrowed pointer views whose policy cannot retain a descriptor owner or - provide descriptor extraction; -- `Ownership("native")` with `Destruction("python_refcount")` for the same - authoritative allocation; and -- `Ownership("python")` with `Destruction("native_owner")`. - -The diagnostic identifies the declaration and rejected policy. x2py does not -silently replace these combinations with its default policy. - -## Package Exports And Namespaces - -The entry `__init__.pyi` controls which leaf modules and declarations enter the -extension's Python export tree. Removing an entry import removes that branch; -adding a relative import adds a contract fragment: - -```python -from . import solver -from .helpers import norm2 -``` - -Leaf files continue to identify native modules. Entry imports compose the -Python package; they do not rename native modules or infer object files. -Supported relative imports include module imports, selective declaration -imports, wildcard flattening, and `as` aliases. Repeating the same export is -idempotent, and exporting both the original name and an alias is allowed when -both exports are explicit. Alias exports share the same native target or -storage, but they do not promise Python object identity: module-variable reads -may return distinct Python objects with the same current value, and function -introspection may show the alias name. Conflicting exports to the same Python -name, missing relative files, and import cycles fail while the contract graph is -loaded. -Only declarations reachable from the entry export policy are emitted as public -Python extension bindings; omitted leaf declarations are not wrapped just -because their leaf file was discovered. - -## Diagnostics For Edited Contracts - -Failures occur at the first layer with enough information: - -1. **Load errors**: invalid Python syntax, unsupported decorators or metadata, - untyped parameters, invalid imports, or import cycles. File-based errors - include the `.pyi` path. -2. **Structural validation errors**: incomplete projections, duplicate native - positions, invalid `@bind`/`@overload` links, conflicting exports, or public - declarations exposing private types. -3. **Policy/planning errors**: incomplete ownership, lifetime, pointer, - coercion, mutation, allocation, or release behavior. -4. **Native build/runtime errors**: the supplied artifact does not implement - the declared symbol or ABI. - -Do not fix a policy blocker by deleting metadata until the wrapper happens to -build. The corrected contract must explicitly describe the intended boundary -behavior and its owner. - -## Runtime Evidence - -Editable-contract runtime fixtures live under -[`tests/wrapper/fortran/edit_pyi_contracts`](../../../tests/wrapper/fortran/edit_pyi_contracts/README.md): - -- `test_native_order_contracts.py` removes `@native_call` and exposes native - argument order; -- `test_ownership_contracts.py` applies explicit native-owned module, - wrapper-owned field, and wrapper-owned result-handle lifetime policies to the - same descriptor concept; -- `test_visibility_contracts.py` removes and hides declarations while checking - unaffected runtime behavior; -- `test_surface_edit_contracts.py` removes classes, methods, constructors, - fields, and overload candidates and adds renamed bindings and overloads; and -- `test_policy_dispatch_contracts.py` proves immutable replacement through the - completed ownership/action policy. - -Broader handle ownership and lifetime evidence is in -[`test_allocatable_views.py`](../../../tests/wrapper/fortran/module_state/test_allocatable_views.py). -The Semantic `.pyi` Wrapper Checklist later provides the active completion -ledger. diff --git a/docs/user/guide/enumerations.md b/docs/user/guide/enumerations.md index 7234c7c08..76cc1c381 100644 --- a/docs/user/guide/enumerations.md +++ b/docs/user/guide/enumerations.md @@ -1,35 +1,41 @@ --- title: Enumerations +description: How x2py handles Fortran `enum` and enumerators audience: users prerequisites: wrapping modules, data types -related: wrapping-modules.md, generic-interfaces.md, ../language-support/feature-matrix.md +related: wrapping-modules.md, generic-interfaces.md status: maintained +publication: reviewed --- # Enumerations -Supported Fortran enumerators become typed integer constants. x2py does not -generate Python `Enum` or `IntEnum` classes, and values passed through -procedures or fields remain the resolved integer dtype. +x2py turns supported Fortran `enum` declarations into **typed integer constants**. It does **not** generate Python `Enum` or `IntEnum` classes — values remain plain integers with the resolved dtype. -## Complete Enumeration Example +--- + +## Complete Example Create `colors.f90`: ```fortran module colors_api implicit none + enum, bind(C) enumerator :: red = -1 enumerator :: blue enumerator :: green = 10 enumerator :: yellow end enum + contains + integer(4) function round_trip_color(value) result(output) integer(4), intent(in) :: value output = value end function round_trip_color + end module colors_api ``` @@ -39,51 +45,42 @@ Build it: python3 -m x2py colors.f90 --out-dir build/colors ``` -The generated constants retain explicit and implicit values: +--- + +## Usage in Python ```python import sys -import numpy as np sys.path.insert(0, "build/colors") -import colors - -api = colors.colors_api -assert api.red == np.int32(-1) -assert api.blue == np.int32(0) -assert api.green == np.int32(10) -assert api.yellow == np.int32(11) -assert api.round_trip_color(np.int32(api.green)) == np.int32(10) -``` +from colors.colors_api import blue, green, red, round_trip_color, yellow -## Generated Contract +print(red, blue, green, yellow) # -1 0 10 11 -The semantic `.pyi` exposes constants as `Final[Int32]` values for this -resolved representation. A variable or field holding one of these values still -uses `Int32`; it does not acquire a distinct Python enum type. +# Pass enumerator values to procedures +result = round_trip_color(green) +print(result) # 10 +``` -The constants are read-only native facts. Python assignment can only shadow a -module attribute; it cannot mutate the native enumerator. +--- -## Naming And Type Checking +## Key Points -Generated names follow the normal visibility, keyword escaping, and collision -policy. Static type checkers see integer constants and integer parameters. Code -that needs a project-specific Python `Enum` may define one in application code -and pass `numpy.int32(member.value)` to the wrapper. +- Enumerators become **read-only** constants on the module. +- They use the resolved integer dtype (usually `Int32`). +- Assigning to them in Python only creates a local shadow — it does **not** change the native value. +- No automatic runtime validation — passing any integer of the correct dtype works. +- Static type checkers see them as integer constants. + +--- ## Limitations -- No runtime validation restricting an integer parameter to declared - enumerator values unless the native routine performs that validation. -- Unsupported source enum forms stop at parsing, semantic conversion, or wrapper - planning instead of being converted into unrelated constants. +- No native `Enum` class is generated in Python. +- If you want a proper Python `Enum`, define one in your application code and pass `.value` (as `np.int32`). -## Evidence And Troubleshooting +--- -Value preservation, `Final[Int32]` emission, absence of Python enum classes, -field behavior, and runtime round trip are exercised by -[`test_fortran_enums.py`](../../../tests/wrapper/fortran/scalars/test_fortran_enums.py). +## Next -Use [Data Types](data-types.md) for integer width and -[Wrapping Modules](wrapping-modules.md) for constant attribute behavior. +- Continue with [Raw Addresses](raw-addresses.md). diff --git a/docs/user/guide/error-handling.md b/docs/user/guide/error-handling.md index 5265e3224..53e971032 100644 --- a/docs/user/guide/error-handling.md +++ b/docs/user/guide/error-handling.md @@ -1,177 +1,107 @@ --- -title: Error Handling +title: Error Handling & Diagnostics +description: How x2py reports errors at different stages and how to diagnose them audience: users, advanced users prerequisites: common beginner workflow, data types -related: ../reference/diagnostic-codes.md, ../troubleshooting/index.md, callbacks.md +related: callbacks.md status: maintained +publication: reviewed --- -# Error Handling +# Error Handling & Diagnostics -Failures occur at distinct stages. Parsing rejects syntax that x2py cannot -model; semantic conversion records contract facts; post-IR policy completion -records every wrapper decision and its precise unsupported reason; wrapper -planning retrieves those completed decisions and raises at the owning -declaration; compilation and linking diagnose native-language and build failures; -Python calls validate values at runtime. Some native termination and callback -failures terminate the process. +x2py reports failures at several distinct stages. Understanding which stage failed helps you know where to look and what to fix. -This is the general error guide for x2py. The Diagnostic Codes reference lists -stable parser and preprocessing categories; this page explains what to do at -each stage. +--- -## Complete Status-Projection Example +## Failure Stages -Create `solver.f90`: +| Stage | Typical Cause | What to do | +|-----------------------------|----------------------------------------------------|----------| +| Parsing | Syntax x2py cannot model, missing include | Check the diagnostic code and source location | +| Interface conversion | Unresolved types or missing interface details | Fix the source or edit the generated `.pyi` | +| Wrapper planning | Unsupported storage, layout, or callback combination | Read the full error message; it points to the declaration | +| Compilation / Linking | Compiler issues, missing modules/libraries | Run with `--verbose` to see native commands | +| Import | Missing library or incompatible build tools | Check paths and environment | +| Python Call | Wrong dtype, shape, layout, class, etc. | Match the generated contract | +| Native Execution | Application-level status, such as an error code | Convert it with `@raises` or handle it manually | +| Callback / Fatal | Exception in callback, `stop`, `error stop` | Process usually aborts | -```fortran -module solver - implicit none -contains - subroutine solve(value, status, message) - integer(4), intent(in) :: value - integer(4), intent(out) :: status - character(len=32), intent(out) :: message - - if (value < 0) then - status = 1 - message = "negative input" - else - status = 0 - message = "" - end if - end subroutine solve -end module solver -``` +--- -Generate an editable contract package: +## Verbose Output And Tracebacks -```bash -python3 -m x2py generate --pyi solver.f90 --out contracts/solver -``` +Use the two diagnostic flags for different problems: -In `contracts/solver/solver.pyi`, keep the generated native types and add -the explicit status policy: +| Flag | Use it when | +| --- | --- | +| `--verbose` | A build or link fails and you need the generated files, build steps, timings, or compiler commands. | +| `--debug` | x2py fails unexpectedly and you need the full Python traceback. | -```python -from x2py.contracts import Addr, Arg, Int32, Return, String, native_call, raises +`--verbose` keeps the normal concise error message. `--debug` exposes x2py's +internal call stack, so it is mainly useful when reporting or investigating an +x2py bug. -@raises(status="status", message="message", success=0) -@native_call([Addr(Arg(0)), Return("status", 0), Return("message", 1)]) -def solve( - value: Int32, -) -> tuple[Int32, String[32]]: ... -``` +--- -Build that contract against the same simple native source: +## Status Projection Example -```bash -python3 -m x2py contracts/solver/__init__.pyi \ - --native-fortran-sources solver.f90 \ - --out-dir build/solver +You can turn Fortran status codes into Python exceptions using the `@raises` decorator in an edited contract. + +**Example:** + +```fortran +subroutine solve(value, status, message) + integer(4), intent(in) :: value + integer(4), intent(out) :: status + character(len=32), intent(out) :: message + ... +end subroutine ``` -The success outputs are consumed, while a nonzero status becomes -`RuntimeError` with the native message: +In your edited `.pyi`: ```python -import sys -import numpy as np +@raises(status="status", message="message", success=0) +def solve(value: Int32) -> None: ... +``` -sys.path.insert(0, "build/solver") -import solver +Then: -api = solver.solver -assert api.solve(np.int32(1)) is None +```python +import numpy as np try: api.solve(np.int32(-1)) -except RuntimeError as error: - assert "negative input" in str(error) -else: - raise AssertionError("expected RuntimeError") +except RuntimeError as e: + print(e) ``` -Status projection is opt-in. Without `@raises`, status and message remain -ordinary outputs. Editing Semantic `.pyi` Contracts later defines the supported -workflow for changing a generated contract. +For the complete status and message rules, see +[Translate Status Results into Exceptions](../reference/pyi-contracts/calls-and-results.md#translate-status-results-into-exceptions). -## Failure Layers +--- -| Layer | Typical failure | User action | -| --- | --- | --- | -| preprocessing or parsing | missing include, unsupported parser syntax, or a declaration x2py cannot model | read the diagnostic code and source location | -| semantic conversion | unresolved contract type, missing compile-time fact, or incomplete imported contract data | correct the source facts or edit the generated `.pyi` contract | -| post-IR policy completion and wrapper planning | unsupported ownership, ABI, pointer, array, callback, overload, or storage decision; inconsistent completed policy | read the owner path and reason in the wrapper-build error; planning does not retry another route | -| compilation or linking | missing compiler, module, object, symbol, or library | rerun with `--verbose`; inspect the native build plan | -| import | missing shared dependency, wrong ABI, wrong output path | inspect the shared library and runtime environment | -| Python call | wrong dtype, rank, shape, layout, writeability, class, or callable | pass a value matching the generated contract | -| native execution | application status output | return it normally or opt into documented `@raises` policy | -| native termination | `stop`, `error stop`, abort, fatal finalizer | isolate risky calls; Python cannot recover | -| callback boundary | callback exception or invalid result | traceback is printed and the host process aborts | +## Common Python Exceptions -## Wrapper Build Errors +- `TypeError` — Wrong dtype, rank, shape, layout, class, or callback +- `ValueError` — Invalid options or contract values +- `RuntimeError` — Native status projected as exception +- `ImportError` / `OSError` — Extension loading problems -The default wrapper build is the only compiled-wrapper decision path. With no -subcommand, it completes semantic policy, projects the typed wrapper plan, -generates source, and invokes the native build. There is no separate wrapper -selector, preflight report, or support-analysis command to run. +--- -An unsupported completed policy stops at its owner while the wrapper build -follows that path. For example: +## Best Practices -```text -x2py: error: Semantic class 'shapes.shape' has unsupported derived-type policy: abstract derived types need a non-instantiable Python class policy -``` +- Always start with the **full error message** — it usually tells you exactly what went wrong. +- Use `--verbose` when investigating build failures. +- Use `--debug` only when an unexpected x2py failure requires a Python + traceback. +- For complex contracts, generate the `.pyi` first and inspect it. +- Run risky or untrusted callbacks in a subprocess if you need the main process to survive failures. + +--- + +## Next -The owner path points at the declaration whose completed decision cannot be -implemented. Fix that contract in the source or editable `.pyi`, then rerun -the same wrapper-build command. - -Examples include a missing completed policy, an unsupported derived-type shape, -an incomplete callback contract, or an unsafe ownership/array layout. These are -ordinary errors from the real policy-completion or planning call, not entries in -a separate report. - -Do not duplicate native-language validation in these stages. If x2py has enough -information to build the semantic contract and plan, errors such as an invalid -defined operator or invalid native source syntax remain the native compiler's -responsibility. The compiler command and diagnostic are shown by `--verbose`. - -## Python Exception Types - -- `TypeError` covers wrong Python object type, scalar dtype, array dtype/rank/ - shape/layout/writeability, wrong generated class, non-callable callback, and - failed result conversion. -- `ValueError` covers invalid wrapper options and contract values where a Python - value is structurally wrong rather than the wrong object category. -- `MemoryError` reports failure to allocate a required Python result copy. -- `RuntimeError` is used by explicit native status projection. -- `ImportError` or loader-specific `OSError` can report extension or shared - dependency loading failures. - -Exact wording is not a substitute for the stable category. Diagnostic codes -for inspection stages are catalogued later in the Diagnostic Codes reference. - -## Cleanup Guarantees - -Validation that fails before native entry releases generated temporaries and -does not call the routine. Successful and exceptional conversion paths release -call-local storage according to completed ownership policy. Wrapper-owned -objects use their generated deallocator; borrowed views do not free native -storage. - -No cleanup promise can recover from process termination, native memory -corruption, or a fatal callback boundary. - -## Evidence And Troubleshooting - -Status-to-exception projection and GIL policy are exercised by -[`test_runtime_policies.py`](../../../tests/wrapper/fortran/runtime_behavior/test_runtime_policies.py). -Validation failures are exercised throughout the focused wrapper suites, and -fatal callback behavior by -[`test_scalar_callbacks.py`](../../../tests/wrapper/fortran/callbacks/test_scalar_callbacks.py). - -Troubleshooting later provides focused routes for environmental failures. Use -`--debug` only when an x2py traceback is needed; ordinary user diagnostics -should remain concise. +- Finish with [Building the Shared Library](building-shared-library.md). diff --git a/docs/user/guide/generic-interfaces.md b/docs/user/guide/generic-interfaces.md index 066309971..bc06094a8 100644 --- a/docs/user/guide/generic-interfaces.md +++ b/docs/user/guide/generic-interfaces.md @@ -1,19 +1,22 @@ --- -title: Generic Interfaces +title: Generic Interfaces (Overloading) +description: How x2py supports Fortran named generic interfaces and exact overload dispatch audience: users, advanced users prerequisites: wrapping functions, wrapping subroutines, data types related: optional-arguments.md, wrapping-derived-types.md, error-handling.md status: maintained +publication: reviewed --- -# Generic Interfaces +# Generic Interfaces (Overloading) -Named module and type-bound generic interfaces become one Python-visible -callable backed by a checked overload set. Dispatch uses exact scalar or array -dtype, rank, and generated extension class; it does not perform broad numeric -coercion. +x2py turns a Fortran generic interface into one Python callable. The callable +dispatches to a concrete native procedure by exact dtype, rank, and generated +class. It does not apply implicit numeric coercion. -## Complete Generic Example +--- + +## Complete Example Create `generic.f90`: @@ -22,11 +25,14 @@ module conversions implicit none private public :: convert + interface convert module procedure convert_integer module procedure convert_real end interface convert + contains + integer(4) function convert_integer(value) result(output) integer(4), intent(in) :: value output = value + 10 @@ -36,127 +42,159 @@ contains real(8), intent(in) :: value output = value + 0.5_8 end function convert_real + end module conversions ``` -Inspecting `generic.f90` prints the private specifics and public overload -contracts: +Build it: + +```bash +python3 -m x2py generate --pyi generic.f90 +python3 -m x2py generic.f90 --out-dir build/generic +``` + +--- + +## Generated Contract + +The semantic `.pyi` keeps the concrete procedures as private link targets. +Each public declaration adds one candidate to `convert`: + +`@private` hides a concrete procedure from Python. `@overload` links a public +candidate to that procedure, and `@bind` selects the public native generic when +the concrete procedure is private in Fortran. ```python -from x2py.contracts import Addr, Arg, Float64, Int32, native_call, overload, private +from x2py.contracts import Float64, Int32, bind, overload, private @private -@native_call([Addr(Arg(0))]) -def convert_integer( - value: Int32 -) -> Int32: ... +def convert_integer(value: Int32) -> Int32: ... @private -@native_call([Addr(Arg(0))]) -def convert_real( - value: Float64 -) -> Float64: ... +def convert_real(value: Float64) -> Float64: ... +@bind("convert") @overload("convert_integer") -def convert( - value: Int32 -) -> Int32: ... +def convert(value: Int32) -> Int32: ... +@bind("convert") @overload("convert_real") -def convert( - value: Float64 -) -> Float64: ... +def convert(value: Float64) -> Float64: ... ``` -Build it: +The source exports `convert`, not `convert_integer` or `convert_real`. Since +those concrete procedures are native-private, +[`@bind("convert")`](wrapping-functions.md#python-and-native-names) routes both +candidates through the public generic. -```bash -python3 -m x2py generic.f90 --out-dir build/generic -``` +--- -The public generic dispatches by exact dtype: +## Usage in Python ```python import sys + import numpy as np sys.path.insert(0, "build/generic") -import generic +from generic.conversions import convert -api = generic.conversions -assert api.convert(np.int32(4)) == np.int32(14) -assert api.convert(np.float64(4.0)) == np.float64(4.5) +print(convert(np.int32(4))) # 14 +print(convert(np.float64(4.0))) # 4.5 ``` -## Calling A Generic +The argument type selects the concrete procedure. `np.int32` calls +`convert_integer`; `np.float64` calls `convert_real`. -The complete example covers integer and real overloads. Complex, array, and -generated-class overloads follow the same exact dtype/rank/class rule when -their specifics are supported. +--- -The generated `.pyi` contains overload declarations associated with concrete -native targets. The public generic name remains one callable. `@native_call` -belongs on the concrete specific procedure, not on the `@overload(...)` -declaration that links the public Python signature to that target. +## Inspect the Overloads -## Type-Bound Generics +The module docstring lists one public callable: -Type-bound overloads dispatch after accounting for the implicit passed object. -For example, one documented generated method may provide distinct `Int32` and -`Float64` call shapes under the same public name. +```python +import generic.conversions as conversions -Supported scalar polymorphic input dispatch uses the generated base and -descendant wrapper classes. Descendants are checked before the base class so a -concrete descendant selects its concrete bridge. +print(conversions.__doc__) # includes convert(*args, **kwargs) +``` -## No Match And Ambiguity +The callable docstring lists every accepted signature: -A value with no matching specific raises `TypeError`. If two native specifics -collapse to the same Python dtype/rank/class signature, wrapper generation -rejects the overload set deterministically. Declaration order is never used as -an ambiguity tiebreaker. +```python +print(conversions.convert.__doc__) +``` + +The relevant part is: + +```text +convert(*args, **kwargs) + +Supported Signatures +-------------------- +convert(value: int32) -> int32 +convert(value: float64) -> float64 +``` + +Private procedures such as `convert_integer` do not appear. + +--- + +## Extend an Overload Set + +An edited contract can add another existing native procedure to the same +Python callable. Suppose the native module and contract also contain a public +`convert_logical`: + +```python +from x2py.contracts import Bool, Int32, overload, private + +@private +def convert_logical(value: Bool) -> Int32: ... + +@overload("convert_logical") +def convert(value: Bool) -> Int32: ... +``` + +The new declaration makes `convert(np.bool_(...))` select +`convert_logical`. It does not create the native procedure; that procedure +must already exist and match the declaration. `@private` means users reach the +procedure only through `convert`. -Changing an overload set in an edited semantic `.pyi` must preserve distinct -supported signatures and valid native targets. Removing an overload removes -that Python call shape; it does not remove the native implementation. +If the concrete procedure is private in Fortran, keep +`@bind("convert")` on the overload so the native call goes through the public +generic. -## Defined Operators +For the complete add, remove, and binding rules, see +[Edit an Overload Set](../reference/pyi-contracts/functions-and-classes.md#edit-an-overload-set). -Defined operators use Python data-model slots only where Python has equivalent -syntax. Supported arithmetic, unary, comparison, reverse, and safe in-place -forms can therefore appear as normal Python operations such as `left + right` -or a reverse operation when the native specifics define that operand order. +--- -Named native operators without Python syntax become documented methods rather -than invented operators. +## Key Rules -## Defined Assignment +- Dispatch uses **exact** match on dtype, rank, and generated class. +- If no overload matches, a `TypeError` is raised. +- If two candidates have the same runtime signature, wrapper generation fails. +- Each `@overload` declaration links to exactly one concrete procedure. +- `@bind` changes the final native target, not the linked candidate contract. +- `@private` controls Python visibility only. -Python `=` rebinds a name and cannot invoke native defined assignment. x2py -exposes supported native assignment as an explicit mutating `assign(...)` -method that returns the same receiver object. +Type-bound generics, defined operators, and defined assignment become methods +on generated derived-type classes. They are introduced after ordinary methods +in Wrapping Derived Types. -Named generics and operator/assignment lowering are separate contracts even -though both use overload dispatch. +--- ## Limitations -- Generic constructor interfaces and overloaded runtime initialization are - blocked. -- Polymorphic results, mutable polymorphic arguments, arrays, pointer/allocatable - polymorphic scalars, and `class(*)` are blocked. -- Unsupported operands raise deterministic Python errors; x2py does not fall - back to a different specific. - -## Evidence And Troubleshooting - -Named and type-bound generic dispatch is exercised by -[`test_generic_interfaces.py`](../../../tests/wrapper/fortran/naming/test_generic_interfaces.py), -operator and assignment behavior by -[`test_defined_operators.py`](../../../tests/wrapper/fortran/naming/test_defined_operators.py), -and scalar inheritance dispatch by -[`test_inheritance.py`](../../../tests/wrapper/fortran/derived_types/test_inheritance.py). - -For `TypeError`, compare the argument dtype, rank, and class with generated -overloads. For generation-time ambiguity, rename or redesign the native call -shapes; declaration reordering is not a fix. +- Source generic interfaces are not inferred as constructors automatically. + Edited exact constructor overload sets are supported. +- Polymorphic (`class(*)`) arguments and results are blocked. +- Arrays of derived types and complex polymorphic cases are not supported yet. + +--- + +## Next + +- Continue with [Wrapping Derived Types](wrapping-derived-types.md) for + type-bound generics and operators +- See [Error Handling](error-handling.md) for dispatch errors diff --git a/docs/user/guide/index.md b/docs/user/guide/index.md index 371bb724d..be5b72c9a 100644 --- a/docs/user/guide/index.md +++ b/docs/user/guide/index.md @@ -1,59 +1,67 @@ --- title: User Guide +description: Detailed guides for wrapping Fortran code with x2py audience: users prerequisites: getting started -related: data-types.md, fortran-wrapper.md, ../language-support/index.md +related: data-types.md status: maintained +publication: reviewed --- # User Guide -The user guide continues from the completed -[Getting Started](../getting-started/index.md) workflow. Start with the datatype -mapping, then follow the workflow group that matches the native API you are -wrapping. Each page states the current supported subset, Python API shape, -limitations, troubleshooting route, and runtime evidence. +This section continues the [Getting Started](../getting-started/index.md) +workflow. Read it in sidebar order to move from basic values and procedures to +objects, storage, and advanced runtime behavior. + +--- ## Start Here -- [Data types](data-types.md): Fortran storage, semantic `.pyi` names, exact - NumPy dtypes, strings, arrays, and generated classes. -- [Wrapping functions](wrapping-functions.md) -- [Wrapping subroutines](wrapping-subroutines.md) -- [Wrapping modules](wrapping-modules.md) -- [Arrays](arrays.md) -- [Optional arguments](optional-arguments.md) -- [Generic interfaces](generic-interfaces.md) +- [Data Types](data-types.md) — Fortran types, semantic `.pyi` names, exact NumPy dtypes, strings, and arrays +- [Arrays](arrays.md) — Rank, shape, strides, contiguity, and layout rules +- [Strings](strings.md) — Immutable text, mutable byte storage, and string arrays +- [Wrapping Functions](wrapping-functions.md) +- [Wrapping Subroutines](wrapping-subroutines.md) +- [Wrapping Modules](wrapping-modules.md) +- [Optional Arguments](optional-arguments.md) +- [Generic Interfaces](generic-interfaces.md) +- [Wrapping Derived Types](wrapping-derived-types.md) + +--- -## Storage And Objects +## Storage and Objects - [Allocatables](allocatables.md) - [Pointers](pointers.md) -- [Wrapping derived types](wrapping-derived-types.md) -- [Memory management](memory-management.md) +- [Memory Management](memory-management.md) + +--- ## Runtime Behavior - [Callbacks](callbacks.md) - [Enumerations](enumerations.md) -- [Error handling](error-handling.md) +- [Raw Addresses](raw-addresses.md) — Advanced primitive, array, and fixed-string address boundaries +- [Error Handling](error-handling.md) -## Build And Deployment +--- + +## Building + +- [Building the Shared Library](building-shared-library.md) -- [Packaging](packaging.md) -- [Distribution](distribution.md) +--- + +**Important Note** -## Contract References +The recommended workflow starts from Fortran source. The generated semantic +`.pyi` file describes the Python interface and native call. Editing that file +lets you customize the wrapper without changing the native implementation. +This guide introduces useful edits on the pages where they matter. The +[editing reference](../reference/pyi-contracts/index.md) collects +the complete rules in one place. -- [Fortran wrapper guide](fortran-wrapper.md): complete contract and evidence - ledger for the generated runtime surface. -- [Editing semantic `.pyi` contracts](editing-semantic-pyi-contracts.md): - intentional changes to generated wrapper policy. -- [Semantic `.pyi` format](../reference/semantic-pyi-format.md): annotation and - metadata reference. -- [Language feature matrix](../language-support/feature-matrix.md): central - supported, partial, unsupported, and planned status. +--- -The workflow pages explain the normal source-driven wrapper. Edit a semantic -`.pyi` only after the generated behavior is understood and the native artifacts -needed by a `.pyi`-driven build are available. +Start with **[Data Types](data-types.md)**. diff --git a/docs/user/guide/memory-management.md b/docs/user/guide/memory-management.md index 466f73b8f..7b9a5a588 100644 --- a/docs/user/guide/memory-management.md +++ b/docs/user/guide/memory-management.md @@ -1,128 +1,203 @@ --- title: Memory Management +description: Ownership, live views, copies, and safe cleanup in x2py audience: users, advanced users -prerequisites: arrays, wrapping derived types -related: allocatables.md, pointers.md, editing-semantic-pyi-contracts.md +prerequisites: arrays +related: allocatables.md, pointers.md, wrapping-derived-types.md status: maintained +publication: reviewed --- # Memory Management -Ownership determines whether Python sees a value, copy, live view, or generated -native object; whether mutation reaches native storage; and which runtime is -responsible for destruction. x2py completes these decisions before wrapper -generation. Bridge and binding code consume the completed policy and do not -guess from datatype or intent. +x2py can give Python direct access to storage created by Fortran. This avoids +unnecessary copies, but Python must not use that storage after its owner +releases or replaces it. -## Ownership Vocabulary +Two questions keep these cases simple: -| Owner or transfer | Meaning | First complete example | -| --- | --- | --- | -| Python-owned value or copy | Python or NumPy releases detached storage after references are gone. | an ordinary array function result or `.copy()` of an extracted view | -| Caller-owned storage | The Python caller retains the exact object supplied to the call. | [`outputs.f90` output array](wrapping-subroutines.md#complete-output-example) | -| Wrapper-owned instance | A generated Python extension object owns one native derived instance. | [`points.f90` result](wrapping-derived-types.md#complete-derived-type-example) | -| Native-owned storage | Native module state or another native owner controls allocation and release. | [`allocations.f90` module handle](allocatables.md#complete-allocatable-example) | -| Borrowed view or child | Python refers to storage owned by a module or containing wrapper. | [`points.f90` nested child](wrapping-derived-types.md#complete-derived-type-example) | -| Detached copy (`snapshot_copy` policy) | Python receives copied current native state where an explicit value-copy policy requires it. Native-array-handle `to_numpy()` and derived module-object reads do not select this behavior. | Explicit copy-result contracts | -| Call-local association | Native code may refer to Python storage only during one wrapped call. | [`pointers.f90` input](pointers.md#complete-pointer-example) | - -Those linked pages contain the full source, build commands, and asserted -results. The examples are not repeated here so ownership differences remain -attached to one canonical source listing. - -## Core Invariants - -1. Exactly one owner destroys each owned native allocation. -2. Python-owned copies are independent of later native mutation. -3. Caller-owned arrays are never freed by x2py. -4. A borrowed child or component view retains its generated wrapper owner. -5. Owner retention does not protect a view from explicit native reallocation or deallocation. -6. A pointer declaration never proves ownership of its target. -7. Missing owner, lifetime, release, shape, dtype, mutability, nullability, or aliasing facts block generation. -8. Addressability is an object-origin fact: generated constructors allocate - pointer-backed instances, while pre-existing derived module objects need - either proved `Aliased` addressability or typed module-specific bridge - operations. A backend must not fabricate an address. -9. Native array descriptor state lives in `Allocatable[T[...]]` and - `Pointer[T[...]]` handles; borrowed NumPy views and detached NumPy copies are - explicit extraction results from `to_numpy()`. - -## Destruction Responsibilities - -| Value | Release responsibility | +1. Who owns the Python object? +2. Who owns the storage behind it? + +The answers are not always the same. + +## The Python Object And Its Storage + +An [allocatable](allocatables.md) or [pointer](pointers.md) handle is a Python +object that describes native array storage. The storage can belong to the +handle itself, a Fortran module, a parent object, or a separate pointer target: + +```python +handle = api.values +view = handle.to_numpy() +``` + +Python owns the `handle` and `view` objects. The storage visible through +`view`, however, may belong to a Fortran module, a generated result handle, or +another native object. + +Common cases are: + +| Value seen by Python | Who owns the storage? | | --- | --- | -| scalar, string, copy-return array, scalar pointer copied value | Python, NumPy, or its generated base capsule | -| caller-supplied NumPy array | Python caller | -| wrapper-owned derived instance | generated wrapper deallocator and native finalization | -| borrowed nested component | containing wrapper owner | -| allocatable or pointer array handle | containing wrapper, native module, or explicit x2py owner storage | -| borrowed view extracted from a handle | the handle's completed owner policy | -| call-local temporary | generated bridge before return | -| pointer target | explicit proved owner, never the pointer declaration alone | +| Ordinary Python value or independently created NumPy array | Python | +| `view.copy()` | Python | +| Fortran module variable | The Fortran module | +| Derived-type object constructed or returned by x2py | Its generated Python wrapper | +| Derived-type field that exposes native storage | Usually its parent object | +| Allocatable or pointer handle | Depends on where the handle came from and how its current storage was created | + +There must be one clear owner for every allocation. Other objects may view or +refer to that allocation, but they must not release it. + +--- + +## Live Views And Copies + +Calling `handle.to_numpy()` gives direct access to the handle's current native +storage: + +```python +view = handle.to_numpy() +if view is not None: + view[0] = 42.0 # changes the native storage +``` + +This is fast because no data is copied. It also means that the view is safe +only while the same native storage remains alive. + +Copy the data when it must survive a later native change: + +```python +view = handle.to_numpy() +saved = None if view is None else view.copy() + +api.replace_values() + +# Use saved here. Do not keep using view. +``` + +Reallocation, deallocation, pointer reassociation, resizing, or explicit +cleanup can make an older view invalid. Get a new view after such an operation. + +--- + +## Allocatables And Pointers + +Allocatable and pointer handles both describe native arrays, but they do not +have the same ownership rules: + +| Operation | Allocatable handle | Pointer handle | +| --- | --- | --- | +| Check current state | `allocated` | `associated` | +| View current data | `to_numpy()` | `to_numpy()` | +| Remove current storage | `deallocate()` releases the allocation when the operation is available | `deallocate()` releases only a target allocated through this pointer | +| Stop referring to storage without releasing it | Not applicable | `nullify()` | +| End a returned or caller-created handle | `close()` releases the descriptor and any remaining allocation | `close()` releases the descriptor, not the target | -Users do not call a generated `destroy()` method for ordinary wrapper-owned -objects. Explicit native allocation and deallocation routines remain normal -wrapped calls, but using one can invalidate previously borrowed storage. +A pointer association does not by itself make the pointer responsible for the +target. If a target was allocated through a pointer, call `deallocate()` before +`nullify()`, reassociating that pointer, or closing it. Otherwise, the target +may be left without an owner. -## Copies Versus Views +See [Allocatables](allocatables.md) and [Pointers](pointers.md) for their full +APIs and examples. -Use a copy when Python needs an independent lifetime: +--- + +## Closing Handles + +Caller-created handles and function-result handles have their own descriptor +storage. Their `close()` method permanently ends the handle: ```python -independent = borrowed_view.copy() +result.close() +assert result.closed ``` -This operation is ordinary NumPy behavior applied after obtaining the view from -the complete `allocations.f90` example. It is the safe boundary before a native -operation that may reallocate or deallocate the authoritative storage. - -Do not use `del view` as a native deallocation mechanism. Releasing a borrowed -Python object only releases the view and any owner-retaining Python reference; -it does not transfer native release responsibility. - -## Mutability And Replacement - -- Ordinary caller-owned arrays **mutate in place**; -- Python strings use **replacement** because `str` is immutable; -- Allocatable array descriptors use **handles** because native allocation identity may change; -- Ordinary non-descriptor array/function results use **copy-return**; -- Allocatable array results use wrapper-owned handles whose finalizer releases - x2py-owned descriptor storage; -- Pointer-array handle results stop wrapper planning until owner storage, target - lifetime, descriptor extraction, and destroy behavior are implemented; -- plain and `Aliased` derived module variables remain live native-owned objects - through module-specific or address-backed mechanisms respectively; and -- Borrowed views extracted from handles **share native storage** until native - invalidation. - -Native-array-handle extraction remains live-view-or-`None`; callers use -`.copy()` on an extracted NumPy view for independent array storage. - -Return projection and ownership are one contract. An edited `.pyi` cannot ask -for copy-return without a projected replacement, or combine immutable storage -with a writable borrowed view. - -## Policy Source Of Truth - -Generated source facts enter semantic IR, then post-IR policy completion chooses -object kind, ownership, transfer, destruction, mutability, nullability, output -projection, release responsibility, storage mode, getter behavior, native -setter assignment, and Python setter exposure. Unsupported or contradictory -combinations stop before wrapper lowering. - -Advanced users can inspect or edit explicit `Ownership(...)`, `Transfer(...)`, -and `Destruction(...)` metadata. Editing Semantic `.pyi` Contracts and the -semantic format reference explain the editable forms later. Metadata can select -an implemented policy; it cannot invent a backend path. - -## Evidence And Troubleshooting - -The same array concept under native-owned, wrapper-owned, and Python-owned -lifetimes is exercised by -[`test_ownership_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_ownership_contracts.py). -Exactly-once wrapper finalization is exercised by -[`test_borrowed_finalizers.py`](../../../tests/wrapper/fortran/derived_types/test_borrowed_finalizers.py). - -Treat use-after-deallocation risk as an application lifetime bug, not a signal -to guess ownership. Copy before native reallocation. Runtime Issues later -covers reproducible lifetime or cleanup symptoms. +Do not use the handle after calling `close()` on it. These handles close +automatically when Python no longer uses them, so call `close()` yourself only +when the resource must be released immediately. + +Module and derived-field handles expose descriptors belonging to the Fortran +module or parent object. Calling `close()` on one does nothing: it does not +close the handle or release that storage. + +The resource released by `close()` depends on the handle: + +- Closing a returned or caller-created allocatable handle releases its descriptor and any + allocation it still contains. +- Closing a returned or caller-created pointer handle releases only its descriptor. Its target has + a separate lifetime. + +--- + +## Sharing Handles Between Extensions + +The same allocatable or pointer handle can be passed between separately built +x2py extensions. Their matching arguments must have the same descriptor kind, +element type, and rank. + +The handoff does not copy array data. Both extensions must use compatible x2py +versions, the same Fortran compiler toolchain, and compatible Fortran +runtimes. An incompatible handle is rejected. Sharing a pointer handle does +not extend the lifetime of its target. + +--- + +## Passing Objects To Functions + +Passing an object to a wrapped function does not give the function ownership of +that object. + +- A writable NumPy array remains the same Python array, although the function + may change its elements. +- A writable allocatable handle remains the same handle, although the function + may allocate, deallocate, or replace its current storage. +- A writable pointer handle remains the same handle, although the function may + change its association. + +If a call may change native storage, finish using or copy any existing views +before the call. Ask the handle for a new view afterward. + +--- + +## Derived Objects And Fields + +A generated wrapper for a derived-type object can own a native instance. The +wrapper releases that instance automatically when it is finalized. + +Derived module variables remain live objects. +The Fortran module owns their storage. Python only accesses it. + +A field returned from that object may refer to storage inside its parent. The +generated field object keeps its parent alive, but it cannot stop native code +from replacing or releasing the field's storage. Such a change can invalidate +existing views. + +See [Wrapping Derived Types](wrapping-derived-types.md) for construction, +fields, and function arguments. + +--- + +## Safety Checklist + +- Check `allocated` on an allocatable handle or `associated` on a pointer handle + before reading through it. +- Treat every result of `to_numpy()` as a live view. +- Copy a view before a call that may replace or release its storage. +- Call `deallocate()` on an allocatable handle only when it may release that + allocation. Call it on a pointer handle only for a target allocated through + that pointer. +- Call `nullify()` on a pointer handle to remove its association without + destroying the target. +- Do not use a returned or caller-created handle after `close()`. +- Synchronize access when another thread may change the same native storage. + +--- + +## Next + +- Continue with [Callbacks](callbacks.md). +- Return to [Allocatables](allocatables.md), [Pointers](pointers.md), or + [Wrapping Derived Types](wrapping-derived-types.md) for their full APIs. diff --git a/docs/user/guide/optional-arguments.md b/docs/user/guide/optional-arguments.md index 2cca55a19..281543242 100644 --- a/docs/user/guide/optional-arguments.md +++ b/docs/user/guide/optional-arguments.md @@ -1,19 +1,21 @@ --- title: Optional Arguments +description: How x2py handles Fortran `optional` arguments — inputs, outputs, arrays, and None behavior audience: users prerequisites: wrapping subroutines, data types related: generic-interfaces.md, arrays.md, error-handling.md status: maintained +publication: reviewed --- # Optional Arguments -Supported optional scalars, arrays, strings, derived types, outputs, and inout -arguments preserve native `present(...)` behavior. The generated Python -signature places required parameters before optional parameters without -changing native argument positions. +x2py supports optional scalars, arrays, strings, derived types, and outputs. +It preserves native `present(...)` semantics. -## Complete Optional Example +--- + +## Complete Example Create `optional.f90`: @@ -21,6 +23,7 @@ Create `optional.f90`: module adjustments implicit none contains + integer(4) function adjust(value, offset) result(output) integer(4), intent(in) :: value integer(4), intent(in), optional :: offset @@ -28,19 +31,22 @@ contains output = value if (present(offset)) output = output + offset end function adjust -end module adjustments -``` -Inspecting `optional.f90` prints this optional-input contract: + subroutine make_values(size, count, values) + integer(4), intent(in) :: size + integer(4), intent(out) :: count + real(8), intent(out), optional :: values(size) + integer(4) :: index -```python -from x2py.contracts import Addr, Arg, Int32, native_call + count = size + if (present(values)) then + do index = 1, size + values(index) = real(index, 8) + end do + end if + end subroutine make_values -@native_call([Addr(Arg(0)), Addr(Arg(1))]) -def adjust( - value: Int32, - offset: Int32 = ... -) -> Int32: ... +end module adjustments ``` Build it: @@ -49,76 +55,107 @@ Build it: python3 -m x2py optional.f90 --out-dir build/optional ``` -Omission and explicit `None` both make `offset` absent: +--- + +## Usage in Python ```python import sys - import numpy as np sys.path.insert(0, "build/optional") -import optional +from optional.adjustments import adjust, make_values -api = optional.adjustments -assert api.adjust(np.int32(5)) == np.int32(5) -assert api.adjust(np.int32(5), None) == np.int32(5) -assert api.adjust(np.int32(5), offset=np.int32(3)) == np.int32(8) +print(adjust(np.int32(5))) # 5 (omitted) +print(adjust(np.int32(5), None)) # 5 (explicit None) +print(adjust(np.int32(5), np.int32(3))) # 8 (provided) +print(adjust(np.int32(5), offset=np.int32(10))) # 15 (keyword) ``` -## Omission And `None` +--- + +## Key Rules + +- For ordinary optional inputs, **omission** and `None` both mean the argument + is **not present** to Fortran. +- Providing a concrete value makes the argument **present**. +- Use **keyword arguments** when skipping earlier optional parameters. +- Optional arrays and derived types also accept `None` to indicate absence. +- Optional `intent(out)` / `intent(inout)` arguments remain visible in Python + so you can control `present(...)`. +- An optional argument without `intent` uses the same conservative + `intent(inout)` behavior when present. + +### Scalar Allocatables And Pointers -For a Python-visible optional input, omission and explicit `None` both mean the -native actual argument is absent. The `adjust` calls above show omission, -explicit `None`, and a concrete keyword value. +For an optional scalar allocatable or pointer, omission and `None` have +different meanings: -A concrete value means the native argument is present. Use keywords when skipping -an earlier optional argument; do not depend on native declaration order after -required and optional Python parameters have been normalized. +| Python call | What Fortran receives | +| --- | --- | +| `func()` | The argument is absent: `present(value)` is false. | +| `func(None)` | The argument is present but unallocated or unassociated. | +| `func(value)` | The argument is present with `value`. | -## Optional Arrays And Objects +This is the only scalar optional case where explicit `None` does not mean +absence. The scalar crosses the call as a value, not as a persistent handle. -An optional array still requires exact dtype, rank, shape, layout, alignment, -and writeability when supplied. `None` means no native argument; it does not -mean a zero-sized array. An optional derived-type argument accepts `None` or an -instance of the required generated class. +--- + +## Optional Outputs + +An optional ordinary output remains visible in the Python call. This lets the +caller decide whether the native routine receives it. + +Pass writable storage to make `values` present: + +```python +values = np.empty(3, dtype=np.float64) +count = make_values(np.int32(3), values) + +print(count) # 3 +print(values) # [1. 2. 3.] +``` -## Optional Native Outputs +Omit the argument, or pass `None`, to make it absent: -Optional `intent(out)` and `intent(inout)` dummies stay Python-visible so the -caller controls native `present(...)`: +```python +omitted_count = make_values(np.int32(3)) +none_count = make_values(np.int32(3), None) -- a supplied optional scalar output uses mutable rank-zero storage such as - `Int32[()]`, mutates that storage, and returns it when projected; -- a supplied optional output array is mutated and returned as documented; -- an absent optional output contributes `None` to its result position; -- an optional inout argument mutates normally when supplied and does nothing - when absent; and -- a hidden `Return(...)` output is not caller-optional because the wrapper - requests it with generated temporary storage on every call. +print(omitted_count) # 3 +print(none_count) # 3 +``` -Always review the generated return annotation when optional outputs are mixed -with required outputs. +`count` is a required scalar output, so it is always returned. `values` is +caller-owned mutable storage, so it is never added to the result. -## Defaults +For optional ordinary array outputs: -The generated Python default is normally `None`, meaning native absence. x2py -does not invent a native default value from a Python literal unless the semantic -contract explicitly defines that behavior. A native procedure remains -responsible for its own `present(...)` branch. +- Supplying writable storage mutates that array in place. +- Passing `None` or omitting it makes the native dummy absent. +- Presence does not add an array-or-`None` position to the result. +- A routine with only optional ordinary array outputs returns `None`, whether + those arrays are present or absent. -## Unsupported Combinations +Optional scalar derived-type outputs follow the same in-place rule as arrays. +For an optional scalar allocatable or pointer output, omit the argument to make +it absent. Pass `None` to make it present without an initial allocation or +association. If its updated value is returned, Python receives a scalar or +`None`, not a handle. -Optional passed procedures, procedure pointers, and combinations without a -complete native presence and ownership contract make wrapper planning fail. x2py -does not convert an unsupported optional form into an always-present argument -or silently drop it. +--- -## Evidence And Troubleshooting +## Limitations + +- Optional procedure pointers and passed procedures are not yet supported. +- x2py does not invent default values. The Fortran procedure handles missing + arguments. + +--- -Optional scalar, array, string, derived, output, and inout behavior is exercised -by -[`test_optional_arguments.py`](../../../tests/wrapper/fortran/function_calls/test_optional_arguments.py). +## Next -Use [Wrapping Subroutines](wrapping-subroutines.md) for result projection and -the later Error Handling page when an unsupported optional combination stops at -wrapper planning. +- Continue with [Generic Interfaces](generic-interfaces.md). +- For optional outputs and memory, see [Error Handling](error-handling.md) and + [Memory Management](memory-management.md). diff --git a/docs/user/guide/packaging.md b/docs/user/guide/packaging.md deleted file mode 100644 index 93f9f5ed0..000000000 --- a/docs/user/guide/packaging.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Packaging -audience: users, packagers -prerequisites: common beginner workflow -related: distribution.md, ../reference/cli-commands.md, ../tutorials/packaging.md -status: maintained ---- - -# Packaging - -x2py currently produces an importable native extension and its build artifacts; -it does not provide a stable Python wheel backend or project template. The -supported packaging workflow is therefore local project integration: keep the -native source and Python tests under version control, rebuild into an explicit -directory, and treat generated native artifacts as replaceable build output. - -## Complete Local Project Example - -Reuse `scale.f90`, whose complete source is first shown in the -[README Quick Start](../../../README.md#quick-start). Place that file in this -simple project: - -```text -scale-project/ - src/ - scale.f90 - build/ - python/ - check_scale.py -``` - -Build from the project root: - -```bash -python3 -m x2py src/scale.f90 --out-dir build/scale -``` - -Put the following result check in `python/check_scale.py`: - -```python -import sys -import numpy as np - -sys.path.insert(0, "build/scale") -import scale - -assert scale.scale(np.float64(3.0), np.float64(2.5)) == np.float64(7.5) -``` - -Run it from the project root: - -```bash -python3 python/check_scale.py -``` - -No output means the assertion passed. - -## Generated Package Shape - -The extension module name normally comes from the first source filename. -Contained native modules become child Python modules; standalone procedures -remain at the extension root. `--out NAME` selects a different extension name. - -Use the selected output directory as the import location during development. -The shared-library filename is platform-specific, so avoid hard-coding a suffix -outside project-specific build scripts. - -## Generated Artifacts - -An output directory can contain native object and module files, generated -wrapper sources, header-only native binding support, build metadata, and the importable extension. -These files are build products. Do not edit them as the source of the public -API; change the native source or an intentional semantic `.pyi` contract. - -The extension is tied to its Python implementation, NumPy ABI, platform, -architecture, compiler ABI, and linked native dependencies. Merely copying it -into another project is not a portable packaging guarantee. - -## Editable Makefile - -Generate a Makefile when a local build needs inspectable commands or controlled -flags: - -```bash -python3 -m x2py generate --makefile src/scale.f90 \ - --out-dir build/scale - -make -f build/scale/Makefile.x2py X2PY_FFLAGS=-O3 X2PY_CFLAGS=-O3 -``` - -`generate --makefile` selects the editable wrapper-build mode directly. Makefile mode -and verbose direct compilation are separate modes. The generated -Makefile expects GNU Make and a POSIX-style shell. Semantic `.pyi` Makefile -builds also write `x2py-build.json`, which can regenerate or replay the build. - -## Rebuild Policy - -Rebuild when source, source order, compiler, flags, Python, NumPy, native -dependencies, or the semantic contract changes. For a contract-changing build, -remove the selected output directory first so stale objects and modules cannot -mask the new build: - -```bash -rm -rf build/scale -python3 -m x2py src/scale.f90 --out-dir build/scale -``` - -Keep sources, explicit contracts, build commands, and Python assertions under -version control. Keep `build/` out of version control unless a release process -deliberately captures platform-specific artifacts. - -## Import Paths - -During local development, add the build directory to `sys.path`, set -`PYTHONPATH`, or run Python from a location where the extension is importable. -x2py does not currently install the extension into a project package or manage -editable Python installs automatically. - -## Limitations - -- No stable wheel-building backend or generated `pyproject.toml` integration. -- No automatic repair or bundling of external native shared libraries. -- No cross-platform artifact promise. -- No automatic native dependency discovery or source reordering. -- No guarantee that a copied extension imports under another Python or NumPy ABI. - -## Evidence And Troubleshooting - -Output names, directories, native build plans, verbose mode, and Makefile option -validation are exercised by -[`test_build_modes.py`](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py). -Multi-source package shape is exercised by -[`test_multi_source_builds.py`](../../../tests/wrapper/fortran/multiple_files/test_multi_source_builds.py). - -For compile or link failures, rerun with `--verbose` and inspect the emitted -native commands; Build Issues expands that diagnosis later. Distribution later -explains the requirements for sharing an artifact with another machine or -environment. diff --git a/docs/user/guide/pointers.md b/docs/user/guide/pointers.md index ed0d495cc..9cc1d02ca 100644 --- a/docs/user/guide/pointers.md +++ b/docs/user/guide/pointers.md @@ -1,328 +1,423 @@ --- title: Pointers +description: How x2py handles Fortran `pointer` variables, results, fields, and descriptors audience: advanced users -prerequisites: arrays, memory management -related: allocatables.md, memory-management.md, ../reference/semantic-pyi-format.md +prerequisites: arrays, allocatables +related: allocatables.md, memory-management.md status: maintained +publication: reviewed --- # Pointers -A Fortran pointer does not identify the target owner. Scalar pointers cross -procedure boundaries as ordinary nullable Python values plus bridge descriptor -metadata. Pointer arrays use `Pointer[T[...]]`, which is a Python handle to -native pointer association state, not a NumPy array. +A Fortran pointer describes an association with target storage. The pointer +descriptor records whether a target is present and, for arrays, its address, +shape, and strides. It does not by itself say who owns that target. + +## Key Concepts + +- A pointer descriptor refers to target storage; it does not own that storage + by default. +- Scalar pointers appear as `T | None`; array pointers use live + `Pointer[T[...]]` handles. +- `associated` describes association, not ownership or target lifetime. +- NumPy arrays returned by `to_numpy()` are live views, not copies. +- Reassociation, resizing, or deallocation can invalidate existing views. +- `associate(other)` makes two pointer handles refer to the same target without + copying it. +- Use `deallocate()` only if this pointer was used to create its current target + with `allocate()`. Otherwise, use `nullify()`. +- `close()` releases a returned or caller-created descriptor, not its target. -## Array Handles +--- + +## When To Use A Pointer Handle -`Pointer[T[...]]` is the active pointer-array spelling in semantic `.pyi` -contracts: +Use `Pointer[T[...]]` when the native callable needs the pointer descriptor: ```python -from x2py.contracts import Float64, Int32, Pointer +from x2py.contracts import Float64, Pointer -values: Pointer[Float64[:]] +module_values: Pointer[Float64[:]] -def reassociate(values: Pointer[Float64[:]], target: Pointer[Float64[:]]) -> None: ... -def scale(values: Float64[:], factor: Int32) -> None: ... +def inspect_pointer(values: Pointer[Float64[:]]) -> Float64: ... ``` -The handle owns association state. An unassociated descriptor is still a -present handle: `p.associated is False`, `p.shape is None`, and -`p.to_numpy() is None`. `| None` means the handle object itself may be absent -for an optional native dummy, making native `present(values)` false: +Use ordinary `T[...]` when the callable needs only array data: ```python -def maybe_use(values: Pointer[Float64[:]] | None = ...) -> None: ... +def sum_values(values: Float64[:]) -> Float64: ... ``` -That spelling is valid only for optional callable arguments. Do not use -`Pointer[T[...]] | None` for module variables, derived-type fields, or function -results; those surfaces return a present handle, and unassociated state is -represented inside that handle. - -Passing a handle to `Pointer[T[...]]` passes the native pointer descriptor. -Passing an associated handle to a normal `T[...]` parameter uses ordinary -Fortran array-actual semantics by handing off the handle's native array data -facet. It is not an implicit call to `.to_numpy()`. A normal `T[...]` parameter -rejects an unassociated pointer handle because there is no valid array actual -to pass; an associated zero-length target remains valid. The pointer/shape -handoff accepts only targets proved contiguous. A noncontiguous target is -rejected until descriptor-backed stride handoff is selected; x2py never treats -such a target as contiguous. - -Plain NumPy arrays are accepted by normal `T[...]` array parameters. They are -rejected for `Pointer[T[...]]` descriptor parameters because a NumPy array does +An associated pointer handle may satisfy an ordinary array parameter when its +dtype, rank, shape, layout, and contiguity meet that parameter's contract. A +plain NumPy array cannot satisfy a `Pointer[T[...]]` parameter because it does not carry a native pointer descriptor. -`p.to_numpy()` is the explicit extraction operation. It returns `None` when the -handle is unassociated. When descriptor extraction is supported, it returns the -current target view and may expose strided targets. It never creates an -automatic detached snapshot or copy. If no supported live-view mechanism can -expose the current target, policy completion or wrapper planning fails explicitly; -x2py does not guess compiler-specific descriptor layout or fall back to a copy. - -Any NumPy view returned by `p.to_numpy()` is tied to the pointer target at the -time of extraction. After native code nullifies, reassociates, deallocates, or -otherwise changes that target, discard older views and call `p.to_numpy()` -again. Accessing a stale view is unsupported and may crash. Users who need -independent storage must call `.copy()` before the target-changing operation. -Each fresh extraction inspects the current descriptor and starts at the -target's current native lower bounds rather than assuming a fixed Fortran lower -bound. +--- -`p.nullify()` is the default pointer descriptor operation. `allocate(shape)`, -`deallocate()`, and `resize(shape)` are exposed only when completed pointer -policy explicitly allows those operations. A pointer handle does not imply -target ownership. +## Pointer Array Handle API -## Scalar Pointer Projections +`Pointer[T[...]]` is the type annotation. At runtime, generated Python APIs use +a `PointerArray`. You can also create an unassociated handle when a routine +needs a present pointer descriptor that it will associate: -Supported scalar pointer dummies use ordinary nullable Python values. The -semantic `.pyi` uses `Pointer(...)` inside `@native_call` to construct or read -the native pointer descriptor without exposing a Python pointer handle. +```python +import x2py.contracts as xc -For example: +target = xc.Pointer[xc.Float64[:]]() +assert target.associated is False -```fortran -real(8), target :: target_scale +api.choose_target(target) +assert target.associated is True +``` -subroutine update_pointer(scale) - real(8), pointer, intent(inout) :: scale +The annotation supplies the element dtype and rank. The handle creates its +native descriptor storage when first passed to a matching writable argument. +It stays the same Python object after the call. +`Pointer[Float64]()` is not supported because scalar pointers cross the Python +boundary as values rather than array handles. + +| Member | Type | Behavior | +| --- | --- | --- | +| `associated` | `bool` | Whether the descriptor currently has a target. | +| `shape` | `tuple[int, ...] \| None` | Current target dimensions, or `None` when unassociated. | +| `dtype` | `numpy.dtype` | Declared target element type. | +| `rank` | `int` | Declared number of dimensions. | +| `to_numpy()` | `numpy.ndarray \| None` | A live target view, or `None` when unassociated. | +| `associate(other)` | `(PointerArray) -> None` | Makes this pointer's association match `other` without copying data. | +| `nullify()` | `() -> None` | Removes the association without destroying the target. | +| `allocate(shape)` | `(int \| Sequence[int]) -> None` | Creates and associates a target for an unassociated pointer. | +| `deallocate()` | `() -> None` | Destroys the current target if this pointer was used to allocate it. | +| `resize(shape)` | `(int \| Sequence[int]) -> None` | Replaces the current target when `deallocate()` is valid. | +| `close()` | `() -> None` | Permanently releases returned or caller-created descriptor storage; it does not deallocate the target. It does nothing on a module or field handle. | +| `closed` | `bool` | Whether a closable handle has been closed. | + +`associate()` and `nullify()` are available by default. A handle may also +support allocation, target deallocation, resizing, and NumPy extraction. +An unavailable operation raises `NotImplementedError`. - if (associated(scale)) then - scale = scale + 1.0_8 - else - scale => target_scale - end if -end subroutine update_pointer +--- -function maybe_pointer(enabled) result(scale) - integer(4), intent(in) :: enabled - real(8), pointer :: scale +## Associate Two Pointers - nullify(scale) - if (enabled /= 0) scale => target_scale -end function maybe_pointer +```python +p1 = xc.Pointer[xc.Float64[:]]() +p1.associate(p2) ``` -The corresponding semantic contract keeps scalar pointer values nullable and -uses `Pointer(...)` only for native descriptor projection: +Both pointers must have the same dtype and rank. If `p2` is associated, both +pointers refer to the same target. If `p2` is unassociated, `p1` becomes +unassociated. No data is copied. + +Any previous association of `p1` is removed without destroying its old target. +If `p1` is responsible for a target created with `p1.allocate()`, call +`p1.deallocate()` before reassociating it. Otherwise, that memory may be left +without a pointer that can release it. + +--- + +## Nullify, Deallocate, And Close + +| Operation | What it releases | Handle afterward | +| --- | --- | --- | +| `nullify()` | This descriptor's association. It does not destroy the target. | Open and usable, with `associated == False`. | +| `deallocate()` | A target this pointer was used to allocate. | Open and usable, with `associated == False`. | +| `close()` | This handle's descriptor storage. It does not destroy the target. | Permanently closed and unusable. | + +Returned and caller-created descriptors close automatically when Python no +longer uses them. Call `close()` explicitly only when immediate descriptor +release matters. It never destroys the pointer target because the descriptor +and target have separate lifetimes. + +Calling `close()` on a module or field pointer handle does nothing. It leaves +the descriptor, target, and handle unchanged. + +--- + +## Where Handles Come From + +### Module Variables And Derived Fields + +A module handle observes the live module pointer descriptor. A field handle +retains its parent wrapper and observes the live pointer component inside it. +Native reassociation is visible through the same Python handle: ```python -from x2py.contracts import Addr, Arg, Float64, Int32, Pointer, Return, Returns, native_call +p = api.values +assert not p.associated -@native_call([Pointer(Arg(0))]) -def update_pointer( - scale: Float64 | None, -) -> Returns["scale", Float64] | None: ... +api.associate_values() +assert p.associated -@native_call([Addr(Arg(0))], result=Pointer(Return(0))) -def maybe_pointer(enabled: Int32) -> Float64 | None: ... +api.choose_different_values() +print(p.shape) # reflects the new target ``` -Passing `None` creates a present but unassociated call-local descriptor. -Omitting a defaulted scalar descriptor argument creates native optional absence, -so `present(scale)` is false. Passing a value creates a present associated -call-local descriptor. An unassociated function result or projected output -becomes `None`. Ordinary scalar projection rules remain unchanged: `intent(out)` -uses `Pointer(Return("name", j))`, and `intent(inout)` uses `Pointer(Arg(i))` -plus a matching `Returns["name", T] | None` readback. Scalar pointer values do -not expose a handle API. +### Function Results + +A pointer-array function result becomes a returned `PointerArray`. The handle +has persistent descriptor storage, but the target can belong to another +object: + +```python +p = api.selected_values(True) +if p.associated: + print(p.shape) +``` + +An unassociated native result is a present handle with `associated == False`. +It is not returned as `None`. + +### Output And Inout Arguments + +A nonoptional pointer-array `intent(out)` does not consume an incoming +association, so it is hidden and returned as a new handle: + +```python +p = api.select_values() +``` -Use a default only when the native scalar dummy is optional: +An optional `intent(out)` remains visible so omission can preserve native +`present(...)` behavior. Pointer-array `intent(inout)` also remains visible +because its incoming association is part of the call: ```python -@native_call([Pointer(Arg(0))]) -def update_pointer(scale: Float64 | None = ...) -> None: ... +p = api.values +api.reassociate_values(p) +assert p.associated # the same descriptor was updated in place ``` -This scalar rule is separate from array pointer handles. Array arguments use -`Pointer[T[...]] | None` only for an optional absent handle; unassociated array -state stays inside a present handle. +For an optional pointer descriptor, omission or `None` means the native +argument is absent. Passing an unassociated handle makes the argument present +but unassociated. + +--- -## Complete Pointer Example +## Complete Module Example Create `pointers.f90`: ```fortran module pointers_api implicit none - real(8), target :: storage(3) = [1.0_8, 2.0_8, 3.0_8] + real(8), target :: storage(6) = [1, 2, 3, 4, 5, 6] real(8), pointer :: values(:) => null() contains + subroutine associate_values() - values => storage + values => storage(1:6:2) end subroutine associate_values - real(8) function sum_array(actual) result(total) - real(8), intent(in) :: actual(:) - total = sum(actual) - end function sum_array - - real(8) function sum_pointer(actual) result(total) - real(8), pointer, intent(in) :: actual(:) - - if (associated(actual)) then - total = sum(actual) + real(8) function sum_pointer(p) result(total) + real(8), pointer, intent(in) :: p(:) + if (associated(p)) then + total = sum(p) else total = -1.0_8 end if end function sum_pointer + end module pointers_api ``` -The generated semantic contract distinguishes the module descriptor, an -ordinary array parameter, and a pointer-descriptor parameter: +Build and use it: + +```bash +python3 -m x2py pointers.f90 --out-dir build/pointers +``` ```python -from x2py.contracts import ( - Aliased, - Annotated, - Destruction, - Float64, - Ownership, - Pointer, - PointerAssociation, - Transfer, -) - -storage: Annotated[Float64[3], Aliased] -values: Annotated[Pointer[Float64[:]], PointerAssociation("runtime")] - -def associate_values() -> None: ... -def sum_array(actual: Float64[::]) -> Float64: ... -def sum_pointer( - actual: Annotated[ - Pointer[Float64[:]], - PointerAssociation("runtime"), - Ownership("caller"), - Transfer("call_local"), - Destruction("none"), - ] -) -> Float64: ... +import sys + +sys.path.insert(0, "build/pointers") +import pointers.pointers_api as pointers_api + +handle = pointers_api.values +assert not handle.associated +assert pointers_api.sum_pointer(handle) == -1.0 + +pointers_api.associate_values() +assert handle.associated +assert pointers_api.sum_pointer(handle) == 9.0 + +handle.nullify() +assert not handle.associated ``` -Build it: +--- -```bash -python3 -m x2py pointers.f90 --out-dir build/pointers +## Contiguous And Strided Targets + +A pointer may describe a whole array, a contiguous section, or a strided +section: + +```fortran +real(8), target :: storage(6) = [10, 20, 30, 40, 50, 60] +real(8), pointer :: selected(:) + +selected => storage(1:6:2) ``` -Then verify descriptor state, descriptor passing, and normal array-actual -handoff from the same handle: +The NumPy view preserves that layout: ```python -import sys +view = api.selected.to_numpy() +print(view) # [10. 30. 50.] +print(view.shape) # (3,) +print(view.strides) # (16,) for eight-byte elements -import numpy as np +view[1] = 99.0 # updates storage(3) +``` -sys.path.insert(0, "build/pointers") -import pointers +A strided pointer can be passed to a pointer-descriptor parameter. Passing the +same handle to an ordinary array parameter that requires contiguous data is +rejected. + +--- -api = pointers.pointers_api -handle = api.values +## Safety Checklist -assert handle.associated is False -assert handle.shape is None -assert api.sum_pointer(handle) == np.float64(-1.0) +Pointer safety depends on the target owner and lifetime, not only on descriptor +state. -api.associate_values() -assert handle.associated is True -assert handle.shape == (3,) -assert api.sum_pointer(handle) == np.float64(6.0) -assert api.sum_array(handle) == np.float64(6.0) +### Check Association And Lifetime -handle.nullify() -assert handle.associated is False +```python +view = p.to_numpy() +view[0] = 1.0 # NOT OK: view may be None +``` + +```python +if p.associated: + view = p.to_numpy() + if view is not None: + view[0] = 1.0 +``` + +Association is necessary but cannot prove that an externally managed target is +still alive. Native code must not leave a pointer associated with expired +storage. + +### Do Not Return A Pointer To Expired Local Storage + +```fortran +function invalid_result() result(values) + real(8), target :: local_values(3) + real(8), pointer :: values(:) + values => local_values ! NOT OK: local_values expires on return +end function invalid_result +``` + +Putting the pointer inside a returned derived object does not repair this +native lifetime error. + +### Copy Or Discard Views Before Target Changes + +```python +view = p.to_numpy() +saved = None if view is None else view.copy() +api.point_at_different_storage() +current = p.to_numpy() +``` + +Do not use `view` after target deallocation, reassociation, resizing, or +reallocation behind the pointer. Extract `current` for the new target. The +independent `saved` copy remains safe. + +### Deallocate Only What This Pointer Allocated + +```python +p.allocate(10) +p.deallocate() +``` + +The `allocate()` may be called from Python or from a native routine using the +same pointer. If the pointer was only associated with existing storage, use +`nullify()` instead: + +```python +p.nullify() # does not destroy the target +``` + +Do not use `nullify()`, `associate()`, or `close()` while `p` is responsible +for an allocated target. The target remains allocated and may become +unreachable. Use `p.deallocate()` first. + +Use `resize()` only in the same cases where `deallocate()` is valid. + +### Nullifying One Pointer Does Not Change Other Pointers + +```python +first = api.first_pointer +second = api.second_pointer +assert first.associated and second.associated + +first.nullify() +assert second.associated +``` + +`nullify()` removes only `first`'s association. It does not destroy the target +or change `second`. Deallocating their shared target makes every pointer to +that target invalid. + +### Do Not Keep Using A Closed Handle + +```python +view = returned_pointer.to_numpy() +returned_pointer.close() +returned_pointer.shape # NOT OK: the descriptor has been released +``` + +`close()` releases a returned descriptor but never deallocates its target. +An existing NumPy view may still refer to the target, but its safety now depends +entirely on that target's separate owner and lifetime. Do not use the closed +handle to reason about the view. + +### Respect Contiguity Requirements + +```python +strided = api.selected_slice +api.requires_contiguous_array(strided) # NOT OK: rejected before the call +``` + +Pass the descriptor to a pointer parameter, or make an explicit contiguous +copy when ordinary array data is required: + +```python +view = strided.to_numpy() +copy = None if view is None else view.copy(order="F") +``` + +### Synchronize Target Changes + +```python +view = p.to_numpy() +# Another thread reassociates or deallocates p here. +value = view[0] # NOT OK without native synchronization ``` -The ordinary `sum_array` call uses the handle's valid contiguous array actual; -it does not call `to_numpy()`. The descriptor-typed `sum_pointer` call requires -the pointer handle and can observe unassociated state. Add explicit pointer -policy when public NumPy extraction or ownership-changing operations are -required. - -## Call Compatibility - -A normal `T[...]` array parameter may accept a plain NumPy array or an -associated `Pointer[T[...]]` handle. The NumPy path passes caller-owned array -storage. The handle path validates pointer association, dtype, rank, shape, -layout, and mutability, then passes the handle's native array actual to the -normal native array dummy. The two paths share validation policy but remain -separate implementation methods. - -If the user writes `api.scale(p.to_numpy())`, that is an explicit ndarray path. -The returned value from `to_numpy()` follows ordinary ndarray validation, -including rejection of `None` and read-only arrays when writable native storage -is required. - -## Pointer Results - -An associated pointer scalar result becomes a copied Python value. An -unassociated scalar result becomes `None`. - -Pointer-array handle results remain blocked until x2py has stable owner storage, -target lifetime, descriptor extraction, and generated destroy behavior for the -returned handle. The wrapper does not silently fall back to a detached NumPy -copy for `Pointer[T[...]]` results. - -## Pointer Fields And Module Variables - -Pointer-backed array fields and module variables expose `Pointer[T[...]]` -handles. Their runtime Python class is `PointerArray`. A scalar pointer to a -derived object instead returns its generated live wrapper or `None`. -The containing object or module does not automatically own the pointer target. -Derived-field handles keep the parent wrapper alive for descriptor access, but -that retention is not target ownership. Plain `Pointer[T[...]]` has a default -conservative handle policy for association inspection and legal descriptor -operations. Generated field operations address the component through its parent -wrapper, and generated module operations address the native module variable. -Neither path invents target ownership or enables ownership-changing operations -that completed pointer policy did not allow. - -An associated scalar derived module pointer can be passed to an ordinary, -target, input-only pointer, or value dummy through its current target. For a -reassociable pointer dummy, x2py uses a typed local pointer holder and restores -the final association—associated, reassociated, allocated, disassociated, or -deallocated—to the module pointer exactly once. A wrapper-owned pointer result -uses the same persistent holder component directly. The holder owns its -association variable, not an unknown target, and its destructor never -deallocates native-owned target storage. Nullification or reassociation makes an -older payload proxy stale, and later field access raises `ReferenceError`. - -The later Wrapping Derived Types guide gives the canonical “Scalar Actuals And -Native Dummies” matrix, including the `INTENT(IN)` exception for nonpointer -actuals, empty-state behavior, module transactions, and multi-argument cleanup. - -## Unsupported Forms - -- pointer array `intent(out)` and `intent(inout)` reassociation without a - completed descriptor policy; -- pointer `allocate()`, `deallocate()`, or `resize()` without explicit policy; -- unknown target owners or release responsibility; -- persistent associations to Python storage after return; and -- stale-view invalidation after target reassociation, nullification, or - deallocation; and -- scalar-derived pointer targets whose lifetime or release responsibility is - neither native nor tied to a retained known owner. - -Semantic `.pyi` metadata can record these policy facts, but metadata does not -implement a missing runtime path. - -## Evidence And Troubleshooting - -Scalar pointer inputs, outputs, inout readback, nullable results, array pointer -handles, descriptor views, normal array-actual handoff, and dtype rejection are exercised by -[`test_pointers.py`](../../../tests/wrapper/fortran/derived_types/test_pointers.py). -Scalar derived module-pointer state, reassociation writeback, wrapper pointer -holders, stale proxy rejection, and multi-argument cleanup are exercised by -[`test_scalar_derived_actual_dummy_matrix.py`](../../../tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py). -The scalar `out` and `inout` parity cases are exercised by -[`test_allocatable_views.py`](../../../tests/wrapper/fortran/module_state/test_allocatable_views.py). - -If wrapper planning rejects a pointer, do not replace the diagnostic with guessed -ownership metadata. Detached pointer result behavior is expressible only when shape, -nullability, target owner, lifetime, and release facts are complete. Memory -Management and the semantic `.pyi` ownership reference expand those decisions -later. +x2py does not lock native pointer association or track outstanding NumPy views. +The application must synchronize concurrent native changes. + +--- + +## Scalar Pointers + +Scalar pointers appear as `T | None` values at the Python boundary rather than +`PointerArray` handles. An unassociated projected scalar result becomes `None`. +Scalar values do not expose persistent association, `to_numpy()`, or pointer +descriptor operations. + +For an optional scalar pointer argument, omission makes the argument absent. +Passing `None` makes it present but unassociated, while passing a value makes it +present with that value. See [Optional Arguments](optional-arguments.md). + +--- + +## Next + +- Review [Memory Management](memory-management.md) for the ownership and live + view rules shared by all native storage. +- Compare [Allocatables](allocatables.md) when the native object owns an + allocation rather than a pointer association. diff --git a/docs/user/guide/raw-addresses.md b/docs/user/guide/raw-addresses.md new file mode 100644 index 000000000..9664265ad --- /dev/null +++ b/docs/user/guide/raw-addresses.md @@ -0,0 +1,197 @@ +--- +title: Raw Addresses +description: Pass primitive, array, and fixed-string storage addresses through semantic contracts +audience: advanced users +prerequisites: arrays, strings, editing .pyi contracts +related: data-types.md, arrays.md, strings.md +status: maintained +publication: reviewed +--- + +# Raw Addresses + +`Addr(T)` makes an integer address part of the Python API. +x2py casts the address and passes it to native code without owning the memory. + +Use this boundary only when the API must expose an address. Prefer checked +scalar storage, arrays, and strings for normal wrappers. + +## Checked Storage Or Raw Address + +| Contract | Python argument | Validation | +| --- | --- | --- | +| `Int32[()]` | 0-D array with dtype `np.int32` | Dtype, rank, and writeability | +| `Addr(Int32)` | Integer address | Integer that fits a native address | +| `Float64[rows, columns]` | NumPy array | Dtype, shape, order, and writeability | +| `Addr(Float64[rows, columns])` | Integer address | Declared array sizes only | +| `String[8][()]` | 0-D NumPy bytes array with dtype `S8` | Dtype, length, and writeability | +| `Addr(String[8])` | Integer address | Declared fixed length only | + +`T[()]` changes the Python storage representation, not the native primitive +datatype. It is usually the better choice for scalar mutation. + +## `Addr(T)` And `Addr(Arg(...))` + +These spellings describe different boundaries: + +- `Addr(T)` means the Python caller passes an integer address. +- Inside `@native_call(...)`, `Arg(i)` selects Python argument `i`, and + `Addr(Arg(i))` tells x2py to pass that converted scalar by address. + +The `@native_call(...)` decorator records how Python arguments are placed in +the native call. Arrays, rank-zero storage, strings, and raw addresses already +use storage addresses, so their `Arg(i)` entry does not need another +`Addr(...)`. + +## Complete Example + +Create `raw_api.f90`: + +```fortran +module raw_api + implicit none +contains + + subroutine increment(value) + integer(4), intent(inout) :: value + value = value + 1 + end subroutine increment + + subroutine scale(rows, columns, values) + integer(4), intent(in) :: rows, columns + real(8), intent(inout) :: values(rows, columns) + values = 2.0_8 * values + end subroutine scale + + subroutine edit_label(label) + character(len=8), intent(inout) :: label + label(1:1) = "X" + end subroutine edit_label + +end module raw_api +``` + +Generate a starter contract: + +```bash +python3 -m x2py generate --pyi raw_api.f90 --out contracts/raw +``` + +Edit `contracts/raw/raw_api.pyi`: + +```python +from x2py.contracts import Addr, Arg, Float64, Int32, String, native_call + +def increment(value: Addr(Int32)) -> None: ... + +@native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2)]) +def scale( + rows: Int32, + columns: Int32, + values: Addr(Float64[rows, columns]), +) -> None: ... + +def edit_label(label: Addr(String[8])) -> None: ... +``` + +Build from the edited contract and native source: + +```bash +python3 -m x2py contracts/raw/__init__.pyi \ + --native-fortran-sources raw_api.f90 \ + --out-dir build/raw +``` + +For other argument-order and result mappings, see +[Reorder Arguments and Project Outputs](../reference/pyi-contracts/calls-and-results.md#reorder-arguments-and-project-outputs). + +## Primitive Address + +Keep the NumPy owner in a variable for the full call: + +```python +import sys + +import numpy as np + +sys.path.insert(0, "build/raw") +from raw.raw_api import increment + +value = np.array(3, dtype=np.int32) +increment(value.ctypes.data) + +print(value[()]) # 4 +``` + +For checked mutation, use `Int32[()]` instead. The call then accepts `value` +directly and validates its storage. + +## Array Address + +`array.ctypes.data` is the address of the first array element. +The owner must contain enough storage for every declared extent. + +```python +import sys + +import numpy as np + +sys.path.insert(0, "build/raw") +from raw.raw_api import scale + +values = np.asfortranarray( + [[1.0, 2.0], [3.0, 4.0]], + dtype=np.float64, +) + +scale(np.int32(2), np.int32(2), values.ctypes.data) +print(values) +# [[2. 4.] +# [6. 8.]] +``` + +The raw address does not carry shape, order, or strides. Passing C-order +storage does not make a Fortran routine use C ordering. + +Every raw array extent must use a literal or a visible scalar argument. +Unresolved forms such as `Addr(Float64[:])` are invalid. + +## Fixed-String Address + +A fixed string address points to exactly the declared number of bytes. +Use a NumPy `S8` owner for `Addr(String[8])`: + +```python +import sys + +import numpy as np + +sys.path.insert(0, "build/raw") +from raw.raw_api import edit_label + +label = np.array("alpha ", dtype="S8") +edit_label(label.ctypes.data) + +print(label[()]) # b'Xlpha ' +``` + +This mutates bytes storage. It does not return a Python `str`. +Use `String[8]` with `Returns[...]` when the result should be immutable text. +Use `String[8][()]` when the wrapper should validate mutable storage. + +## Safety Rules + +- Keep the NumPy or ctypes owner alive until the call returns. +- Do not pass the address of a temporary expression. +- Use the exact native dtype and alignment. +- Supply enough storage for every declared array extent. +- Match the native array ordering and layout. +- Use writable memory when native code may modify it. +- Treat address zero as null only when the native routine allows null. + +x2py cannot validate the addressed memory's lifetime, dtype, size, shape, order, +alignment, ownership, or writeability. A wrong address can crash the process. + +## Next + +- Continue with [Error Handling](error-handling.md). diff --git a/docs/user/guide/strings.md b/docs/user/guide/strings.md new file mode 100644 index 000000000..433322978 --- /dev/null +++ b/docs/user/guide/strings.md @@ -0,0 +1,190 @@ +--- +title: Strings +description: Immutable strings, mutable character storage, and NumPy byte arrays in x2py +audience: users +prerequisites: data types, arrays +related: data-types.md, arrays.md, raw-addresses.md +status: maintained +publication: reviewed +--- + +# Strings + +x2py uses Python `str` for scalar character values. +Mutable character storage uses fixed-width NumPy bytes arrays. + +The contract decides whether native mutation becomes a new `str` or changes +caller-owned storage. + +## Choose A String Boundary + +| Contract | Python value | Native mutation | +| --- | --- | --- | +| `String` | Variable-length `str` | Returned only when projected | +| `String[8]` | `str` encoded as exactly 8 bytes | Returned as a new `str` | +| `String[8][()]` | Rank-zero NumPy array with dtype `S8` | Visible in place | +| `String[8][count]` | NumPy bytes array with dtype `S8` | Visible in place | +| `Addr(String[8])` | Integer address | Visible through caller-owned memory | + +Use normal string and NumPy contracts by default. Raw addresses are an +advanced boundary covered later in the guide. +`Returns[...]` tells the wrapper to return the changed value of an argument. + +## Complete Example + +Create `strings_api.f90`: + +```fortran +module strings_api + implicit none +contains + + subroutine edit_text(text) + character(len=8), intent(inout) :: text + text(1:1) = "X" + end subroutine edit_text + + subroutine edit_buffer(text) + character(len=8), intent(inout) :: text + text(1:1) = "X" + end subroutine edit_buffer + + function make_text() result(text) + character(len=8) :: text + text = "ready" + end function make_text + + subroutine edit_labels(count, labels) + integer(4), intent(in) :: count + character(len=8), intent(inout) :: labels(count) + integer(4) :: index + + do index = 1, count + labels(index)(1:1) = "X" + end do + end subroutine edit_labels + +end module strings_api +``` + +Generate a starter contract: + +```bash +python3 -m x2py generate --pyi strings_api.f90 --out contracts/strings +``` + +Edit the declarations in `contracts/strings/strings_api.pyi` to use these +Python boundaries. Keep the other generated decorators and native-call +metadata unchanged: + +```python +from x2py.contracts import Int32, Returns, String + +def edit_text(text: String[8]) -> Returns["text", String[8]]: ... + +def edit_buffer(text: String[8][()]) -> None: ... + +def make_text() -> String[8]: ... + +def edit_labels( + count: Int32, + labels: String[8][count], +) -> None: ... +``` + +Build from the edited contract and native source: + +```bash +python3 -m x2py contracts/strings/__init__.pyi \ + --native-fortran-sources strings_api.f90 \ + --out-dir build/strings +``` + +For the complete result-mapping rules, see +[Reorder Arguments and Project Outputs](../reference/pyi-contracts/calls-and-results.md#reorder-arguments-and-project-outputs). + +## Immutable Values + +`String[8]` accepts a Python `str` whose encoded length is exactly eight bytes. +The wrapper copies it into native storage. + +```python +import sys + +sys.path.insert(0, "build/strings") +from strings.strings_api import edit_text, make_text + +original = "alpha " +changed = edit_text(original) + +print(repr(original)) # 'alpha ' +print(repr(changed)) # 'Xlpha ' +print(repr(make_text())) # 'ready ' +``` + +Python strings are immutable. `Returns[...]` copies the changed native buffer +into a new `str`. Without that projection, the mutation is discarded. + +## Mutable Scalar Storage + +`String[8][()]` accepts a rank-zero NumPy bytes array. +Native writes change the same object. + +```python +import sys + +import numpy as np + +sys.path.insert(0, "build/strings") +from strings.strings_api import edit_buffer + +buffer = np.array("alpha ", dtype="S8") +edit_buffer(buffer) + +print(buffer[()]) # b'Xlpha ' +``` + +The public value is bytes storage. Reading `buffer[()]` returns `np.bytes_`, +not `str`. + +## String Arrays + +String arrays use fixed-width NumPy bytes dtypes. The dtype item size is the +Fortran character length. + +```python +import sys + +import numpy as np + +sys.path.insert(0, "build/strings") +from strings.strings_api import edit_labels + +labels = np.array([b"alpha ", b"beta "], dtype="S8") +edit_labels(np.int32(labels.size), labels) + +print(labels) # [b'Xlpha ' b'Xeta '] +``` + +The wrapper checks rank, shape, dtype, and writeability before the call. +Unicode and object arrays are rejected. + +## Length And Encoding + +- `String[8]` requires exactly eight encoded bytes. +- `String` accepts a runtime character length. +- Fixed-width results retain trailing Fortran blanks. +- Embedded NUL bytes are rejected for scalar Python strings. +- `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. + +## Next + +- Continue with [Wrapping Functions](wrapping-functions.md). +- [Wrapping Subroutines](wrapping-subroutines.md) for complete `intent` and + result-projection rules. +- [Raw Addresses](raw-addresses.md) for the advanced `Addr(String[n])` + boundary. diff --git a/docs/user/guide/wrapping-derived-types.md b/docs/user/guide/wrapping-derived-types.md index e5ef739b8..517d4cb9e 100644 --- a/docs/user/guide/wrapping-derived-types.md +++ b/docs/user/guide/wrapping-derived-types.md @@ -1,24 +1,33 @@ --- title: Wrapping Derived Types +description: How x2py wraps Fortran derived types as Python classes with methods, fields, constructors, and ownership rules audience: users, advanced users prerequisites: wrapping modules, data types -related: memory-management.md, generic-interfaces.md, fortran-wrapper.md +related: memory-management.md, generic-interfaces.md status: maintained +publication: reviewed --- # Wrapping Derived Types -A supported Fortran-derived type becomes a generated Python extension class. -The wrapper owns an opaque native instance; Python field access and methods use -generated native operations rather than assuming a public memory layout. +A supported Fortran `type` becomes a **generated Python extension class**. +Constructors and ordinary function results create independent Fortran +instances that are released with their Python objects. A nested component +belongs to its parent, and a module object belongs to the Fortran module. +Python accesses fields through generated getters and setters. Methods call +wrapped Fortran procedures. Python never reads the native memory layout +directly. -## Complete Derived-Type Example +--- + +## Complete Example Create `points.f90`: ```fortran module points implicit none + type :: point real(8) :: x = 0.0_8 real(8) :: y = 0.0_8 @@ -27,7 +36,9 @@ module points type :: holder type(point) :: origin end type holder + contains + subroutine move(item, dx, dy) type(point), intent(inout) :: item real(8), intent(in) :: dx, dy @@ -47,6 +58,7 @@ contains type(point), intent(in) :: item container%origin = item end subroutine set_origin + end module points ``` @@ -56,260 +68,320 @@ Build it: python3 -m x2py points.f90 --out geometry --out-dir build/geometry ``` -Then construct, mutate, return, and borrow generated objects: +--- + +## Usage in Python ```python import sys + import numpy as np sys.path.insert(0, "build/geometry") -import geometry +import geometry.points as points -points = geometry.points +# Create new object item = points.point(x=np.float64(1.0), y=np.float64(2.0)) + +# Call method (inout mutation) points.move(item, np.float64(3.0), np.float64(4.0)) -assert item.x == np.float64(4.0) -assert item.y == np.float64(6.0) +print(item.x, item.y) # 4.0 6.0 +# Function returning derived type made = points.make_point(np.float64(8.0), np.float64(9.0)) -assert isinstance(made, points.point) +# Nested component container = points.holder() points.set_origin(container, made) -origin = container.origin -origin.x = np.float64(12.0) -assert container.origin.x == np.float64(12.0) +container.origin.x = np.float64(12.0) +print(container.origin.x) # 12.0 +``` + +--- + +## Inspect the Class + +The class docstring gives a short index: + +```python +print(points.point.__doc__) +``` + +```text +point + +Opaque wrapper for native type point. + +Constructor +----------- +point(*, x=0.0, y=0.0) -> point + +Fields +------ +x : float64 +y : float64 +``` + +The constructor has its own detailed docstring: + +```python +print(points.point.__init__.__doc__) +``` + +--- + +## Key Concepts + +- **Lifetime**: A constructed or returned object is released when its Python + object is no longer used. A nested component stays tied to its parent. +- **Mutation**: `intent(out)` and `intent(inout)` modify a caller-provided + instance and do not return it again. +- **Missing intent**: A dummy without `intent` follows the same conservative + in-place rule as `intent(inout)`. +- **Fields**: Public scalar numeric/logical/complex fields become Python attributes. +- **Nested types**: Appear as generated objects tied to their parent. +- **Results**: Derived-type function results create new independent objects. +- **Default constructor**: Automatically generated from public, writable + primitive scalar fields. +- **Constructor fields**: Passed by keyword (`logical`, `integer`, `real`, and + `complex`). + +--- + +## Custom Constructor + +The default constructor assigns public fields directly. If the native module +already provides `initialize_point(item, x, y)`, an edited contract can use it +as the constructor. + +In this mapping, `@bind` selects the native initializer, +`@native_call(...)` gives its argument order, `Pass()` inserts the new +`point`, and `Addr(Arg(i))` passes Python argument `i` by address: + +```python +from x2py.contracts import Addr, Arg, Float64, Pass, bind, native_call + +class point: + x: Float64 + y: Float64 + + @bind("initialize_point") + @native_call([Pass(), Addr(Arg(0)), Addr(Arg(1))]) + def __init__(self, x: Float64, y: Float64) -> None: ... +``` + +Replace the generated field-keyword `__init__` declaration with this one. +The edit changes construction only; it does not create +`initialize_point` in the native module. + +After rebuilding, `points.point.__init__.__doc__` starts with +`point(x, y) -> point` and lists both parameters. + +For the complete replacement rules, see +[Replace the Constructor](../reference/pyi-contracts/functions-and-classes.md#replace-the-constructor). + +--- + +## Type-Bound Methods + +A public type-bound procedure becomes a method on the generated class. The +passed object becomes `self` and is not repeated in the Python call. + +```fortran +type :: counter + integer(4) :: value = 0 +contains + procedure :: increment +end type counter + +contains + +subroutine increment(self, amount) + class(counter), intent(inout) :: self + integer(4), intent(in) :: amount + self%value = self%value + amount +end subroutine increment +``` + +```python +item = counters.counter(value=np.int32(4)) +item.increment(np.int32(3)) +print(item.value) # 7 ``` -## Arguments And Results - -- `intent(in)` passes an existing wrapper instance without transferring ownership. -- `intent(inout)` mutates the same native instance. -- hidden `intent(out)` returns a new wrapper-owned instance. -- a function result is copied into a new wrapper-owned native instance before - the native temporary expires. - -When an edited semantic contract keeps a writable derived argument visible and -projects it with `Returns["name", T]`, the return value is the exact same Python -wrapper that the caller supplied. Native code mutates that wrapper's existing -storage; the binding does not construct a replacement object, copy the derived -value, or assume destruction responsibility. Omitting the projection leaves the -same mutation in place and returns only the other declared results (or `None`). - -The complete example shows inout mutation and a wrapper-owned function result. - -A native by-value argument is preserved in generated semantic contracts as -`@native_call([Value(Arg(0)), ...])`. Python still passes an existing `point` -wrapper. -The generated Fortran bridge imports the exact native type and performs the -typed by-value call. The foreign boundary never lays out or byte-copies the -aggregate, -so the same opaque mechanism applies to exact ordinary, `sequence`, and -`bind(C)` types. Polymorphic or unresolved types remain blocked. - -## Scalar Actuals And Native Dummies - -This section is the canonical compatibility reference for rank-zero, -monomorphic derived objects. It applies when a wrapper object—including a live -module attribute—is passed to another wrapped Fortran procedure. Arrays and -polymorphic objects follow different rules. - -An actual object has one of five relevant Fortran declaration forms: - -| Key | Actual declaration | -| --- | --- | -| `O` | `type(item) :: value` | -| `T` | `type(item), target :: value` | -| `A` | `type(item), allocatable :: value` | -| `AT` | `type(item), allocatable, target :: value` | -| `P` | `type(item), pointer :: value` | - -A native dummy has six forms: - -| Key | Dummy declaration | -| --- | --- | -| `O` | `type(item) :: arg` | -| `T` | `type(item), target :: arg` | -| `A` | `type(item), allocatable :: arg` | -| `AT` | `type(item), allocatable, target :: arg` | -| `P` | `type(item), pointer :: arg` | -| `V` | `type(item), value :: arg` | - -`OPTIONAL`, `INTENT`, rank, and the qualified native type are additional facts; -they are not extra declaration rows. In the table, “payload” requires an -allocated allocatable or associated pointer. “Holder” means persistent -wrapper-owned Fortran storage. “Scoped” means the originating module exposes an -address only for the duration of the synchronous native call. “Allocation -transaction” and “pointer transaction” write descriptor or association changes -back to the originating module variable before control returns to Python. - -| Actual and origin | `O` dummy | `T` dummy | `A` dummy | `AT` dummy | `P` dummy | `V` dummy | -| --- | --- | --- | --- | --- | --- | --- | -| `O`, wrapper-owned | direct reference | call-scoped target | incompatible | incompatible | input-only pointer adapter | typed value | -| `O`, module | scoped reference | scoped target | incompatible | incompatible | scoped input-only pointer adapter | scoped typed value | -| `T`, wrapper-owned | direct reference | direct target | incompatible | incompatible | input-only pointer adapter | typed value | -| `T`, module | module address | module target | incompatible | incompatible | input-only pointer adapter | typed value | -| `A`, wrapper holder | payload | payload as holder target | allocatable holder | allocatable holder | payload input-only pointer adapter | payload typed value | -| `A`, module | scoped payload | scoped payload target | allocation transaction | allocation transaction with call target | scoped payload input-only pointer adapter | scoped payload typed value | -| `AT`, wrapper holder | payload | payload as holder target | allocatable holder | allocatable holder | payload input-only pointer adapter | payload typed value | -| `AT`, module | module payload address | module payload target | target-preserving allocation transaction | target-preserving allocation transaction | payload input-only pointer adapter | payload typed value | -| `P`, wrapper holder | pointee | pointee target | incompatible | incompatible | pointer holder | pointee typed value | -| `P`, module | module pointee | module pointee target | incompatible | incompatible | pointer transaction | pointee typed value | - -“Incompatible” is a deliberate `TypeError` before native entry, not an -unimplemented Phase 8 fallback. A nonpointer actual can satisfy `P` only when -the pointer dummy is proved `INTENT(IN)`. A pointer dummy with no `INTENT`, or -with `INTENT(OUT)`/`INTENT(INOUT)`, may change association and therefore -requires a pointer actual. If the edited contract omits `INTENT` but an imported -Fortran interface is authoritative, x2py emits the target adapter and lets the -Fortran compiler enforce this rule. Without either source of authority, wrapper -generation reports an interface error. - -For payload calls, an unallocated `A`/`AT` actual or disassociated `P` actual -raises `ValueError` before native entry. Descriptor dummies `A`, `AT`, and `P` -instead accept empty state so the native procedure can allocate, deallocate, -nullify, or reassociate it. Empty state is still a present argument; only an -omitted optional argument or explicit optional `None` means absence. - -### Module Transactions And Multiple Arguments - -Module allocation and pointer state stays in Fortran. x2py uses one shared -typed allocatable holder and pointer holder per qualified native type. The -interoperable boundary carries only an opaque holder address and typed operation -pointers; no native descriptor crosses that boundary. - -For an allocatable module actual passed to `A` or `AT`, a module operation uses -`move_alloc` to place its allocation in a bridge-local typed holder. The native -procedure receives that holder component, and a cleanup operation moves the -final allocation back exactly once. For a module pointer passed to `P`, a local -pointer holder starts with the current association; cleanup restores its final -association to the module pointer exactly once. A module target needs neither -transaction: its durable native address is sufficient. - -A procedure may take any number of scalar-derived arguments. x2py validates all -slots first, acquires module origins in deterministic order, nests scoped -address producers, invokes the native procedure once, and restores transactions -in reverse order. It does not generate `2**N` call variants. Repeated read-only -use of the same object shares one acquisition; ambiguous writable aliasing is -rejected before any module state moves. - -This also applies when arguments are module variables from different Fortran -modules and have different qualified derived types. Each module variable owns a -separate bridge operation table and scoped callback; the binding validates the -table's qualified native type before the callback is invoked. There is no -shared type-specific callback slot, so one module variable cannot overwrite -another argument's transport. - -If a later acquisition or the native call reports a normal ABI error, cleanup -continues for every acquired origin and Python raises only after the Fortran -frames have returned. Concurrent or recursive use of the same active module -transaction raises `RuntimeError`. A restoration failure also raises -`RuntimeError` and poisons that module proxy rather than pretending its state is -usable. Process termination, `error stop`, signals, and invalid native pointers -cannot be converted into recoverable Python exceptions. - -A pointer holder owns its association variable, not its target. Native storage -remains native-owned unless completed policy identifies and retains a known -module, parent, or wrapper target. Destroying the Python holder nullifies its -component and releases only the holder; it never deallocates an unknown target. - -## Fields And Nested Components - -Public supported scalar fields become Python descriptors. Private fields are -omitted. A nested scalar derived component is a borrowed child wrapper: it -retains its parent owner and never destroys the component independently. -The same readable and, when policy permits, writable descriptor surface is used -for wrapper-owned instances, borrowed objects, and live module objects. A -target/addressable module object may use a direct native address; a plain module -object uses typed member getter and setter operations instead. Neither path -creates a detached whole-object copy. - -Allocatable fields expose `Allocatable[T[...]]` handles, and pointer-array -fields expose `Pointer[T[...]]` handles. Each field handle retains the parent -wrapper for descriptor access. Call `to_numpy()` to extract the current NumPy -view or `None`; extraction never copies. Discard old views after native -deallocation, reallocation, nullification, or reassociation because accessing a -stale view is unsupported and may crash. Call `.copy()` explicitly for -independent NumPy storage. Pointer fields use a conservative default operation -policy, while ownership-changing operations require explicit pointer policy. -Arrays of derived types remain blocked because element construction, -destruction, layout, aliasing, and copy policy are incomplete. -Rank-zero allocatable or pointer components whose value is itself a derived -object use the same completed holder and ownership policy when their origin is -supported. Rank-zero allocatable and pointer module variables expose persistent -live descriptor proxies, including while the allocatable is unallocated or the -pointer is disassociated. Payload-field access then raises `ReferenceError`, -but the same proxy can still be passed to an `A`, `AT`, or `P` dummy so native -code can establish new state. Wrapper-owned allocatable and pointer results use -persistent typed holders with the same empty-state rule. -Their complete call compatibility and transaction rules are defined in -[Scalar Actuals And Native Dummies](#scalar-actuals-and-native-dummies). x2py -does not silently turn an unsupported origin into an owned object or detached -copy. - -## Constructors - -Native allocation runs native default component initialization. x2py generates -a keyword-only Python initializer for public rank-zero numeric, logical, and -complex fields. Omitted keywords preserve the native initialized values. - -Private components, arrays, allocatables, pointers, strings, and nested derived -components are not automatic constructor keywords. A type with fields but no -eligible keywords still receives explicit default construction when supported. - -An edited semantic `.pyi` may remove the generated constructor or bind one -concrete initializer. x2py does not regenerate a constructor that the edited -contract intentionally removed. - -## Finalizers - -An owned wrapper invokes native finalization exactly once when its owning Python -wrapper is collected. Failed initialization still releases the allocated native -instance. Borrowed child wrappers do not finalize their component; finalization -belongs to the containing owner. - -A native finalizer has no recoverable Python status channel during object -deallocation. Native termination from a finalizer terminates the process. - -## Inheritance And Polymorphism - -Supported extension types form a matching Python inheritance hierarchy. A -scalar polymorphic input over a known hierarchy dispatches descendant-first. - -Ordinary native `class(T)` arguments retain `Annotated[T, Polymorphic]` in the -semantic `.pyi` because that source fact selects the accepted dynamic-type -dispatch. The passed-object dummy of a type-bound procedure is different: its -class binding already proves that it is polymorphic, so generated contracts use -the plain declared type for that one argument and restore the fact when loading -the binding. - -Polymorphic results, mutable polymorphic arguments, arrays, allocatable or pointer -polymorphic scalars, `class(*)`, abstract instantiation, and deferred bindings -are blocked. - -## Opaque Layout - -Generated wrappers do not expose a direct binary-layout promise for ordinary -derived types. Component order and native facts remain in semantic IR, but -Python access follows generated accessors. Do not use `ctypes` offsets or assume -that Python-visible fields imply a stable binary layout. - -## Evidence And Troubleshooting - -Scalar boundaries and nested lifetime are exercised by -[`test_derived_type_boundaries.py`](../../../tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py), -direct live object and field behavior by -[`test_phase8_derived_plan.py`](../../../tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py), -the complete scalar actual/dummy matrix and multi-argument transactions by -[`test_scalar_derived_actual_dummy_matrix.py`](../../../tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py), -methods by -[`test_derived_type_methods.py`](../../../tests/wrapper/fortran/derived_types/test_derived_type_methods.py), -constructors/finalizers by -[`test_constructors_and_finalizers.py`](../../../tests/wrapper/fortran/derived_types/test_constructors_and_finalizers.py), -and borrowed finalization by -[`test_borrowed_finalizers.py`](../../../tests/wrapper/fortran/derived_types/test_borrowed_finalizers.py). - -Treat nested child wrappers as borrowed from their containing wrapper rather -than independently owned native objects. Memory Management later expands -ownership, and Error Handling later covers constructor, type, and wrapper-planning -failures. +The method mutates the existing `counter`; it does not replace the Python +object. + +### Expose a Module Procedure as a Method + +The `move(item, dx, dy)` procedure from this page's example can remain a +module-level function and also become `point.move(dx, dy)`. + +`Pass()` supplies `self` to the native call. `Arg(i)` refers to a visible +Python argument. Add the method to the existing `point` class while keeping +the module declaration: + +```python +from x2py.contracts import Addr, Arg, Float64, Pass, native_call + +class point: + @native_call([Pass(), Addr(Arg(0)), Addr(Arg(1))]) + def move(self, dx: Float64, dy: Float64) -> None: ... + +@native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2))]) +def move(item: point, dx: Float64, dy: Float64) -> None: ... +``` + +Both declarations call the existing native `move` procedure: + +```python +points.move(item, np.float64(2.0), np.float64(3.0)) +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. + +The class docstring now lists `move(dx, dy) -> None` under `Methods`. +`points.point.move.__doc__` contains its complete parameter and return details. + +For the complete mapping rules, see +[Expose a Module Procedure as a Method](../reference/pyi-contracts/functions-and-classes.md#expose-a-module-procedure-as-a-method). + +--- + +## Type-Bound Generics + +A type-bound generic groups several concrete methods under one Python method. +For example, `add` can accept exact integer or real amounts: + +```fortran +type :: counter + integer(4) :: value = 0 +contains + procedure :: add_integer + procedure :: add_real + generic :: add => add_integer, add_real +end type counter +``` + +The generated contract uses the same explicit overload links as a module-level +generic: + +```python +from x2py.contracts import Float64, Int32, bind, overload, private + +class counter: + @private + def add_integer(self, amount: Int32) -> Int32: ... + + @private + def add_real(self, amount: Float64) -> Float64: ... + + @bind("add") + @overload("add_integer") + def add(self, amount: Int32) -> Int32: ... + + @bind("add") + @overload("add_real") + def add(self, amount: Float64) -> Float64: ... +``` + +```python +print(item.add(np.int32(2))) # exact Int32 candidate +print(item.add(np.float64(0.5))) # exact Float64 candidate +``` + +The passed object participates in native dispatch but is already fixed by the +generated class. The remaining arguments must still match one candidate +exactly. Each `@overload` retains a concrete contract. `@bind("add")` routes +the native call through the public type-bound generic because its specifics +are private. + +--- + +## Defined Operators + +A defined operator with a wrapped derived-type operand becomes a Python magic +method. Its overload candidates are attached to the generated class. + +```fortran +interface operator(+) + module procedure add_points +end interface operator(+) + +contains + +function add_points(left, right) result(output) + type(point), intent(in) :: left, right + type(point) :: output + output%x = left%x + right%x + output%y = left%y + right%y +end function add_points +``` + +The generated contract exposes `operator(+)` as `__add__`: + +```python +from x2py.contracts import overload, private + +class point: + @overload("add_points") + def __add__(self, right: point) -> point: ... + +@private +def add_points(left: point, right: point) -> point: ... +``` + +Python uses the normal operator: + +```python +left = points.point(x=np.float64(1.0), y=np.float64(2.0)) +right = points.point(x=np.float64(3.0), y=np.float64(4.0)) +total = left + right +print(total.x, total.y) # 4.0 6.0 +``` + +The magic method docstring shows the accepted operator signatures: + +```python +print(points.point.__add__.__doc__) +``` + +The relevant part is: + +```text +__add__(*args, **kwargs) + +Supported Signatures +-------------------- +__add__(right: point) -> point +``` + +| Fortran generic | Python method | Python syntax | +|-----------------|---------------|---------------| +| Binary `+`, `-`, `*`, `/`, `**` | Direct and reflected magic methods | `left + right` | +| Unary `+`, `-` | `__pos__`, `__neg__` | `+value`, `-value` | +| Relational operators | `__eq__`, `__lt__`, and related methods | `left == right` | +| `.and.`, `.or.`, `.not.` | `__and__`, `__or__`, `__invert__` | `left & right`, `~value` | +| Named operator `.name.` | `operator_name` or `r_operator_name` | Explicit method call | +| `assignment(=)` | `assign` | `target.assign(value)` | + +Python `and`, `or`, and `not` cannot be overloaded, so logical operator +generics use `&`, `|`, and `~`. Python assignment only rebinds a name, so +defined assignment uses `.assign(...)`. + +At least one operand must be a wrapped derived type. Other operands can be +supported primitive scalars, arrays, or generated classes. Their dispatch is +exact. + +For the overload rules shared by type-bound generics and operators, see +[Edit an Overload Set](../reference/pyi-contracts/functions-and-classes.md#edit-an-overload-set). + +--- + +## Next + +- Continue with [Allocatables](allocatables.md). +- Read [Memory Management](memory-management.md) for the lifetime of native + storage and NumPy views. diff --git a/docs/user/guide/wrapping-functions.md b/docs/user/guide/wrapping-functions.md index f3bbc8ad4..d348d35a0 100644 --- a/docs/user/guide/wrapping-functions.md +++ b/docs/user/guide/wrapping-functions.md @@ -1,206 +1,137 @@ --- title: Wrapping Functions +description: How x2py wraps Fortran `function` procedures — return values, output arguments, arrays, and contracts audience: users prerequisites: data types, first wrapped function -related: wrapping-subroutines.md, arrays.md, fortran-wrapper.md +related: wrapping-subroutines.md, arrays.md status: maintained +publication: reviewed --- # Wrapping Functions -A Fortran `function` becomes a Python callable. The direct result of the function becomes the first returned value in Python. -All arguments follow the exact semantic types shown in the generated `.pyi` file. +A Fortran `function` becomes a Python callable. Its direct result is the first +Python return value. Other outputs follow only when their contract marks them +as Python results. -See [Data Types](data-types.md) for details on how Fortran types are mapped to Python/NumPy. +--- + +## Basic Scalar Function + +The `scale` function built in +[First Wrapped Function](../getting-started/first-wrapped-function.md) returns +its direct result as one NumPy scalar: -For this example, we'll use `scale.f90` (from [README Quick Start](../../../README.md#quick-start)). +```python +import numpy as np -```bash -python3 -m x2py generate --pyi scale.f90 -python3 -m x2py scale.f90 --out-dir build/scale +import scale + +result = scale.scale(np.float64(3.0), np.float64(2.5)) +print(result) # 7.5 ``` -## Scalar Functions +--- + +## Python And Native Names -The generated contract for the scale function is: +A contract declaration normally uses one name for both Python and the native +procedure. Use `@bind("native_name")` only when those names differ. + +For example, rename the generated declaration to `multiply` and add +`@bind("scale")`. The Python name changes, while the native target remains +`scale`: ```python -from x2py.contracts import Addr, Arg, Float64, external, native_call +from x2py.contracts import Addr, Arg, Float64, bind, external, native_call +@bind("scale") @external @native_call([Addr(Arg(0)), Addr(Arg(1))]) -def scale( +def multiply( value: Float64, - factor: Float64, + factor: Float64 ) -> Float64: ... ``` -Call it like this: - ```python -result = scale.scale(np.float64(3.0), np.float64(2.5)) -assert result == np.float64(7.5) +result = scale.multiply(np.float64(3.0), np.float64(2.5)) +print(result) # 7.5 ``` -Contained module functions appear on their generated child module instead of -the extension root. Standalone procedures carry `@external` in the semantic -contract. These placement details do not change the Python argument types. - -Supported scalar function results include resolved signed integer, real, -complex, logical, scalar character, and supported derived-type values. Read -[Data Types](data-types.md) for the complete mapping. Scalar character results -are Python-owned `str` values; derived results are wrapper-owned generated -class instances. - -## Array Results - -Functions can return numeric arrays. These are returned as new NumPy arrays with Fortran (column-major) ordering. - -Example (`function_results.f90`): - -```fortran -module results - implicit none -contains - function squares(count) result(values) - integer(4), intent(in) :: count - real(8) :: values(count) - integer(4) :: index - - values = [(real(index, 8) * real(index, 8), index = 1, count)] - end function squares -end module results -``` +`@bind` changes the native target name. It does not change the argument +contract or adapt an incompatible native interface. Matching names need no +`@bind`. -Generated contract: +Also update the import in the contract package's `__init__.pyi` when it +re-exports the old Python name. Build the edited package using the +[editable-contract workflow](../getting-started/beginner-workflow.md#4-optionally-edit-the-contract). -```python -from x2py.contracts import Addr, Arg, Float64, Int32, native_call +The same rule applies to functions, subroutines, and methods. -@native_call([Addr(Arg(0))]) -def squares( - count: Int32 -) -> Float64[count]: ... -``` +For the complete naming rules, see +[Add or Rename a Native Procedure](../reference/pyi-contracts/exports-and-modules.md#add-or-rename-a-native-procedure). -Build it: +--- -```bash -python3 -m x2py function_results.f90 --out-dir build/function-results -``` +## Array Return Values -Usage: +Functions can return arrays. An ordinary array result becomes a new NumPy +array in Fortran order, as described in +the [`automatic_vector` example](arrays.md#complete-example): ```python -import sys import numpy as np -sys.path.insert(0, "build/function-results") -import function_results - -api = function_results.results -result = api.squares(np.int32(4)) - -np.testing.assert_array_equal( - result, - np.array([1.0, 4.0, 9.0, 16.0], dtype=np.float64), -) +result = automatic_vector(np.int32(4)) +print(result) # [ 1. 4. 9. 16.] ``` -The `squares` result is an ordinary array contract, so allocated zero-sized -results remain zero-sized NumPy arrays. Multidimensional results retain -Fortran-oriented element ordering, and returned ordinary arrays are independent -Python-owned copies. +--- + +## Functions with Output Arguments -An allocatable array function result has a different public shape: it returns a -present `AllocatableArray`, including when the native result is unallocated. -Check `handle.allocated` and call `handle.to_numpy()` for explicit extraction. -Pointer-array function results remain blocked until x2py can prove stable owner -storage and target lifetime. +When a function has projected scalar or native-created outputs, Python returns +a **tuple**: -## Functions With Output Arguments +> `(function_result, out_arg1, out_arg2, ...)` -If a function also has output arguments, Python returns a tuple: **first the direct function result, then the output arguments** in their native -argument order. +Caller-provided ordinary arrays are mutated in place and are not added to this +tuple. -Create `function_outputs.f90`: +**Example:** ```fortran -module outputs - implicit none -contains - function sum_with_count(values, count) result(total) - real(8), intent(in) :: values(:) - integer(4), intent(out) :: count - real(8) :: total - - total = sum(values) - count = size(values) - end function sum_with_count -end module outputs +function sum_with_count(values, count) result(total) + real(8), intent(in) :: values(:) + integer(4), intent(out) :: count + real(8) :: total + total = sum(values) + count = size(values) +end function ``` -Inspecting `function_outputs.f90` prints this function contract: +**Python call:** ```python -from x2py.contracts import Arg, Float64, Int32, Return, native_call - -@native_call([Arg(0), Return('count', 1)]) -def sum_with_count( - values: Float64[::] -) -> tuple[Float64, Int32]: ... +total, count = sum_with_count(data_array) ``` -Build it: - -```bash -python3 -m x2py function_outputs.f90 --out-dir build/function-outputs -``` - -Then assert the tuple order: - -```python -import sys +--- -import numpy as np +## Important Rules -sys.path.insert(0, "build/function-outputs") -import function_outputs +- Always pass **exact NumPy dtypes** (`np.float64`, `np.int32`, etc.). +- Array results are returned as new NumPy arrays (copies). +- Projected scalar outputs follow the direct function result in the return + tuple. +- Caller-provided arrays and derived objects mutate in place. They are not + repeated in the return tuple. +- Without `intent`, an argument uses conservative `intent(inout)` behavior. + Primitive scalar replacements follow the direct function result in the + Python return tuple. -api = function_outputs.outputs -source = np.array([4.0, -2.0, 7.0], dtype=np.float64) -total, count = api.sum_with_count(source) -assert total == np.float64(9.0) -assert count == np.int32(3) -``` +## Next -Caller-provided output arrays remain arguments because the caller must allocate -their storage. Their return projection, when present, refers to that same -object. The same `intent(out)` and `intent(inout)` projection rules apply to -subroutines that have no direct function result. - -## Call Limits - -- Exact input dtype is required where the generated contract names one; x2py - does not silently narrow or widen a native scalar or array. -- Numeric and fixed-width bytes character array results support ranks 1 through - 15. Arrays of derived types are blocked. -- Wider-than-supported real, complex, or explicit logical storage is blocked - rather than narrowed. -- A function result never creates an unproven borrowed pointer view. -- Native `stop`, `error stop`, or process abort cannot be converted into a - normal Python return. - -## Evidence And Troubleshooting - -Scalar calls are exercised by -[`test_verified_baseline.py`](../../../tests/wrapper/fortran/scalars/test_verified_baseline.py), -array results by -[`test_array_results.py`](../../../tests/wrapper/fortran/arrays/test_array_results.py), -and mixed result projection by -[`test_output_arguments.py`](../../../tests/wrapper/fortran/function_calls/test_output_arguments.py). - -For a rejected Python value, compare it with generated `.pyi` output and use -the reported dtype, rank, shape, and layout facts. Runtime Issues later provides -additional diagnosis. For an unsupported wrapper plan, the language feature matrix -later records whether that form is supported before compilation. +- [Wrapping Subroutines](wrapping-subroutines.md) for the complete argument + projection rules. diff --git a/docs/user/guide/wrapping-modules.md b/docs/user/guide/wrapping-modules.md index 3321955fd..d981f95f0 100644 --- a/docs/user/guide/wrapping-modules.md +++ b/docs/user/guide/wrapping-modules.md @@ -1,176 +1,190 @@ --- title: Wrapping Modules +description: How x2py exposes Fortran modules as Python namespaces with procedures, variables, and state audience: users prerequisites: data types, first wrapped module -related: wrapping-functions.md, memory-management.md, packaging.md +related: wrapping-functions.md, memory-management.md, building-shared-library.md status: maintained +publication: reviewed --- # Wrapping Modules -A contained Fortran module becomes a child Python module inside the generated -extension. Standalone procedures stay at the extension root. x2py preserves -this namespace instead of flattening native module membership implicitly. +A Fortran `module` becomes a **child Python module** (namespace) inside the generated extension. -As seen in the introductory example, building the source file `module_state.f90` -creates an extension named module_state, allowing you to import its contained module: +--- + +## Basic Usage + +After building `module_state.f90`: ```python +import sys + +import numpy as np + +sys.path.insert(0, "build/first-module") import module_state -module = module_state.module_state +mod = module_state.module_state # child namespace ``` -See [First Wrapped Module](../getting-started/first-wrapped-module.md) for the -complete source, build command, generated contract, and checked calls. +See [First Wrapped Module](../getting-started/first-wrapped-module.md) for the complete source, build command, and usage examples. -## Procedures And Package Shape +--- + +## Procedures -Module functions and subroutines are attributes of the child module: +Module functions and subroutines become attributes of the child module: ```python -assert module.summarize() == np.int32(15) +print(mod.summarize()) # 15 +print(mod.scaled_counter()) # 4.5 ``` -When compiling multiple ordered source files, a single generated extension can contain multiple child modules. -Each native module retains its own child namespace, while standalone -procedures remain on the extension root. The first source determines the -default extension name unless `--out` selects another name. +Standalone procedures (outside any module) remain at the extension root. + +When compiling multiple source files, each Fortran module becomes its own child namespace, while standalone procedures stay on the extension root. The first source file usually determines the extension name (you can override with `--out`). + +--- -## Public Variables +## Public Variables and Constants -Supported public scalar integer, real, complex, and logical module variables -are direct Python attributes. Reading fetches its current native state, and assigning -an exact matching value writes through to native storage: +Supported public scalar variables are exposed as direct Python attributes: ```python -module.counter = np.int32(9) -assert module.counter == np.int32(9) -assert module.summarize() == np.int32(21) +mod.counter = np.int32(9) +print(mod.counter) # 9 +print(mod.summarize()) # 21 + +print(mod.nmax) # 12 (read-only parameter) ``` -Generated getter and setter bridge routines are internal and do not appear as -Python callables. Private variables are omitted. +- `parameter` declarations become read-only constants in the generated + contract. +- Assigning to a constant in Python only creates a local shadow — it does **not** mutate the native value. -## Constants And Saved State +--- -Representable native parameters become `Final[...]` constants in the generated -contract: +## Module Arrays & Saved State + +- Allocatable module arrays use the `Allocatable[T[...]]` API. +- Allocation, lifetime, NumPy views, and mutation rules are covered in + the storage and objects section. +- `save` attributes (including procedure-local `save` variables) persist across calls. +- Multiple Python imports of the same extension share the same native module state. + +--- + +## Shape the Module API With the Contract + +Small contract edits can set initial values or hide names from Python: ```python -from x2py.contracts import Final, Int32 +from x2py.contracts import Final, Float64, Int32, private nmax: Final[Int32] = 12 +counter: Int32 = 9 +scale: Float64 = 2.0 +saved_counter: private[Int32] + +@private +def scaled_counter() -> Float64: ... ``` -No native setter exists for a parameter. Assigning `module.nmax` in Python can -only shadow the attribute on that Python module object; it does not mutate the -native parameter. +- `counter` and `scale` are set in the Fortran module when the extension is + imported. They remain writable. +- `private[T]` hides a module variable; `@private` hides a procedure. Both + still exist in Fortran. +- `Final[T] = value` is only for a true constant, such as a Fortran + `parameter`. It does not turn a writable Fortran variable into a read-only + view. -Derived-type parameters follow the same constant rule. A copy-safe -`Final[DerivedType]` is materialized as a wrapper-owned value copy and has no -native setter; it is not mutable native module storage and does not require an -`Aliased` annotation. +Deleting a declaration removes that name from the generated Python API. These +edits do not create or rename native variables and procedures; those still +need to exist in the compiled module. -Public module variables already have module lifetime, whether or not `save` is -written explicitly. Procedure-local saved variables remain internal, but their -state persists across calls. Multiple imported Python module objects backed by -the same extension observe the same native module storage. +For the complete rules, see +[Remove or Hide a Declaration](../reference/pyi-contracts/exports-and-modules.md#remove-or-hide-a-declaration) +and +[Set Module Values at Import](../reference/pyi-contracts/exports-and-modules.md#set-module-values-at-import). -## Module Arrays +--- + +## Flatten Module Namespaces -An allocatable module array is exposed as a persistent -`Allocatable[T[...]]` handle. Plain and `Aliased` declarations have the same -extraction behavior: a fresh `to_numpy()` call returns a live view of the -current allocation or `None`. `Aliased` preserves the native addressability -fact for other policy; it does not select view versus copy extraction. The -module attribute remains a handle even when native storage is unallocated: +The package entry `__init__.pyi` controls the Python import layout. Suppose an +extension named `library` contains two Fortran modules. x2py generates: ```python -module.allocate_values(np.int32(3)) -handle = module.values -assert handle.allocated is True -view = handle.to_numpy() -view[0] = np.float64(5.0) +# __init__.pyi +from . import module1 +from . import module2 ``` -Mutation through either kind of view reaches native module storage. A later -native deallocation or reallocation may make old views stale; accessing stale -views is unsupported and may crash. Use `view.copy()` first when Python needs -an independent lifetime. The same handle object then reports the new allocation -state. - -Pointer-array module variables expose `Pointer[T[...]]` handles with a default -conservative operation policy. Association inspection and `nullify()` are -available by default. `to_numpy()` requires a completed extraction path, and -ownership-changing operations require explicit pointer policy. +The modules remain child namespaces: -## Derived Module Objects +```python +from library.module1 import func1 +from library.module2 import func2 +``` -A derived-type module variable is not automatically addressable just because -the same type can be constructed from Python. Python construction asks x2py to -allocate a new pointer-backed native instance. A pre-existing module variable -has its own source attributes. Both plain and `Aliased` declarations are read -as live generated objects, but their bridge mechanisms differ: +To place every public name directly on `library`, replace those imports with +wildcard imports: ```python -from x2py.contracts import Aliased, Allocatable, Annotated, Float64 +# __init__.pyi +from .module1 import * +from .module2 import * +``` -class box: - values: Allocatable[Float64[:]] +Python then uses the flattened API: -current: Annotated[box, Aliased] -plain_current: box -``` +```python +import library -Reading either attribute returns a native-owned live object. The wrapper never -copies or destroys module storage. `current` may use its proved native address; -`plain_current` uses typed module-specific bridge operations instead of -fabricating an address. Supported component access such as `.values` returns an -`Allocatable[T[...]]` handle retaining the object; call `to_numpy()` to obtain -its current view. +library.func1() +library.func2() -Whole object replacement through `module.current = other` is not exposed. -Mutate live native module state through its completed module-object policy or a -wrapped native procedure. Use `.copy()` on an extracted array view when -independent NumPy storage is required. +# This is also valid: +from library import func1, func2 +``` -## Common Blocks +Public functions, variables, constants, and generated classes are exported at +the extension root. If the original module imports were replaced, +`library.module1` and `library.module2` are no longer exported. The native +Fortran modules and their storage do not move; only the Python API changes. -Common-block storage is not exported as Python variables. Wrapped procedures -may still read and write common-block state, so the supported surface is the -native procedure API: +Wildcard imports never use import order to resolve a collision. If both +modules export the same name, the wrapper build fails and asks for an explicit +choice. Export aliases instead: ```python -module.write_shared(np.int32(17)) -assert module.read_shared() == np.int32(17) +from .module1 import update as update_module1 +from .module2 import update as update_module2 ``` -x2py does not add locking around module state. The caller remains responsible -for synchronization across Python threads, OpenMP workers, or external native -code. - -## Limitations - -- Private module declarations remain hidden. -- Common-block variables have no generated attribute surface. -- Pointer state is exposed only when association, target lifetime, and a live - descriptor-view mechanism are complete; there is no detached-copy fallback. -- Plain and `Aliased` derived-type module variables both use the live generated - object surface. Plain objects require complete typed member operations; - `Aliased` objects may use a direct native address. Unsupported members block - generation instead of changing the public representation. -- Source ordering and external dependency discovery remain the caller's job. - -## Evidence And Troubleshooting - -Module variables, constants, saved state, visibility, and shared native state -are exercised by -[`test_module_state.py`](../../../tests/wrapper/fortran/module_state/test_module_state.py). -Common-block procedure behavior is exercised by -[`test_common_blocks.py`](../../../tests/wrapper/fortran/module_state/test_common_blocks.py). - -Module array views remain native-owned and can become stale after native -reallocation or deallocation; copy data that needs an independent lifetime. -Memory Management later expands that ownership rule, and Runtime Issues later -covers import, attribute, and shared-state problems. +This produces `library.update_module1` and `library.update_module2`. You can +also import only selected names instead of flattening every public declaration. +Build the edited entry using the +[editable-contract workflow](../getting-started/beginner-workflow.md#4-optionally-edit-the-contract). + +For all supported imports, aliases, and namespace layouts, see +[Choose the Package Shape](../reference/pyi-contracts/exports-and-modules.md#choose-the-package-shape). + +--- + +## Important Rules + +- Private declarations are hidden from the Python API. +- Common blocks are **not** exposed as Python variables (only indirectly through procedures that access them). +- Module state is **shared** native storage — changes made through one reference are visible to all others. +- The extension name is derived from the source filename unless overridden. + +--- + +## Next + +- Continue with [Optional Arguments](optional-arguments.md). +- Read [Memory Management](memory-management.md) before keeping live views of + module storage. diff --git a/docs/user/guide/wrapping-subroutines.md b/docs/user/guide/wrapping-subroutines.md index 221c6c2dc..ae1d92883 100644 --- a/docs/user/guide/wrapping-subroutines.md +++ b/docs/user/guide/wrapping-subroutines.md @@ -1,42 +1,43 @@ --- title: Wrapping Subroutines +description: How x2py wraps Fortran `subroutine` procedures — output arguments, in-place mutation, and result projection audience: users prerequisites: data types, first wrapped function related: wrapping-functions.md, arrays.md, optional-arguments.md status: maintained +publication: reviewed --- # Wrapping Subroutines -A subroutine has no direct native function result, but its output arguments may -become Python return values. The generated signature separates hidden values -from the storage that the Python caller must allocate. +A Fortran `subroutine` has no direct return value. Scalar outputs and objects +created by Fortran form the Python result. Caller-provided mutable objects +change in place. -## Argument Projection - -| Native role | Python call shape | Python result shape | -| --- | --- | --- | -| scalar `intent(in)` | visible exact-type argument | no result | -| scalar `intent(out)` | hidden | returned value | -| immutable scalar replacement | visible input when required | returned replacement | -| array `intent(in)` | visible NumPy array | no result | -| array `intent(out)` | visible writable NumPy array | same array when projected | -| array `intent(inout)` | visible writable NumPy array | mutated in place; not duplicated unless explicitly projected | -| allocatable array `intent(out)` | hidden unless optional | wrapper-owned `AllocatableArray` | -| allocatable array `intent(inout)` | visible `AllocatableArray` argument | same handle when projected; allocation state changes in place | -| supported derived `intent(out)` | hidden | new wrapper-owned instance | +--- -`optional, intent(out)` is the visibility exception for normally hidden -outputs. The argument remains visible so the caller can omit it and make native -`present(...)` false. Optional scalar outputs use mutable rank-zero storage such -as `Int32[()]`. An optional allocatable array argument uses -`Allocatable[T[...]] | None = ...`: omission means native absence, while a -present handle carries allocated or unallocated descriptor state. +## How Arguments Become Python Results + +| Native Argument | Python Call | Python Result | +|-----------------------------|------------------------------|-----------------------------------| +| `intent(in)` scalar/array | Visible argument | Not returned | +| `intent(out)` scalar | Hidden | Returned as value | +| `intent(inout)` scalar | Visible argument | Returned as replacement value | +| `intent(out)` array | Visible writable NumPy array | Filled in place; not returned | +| `intent(inout)` array | Visible writable NumPy array | Mutated in place; not returned | +| 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 | + +Without `intent`, x2py 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`. -The generated `.pyi` is authoritative when a procedure combines several of -these forms. +--- -## Complete Output Example +## Complete Example Create `outputs.f90`: @@ -44,10 +45,10 @@ Create `outputs.f90`: module outputs implicit none contains + subroutine bounds(values, smallest, largest) real(8), intent(in) :: values(:) real(8), intent(out) :: smallest, largest - smallest = minval(values) largest = maxval(values) end subroutine bounds @@ -55,142 +56,82 @@ contains subroutine scale_in_place(values, factor) real(8), intent(inout) :: values(:) real(8), intent(in) :: factor - values = factor * values end subroutine scale_in_place + subroutine scale_scalar(value, factor) + real(8), intent(inout) :: value + real(8), intent(in) :: factor + value = factor * value + end subroutine scale_scalar + subroutine fill(values) real(8), intent(out) :: values(:) values = 1.0_8 end subroutine fill -end module outputs -``` - -Inspecting `outputs.f90` prints these subroutine contracts: -```python -from x2py.contracts import Addr, Arg, Float64, Return, Returns, native_call - -@native_call([Arg(0), Return('smallest', 0), Return('largest', 1)]) -def bounds( - values: Float64[::] -) -> tuple[Float64, Float64]: ... - -@native_call([Arg(0), Addr(Arg(1))]) -def scale_in_place( - values: Float64[::], - factor: Float64 -) -> None: ... - -def fill( - values: Float64[::] -) -> Returns["values", Float64[::]]: ... +end module outputs ``` -Build the extension: +Build it: ```bash python3 -m x2py outputs.f90 --out-dir build/outputs ``` -Then assert scalar projection, in-place mutation, and output-array projection: +--- + +## Python Usage ```python import sys + import numpy as np sys.path.insert(0, "build/outputs") -import outputs +from outputs.outputs import bounds, fill, scale_in_place, scale_scalar + +# Hidden scalar outputs → returned as tuple +data = np.array([4.0, -2.0, 7.0], dtype=np.float64) +smallest, largest = bounds(data) +print(smallest, largest) # -2.0 7.0 -api = outputs.outputs -source = np.array([4.0, -2.0, 7.0], dtype=np.float64) -smallest, largest = api.bounds(source) -assert smallest == np.float64(-2.0) -assert largest == np.float64(7.0) +# Scalar inout replacement +updated = scale_scalar(np.float64(4.0), np.float64(2.5)) +print(updated) # 10.0 -mutable = np.array([1.0, 2.0, 3.0], dtype=np.float64) -assert api.scale_in_place(mutable, np.float64(3.0)) is None -np.testing.assert_array_equal(mutable, np.array([3.0, 6.0, 9.0], dtype=np.float64)) +# In-place mutation +arr = np.array([1.0, 2.0, 3.0], dtype=np.float64) +scale_in_place(arr, np.float64(3.0)) +print(arr) # [3. 6. 9.] +# Caller-provided output array target = np.empty(4, dtype=np.float64) -returned = api.fill(target) -assert returned is target -np.testing.assert_array_equal(target, np.ones(4, dtype=np.float64)) +fill(target) +print(target) # [1. 1. 1. 1.] ``` -## Hidden Scalar Outputs - -A non-allocatable scalar output does not require caller storage in the normal -source-generated API. The `bounds` call above returns `smallest` and `largest` -without corresponding Python arguments. - -Hidden outputs are returned in native argument order. A hidden scalar character -output becomes a new `str`, and a hidden scalar derived output becomes a new -wrapper-owned object. - -In `@native_call`, `Return(...)` always names hidden writable native output -storage. The wrapper passes that storage to the native procedure by address, -because an output argument cannot be written by value. Do not write -`Addr(Return(...))`; `Return(...)` already carries the output-storage contract. - -## Caller-Provided Arrays - -Array output and inout storage remains visible. Allocate it with the exact -dtype, shape, layout, alignment, and writeability required by the contract. The -`fill` call above returns the same `target` object after native mutation, while -`scale_in_place` mutates `mutable` in place and returns `None`. - -The initial contents of an `intent(out)` array are ignored by Fortran, but the array must still be pre-allocated on the Python side. -An `intent(inout)` array is read and written in place. x2py does not create a hidden replacement -for ordinary array storage merely because the supplied array is inconvenient; -an incompatible array is rejected before the native call. - -## Multiple Results - -For a subroutine, projected results follow the native output argument order. For a function, -the function result comes first, followed by output arguments in native argument -order. A caller-provided output can remain visible and also be named in return -metadata; hidden outputs use ordinary result annotations. - -Do not infer tuple order from Python assignment names. Review the generated -`.pyi` and its `Returns[...]` entries when several outputs are present. - -## Scalar Mutation - -Python numbers and strings are immutable. They cannot expose native in-place -mutation. Source-generated output scalars are returned as values, and supported -character `intent(inout)` uses replacement projection: the original `str` -remains unchanged and Python receives a new string. - -An edited semantic contract can deliberately require writable zero-dimensional -NumPy storage for a visible scalar output. That is an advanced native-order -contract, not the normal source-generated subroutine API. Editing Semantic -`.pyi` Contracts explains that advanced form later. - -## Limitations - -- Pointer scalar output and inout use nullable copied-value projection. Pointer - array descriptor arguments use `Pointer[T[...]]` handles; pointer results and - reassociation without completed policy remain blocked. -- Character arrays require fixed-width NumPy bytes dtype storage. Arrays of - derived types are blocked. -- Wrapper-owned allocatable scalar derived results may be passed to compatible - dummies with same-object allocation-state writeback. Module allocatable and - pointer scalar objects use reversible typed transactions for compatible - descriptor dummies. The later Wrapping Derived Types guide gives the complete - “Scalar Actuals And Native Dummies” matrix. -- Unsupported output combinations stop during wrapper planning; code generation does not - silently select another projection. - -## Evidence And Troubleshooting - -Output projection, tuple ordering, caller-provided arrays, allocatable outputs, -and character/derived outputs are exercised by -[`test_output_arguments.py`](../../../tests/wrapper/fortran/function_calls/test_output_arguments.py) -and -[`test_native_call_examples.py`](../../../tests/wrapper/fortran/function_calls/test_native_call_examples.py). - -For array validation failures, compare the value with the exact generated -dtype, rank, shape, layout, and writeability contract. Arrays, Memory -Management, and Error Handling expand validation, ownership, and projected -status exceptions later in the guide. +--- + +## Key Rules + +- Scalar `intent(out)` values are hidden in the call and returned. +- Scalar `intent(inout)` values are visible inputs and are also returned as + replacement values; the original Python scalar object is unchanged. +- Array `intent(out/inout)` arguments must be pre-allocated by the caller and + are mutated in place. +- Ordinary `intent(out/inout)` arrays are not added to the Python result. +- Scalar derived-type objects follow the same in-place rule as arrays. +- Array function results and hidden allocatable outputs still return new + Python-visible objects because the caller did not supply their storage. +- The generated `.pyi` contract is the source of truth for what is returned. +- For functions with both a return value **and** outputs, the function result comes first in the tuple. + +--- + +## Next + +- Continue with [Wrapping Modules](wrapping-modules.md). +- Then read [Optional Arguments](optional-arguments.md) to control whether a + native argument is present. +- For advanced memory management, see [Allocatables](allocatables.md) and [Pointers](pointers.md). diff --git a/docs/user/index.md b/docs/user/index.md index 5bcb58e16..e815cc055 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -2,27 +2,23 @@ title: User Documentation audience: users prerequisites: none -related: getting-started/index.md, guide/index.md, reference/index.md +related: getting-started/index.md, guide/index.md status: maintained +publication: reviewed --- # User Documentation -This lane explains how to use x2py. Follow the learning material in navigation -order; instructional pages link back to established concepts and name later -topics without interrupting the current task. +Use these pages to install x2py, verify your environment, build your first +Fortran wrappers, and understand the supported behavior of generated Python +extensions. -## Learn And Build +## Start Here 1. [Getting Started](getting-started/index.md) 2. [User Guide](guide/index.md) -3. [Tutorials](tutorials/index.md) -4. [Examples](examples/index.md) -## Look Up Behavior - -- [Reference](reference/index.md) -- [Language Support](language-support/index.md) -- [FAQ](faq/index.md) -- [Troubleshooting](troubleshooting/index.md) -- [Changelog](changelog/index.md) +Getting Started covers installation, environment verification, the first +standalone wrapper, the first module wrapper, and the beginner edit-build-test +loop. The User Guide covers supported Fortran wrapper features, runtime +behavior, packaging, and distribution. diff --git a/docs/user/language-support/feature-matrix.md b/docs/user/language-support/feature-matrix.md index 960ee3909..025eff9f6 100644 --- a/docs/user/language-support/feature-matrix.md +++ b/docs/user/language-support/feature-matrix.md @@ -1,15 +1,16 @@ --- title: Language Feature Matrix audience: users, developers -prerequisites: user guide, verified examples cookbook -related: supported-features.md, partially-supported-features.md, unsupported-features.md, planned-features.md, ../guide/index.md +prerequisites: user guide +related: ../guide/index.md, ../reference/fortran-wrapper.md status: maintained +publication: draft --- # Language Feature Matrix This matrix is the user-facing support index for native-language features. It -does not replace the detailed [Fortran wrapper guide](../guide/fortran-wrapper.md); +does not replace the detailed [Fortran wrapper reference](../reference/fortran-wrapper.md); it points each feature to the owning docs, implementation route, evidence, and limitations. @@ -38,7 +39,7 @@ inspection-only or partial support. | Output arguments and multiple results | Supported | [Subroutine projection](../guide/wrapping-subroutines.md) | [Ownership and lowering](../../developer/source-map.md#common-change-routes) | [Output argument tests](../../../tests/wrapper/fortran/function_calls/test_output_arguments.py) | Tuple ordering and caller-provided array behavior follow the wrapper guide. | | Optional arguments | Supported | [Optional arguments](../guide/optional-arguments.md) | [Binding generation](../../developer/source-map.md#common-change-routes) | [Optional argument tests](../../../tests/wrapper/fortran/function_calls/test_optional_arguments.py) | Unsupported optional combinations fail during wrapper planning. | | Allocatable array handles, descriptor arguments, and owned results | Supported | [Allocatables](../guide/allocatables.md) | [Ownership policy](../../developer/source-map.md#common-change-routes) | [Allocatable runtime tests](../../../tests/wrapper/fortran/module_state/test_allocatable_views.py), [scalar-derived matrix tests](../../../tests/wrapper/fortran/derived_types/test_scalar_derived_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/source-map.md#common-change-routes) | [Pointer tests](../../../tests/wrapper/fortran/derived_types/test_pointers.py), [scalar descriptor tests](../../../tests/wrapper/fortran/module_state/test_allocatable_views.py), [scalar-derived matrix tests](../../../tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py) | Descriptor arguments, module/field handles, strided views, wrapper-owned scalar-derived pointer holders, and module pointer reassociation transactions are supported. Unknown target ownership and unsupported pointer arrays remain blocked. | +| Pointer scalar projections and array handles | Partially supported | [Pointers](../guide/pointers.md) | [Ownership policy](../../developer/source-map.md#common-change-routes) | [Pointer tests](../../../tests/wrapper/fortran/derived_types/test_pointers.py), [scalar descriptor tests](../../../tests/wrapper/fortran/module_state/test_allocatable_views.py), [scalar-derived matrix tests](../../../tests/wrapper/fortran/derived_types/test_scalar_derived_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/source-map.md#common-change-routes) | [Array result tests](../../../tests/wrapper/fortran/arrays/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/source-map.md#common-change-routes) | [Array contract tests](../../../tests/wrapper/fortran/arrays/test_array_contracts.py), [multidimensional tests](../../../tests/wrapper/fortran/arrays/test_multidimensional_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/source-map.md#common-change-routes) | [Derived boundary tests](../../../tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py), [method tests](../../../tests/wrapper/fortran/derived_types/test_derived_type_methods.py) | Derived-type arrays and some polymorphic forms are not included. | @@ -46,18 +47,18 @@ inspection-only or partial support. | Generic constructor interfaces and overloaded runtime initialization | Supported | [Constructors](../guide/wrapping-derived-types.md#constructors) | [Class policy and lowering](../../developer/source-map.md#common-change-routes) | [Bound constructor tests](../../../tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py), [overload tests](../../../tests/wrapper/fortran/naming/test_phase9_class_overloads.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#workflow-feature-pointers) | [Module state tests](../../../tests/wrapper/fortran/module_state/test_module_state.py), [scalar-derived matrix tests](../../../tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py), [common-block tests](../../../tests/wrapper/fortran/module_state/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/source-map.md#common-change-routes) | [Enum tests](../../../tests/wrapper/fortran/scalars/test_fortran_enums.py) | No Python `Enum` or `IntEnum` classes are generated. | -| Scalar character arguments, results, and fields | Supported | [Strings](../guide/data-types.md#strings) | [Character bridge route](../../developer/source-map.md#common-change-routes) | [Character argument tests](../../../tests/wrapper/fortran/strings/test_character_arguments.py), [edge-case tests](../../../tests/wrapper/fortran/strings/test_character_edge_cases.py) | Character arrays use fixed-width NumPy bytes dtype; mutable scalar deferred-length storage is blocked. | +| Scalar character arguments, results, and fields | Supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/source-map.md#common-change-routes) | [Character argument tests](../../../tests/wrapper/fortran/strings/test_character_arguments.py), [edge-case tests](../../../tests/wrapper/fortran/strings/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/source-map.md#hotspot-index) | [Scalar kind tests](../../../tests/wrapper/fortran/scalars/test_scalar_kinds.py) | Wider real, complex, and explicit logical storage is blocked without portable NumPy mapping. | -| Caller-ordered multi-source builds, Makefiles, verbose mode, and output placement | Supported | [Packaging](../guide/packaging.md), [multi-source recipe](../examples/recipes/build-multiple-fortran-sources.md) | [Wrapper orchestration](../../developer/source-map.md#common-change-routes) | [Multi-source tests](../../../tests/wrapper/fortran/multiple_files/test_multi_source_builds.py), [compiler verbose tests](../../../tests/wrapper/fortran/build_from_source/test_compiler_verbose.py) | x2py does not discover, reorder, or resolve all external source dependencies. | -| Visibility, naming, keyword escaping, and collision policy | Supported | [Visibility and naming](../guide/fortran-wrapper.md#visibility-naming-and-the-python-surface) | [Naming policy](../../developer/source-map.md#hotspot-index) | [Visibility/naming tests](../../../tests/wrapper/fortran/naming/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/source-map.md#common-change-routes) | [Multi-source tests](../../../tests/wrapper/fortran/multiple_files/test_multi_source_builds.py), [compiler verbose tests](../../../tests/wrapper/fortran/build_from_source/test_compiler_verbose.py) | x2py 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/source-map.md#hotspot-index) | [Visibility/naming tests](../../../tests/wrapper/fortran/naming/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/source-map.md#common-change-routes) | [Callback plan tests](../../../tests/wrapper_codegen/test_phase10_callbacks.py), [scalar callback tests](../../../tests/wrapper/fortran/callbacks/test_scalar_callbacks.py), [array callback tests](../../../tests/wrapper/fortran/callbacks/test_array_callbacks.py), [derived callback tests](../../../tests/wrapper/fortran/callbacks/test_derived_callbacks.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/source-map.md#common-change-routes) | [Runtime policy tests](../../../tests/wrapper/fortran/runtime_behavior/test_runtime_policies.py), [recursion tests](../../../tests/wrapper/fortran/runtime_behavior/test_runtime_recursion.py), [OpenMP tests](../../../tests/wrapper/fortran/runtime_behavior/test_openmp_runtime.py), [ABI tests](../../../tests/wrapper/fortran/build_from_source/test_runtime_abi.py) | OpenMP and ABI evidence is compiler/platform-specific; callers still own native synchronization. | -| Fortran source wrapper builds | Supported | [Packaging](../guide/packaging.md), [CLI recipe](../examples/recipes/build-and-import-cli.md) | [Wrapper orchestration](../../developer/source-map.md#common-change-routes) | [Build modes](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py), [runtime ABI](../../../tests/wrapper/fortran/build_from_source/test_runtime_abi.py) | Implemented for ordered Fortran source inputs. | +| Fortran source wrapper builds | Supported | [Building the shared library](../guide/building-shared-library.md) | [Wrapper orchestration](../../developer/source-map.md#common-change-routes) | [Build modes](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py), [runtime ABI](../../../tests/wrapper/fortran/build_from_source/test_runtime_abi.py) | Implemented for ordered Fortran source inputs. | ## Supported Inspection Features @@ -72,7 +73,7 @@ X2PY_C_DOCS_END --> ## Unsupported Or Blocked Forms @@ -81,16 +82,16 @@ X2PY_C_DOCS_END --> | --- | --- | --- | --- | --- | --- | | Pointer-array results and unproved reassociation | Unsupported | [Pointer limitations](../guide/pointers.md#unsupported-forms) | [Ownership policy](../../developer/source-map.md#common-change-routes) | [Pointer tests](../../../tests/wrapper/fortran/derived_types/test_pointers.py) | Result handles need stable owner storage and target lifetime; reassociation and ownership-changing operations need explicit completed policy. | | Persistent callbacks and procedure pointers | Unsupported | [Callback limitations](../guide/callbacks.md#unsupported-forms) | [Callback route](../../developer/source-map.md#common-change-routes) | [Callback tests](../../../tests/wrapper/fortran/callbacks/test_scalar_callbacks.py) | Callbacks are valid only during the wrapped call. | -| Advanced multi-source dependency discovery and external-library integration | Unsupported | [Packaging limits](../guide/packaging.md#limitations) | [Build orchestration](../../developer/source-map.md#common-change-routes) | [Multi-source tests](../../../tests/wrapper/fortran/multiple_files/test_multi_source_builds.py) | x2py does not infer dependency graphs, prebuilt module paths, or external library discovery. | +| Advanced multi-source dependency discovery and external-library integration | Unsupported | [Multiple source files](../guide/building-shared-library.md#multiple-source-files) | [Build orchestration](../../developer/source-map.md#common-change-routes) | [Multi-source tests](../../../tests/wrapper/fortran/multiple_files/test_multi_source_builds.py) | x2py 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/source-map.md#common-change-routes) | [Array contract tests](../../../tests/wrapper/fortran/arrays/test_array_contracts.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](../guide/wrapping-derived-types.md#inheritance-and-polymorphism) | [Class policy route](../../developer/source-map.md#common-change-routes) | [Inheritance tests](../../../tests/wrapper/fortran/derived_types/test_inheritance.py) | Results, mutable dummies, arrays, polymorphic allocatable/pointer scalars, and `class(*)` are blocked. | | Ambiguous or incomplete constructor overload sets | Unsupported | [Constructor limitations](../guide/wrapping-derived-types.md#constructors) | [Constructor route](../../developer/source-map.md#common-change-routes) | [Constructor overload tests](../../../tests/wrapper/fortran/naming/test_phase9_class_overloads.py), [class-plan validation tests](../../../tests/wrapper_codegen/test_phase9_class_surfaces.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/data-types.md#strings) | [Character bridge route](../../developer/source-map.md#common-change-routes) | [Character edge tests](../../../tests/wrapper/fortran/strings/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. | +| Character arrays and mutable deferred-length character storage | Partially supported | [Strings](../guide/strings.md) | [Character bridge route](../../developer/source-map.md#common-change-routes) | [Character edge tests](../../../tests/wrapper/fortran/strings/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/source-map.md#hotspot-index) | [Scalar kind tests](../../../tests/wrapper/fortran/scalars/test_scalar_kinds.py) | x2py blocks rather than silently losing precision or Boolean storage semantics. | ## Planned Or Reserved Areas diff --git a/docs/user/language-support/index.md b/docs/user/language-support/index.md index 4c84369ee..651fefa6c 100644 --- a/docs/user/language-support/index.md +++ b/docs/user/language-support/index.md @@ -2,8 +2,9 @@ title: Language Support audience: users, developers prerequisites: user guide -related: feature-matrix.md, ../guide/fortran-wrapper.md +related: feature-matrix.md, ../reference/fortran-wrapper.md status: maintained +publication: draft --- # Language Support @@ -18,11 +19,3 @@ The matrix links each row to: - the source-navigation route for developers; - runtime, parser, semantic, or documentation evidence; and - the current limitation or blocker. - -## Pages - -- [Feature matrix](feature-matrix.md) -- [Supported features](supported-features.md) -- [Partially supported features](partially-supported-features.md) -- [Unsupported features](unsupported-features.md) -- [Planned features](planned-features.md) diff --git a/docs/user/language-support/partially-supported-features.md b/docs/user/language-support/partially-supported-features.md deleted file mode 100644 index 0cb2c3a46..000000000 --- a/docs/user/language-support/partially-supported-features.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Partially Supported Features -audience: users, developers -prerequisites: feature matrix -related: feature-matrix.md, unsupported-features.md -status: maintained ---- - -# Partially Supported Features - -Partially supported means a tested subset exists, but related forms are -unsupported during planning, or tracked as future work. - - diff --git a/docs/user/language-support/planned-features.md b/docs/user/language-support/planned-features.md deleted file mode 100644 index f7c5cf851..000000000 --- a/docs/user/language-support/planned-features.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Planned Features -audience: users, developers -prerequisites: feature matrix -related: unsupported-features.md, feature-matrix.md -status: maintained ---- - -# Planned Features - -Planned means the documentation or roadmap reserves space for a future feature, -but current docs must not present it as supported behavior. - -Use the [Planned Or Reserved Areas](feature-matrix.md#planned-or-reserved-areas) -section of the matrix for the current list. diff --git a/docs/user/language-support/supported-features.md b/docs/user/language-support/supported-features.md deleted file mode 100644 index 93a95259d..000000000 --- a/docs/user/language-support/supported-features.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Supported Features -audience: users, developers -prerequisites: feature matrix -related: feature-matrix.md, ../guide/fortran-wrapper.md -status: maintained ---- - -# Supported Features - -Supported means the documented subset has current runtime or inspection -evidence. Runtime wrapper rows must link to wrapper tests that compile, import, -call, and check behavior. - -Use the [Supported Runtime Features](feature-matrix.md#supported-runtime-features) -and [Supported Inspection Features](feature-matrix.md#supported-inspection-features) -sections of the matrix for the current list. diff --git a/docs/user/language-support/unsupported-features.md b/docs/user/language-support/unsupported-features.md deleted file mode 100644 index 347f21696..000000000 --- a/docs/user/language-support/unsupported-features.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Unsupported Features -audience: users, developers -prerequisites: feature matrix -related: partially-supported-features.md, planned-features.md -status: maintained ---- - -# Unsupported Features - -Unsupported means x2py intentionally blocks the form or has no safe wrapper -contract for it yet. Unsupported rows should link to the user-facing limitation -and to wrapper-planning or runtime evidence where possible. - -Use the [Unsupported Or Blocked Forms](feature-matrix.md#unsupported-or-blocked-forms) -section of the matrix for the current list. diff --git a/docs/user/reference/callbacks.md b/docs/user/reference/callbacks.md deleted file mode 100644 index b479fb43b..000000000 --- a/docs/user/reference/callbacks.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Callbacks Reference -audience: advanced users, developers -prerequisites: semantic .pyi format, data types -related: semantic-pyi-format.md, ../guide/callbacks.md, generated-functions.md -status: maintained ---- - -# Callbacks Reference - -Callback contracts are native-facing. A normal generated function signature -describes how Python calls the wrapper; a `@prototype` declaration describes -the native callback signature invoked through the generated adapter. - -The callable argument list therefore preserves callback argument order, -value/reference calling, rank, shape, character length, and result shape. It -does not repeat native callback direction. - -## Immediate Callback Scope - -Supported callbacks are call-scoped. The generated wrapper keeps the Python -callable alive only for the active wrapped call, installs a callback context, -passes a generated Fortran adapter to the native routine, and clears the context -when the wrapped call returns. - -Policy completion records the callback ABI, primitive-scalar value projection, -reference writeback for non-scalar storage, context lifecycle, entering-thread -rule, GIL actions, and fatal action before wrapper planning. Direct -wrapper-plan generation then emits one typed Fortran adapter and one native -trampoline per callback site. Neither backend infers callback transport, shape, -or ownership from generated locals. - -Native code must not store the callback or call it later. Stored procedure -pointers, optional dummy procedures, asynchronous callbacks, and cross-thread -callback invocation are unsupported. - -## Prototype Argument Forms - -Declare a named prototype once and use its name as the callback argument type: - -```python -from x2py.contracts import Float64, Int32, prototype - -@prototype -def transform(count: Int32, values: Float64[count]) -> Float64[count]: ... - -def apply_transform(callback: transform, ...) -> ...: ... -``` - -Prototype arguments use ordinary semantic types. Reference passing is the -default; `Value(T)` is the only callback argument ABI override. - -| Spelling | Fortran callback dummy | Python callback object | -| --- | --- | --- | -| `Int32` | scalar reference dummy | owned `np.int32` scalar value | -| `Value(Float64)` | scalar `value` dummy | owned `np.float64` scalar value | -| `Float64[n]` | array reference dummy | writable NumPy array view | -| `point_t` | derived reference dummy | generated wrapper object | -| `Value(point_t)` | derived `value` dummy | wrapper over the call-local value copy | - -`Addr(...)` is unnecessary inside callback signatures because reference is the -default. `Value(...)` is required only when the native callback dummy has the -Fortran `value` attribute. - -Prototypes are semantic declarations and never become Python runtime exports. -A prototype defined in another contract module is referenced through a normal -relative semantic import. That import supplies the signature identity and -complete transport contract. Post-IR policy decides whether the backend may use -an implicit external declaration or must import and use the named native -prototype. The explicit path obtains native direction from that real interface; -the semantic prototype does not repeat it. - -## Character Arguments - -Fixed-length character reference dummies use their ordinary type spelling: - -```python -from x2py.contracts import String, prototype - -@prototype -def update_label(label: String[8]) -> None: ... -``` - -The Python callback receives a NumPy scalar bytes array, such as `np.ndarray` -with shape `()` and dtype `S8`, and writes through that storage: - -```python -def update(label): - label[...] = b"done " -``` - -Generated `.pyi` contracts emit `String[n]` regardless of native callback -direction. Callback context makes reference character storage mutable without -adding direction metadata to the annotation. - -## Results And Copy-Back - -Scalar callback results are converted from the Python return value. Primitive -scalar arguments are always owned NumPy scalar values and never scalar-storage arrays, so -scalar reference writeback is unsupported. Array, derived, and -character-storage reference arguments are copied back before the Fortran -adapter returns to native code. - -Callback exceptions, invalid callback return conversion, and unsupported -cross-thread callback execution are fatal at the callback boundary: x2py prints -the Python traceback and aborts the host process. diff --git a/docs/user/reference/cli-commands.md b/docs/user/reference/cli-commands.md index b6fdea972..56179047e 100644 --- a/docs/user/reference/cli-commands.md +++ b/docs/user/reference/cli-commands.md @@ -4,6 +4,7 @@ audience: users, developers prerequisites: installation related: python-api.md, configuration-files.md status: maintained +publication: draft --- # CLI Commands Reference @@ -81,8 +82,8 @@ 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 `scale.f90` source and exact commands from the -[README Quick Start](../../../README.md#quick-start), which contains the -complete source, expected artifacts, contract, and import flow. +[homepage example](../../index.md#try-x2py), which contains the +complete source, basic build, import flow, and expected result. The full build help uses the following two forms: @@ -106,7 +107,7 @@ default output directory shown there is `./__x2py__`. saved build; it does not generate a manifest. Manifest replay accepts only overrides that the replay implementation consumes: `--out`, `--compiler`, `-I`/`--include-dir`, `--json`, `--verbose`, -`--no-color`, and `--debug`/`--debug-traceback`. The manifest owns its output +`--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. @@ -182,7 +183,7 @@ python3 -m x2py generate --makefile scale.f90 --out-dir build ``` These examples reuse `scale.f90` from the -[README Quick Start](../../../README.md#quick-start). +[homepage example](../../index.md#try-x2py). These modes are mutually exclusive. Source and Makefile generation still run the preprocessing and semantic-policy stages needed to produce a valid wrapper @@ -376,7 +377,7 @@ Important boundaries: --build-manifest PATH` regenerates `Makefile.x2py` without positional contracts or repeated native flags. Replay may override only `--out`, `--compiler`, `-I`/`--include-dir`, `--json`, `--verbose`, `--no-color`, and - `--debug`/`--debug-traceback`; all other build settings come from the + `--debug`; all other build settings come from the manifest. | `--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. | | `--no-color` | Disables ANSI color in parse diagnostics. | -| `--debug`, `--debug-traceback` | Re-raises parser errors so Python prints a traceback. | +| `--debug` | Re-raises command failures so Python prints a traceback. | When `rich-argparse` is installed, x2py uses its colored help formatter automatically. Install the optional UI dependencies for a published package @@ -438,7 +439,7 @@ X2PY_C_DOCS_END --> ## Related pages - Use [Python API Reference](python-api.md) when calling x2py from Python. -- Use [Fortran Wrapper Guide](../guide/fortran-wrapper.md) for wrapper +- Use [Fortran Wrapper Reference](fortran-wrapper.md) for wrapper build workflows. - Use [Semantic .pyi Format](semantic-pyi-format.md) when editing wrapper contracts. diff --git a/docs/user/reference/configuration-files.md b/docs/user/reference/configuration-files.md index ff137bbfc..bea78a8fb 100644 --- a/docs/user/reference/configuration-files.md +++ b/docs/user/reference/configuration-files.md @@ -2,8 +2,9 @@ title: Configuration Files Reference audience: users, developers prerequisites: packaging, CLI commands -related: cli-commands.md, python-api.md, ../guide/packaging.md, ../../developer/quality-assurance.md +related: cli-commands.md, python-api.md, ../guide/building-shared-library.md, ../../developer/quality-assurance.md status: maintained +publication: draft --- # Configuration Files Reference @@ -63,7 +64,7 @@ build flags. The preceding `generate --makefile` command is what writes a new Replay accepts only settings that are defined as overrides: `--out`, `--compiler`, `-I`/`--include-dir`, `--json`, `--verbose`, `--no-color`, and -`--debug`/`--debug-traceback`. The manifest remains authoritative for its +`--debug`. The manifest remains authoritative for its output directory, language, preprocessing recipe, wrapper behavior, native inputs, and ordered link plan. Passing one of those saved settings again is an error rather than an ignored command-line value. @@ -114,21 +115,51 @@ Wrapper users select inputs through CLI flags, Python API arguments, semantic ## `mkdocs.yml` -`mkdocs.yml` is the documentation-site seed configuration. It sets -`docs_dir: docs` and owns the visible navigation tree. A page that becomes -maintained reference material should be reachable from the appropriate area -index and from `mkdocs.yml`. +`mkdocs.yml` is the documentation-site configuration. It sets `docs_dir: docs`, +selects MkDocs' built-in Read the Docs theme, owns the complete intended +navigation tree, and loads the publication hook. The theme configuration keeps +the sidebar expanded through four navigation levels. A local stylesheet keeps +its scrollbar visible and draggable when the navigation is longer than the +screen. The same stylesheet keeps the page body adjacent to the sidebar with a +`1200px` maximum width, balancing readable prose with room for code and tables. +Code and result blocks use the available page width up to a consistent `56rem` +cap; long lines scroll inside the block. Local JavaScript and CSS add an +accessible copy control to every rendered code, command-output, and result +block, with separate space reserved beside the text. The production hook +includes only pages whose front matter says `publication: reviewed`. A draft +lane index suppresses its complete User, Developer, or Maintainer lane. Links +from documentation pages to existing source, tests, configuration, and other +repository evidence are rendered as GitHub links because those files are +outside the MkDocs source tree. Links between documentation pages remain +site-relative and are never rewritten to GitHub. + +Preview exactly what GitHub Pages will publish with: + +```bash +python3 -m mkdocs serve +``` + +Include unpublished pages locally while reviewing them with: + +```bash +X2PY_DOCS_INCLUDE_DRAFTS=1 python3 -m mkdocs serve +``` + +Changing a page from `publication: draft` to `publication: reviewed` makes it +eligible for the next production deployment. New pages must also be reachable +from the appropriate area index and `mkdocs.yml` navigation. Documentation-only changes normally run: ```bash -python3 -m pytest -q tests/docs/test_examples.py tests/docs/test_structure.py +python3 -m pytest -q tests/docs +python3 -m mkdocs build --strict git diff --check ``` -The structure test verifies metadata, TODO policy for unfinished pages, -navigation for required areas, visible deferred-doc boundaries, reference -links, and documentation checklist synchronization. +The documentation tests verify metadata, publication filtering, TODO policy +for unfinished pages, navigation for required areas, visible deferred-doc +boundaries, reference links, and documentation checklist synchronization. ## Evidence And Maintenance @@ -143,5 +174,5 @@ Tooling configuration is covered by [`test_check_static_analysis_versions.py`](../../../tests/tools/test_check_static_analysis_versions.py). When a generated file contract changes, update this page with the CLI reference, -Python API reference, packaging guide, and wrapper tests that prove the replay -or Makefile behavior. +Python API reference, shared-library build guide, and wrapper tests that prove +the replay or Makefile behavior. diff --git a/docs/user/reference/diagnostic-codes.md b/docs/user/reference/diagnostic-codes.md index 49429ff17..beaa97f0d 100644 --- a/docs/user/reference/diagnostic-codes.md +++ b/docs/user/reference/diagnostic-codes.md @@ -4,6 +4,7 @@ audience: users, developers prerequisites: error handling related: index.md, ../troubleshooting/index.md status: maintained +publication: draft --- # Diagnostic Codes diff --git a/docs/user/guide/fortran-wrapper.md b/docs/user/reference/fortran-wrapper.md similarity index 82% rename from docs/user/guide/fortran-wrapper.md rename to docs/user/reference/fortran-wrapper.md index 0ae673deb..5ab1b9ca2 100644 --- a/docs/user/guide/fortran-wrapper.md +++ b/docs/user/reference/fortran-wrapper.md @@ -1,39 +1,105 @@ --- -title: Fortran Wrapper Guide +title: Fortran Wrapper Reference audience: users, advanced users prerequisites: first wrapped module, NumPy basics -related: index.md, editing-semantic-pyi-contracts.md, ../language-support/index.md +related: ../guide/index.md, pyi-contracts/index.md, ../language-support/index.md status: maintained +publication: draft --- -# Fortran Wrapper Guide - -This guide describes the Python API generated by x2py for Fortran code. It is -both a user reference and the canonical contract for ownership, lifetime, -naming, supported behavior, and current limitations. - -The guide follows the wrapper by subject. Each subject includes a small example -showing the Fortran interface and the corresponding Python use. Examples omit -unrelated module scaffolding when that makes the contract easier to see. - -Runtime evidence lives in -[`tests/wrapper`](../../../tests/wrapper/fortran/README.md). A behavior is supported -only when generated native sources compile, the extension imports, and Python -tests exercise successful calls, mutation, lifetime, and relevant failures. - -This guide covers the implemented wrapper for Fortran source inputs. +# Fortran Wrapper Reference + +This reference describes the Python API generated by x2py for Fortran code. It +is the canonical contract for ownership, lifetime, naming, supported behavior, +and current limitations. + +The reference follows the wrapper by subject. Each subject includes a small +example showing the Fortran interface and the corresponding Python use. +Examples omit unrelated module scaffolding when that makes the contract easier +to see. + +This reference covers the implemented wrapper for Fortran source inputs. + + ## Contents - Foundations: [building a wrapper](#building-and-importing-a-wrapper), - [support evidence](#how-support-claims-are-established), and + [support boundaries](#how-support-claims-are-established), and [ownership and lifetime](#ownership-and-lifetime) -- Arrays and pointers: [allocatables](#allocatable-arguments-results-and-views), - [pointers](#pointer-arguments-results-and-association), - [array results](#array-valued-function-results), and - [NumPy argument contracts](#numpy-array-argument-contracts) +- Arrays and pointers: [allocatables](../guide/allocatables.md), + [pointers](../guide/pointers.md), [array results](../guide/arrays.md), and + [NumPy argument contracts](../guide/arrays.md) - Objects and state: [derived types](#derived-types-across-procedure-boundaries), [inheritance](#inheritance-and-polymorphism), [constructors/finalizers](#constructors-initialization-and-finalizers), @@ -109,7 +174,8 @@ import numpy as np sys.path.insert(0, "build/fruntime_abi") import fruntime_abi_f90 -assert fruntime_abi_f90.scale(np.float64(3.0), np.float64(2.5)) == np.float64(7.5) +result = fruntime_abi_f90.scale(np.float64(3.0), np.float64(2.5)) +print(result) # 7.5 ``` Native scalar arguments use their exact NumPy dtype. x2py rejects a Python @@ -205,11 +271,11 @@ the current working directory unless `--out` gives it an explicit path. Generate wrapper sources remain build artifacts; users do not edit them to change the Python API. -The semantic `.pyi` is the editable semantic contract and wrapper-planning surface. +The semantic `.pyi` is the editable contract and wrapper-planning surface. The supported edit workflow, including removal, addition, call projection, -ownership, and destruction, is explained later in Editing Semantic `.pyi` -Contracts. The complete grammar appears later in the Semantic `.pyi` Format -reference. +ownership, and destruction, is explained in +[Editing `.pyi` Contracts](pyi-contracts/index.md). The complete grammar +appears in the Semantic `.pyi` Format reference. The normal CLI build is source-driven: recognizable Fortran sources build wrappers without a stage flag and cannot be combined with `--pyi`. A semantic `.pyi` entry contract also selects the wrapper stage automatically when its @@ -304,18 +370,11 @@ the generated Python API while unaffected public declarations keep their runtime behavior. Misuse handling, diagnostic categories, and risky explicit-contract behavior -are covered later in Editing Semantic `.pyi` Contracts and the Semantic `.pyi` -Format reference. +are covered in [Editing `.pyi` Contracts](pyi-contracts/index.md) and the +Semantic `.pyi` Format reference. The Semantic `.pyi` Wrapper Checklist later records parity completion. -Runtime tests: [`test_pyi_wrapper_builds.py`](../../../tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py), -[`test_contract_package_runtime.py`](../../../tests/wrapper/fortran/build_from_pyi/test_contract_package_runtime.py), -[`test_native_order_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_native_order_contracts.py), -[`test_ownership_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_ownership_contracts.py), -[`test_surface_edit_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_surface_edit_contracts.py), -[`test_visibility_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_visibility_contracts.py), and -[`test_policy_dispatch_contracts.py`](../../../tests/wrapper/fortran/edit_pyi_contracts/test_policy_dispatch_contracts.py). Use `--verbose` to execute a build while printing every exact, shell-escaped compiler and linker command. It first announces binding, bridge, and header @@ -425,8 +484,6 @@ Example 2: a `.pyi` build from a native object keeps semantic contracts and native artifacts separate. ```python -from pathlib import Path - from x2py import build_pyi_extension result = build_pyi_extension( @@ -436,8 +493,8 @@ result = build_pyi_extension( output_dir="build/solver", ) -assert result.sources[0] == Path("contracts/solver/__init__.pyi") -assert result.native_build_plan.prebuilt_artifacts[0].kind == "object" +print(result.sources[0]) # contracts/solver/__init__.pyi +print(result.native_build_plan.prebuilt_artifacts[0].kind) # object ``` Example 3: an object followed by a static archive remains ordered in the link @@ -460,8 +517,6 @@ print([item.to_dict() for item in result.native_build_plan.link_items]) Example 4: a direct shared-library path is distinct from a named library. ```python -from pathlib import Path - from x2py import build_pyi_extension result = build_pyi_extension( @@ -472,8 +527,8 @@ result = build_pyi_extension( ) artifact = result.native_build_plan.prebuilt_artifacts[0] -assert artifact.kind == "shared_library" -assert result.native_build_plan.library_dirs == (Path("vendor"),) +print(artifact.kind) # shared_library +print([str(path) for path in result.native_build_plan.library_dirs]) # ['vendor'] ``` Example 5: the ordered representation can express linker control arguments for @@ -510,9 +565,9 @@ A wrapper feature is considered supported only when all applicable layers agree: - the default wrapper build emits a precise error when a declaration is unsupported or lacks policy; - semantic lowering preserves the contract without reconstructing source text; -- runtime tests import the extension and verify results, mutation, lifetime, - ownership, and invalid calls; and -- fixed-form and free-form behavior are both tested when the source feature +- runtime behavior is covered by the project verification policy before it is + presented as supported; and +- fixed-form and free-form behavior are both considered when the source feature exists in both forms. @@ -984,8 +1045,6 @@ contract exists. X2PY_C_DOCS_END --> ## Allocatable Arguments, Results, And Views @@ -995,17 +1054,19 @@ Allocatable behavior depends on where the allocation lives. Top-level allocatable array function results and non-optional hidden allocatable array outputs return wrapper-owned `AllocatableArray` objects. -Allocated and unallocated native states both return a present handle. The -handle owns persistent descriptor storage and releases it on `close()` or -finalization. +The handle owns persistent descriptor storage and releases it on `close()` or +finalization. Plain direct function results must be allocated; use a zero-sized +allocation for empty data. Hidden `intent(out)` allocatable outputs can return +unallocated state portably. A direct function result that may be unallocated +must be annotated as `MaybeUnallocated` in the semantic `.pyi` contract. ```fortran function make_vector(n) result(values) integer, intent(in) :: n real(8), allocatable :: values(:) + allocate(values(max(n, 0))) if (n > 0) then - allocate(values(n)) values = 3.0_8 end if end function make_vector @@ -1013,13 +1074,14 @@ end function make_vector ```python values = make_vector(4) -assert values.allocated is True +print(values.allocated) # True view = values.to_numpy() view[0] = 9.0 -missing = make_vector(0) -assert missing.allocated is False -assert missing.to_numpy() is None +empty = make_vector(0) +print(empty.allocated) # True +print(empty.shape) # (0,) +print(empty.to_numpy().shape) # (0,) ``` ### Allocatable `intent(inout)` Handle Mutation @@ -1044,7 +1106,7 @@ values = make_vector(2) returned = replace_values(values) assert returned is values -np.testing.assert_array_equal(values.to_numpy(), [10.0, 20.0]) +print(values.to_numpy()) # [10. 20.] ``` ### Allocatable Fields And Module Arrays @@ -1070,17 +1132,10 @@ reassociation, or nullification makes previously returned payload proxies stale; field access on such a proxy raises `ReferenceError`. -The full five-actual by six-dummy matrix, including `TARGET`, `VALUE`, empty -state, pointer `INTENT(IN)`, deliberate incompatibilities, and calls with many -derived objects, is maintained in -[Scalar Actuals And Native Dummies](wrapping-derived-types.md#scalar-actuals-and-native-dummies). +The full compatibility matrix includes `TARGET`, `VALUE`, empty state, pointer +`INTENT(IN)`, and deliberate incompatibilities. See +[Derived Objects And Native Dummies](../guide/memory-management.md#derived-objects-and-native-dummies). -Runtime tests: -[`test_allocatable_views.py`](../../../tests/wrapper/fortran/module_state/test_allocatable_views.py) -and -[`test_scalar_derived_actual_dummy_matrix.py`](../../../tests/wrapper/fortran/derived_types/test_scalar_derived_actual_dummy_matrix.py) -and -[`test_allocatable_replacement.py`](../../../tests/wrapper/fortran/module_state/test_allocatable_replacement.py). ## Pointer Arguments, Results, And Association @@ -1096,10 +1151,10 @@ callee allocation, or nothing. x2py therefore supports a conservative subset: `Pointer[T[...]]` handles; - descriptor-backed `to_numpy()` extraction can expose contiguous or strided targets when policy provides the required owner and lifetime facts; -- pointer-array results remain blocked until stable owner storage and target - lifetime are implemented; and -- pointer array `intent(out)` and `intent(inout)` reassociation without complete - policy is blocked. +- pointer-array function results and nonoptional `intent(out)` outputs use + wrapper-owned descriptor handles; and +- pointer array `intent(inout)` reassociation without complete policy is + blocked. ### Call-Local Input @@ -1111,13 +1166,13 @@ end function total ``` ```python -assert total(pointer_handle) == 6.0 +print(total(pointer_handle)) # 6.0 ``` The pointer-descriptor signature requires a `Pointer[T[...]]` handle; a plain NumPy array has no pointer descriptor and is rejected. Scalar pointer inputs remain call-local nullable values and do not expose persistent association to -Python. The complete pointer example earlier in the guide demonstrates a +Python. The complete pointer example earlier in this reference demonstrates a module handle passed to both descriptor and ordinary array parameters. ### Pointer Result Boundary @@ -1133,14 +1188,24 @@ end function selected_values ``` ```python -selected = selected_values(True) # blocked until handle result policy is complete +selected = selected_values(True) missing = selected_values(False) + +assert selected.associated +assert not missing.associated ``` -Pointer-array results are not silently converted to detached NumPy copies. -They remain blocked until owner storage, target lifetime, descriptor extraction, -and generated destroy behavior are implemented for returned handles. Scalar -pointer results still use copied Python values or `None`. +Pointer-array results are not silently converted to detached NumPy copies. The +returned `PointerArray` owns persistent standard-descriptor storage while target +ownership remains governed by pointer policy. Closing the handle releases its +descriptor and never implicitly deallocates the target; target deallocation is +a separate policy-gated operation. Scalar pointer results still use copied +Python values or `None`. + +A nonoptional pointer-array `intent(out)` uses the same owned-descriptor result +path and is hidden from the Python parameters. Optional outputs remain visible +to preserve native presence, while `intent(inout)` remains visible because its +incoming association is meaningful. ### Pointer Policy Metadata @@ -1148,18 +1213,18 @@ Semantic `.pyi` metadata can record `nullable`, transfer mode, target owner, lifetime, deallocation, shape source, contiguity, reassociation, aliasing, and mutability. Contradictory or incomplete facts produce a semantic or wrapper-planning error. Metadata can select implemented descriptor extraction and policy-gated -operations. It cannot invent stable owner storage for a pointer-array result or -make an unproved persistent reassociation safe. +operations. Returned handles provide stable descriptor storage, but metadata +cannot make an expired target valid or make unproved target deallocation safe. -Runtime tests: [`test_pointers.py`](../../../tests/wrapper/fortran/derived_types/test_pointers.py). ## Array-Valued Function Results Numeric explicit-shape and automatic-shape array function results are returned as new Python-owned NumPy arrays. Allocatable array results use owned -`AllocatableArray` objects instead. Pointer-array results remain blocked -because a returned pointer association does not establish stable owner storage -or target lifetime. +`AllocatableArray` objects instead, including matrices and higher-rank arrays. +Pointer-array results use owned `PointerArray` descriptor handles. The pointer +target remains borrowed unless completed policy explicitly assigns target +release responsibility. ```fortran function spectrum(n) result(values) @@ -1172,21 +1237,21 @@ end function spectrum ```python values = spectrum(4) +# The array owns its data or retains the Python owner through its base. assert values.flags.owndata or values.base is not None -np.testing.assert_array_equal(values, [1.0, 2.0, 3.0, 4.0]) +print(values) # [1. 2. 3. 4.] ``` Ordinary returned arrays preserve dtype, rank, required extents, and Fortran ordering for multidimensional results. Numeric results support ranks 1 through 15 and zero-sized dimensions. An allocatable zero-sized -result is a present handle with a zero extent; an unallocated result is a -present handle whose `allocated` property is false and whose `to_numpy()` -result is `None`. +result is a present handle with a zero extent. An unallocated direct +allocatable function result requires `MaybeUnallocated`; then the returned +handle's `allocated` property is false and `to_numpy()` returns `None`. Arrays of derived types are blocked because their element layout, construction, destruction, aliasing, and copy policy are not defined. -Runtime tests: [`test_array_results.py`](../../../tests/wrapper/fortran/arrays/test_array_results.py). ## NumPy Array Argument Contracts @@ -1224,6 +1289,8 @@ subroutine scale_matrix(n, m, values) end subroutine scale_matrix ``` +X2PY_C_DOCS_END --> + Non-default lower bounds are preserved when computing shape constraints; they @@ -1285,7 +1356,7 @@ end subroutine shift ```python values = np.zeros(4, dtype=np.float64) shift(4, values) -np.testing.assert_array_equal(values, np.ones(4)) +print(values) # [1. 1. 1. 1.] ``` ### Assumed Rank @@ -1317,9 +1388,6 @@ Assumed-type `type(*)`, character arrays that cannot be represented as fixed-width NumPy bytes storage, and derived-type arrays are blocked until their descriptor, ABI, element construction, and ownership policies are defined. -Runtime tests: [`test_array_contracts.py`](../../../tests/wrapper/fortran/arrays/test_array_contracts.py), -[`test_assumed_rank_arrays.py`](../../../tests/wrapper/fortran/arrays/test_assumed_rank_arrays.py), -and [`test_multidimensional_arrays.py`](../../../tests/wrapper/fortran/arrays/test_multidimensional_arrays.py). ## Derived Types Across Procedure Boundaries @@ -1333,7 +1401,8 @@ X2PY_C_DOCS_END --> - `intent(in)` passes the existing native instance by address without transferring ownership; - `intent(inout)` mutates that existing instance; -- hidden `intent(out)` produces a new wrapper-owned object; and +- `intent(out)` fills a caller-provided instance without returning it again; +- a dummy without `intent` follows the conservative `intent(inout)` rule; and - a function result is copied into a new wrapper-owned native instance before the Fortran temporary expires. @@ -1353,7 +1422,7 @@ end subroutine move_point ```python p = point(x=1.0, y=2.0) move_point(p, 3.0, 4.0) -assert (p.x, p.y) == (4.0, 6.0) +print(p.x, p.y) # 4.0 6.0 ``` ### Nested Components @@ -1382,10 +1451,6 @@ Private components are omitted from Python descriptors. Allocatable fields use descriptor access; that retention does not make the wrapper owner of a pointer target. Arrays of derived types are blocked. -Runtime tests: [`test_derived_type_boundaries.py`](../../../tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py), -[`test_derived_type_methods.py`](../../../tests/wrapper/fortran/derived_types/test_derived_type_methods.py), -and the direct Phase 8 object and field evidence in -[`test_phase8_derived_plan.py`](../../../tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py). ## Inheritance And Polymorphism @@ -1412,7 +1477,7 @@ end type circle ```python c = circle(radius=2.0) assert isinstance(c, shape) -assert c.area() == pytest.approx(12.566370614359172) +print(c.area()) # 12.566370614359172 ``` A scalar `class(base), intent(in)` dummy dispatches over the closed set of @@ -1437,7 +1502,6 @@ contract for dynamic type, allocation, replacement, and ownership. `class(*)` is blocked with the assumed-type descriptor policy. Abstract types and deferred bindings produce wrapper-planning errors rather than instantiable Python types. -Runtime tests: [`test_inheritance.py`](../../../tests/wrapper/fortran/derived_types/test_inheritance.py). ## Constructors, Initialization, And Finalizers @@ -1470,22 +1534,22 @@ derived components are not automatic constructor keywords. Removing either generated `__init__` form from an edited `.pyi` suppresses public construction; x2py does not regenerate it. To use one concrete native -initializer, bind `__init__` to another same-class method: +initializer, bind `__init__` to its native name and place the new object with +`Pass()`: ```python -from x2py.contracts import Float64, Int32, bind, private +from x2py.contracts import Addr, Arg, Float64, Int32, Pass, bind, native_call class settings: - @bind("initialize") + @bind("initialize_settings") + @native_call([Pass(), Addr(Arg(0)), Addr(Arg(1))]) def __init__(self, iterations: Int32, tolerance: Float64) -> None: ... - - @private - def initialize(self, iterations: Int32, tolerance: Float64) -> None: ... ``` -The target method must have the same Python call shape and return type. A public -target remains callable as a method; `@private` keeps the signature in the -standalone `.pyi` but exposes only construction to users. +Exactly one `Pass()` identifies the allocated `settings` object. Its native +position may appear anywhere. Other `settings` arguments remain ordinary +`Arg(...)` inputs. The original module-level declaration may remain public or +be marked `@private` independently. An edited contract can instead declare multiple `__init__` overload links. The wrapper allocates the ordinary Phase 8 native owner once, selects an exact @@ -1505,11 +1569,6 @@ Final subroutines have no recoverable Python status channel during `tp_dealloc`. A finalizer that executes `stop`, `error stop`, aborts, or otherwise terminates native execution terminates the process. -Runtime tests: [`test_constructors_and_finalizers.py`](../../../tests/wrapper/fortran/derived_types/test_constructors_and_finalizers.py) -and [`test_borrowed_finalizers.py`](../../../tests/wrapper/fortran/derived_types/test_borrowed_finalizers.py). -Bound and overloaded constructors are covered by -[`test_phase9_bound_constructors.py`](../../../tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py) -and [`test_phase9_class_overloads.py`](../../../tests/wrapper/fortran/naming/test_phase9_class_overloads.py). ## Module Variables, Constants, Saved State, And Common Blocks @@ -1531,12 +1590,12 @@ end module state ``` ```python -assert counter == 0 +print(counter) # 0 counter = np.int32(4) advance() -assert counter == 5 +print(counter) # 5 -assert max_count == 100 +print(max_count) # 100 ``` Parameters become `Final[...]` constants when their value is representable as @@ -1550,7 +1609,7 @@ Allocatable module arrays are attributes returning persistent ```python allocate_values(3) handle = values -assert handle.allocated is True +print(handle.allocated) # True view = handle.to_numpy() view[0] = 5.0 # writes native module storage @@ -1579,16 +1638,12 @@ end subroutine write_shared ```python write_shared(np.int32(17)) -assert read_shared() == 17 +print(read_shared()) # 17 ``` x2py adds no independent lock for module or object state. Concurrency rules are covered in [Runtime Errors, The GIL, OpenMP, And Concurrency](#runtime-errors-the-gil-openmp-and-concurrency). -Runtime tests: [`test_module_state.py`](../../../tests/wrapper/fortran/module_state/test_module_state.py) -and [`test_common_blocks.py`](../../../tests/wrapper/fortran/module_state/test_common_blocks.py). -Scalar module-variable route parity is covered by -[`test_scalar_module_variable_plan.py`](../../../tests/wrapper/fortran/module_state/test_scalar_module_variable_plan.py). ## Fortran Enums @@ -1623,7 +1678,6 @@ The underlying `bind(C)` integer representation is retained as metadata. The same integer-constant surface applies to C enums. X2PY_C_DOCS_END --> -Runtime tests: [`test_fortran_enums.py`](../../../tests/wrapper/fortran/scalars/test_fortran_enums.py). ## Character Arguments, Results, And Fields @@ -1652,8 +1706,8 @@ end subroutine edit_name original = "alpha " replacement = edit_name(original) -assert original == "alpha " # Python str is immutable -assert replacement.startswith("X") +print(repr(original)) # 'alpha ' (unchanged) +print(repr(replacement)) # 'Xlpha ' ``` The wrapper copies the input into mutable native storage, calls Fortran, and @@ -1677,7 +1731,7 @@ end function label ``` ```python -assert label() == "ready " +print(repr(label())) # 'ready ' ``` @@ -1784,7 +1835,6 @@ compiler-validated size, alignment, padding, component offsets, and nested layout, with accessor fallback whenever proof is unavailable. X2PY_C_DOCS_END --> -Runtime tests: [`test_derived_layout.py`](../../../tests/wrapper/fortran/derived_types/test_derived_layout.py). ## Multiple Sources And Build Modes @@ -1929,12 +1979,6 @@ python3 -m x2py generate --makefile contracts/solver.pyi \ python3 -m x2py --build-manifest build/solver/x2py-build.json ``` -Runtime tests: [`test_multi_source_builds.py`](../../../tests/wrapper/fortran/multiple_files/test_multi_source_builds.py), -[`test_external_procedures.py`](../../../tests/wrapper/fortran/external_routines/test_external_procedures.py), -[`test_real_blas_lapack.py`](../../../tests/wrapper/fortran/real_libraries/test_real_blas_lapack.py), -[`test_stage7_native_bundles.py`](../../../tests/wrapper/fortran/real_libraries/test_stage7_native_bundles.py), -[`test_build_modes.py`](../../../tests/wrapper/fortran/build_from_source/test_build_modes.py), and -[`test_compiler_verbose.py`](../../../tests/wrapper/fortran/build_from_source/test_compiler_verbose.py). ## Visibility, Naming, And The Python Surface @@ -2014,7 +2058,6 @@ With `--strict-wrapper-names`, x2py applies no fixes. Any name requiring keyword or identifier escaping, or any collision after normalization, raises a generation error before native compilation. -Runtime tests: [`test_visibility_naming.py`](../../../tests/wrapper/fortran/naming/test_visibility_naming.py). ## Immediate Python Callbacks @@ -2040,7 +2083,8 @@ end function apply ``` ```python -assert apply(lambda value: 3.0 * value, np.float64(2.5)) == 7.5 +result = apply(lambda value: 3.0 * value, np.float64(2.5)) +print(result) # 7.5 ``` The generated wrapper keeps a strong reference to the callback only until the @@ -2081,7 +2125,7 @@ def double(array): array *= 2.0 transform(double, values) -np.testing.assert_array_equal(values, [2.0, 2.0, 2.0]) +print(values) # [2. 2. 2.] ``` ### GIL, Threads, And Exceptions @@ -2100,10 +2144,6 @@ X2PY_C_DOCS_END --> Stored callbacks, callback registration, optional dummy procedures, procedure pointers, and invocation after the wrapped call are not supported. -Runtime tests: [`test_all_callback_shapes.py`](../../../tests/wrapper/fortran/callbacks/test_all_callback_shapes.py), -[`test_scalar_callbacks.py`](../../../tests/wrapper/fortran/callbacks/test_scalar_callbacks.py), -[`test_array_callbacks.py`](../../../tests/wrapper/fortran/callbacks/test_array_callbacks.py), and -[`test_derived_callbacks.py`](../../../tests/wrapper/fortran/callbacks/test_derived_callbacks.py). ## Runtime Errors, The GIL, OpenMP, And Concurrency @@ -2176,7 +2216,7 @@ make -f build/Makefile.x2py \ ```python values = np.arange(1, 33, dtype=np.float64) -assert parallel_sum(values) == np.sum(values) +print(parallel_sum(values)) # 528.0 ``` x2py does not infer host-memory synchronization. Callers must protect arrays, @@ -2189,10 +2229,6 @@ The verified compiler path includes GNU Fortran and debug/optimized ABI builds. Other compilers and platforms require their own ABI validation; support is not inferred from GNU results. -Runtime tests: [`test_runtime_policies.py`](../../../tests/wrapper/fortran/runtime_behavior/test_runtime_policies.py), -[`test_runtime_recursion.py`](../../../tests/wrapper/fortran/runtime_behavior/test_runtime_recursion.py), -[`test_openmp_runtime.py`](../../../tests/wrapper/fortran/runtime_behavior/test_openmp_runtime.py), and -[`test_runtime_abi.py`](../../../tests/wrapper/fortran/build_from_source/test_runtime_abi.py). ## Not Handled Or Not Yet Settled @@ -2264,30 +2300,9 @@ wrappers: | Layout | Direct C struct views of Fortran derived types | Compiler-validated size, alignment, padding, offsets, and nested layout. | X2PY_C_DOCS_END --> -## Finding The Runtime Tests - -The subject index in [`tests/wrapper/fortran/README.md`](../../../tests/wrapper/fortran/README.md) -maps each feature to its Python runtime tests and fixture routes. Native source -fixtures are being consolidated under the shared `tests/data/fortran/` corpus so -the same valid source can exercise parser, semantic IR, `.pyi`, and -wrapper stages. Runtime semantic `.pyi` contracts remain with the wrapper tests -that consume them. Subject modules use descriptive test names, and builds that -wrap several related sources together use the -[`multiple_files`](../../../tests/wrapper/fortran/multiple_files) directory. - -Generated `.pyi` package fixtures for source-driven wrapper subjects are checked -by [`test_source_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/build_from_source/test_source_generated_pyi_contracts.py), -[`test_array_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/arrays/test_array_generated_pyi_contracts.py), -[`test_scalar_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/scalars/test_scalar_generated_pyi_contracts.py), -[`test_function_call_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/function_calls/test_function_call_generated_pyi_contracts.py), -[`test_native_call_examples.py`](../../../tests/wrapper/fortran/function_calls/test_native_call_examples.py), -[`test_string_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/strings/test_string_generated_pyi_contracts.py), -[`test_derived_type_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/derived_types/test_derived_type_generated_pyi_contracts.py), -[`test_callback_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/callbacks/test_callback_generated_pyi_contracts.py), -[`test_module_state_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/module_state/test_module_state_generated_pyi_contracts.py), -[`test_runtime_behavior_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/runtime_behavior/test_runtime_behavior_generated_pyi_contracts.py), -and [`test_naming_generated_pyi_contracts.py`](../../../tests/wrapper/fortran/naming/test_naming_generated_pyi_contracts.py). - -Semantic-only details, edited `.pyi` round trips, and wrapper-planning diagnostics also -have narrower tests outside `tests/wrapper`, but those tests do not replace -compiled runtime evidence. +## Troubleshooting + +If a documented wrapper behavior does not match the generated extension, first +compare the native source, generated `.pyi`, Python call, dtype, shape, and +ownership expectations. Use `--verbose` for build failures and reduce runtime +failures to the smallest source and call that still reproduces the mismatch. diff --git a/docs/user/reference/generated-classes.md b/docs/user/reference/generated-classes.md index a0bb55a69..6e4daf0bc 100644 --- a/docs/user/reference/generated-classes.md +++ b/docs/user/reference/generated-classes.md @@ -4,6 +4,7 @@ audience: users, advanced users prerequisites: wrapping derived types related: generated-functions.md, generated-modules.md, semantic-pyi-format.md, ../guide/wrapping-derived-types.md, ../guide/memory-management.md status: maintained +publication: draft --- # Generated Classes Reference @@ -23,9 +24,8 @@ A derived type declared in a Fortran module is exposed from the generated child module for that native module: ```python -import geometry +from geometry.points import point -point = geometry.points.point item = point(x=np.float64(1.0), y=np.float64(2.0)) ``` diff --git a/docs/user/reference/generated-functions.md b/docs/user/reference/generated-functions.md index 61ddda9d9..4e146bf56 100644 --- a/docs/user/reference/generated-functions.md +++ b/docs/user/reference/generated-functions.md @@ -4,6 +4,7 @@ audience: users prerequisites: wrapping functions, wrapping subroutines related: generated-modules.md, generated-classes.md, semantic-pyi-format.md, ../guide/wrapping-functions.md status: maintained +publication: draft --- # Generated Functions Reference @@ -36,36 +37,39 @@ callbacks keep their explicit semantic annotations. ## Return Projection -A Fortran function's direct result is the first Python return value. Native -output or replacement arguments follow in native argument order. A subroutine -with no visible outputs returns `None`. +A Fortran function's direct result is the first Python return value. Projected +scalar, replacement, or native-created outputs follow in native argument +order. Caller-provided ordinary arrays mutate in place and are not projected by +default. A subroutine with no projected outputs returns `None`. + +A dummy argument without `intent` uses conservative `intent(inout)` behavior. +Primitive scalars remain visible and their replacement values are projected +into the Python result. For a known input-only dummy, remove that projected +result from the generated contract. + +Scalar derived-type `intent(out)` and `intent(inout)` arguments follow the same +rule as arrays: the caller supplies a generated mutable object, native code +updates it, and the object is not repeated in the return value. When the Python-visible signature hides or reorders native arguments, the contract uses `@native_call(...)` and `Returns[...]` to preserve the native call shape: ```python -from x2py.contracts import Addr, Arg, Float64, Int32, Returns, native_call +from x2py.contracts import Addr, Arg, Float64, Int32, Return, native_call + +@native_call([Addr(Arg(0)), Return("status", 0)]) +def check_status(n: Int32) -> Int32: ... @native_call([Addr(Arg(0)), Arg(1)]) -def fill_vector( - n: Int32, - values: Float64[n] -) -> Returns["values", Float64[n]]: ... - -@native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2), Arg(3)]) -def shift_matrix( - n: Int32, - m: Int32, - values: Float64[n, m], - out: Float64[n, m] -) -> Returns["out", Float64[n, m]]: ... +def fill_vector(n: Int32, values: Float64[n]) -> None: ... ``` -`Returns["name", Type]` names a projected Python return. `tuple[...]` is used -when a callable has more than one Python return value. `@native_call` entries -such as `Arg(0)`, `Addr(Arg(0))`, `Return("status", 0)`, `Len(...)`, -`IsPresent(...)`, and `Work(...)` are described in +`Returns["name", Type]` names an explicit replacement return for a value that +also remains visible as an argument. `tuple[...]` is used when a callable has +more than one Python return value. `@native_call` entries such as `Arg(0)`, +`Addr(Arg(0))`, `Return("status", 0)`, `Len(...)`, `IsPresent(...)`, and +`Work(...)` are described in [Semantic `.pyi` Format](semantic-pyi-format.md#misuse-diagnostics-and-risk). Edited native-order contracts may omit `@native_call` only when every native @@ -98,13 +102,21 @@ contract keeps one public name and links each public implementation back to a specific native procedure: ```python -from x2py.contracts import Float64, Int32, overload +from x2py.contracts import Float64, Int32, bind, overload, private + +@private +def convert_integer(value: Int32) -> Int32: ... + +@private +def convert_real(value: Float64) -> Float64: ... +@bind("convert") @overload("convert_integer") def convert( value: Int32 ) -> Int32: ... +@bind("convert") @overload("convert_real") def convert( value: Float64 @@ -114,7 +126,8 @@ def convert( Dispatch is exact. Indistinguishable overloads block generation instead of choosing by declaration order. `@overload(...)` and `@native_call(...)` do not coexist on one declaration; native projection metadata belongs to the linked -specific procedure. +specific procedure. An overload-level `@bind(...)` overrides the native call +target without replacing that linked contract. ## Evidence And Maintenance diff --git a/docs/user/reference/generated-modules.md b/docs/user/reference/generated-modules.md index 4985fc97e..61bd9bbc8 100644 --- a/docs/user/reference/generated-modules.md +++ b/docs/user/reference/generated-modules.md @@ -4,6 +4,7 @@ audience: users prerequisites: wrapping modules related: generated-functions.md, generated-classes.md, semantic-pyi-format.md, ../guide/wrapping-modules.md status: maintained +publication: draft --- # Generated Modules Reference @@ -27,13 +28,13 @@ directory name becomes the default extension identity. Contained Fortran modules become child Python modules: ```python -import module_state +from module_state.module_state import summarize -api = module_state.module_state -assert api.summarize() == np.int32(15) +assert summarize() == np.int32(15) ``` -Module members are not automatically flattened onto the extension root. A +Module members are importable through their child module; they are not +automatically flattened onto the extension root. A root-level semantic `.pyi` entry may explicitly reshape the Python export tree with imports, aliases, or star imports. Duplicate root exports fail before code generation. @@ -96,7 +97,7 @@ absent and otherwise exposes the generated fields. Compatible allocatable dummies use a reversible typed `move_alloc` transaction; reassociable pointer dummies use a typed pointer transaction and restore the final association. Payload-only calls use direct or synchronous scoped addresses. See the -[complete scalar-derived compatibility matrix](../guide/wrapping-derived-types.md#scalar-actuals-and-native-dummies). +[derived-object compatibility matrix](../guide/memory-management.md#derived-objects-and-native-dummies). ## Visibility, Binding Names, And Imports diff --git a/docs/user/reference/index.md b/docs/user/reference/index.md index e49c4c59a..27080d2f5 100644 --- a/docs/user/reference/index.md +++ b/docs/user/reference/index.md @@ -2,23 +2,26 @@ title: Reference audience: users, developers prerequisites: getting started -related: cli-commands.md, python-api.md, semantic-ir.md, semantic-pyi-format.md, callbacks.md +related: cli-commands.md, python-api.md, fortran-wrapper.md, semantic-pyi-format.md, pyi-contracts/index.md status: maintained +publication: draft --- # Reference -Reference pages describe the command, API, and data contracts that user guides -and developer guides depend on. Workflow guidance belongs in tutorials, examples, -and user guides; this section stays close to the public surfaces. +Reference pages describe the command, API, generated-wrapper, and semantic +contract surfaces that other documentation depends on. They also cover the +advanced contract-editing boundary. Beginner workflows remain in tutorials, +examples, and user guides. ## Pages - [CLI commands](cli-commands.md) - [Python API](python-api.md) +- [Fortran wrapper reference](fortran-wrapper.md) - [Semantic IR](semantic-ir.md) - [Semantic .pyi format](semantic-pyi-format.md) -- [Callbacks](callbacks.md) +- [Editing .pyi contracts](pyi-contracts/index.md) - [Diagnostic codes](diagnostic-codes.md) - [Generated functions](generated-functions.md) - [Generated modules](generated-modules.md) diff --git a/docs/user/reference/pyi-contracts/calls-and-results.md b/docs/user/reference/pyi-contracts/calls-and-results.md new file mode 100644 index 000000000..f12f2ab37 --- /dev/null +++ b/docs/user/reference/pyi-contracts/calls-and-results.md @@ -0,0 +1,160 @@ +--- +title: .pyi Calls and Results +audience: users, advanced users +prerequisites: editing .pyi contracts overview +related: index.md, functions-and-classes.md, ../semantic-pyi-format.md, ../../guide/arrays.md, ../../guide/error-handling.md +status: maintained +publication: reviewed +--- + +# Calls and Results + +The function signature describes the Python call. `@native_call(...)` +describes how that call supplies the native arguments. + +## Expose Native Arguments Directly + +When every native argument is visible in native order, `@native_call(...)` is +not needed: + +```python +from x2py.contracts import Int32 + +def scalar_status( + base: Int32[()], + status: Int32[()], +) -> None: ... +``` + +Writable scalar slots use zero-dimensional NumPy arrays: + +```python +import numpy as np + +base = np.array(4, dtype=np.int32) +status = np.empty((), dtype=np.int32) + +module.scalar_status(base, status) +print(status[()]) +``` + +This form also works for arrays and derived objects when their annotations +match the native arguments. For a fixed-width string, a Python `str` can be +passed, but changes made to its temporary native buffer are not visible unless +the contract returns a replacement. + +## Reorder Arguments and Project Outputs + +Use `Returns[...]` for a Python result and `@native_call(...)` when the native +procedure needs hidden output storage, reordered arguments, constants, +lengths, presence flags, shapes, or work buffers: + +```python +from x2py.contracts import Addr, Arg, Int32, Return, Returns, native_call + +@native_call([Addr(Arg(0)), Return("status", 0)]) +def scalar_status(base: Int32) -> Returns["status", Int32]: ... +``` + +Here Python passes one value and receives the native `status` output. Every +required native argument must appear exactly once in the mapping. Missing, +duplicate, and out-of-range positions are errors. + +The mapping may use entries such as `Arg(...)`, `Addr(...)`, `Value(...)`, +`Len(...)`, `IsPresent(...)`, and `Work(...)`. These entries describe the +existing native call; they cannot change what the implementation accepts. The +complete projection grammar will be covered by the Semantic `.pyi` Format +reference. + +There is no `intent` annotation in the `.pyi`. The signature, +`Returns[...]`, and `@native_call(...)` are the complete contract after the +file is loaded. + +## Control Mutation + +`Immutable` means the original Python value must not change. A writable native +argument then needs an explicit replacement result or a supported rule that +discards the temporary mutation: + +```python +from x2py.contracts import Annotated, Float64, Immutable, Returns + +def scale( + values: Annotated[Float64[:], Immutable], +) -> Returns["values", Float64[:]]: ... +``` + +x2py calls the native procedure with separate writable storage and returns the +replacement. The original array remains unchanged. + +Do not combine replacement-only mutation with a writable borrowed view. Those +requests contradict each other and are rejected. + +## Edit Types, Shapes, Layout, and Optionality + +Annotations affect runtime checks; they are not only IDE hints: + +```python +from x2py.contracts import Float64 + +def solve( + matrix: Float64[3, 3], + rhs: Float64[3], +) -> Float64[3]: ... +``` + +Supported edits include: + +- changing an open dimension to a fixed size; +- selecting a supported contiguous layout; +- adding `Immutable` for a supported replacement path; and +- using `T | None` or a default `= ...` for an argument that is genuinely + optional in the native procedure. + +Changing dtype or rank, or inventing optionality, changes the declared native +binary interface. It is valid only when the implementation matches. x2py can +check exact NumPy dtype, rank, shape, layout, writeability, byte order, +alignment, and zero-sized-array rules. Plain multidimensional arrays in a +Fortran contract use Fortran order by default. The +[array guide](../../guide/arrays.md#what-x2py-validates) explains these checks. + +## Translate Status Results into Exceptions + +Use `@raises(...)` when a projected native status should become a Python +exception: + +```python +from x2py.contracts import Addr, Arg, Int32, Return, String, native_call, raises + +@raises(status="status", message="message", success=0) +@native_call([Addr(Arg(0)), Return("status", 0), Return("message", 1)]) +def solve(value: Int32) -> tuple[Int32, String[32]]: ... +``` + +The named status and optional message must exist in the projected results. A +non-success status raises the generated exception before an ordinary result is +returned. See [Error Handling](../../guide/error-handling.md#status-projection-example) +for the Python behavior. + +## Keep the GIL When Required + +Ordinary native calls release Python's Global Interpreter Lock (GIL) when +their contract allows it. Use `@hold_gil` when the native call must invoke +Python immediately, such as a synchronous callback: + +```python +from x2py.contracts import hold_gil + +@hold_gil +def run_engine() -> None: ... +``` + +Remove `@hold_gil` to return to the normal GIL-releasing behavior when the call +is safe without it. This changes call behavior, not the native procedure +interface. It does not describe a callback signature; callback contracts are +covered in the [Callbacks](../../guide/callbacks.md) guide. + +## Next + +Most users can stop here. Return to +[Editing `.pyi` Contracts](index.md) to choose another edit. diff --git a/docs/user/reference/pyi-contracts/exports-and-modules.md b/docs/user/reference/pyi-contracts/exports-and-modules.md new file mode 100644 index 000000000..65d2777f7 --- /dev/null +++ b/docs/user/reference/pyi-contracts/exports-and-modules.md @@ -0,0 +1,147 @@ +--- +title: .pyi Exports and Modules +audience: users, advanced users +prerequisites: editing .pyi contracts overview +related: index.md, functions-and-classes.md, ../semantic-pyi-format.md, ../../guide/wrapping-modules.md +status: maintained +publication: reviewed +--- + +# Exports and Modules + +The entry `__init__.pyi` controls the extension's Python namespace. Leaf +`.pyi` files describe native modules and their declarations. + +## Choose the Package Shape + +Generated entry contract: + +```python +from . import module1 +from . import module2 +``` + +Python then uses `package.module1` and `package.module2`. To place both +modules' public names directly in `package`, edit the entry contract: + +```python +from .module1 import * +from .module2 import * +``` + +Selective imports and aliases also work: + +```python +from .module1 import solve +from .module2 import reset as clear +``` + +Changing entry imports changes only the Python namespace. It does not rename a +native module or select a native object file. + +Only declarations reachable from `__init__.pyi` are public. Missing files, +import cycles, and two different exports using the same Python name are +errors. Explicit aliases share the same native target, but Python object +identity is not guaranteed for every read. + +## Remove or Hide a Declaration + +Delete a declaration to remove it from the Python API: + +```python +from x2py.contracts import Int32 + +counter: Int32 + +def summarize() -> Int32: ... +``` + +The removed declaration is not regenerated during this build. This works for +functions, variables, classes, methods, fields, constructors, and individual +overload candidates. + +Use `@private` for a function or class that another contract declaration still +needs: + +```python +from x2py.contracts import Float64, private + +@private +def scaled_counter() -> Float64: ... +``` + +Use `private[...]` for a variable or argument: + +```python +from x2py.contracts import Float64, private + +scale: private[Float64] +``` + +Both forms keep the declaration in the contract while hiding it from Python. + +## Add or Rename a Native Procedure + +You may add a declaration when the procedure already exists in the supplied +native implementation: + +```python +from x2py.contracts import Float64 + +def norm2(values: Float64[:]) -> Float64: ... +``` + +In a module leaf, the filename identifies the native module and the function +name selects the native procedure. Use `@bind(...)` when the Python name +differs: + +```python +from x2py.contracts import Float64, Int32, bind + +@bind("solver_step") +def step(values: Float64[:]) -> Int32: ... +``` + +For a standalone external symbol, also use `@external`: + +```python +from x2py.contracts import Float64, bind, external + +@external +@bind("vendor_norm2") +def norm2(values: Float64[:]) -> Float64: ... +``` + +The declaration must include the correct native arguments, types, ranks, and +call shape. Adding Python syntax cannot create a native procedure that is not +present in the linked implementation. + +## Set Module Values at Import + +A writable scalar module variable may have a literal initial value: + +```python +from x2py.contracts import Int32 + +counter: Int32 = 41 +``` + +x2py sets the module variable when the extension is imported. It remains +writable. This works only when x2py can write that native variable, and the +initializer must be a literal rather than a call, name, or expression. + +Use `Final[...]` only for a true read-only constant: + +```python +from x2py.contracts import Final, Int32 + +nmax: Final[Int32] = 12 +``` + +See [Wrapping Modules](../../guide/wrapping-modules.md#shape-the-module-api-with-the-contract) +for the resulting Python usage. + +## Next + +Use [Functions and Classes](functions-and-classes.md) to add methods, +overloads, or a custom constructor. diff --git a/docs/user/reference/pyi-contracts/functions-and-classes.md b/docs/user/reference/pyi-contracts/functions-and-classes.md new file mode 100644 index 000000000..adb88dfb0 --- /dev/null +++ b/docs/user/reference/pyi-contracts/functions-and-classes.md @@ -0,0 +1,121 @@ +--- +title: .pyi Functions and Classes +audience: users, advanced users +prerequisites: editing .pyi contracts overview +related: index.md, exports-and-modules.md, calls-and-results.md, ../semantic-pyi-format.md, ../../guide/wrapping-derived-types.md, ../../guide/generic-interfaces.md +status: maintained +publication: reviewed +--- + +# Functions and Classes + +Declarations may be moved into a more useful Python shape while still calling +the same native procedures. + +## Expose a Module Procedure as a Method + +Keep the module procedure declaration and add a method that calls it. `Pass()` +places `self` in the native argument list: + +```python +from x2py.contracts import Addr, Arg, Float64, Pass, native_call, private + +class point: + @native_call([Pass(), Addr(Arg(0))]) + def move(self, dx: Float64) -> None: ... + +@private +@native_call([Arg(0), Addr(Arg(1))]) +def move(item: point, dx: Float64) -> None: ... +``` + +Python exposes `item.move(dx)`. The private module declaration keeps the native +procedure information but is not callable from Python. Remove `@private` when +both `move(item, dx)` and `item.move(dx)` should be public. + +The method name normally selects the native procedure. Add `@bind("move")` to +the method when its Python name differs from that procedure. + +## Edit an Overload Set + +Each `@overload(...)` declaration is one runtime candidate: + +```python +from x2py.contracts import Addr, Arg, Float64, Int32, bind, native_call, overload, private + +@private +@native_call([Addr(Arg(0))]) +def scale_integer(value: Int32) -> Int32: ... + +@private +@native_call([Addr(Arg(0))]) +def scale_real(value: Float64) -> Float64: ... + +@overload("scale_integer") +def scale(value: Int32) -> Int32: ... + +@overload("scale_real") +def scale(value: Float64) -> Float64: ... +``` + +- Delete one overload declaration to remove only that accepted signature. +- Add a candidate only when its linked concrete procedure exists. +- Keep candidates distinguishable by supported runtime dtype and rank. +- `@private` changes Python visibility; it does not make a native-private + procedure callable. + +Without `@bind`, a candidate calls the concrete procedure named by +`@overload`. When the callable native target must instead be a public generic, +bind it explicitly: + +```python +@bind("convert") +@overload("convert_integer") +def convert_number(value: Int32) -> Int32: ... +``` + +The overload string still links the concrete contract; `@bind("convert")` +selects the native call target. + +## Replace the Constructor + +Generated classes have either a field-keyword constructor or a no-argument +native constructor. Replace it with one concrete native initializer by editing +`__init__`: + +```python +from x2py.contracts import Addr, Arg, Int32, Pass, bind, native_call + +class state: + @bind("init_state") + @native_call([Pass(), Addr(Arg(0))]) + def __init__(self, size: Int32) -> None: ... +``` + +`Pass()` places the newly created `state` object in the native call. Its +position must match the initializer's native argument order, and the selected +native argument must accept `state`. + +Remove the old generated `__init__` when replacing it. Deleting `__init__` +without adding another one makes public construction unavailable. + +## Type-Bound and Magic Methods + +Type-bound and magic methods follow the same rules: + +- keep a concrete native procedure declaration; +- place `self` with `Pass()` when the native call needs it; +- use `@bind(...)` when the Python and native names differ; and +- use `@overload(...)` when one Python method accepts several native + signatures. + +See [Wrapping Derived Types](../../guide/wrapping-derived-types.md#type-bound-methods) +for ordinary methods and +[Defined Operators](../../guide/wrapping-derived-types.md#defined-operators) +for magic methods. Each public declaration must retain a concrete, callable +native target with an exact argument mapping. + +## Next + +Use [Calls and Results](calls-and-results.md) to change how native arguments +appear at the Python boundary. diff --git a/docs/user/reference/pyi-contracts/index.md b/docs/user/reference/pyi-contracts/index.md new file mode 100644 index 000000000..eac287eb8 --- /dev/null +++ b/docs/user/reference/pyi-contracts/index.md @@ -0,0 +1,124 @@ +--- +title: Editing .pyi Contracts +audience: users, advanced users +prerequisites: generated .pyi contract, wrapper build workflow +related: exports-and-modules.md, functions-and-classes.md, calls-and-results.md, ../semantic-pyi-format.md +status: maintained +publication: reviewed +--- + +# Editing `.pyi` Contracts + +x2py's generated `.pyi` files are editable wrapper contracts. They look like +Python stubs, but they also describe native calls, storage, and results. Edit +them to change the Python API without changing the native implementation. + +This section explains supported edits and their effect. The complete grammar +will be covered by the Semantic `.pyi` Format reference. + +## Workflow + +Generate a starter contract: + +```bash +python3 -m x2py generate --pyi native/solver.f90 --out contracts/solver +``` + +Edit `contracts/solver/__init__.pyi` and its leaf `.pyi` files, then build from +the entry contract: + +```bash +python3 -m x2py contracts/solver/__init__.pyi \ + --native-fortran-sources native/solver.f90 \ + --out-dir build/solver +``` + +You can provide compiled objects or libraries instead of source. In either +case, the `.pyi` files define the Python API and the native files provide its +implementation. x2py does not reread the native source to restore declarations +you removed from the contract. + +Keep an unchanged generated copy while experimenting. It makes each edit easy +to compare and undo. + +## What Do You Want to Change? + +### Names, Visibility, and Modules + +- [How do I rename or alias a function, variable, or class?](exports-and-modules.md#choose-the-package-shape) +- [How do I reorganize a module's Python namespace?](exports-and-modules.md#choose-the-package-shape) +- [How do I flatten modules or choose what appears at the package root?](exports-and-modules.md#choose-the-package-shape) +- [How do I rename a function without changing its native target?](exports-and-modules.md#add-or-rename-a-native-procedure) +- [How do I hide or remove a function, variable, class, or class member?](exports-and-modules.md#remove-or-hide-a-declaration) +- [How do I add a procedure that already exists in the native implementation?](exports-and-modules.md#add-or-rename-a-native-procedure) +- [How do I set a module variable when the extension is imported?](exports-and-modules.md#set-module-values-at-import) +- [How do I declare a true read-only constant?](exports-and-modules.md#set-module-values-at-import) + +### Functions and Classes + +- [How do I turn a module procedure into a method?](functions-and-classes.md#expose-a-module-procedure-as-a-method) +- [How do I add or remove a function overload?](functions-and-classes.md#edit-an-overload-set) +- [How do I replace or remove a class constructor?](functions-and-classes.md#replace-the-constructor) +- [How do I add or edit a type-bound or magic method?](functions-and-classes.md#type-bound-and-magic-methods) + +### Arguments, Calls, and Results + +- [How do I expose every native argument directly in its native order?](calls-and-results.md#expose-native-arguments-directly) +- [How do I reorder or hide arguments, or turn outputs into Python results?](calls-and-results.md#reorder-arguments-and-project-outputs) +- [How do I pass values, addresses, lengths, presence flags, or temporary work storage?](calls-and-results.md#reorder-arguments-and-project-outputs) +- [How do I change a NumPy dtype, shape, layout, or optional argument?](calls-and-results.md#edit-types-shapes-layout-and-optionality) +- [How do I add a default for a genuinely optional native argument?](calls-and-results.md#edit-types-shapes-layout-and-optionality) +- [How do I return a replacement instead of mutating the original Python value?](calls-and-results.md#control-mutation) +- [How do I pass checked storage or a raw memory address?](../../guide/raw-addresses.md#checked-storage-or-raw-address) +- [How do I turn a native status into a Python exception?](calls-and-results.md#translate-status-results-into-exceptions) +- [How do I keep Python's Global Interpreter Lock (GIL) during a call, or return to the normal releasing behavior?](calls-and-results.md#keep-the-gil-when-required) +- [How do I describe a callback signature in the contract?](../../guide/callbacks.md#choosing-the-prototype-spelling) + +Most edits change the Python surface: names, visibility, grouping, or how +native arguments appear as Python parameters and results. + +Some facts must continue to match the supplied implementation: + +- native module and symbol names; +- procedure kind and native argument order; +- datatype, kind, rank, and storage category; +- callback signature; and +- required native imports. + +x2py checks that the contract is internally consistent. It cannot prove that +an arbitrary object or shared library has the binary interface described by +the contract. A contract that gives false native facts may fail while +building, importing, or calling the extension. + +## Safety Checklist + +Before rebuilding: + +- Start from a contract generated for the same native implementation. +- Make one kind of edit at a time. +- Keep native types, ranks, argument order, and symbol names accurate. +- Do not invent optionality, ownership, or a release method. +- Rebuild and call the edited path once before making the next change. + +When x2py rejects an incomplete or unsafe rule, fix the contract instead of +removing metadata until the build happens to pass. + +## Understanding Errors + +- **While loading the `.pyi`:** check Python syntax, imports, decorators, + annotations, and import cycles. +- **While checking the contract:** check duplicate exports, missing links, + invalid projections, and public declarations that expose private types. +- **While planning the wrapper:** check ownership, lifetime, mutation, + allocation, conversion, and release rules. +- **While building or calling:** check that the supplied implementation + matches the declared native symbol and binary interface. + +Errors include the contract path and declaration when that information is +available. Use `--verbose` to see the build commands; use `--debug` when a full +Python traceback is needed. + +## Next + +Start with [Exports and Modules](exports-and-modules.md) for the most common +API edits. diff --git a/docs/user/reference/python-api.md b/docs/user/reference/python-api.md index 3b54bf926..380a9990f 100644 --- a/docs/user/reference/python-api.md +++ b/docs/user/reference/python-api.md @@ -4,6 +4,7 @@ audience: users, developers prerequisites: installation related: cli-commands.md, ../../developer/development-workflow.md status: maintained +publication: draft --- # Python API Reference @@ -124,6 +125,25 @@ they need to distinguish descriptor handles from ordinary NumPy arrays. Borrowed handles do not own native storage. Owned handles expose `close()` and `closed`; their finalizer attempts generated owner-storage destruction at most once. +`Allocatable[T[...]]()` creates an owned, initially unallocated +`AllocatableArray`. `Pointer[T[...]]()` creates an owned, initially +unassociated `PointerArray`. The dtype and rank come from the annotation. On +the first writable descriptor call, the generated wrapper attaches +compiler-compatible persistent storage to the same handle. Closing an +allocatable handle also releases any allocation it still owns; closing a +pointer handle releases only its descriptor, not an associated target. + +`p1.associate(p2)` makes `p1` refer to the same target as `p2`, or makes +`p1` unassociated when `p2` is unassociated. It replaces any current +association of `p1` without copying or deallocating target storage. + +Owned writable handles carry a versioned record defined by x2py's bundled +native binding support. Separately built x2py extensions can accept the same +handle without linking to each other when their x2py handle ABI and Fortran +compiler/runtime ABIs are compatible. Each receiving wrapper validates the +record's version, size, descriptor kind, dtype, and rank before direct +descriptor use. + These classes are array-only. Scalar `Allocatable[T]` and `Pointer[T]` projections remain ordinary `T | None` values and never produce an `AllocatableArray` or `PointerArray`. diff --git a/docs/user/reference/semantic-ir.md b/docs/user/reference/semantic-ir.md index 8218c9fcf..03c212358 100644 --- a/docs/user/reference/semantic-ir.md +++ b/docs/user/reference/semantic-ir.md @@ -4,6 +4,7 @@ audience: advanced users, developers prerequisites: parser references, native datatype model related: index.md status: maintained +publication: draft --- # Semantic IR Reference @@ -20,7 +21,7 @@ Sections through [Deferred C Work](#deferred-c-work) describe current semantic behavior. The final self-contained C runtime-contract section is explicitly a design proposal and is not implemented C-input wrapper support. The current Fortran runtime contract is documented separately in -[Fortran wrapper guide](../guide/fortran-wrapper.md). +[Fortran wrapper reference](fortran-wrapper.md). X2PY_C_DOCS_END --> ## Datatype Mapping diff --git a/docs/user/reference/semantic-pyi-format.md b/docs/user/reference/semantic-pyi-format.md index 63c3539ed..fad42c6e4 100644 --- a/docs/user/reference/semantic-pyi-format.md +++ b/docs/user/reference/semantic-pyi-format.md @@ -4,13 +4,14 @@ audience: users, advanced users, developers prerequisites: semantic IR reference, wrapper build workflow related: index.md, semantic-ir.md status: maintained +publication: draft --- # Semantic `.pyi` Format For the supported edit workflow and runtime consequences of changing a contract, including ownership and destruction examples, see -[Editing semantic `.pyi` contracts](../guide/editing-semantic-pyi-contracts.md). +[Editing `.pyi` contracts](pyi-contracts/index.md). | Text | `String` | | User types | class names and imported type names | | Named callable prototypes | `@prototype` function declarations referenced by name | -| Prototype value override | `Value(T)` inside a `@prototype` declaration only | +| Prototype primitive reference | `Addr(T)` inside a `@prototype` declaration | +| Prototype non-primitive value override | `Value(T)` inside a `@prototype` declaration | The Python argument may provide more storage than the declared explicit @@ -1082,13 +1104,12 @@ Use local constants or generated `Final[...]` names for shape symbols. ## Metadata With `Annotated` -`Annotated[...]` carries storage metadata and semantic constraints. It does -not carry source-language argument direction or per-call value/reference -selection. The Fortran semantic pipeline supplies `ORDER_F` as the default -multidimensional layout. Generated contracts omit that default. Write explicit -layout metadata only when the Python-visible storage deliberately differs from -that Fortran representation, such as a row-major input accepted by a Fortran -wrapper. +`Annotated[...]` carries storage and call-boundary metadata. It does not carry +source-language argument direction or per-call value/reference selection. The +Fortran semantic pipeline supplies `ORDER_F` as the default multidimensional +layout. Generated contracts omit that default. Write explicit layout metadata +only when the Python-visible storage deliberately differs from that Fortran +representation, such as a row-major input accepted by a Fortran wrapper. Native call transport belongs to `@native_call`: a wrapped derived object uses its normal reference handoff with `Arg(i)` and exact typed value handoff with `Value(Arg(i))`. The Python API accepts the same opaque wrapper @@ -1184,6 +1205,12 @@ association state. Both are handles, not NumPy arrays. Extra metadata wraps the handle, for example `Annotated[Allocatable[Float64[:]], Aliased]` or `Annotated[Pointer[Float64[:]], PointerAssociation("runtime")]`. +At runtime, the same annotation can create a present empty descriptor handle: +`Allocatable[Float64[:]]()` starts unallocated, while +`Pointer[Float64[:]]()` starts unassociated. The element annotation and array +rank are required. Ordinary array annotations such as `Float64[:]` and scalar +descriptor annotations such as `Allocatable[Float64]` are not constructors. + `Allocatable[T[...]] | None` and `Pointer[T[...]] | None` are valid only on optional callable arguments, where `None` or omission maps to native `present(...)` false. Module variables, derived-type fields, and function results @@ -1209,9 +1236,9 @@ from x2py.contracts import Allocatable, Arg, Float64, Pointer, Return, Returns, @native_call( [ Allocatable(Arg(0)), + Allocatable(Return("normalized", 0)), Pointer(Return("selected", 2)), ], - result=Allocatable(Return(0)), ) def normalize( value: Float64 | None, @@ -1232,6 +1259,15 @@ result; its nested `Return(j)` selects that result's position among all Python results. Other Python results come from projected `intent(out)` and `intent(inout)` dummies. +Use hidden descriptor outputs for nullable rank-zero results that must preserve +`None`: `Allocatable(Return("name", j))` or `Pointer(Return("name", j))`. +Direct rank-zero allocatable function results are blocked because the bridge +cannot safely preserve the unallocated function-result state across supported +Fortran compilers. +Direct allocatable array function results use wrapper-owned descriptor handles +and preserve allocated, zero-sized, and unallocated state for rank one and +higher. + Descriptor projection uses calls, not type subscriptions. Write `Allocatable(Arg(0))`, not `Allocatable[Arg(0)]`. This is distinct from `Addr(Arg(0))`: a scalar allocatable or pointer is a native descriptor, not just @@ -1273,14 +1309,6 @@ For array descriptors, use `Allocatable[T[...]]` and `Pointer[T[...]]`. active public descriptor spellings. Derived module objects remain live objects; there is no public whole-object snapshot annotation. -Other positional `Annotated` helpers are preserved as semantic constraints: - -```python -from x2py.contracts import Annotated, Bounded, Finite, Int32 - -value: Annotated[Int32, Bounded(1, 8), Finite] -``` - ### Ownership, Transfer, And Destruction Policies Ownership metadata is consumed by the centralized wrapper ownership policy. @@ -1342,10 +1370,10 @@ preserved verbatim so project-specific owner and release names can be expressed; the backend still validates whether the requested transfer and destruction path are implemented. -For native pointer-array handles, descriptor operations are opt-in. `nullify()` -is always available on a present pointer handle. `allocate(shape)` is permitted -only when `reassociation` is `allocate`, `allocate_resize`, `reallocate`, or -`reassociate_allocate`. `deallocate()` is permitted only when `deallocation` is +For native pointer-array handles, `associate(other)` and `nullify()` are always +available. `allocate(shape)` is permitted only when `reassociation` is +`allocate`, `allocate_resize`, `reallocate`, or `reassociate_allocate`. +`deallocate()` is permitted only when `deallocation` is `deallocate`, `deallocate_resize`, `owner_deallocate`, `unsafe_deallocate`, or `wrapper_dealloc`. Use `unsafe_deallocate` only when the contract intentionally makes the caller @@ -1388,9 +1416,10 @@ value: Annotated[ For module and derived-field pointer-array handles, a completed contiguous policy enables `contiguous_view` extraction and checks the target's current contiguity before reading it. General strided extraction still requires the -descriptor-view path. Pointer-array function results remain -blocked until returned-handle owner storage, target lifetime, descriptor -extraction, and destroy behavior are implemented. +descriptor-view path. Pointer-array function results and nonoptional +`intent(out)` outputs use wrapper-owned standard pointer descriptors. Their +handles release descriptor storage on `close()` or finalization without +implicitly deallocating the target. Derived module objects use the normal generated class in both plain and `Aliased` declarations: @@ -1663,6 +1692,13 @@ to the native procedure by address. The optional `result=` keyword records a native scalar descriptor function result, for example `result=Allocatable(Return(0))` or `result=Pointer(Return(0))`. +For nullable rank-zero values, prefer a hidden output dummy: +`Allocatable(Return("name", 0))` or `Pointer(Return("name", 0))`. Direct +rank-zero allocatable function results are rejected when the wrapper would need +to preserve an unallocated result as Python `None`. +Direct allocatable array function results use wrapper-owned descriptor handles +and are supported for matrices and higher-rank arrays. + For descriptor projections, `Pointer(Arg(i))` without a matching `Returns["name", T]` creates a permissive call-local pointer adapter and discards any native reassociation after the call. Adding the matching projected @@ -1692,6 +1728,12 @@ values instead. Class methods use the same stub form. An untyped leading `self` is allowed in a method and is not treated as a native argument. +A declared module procedure may also be projected as an instance method. +`Pass()` places `self` in its native argument list. The module function and +method remain independent Python exports, even when they have the same name. +Matching names select the same native procedure without `@bind`. A different +Python method name requires `@bind("native_name")`. + ## Generic Procedure Overloads The x2py semantic `.pyi` format uses `@overload("specific_name")` to link one @@ -1700,7 +1742,7 @@ decorator is x2py metadata; it is not `typing.overload` and must not be imported from `typing`. ```python -from x2py.contracts import Addr, Arg, Float64, Int32, Pass, native_call, overload, private +from x2py.contracts import Addr, Arg, Float64, Int32, Pass, bind, native_call, overload, private @private @native_call([Addr(Arg(0))]) @@ -1710,16 +1752,20 @@ def convert_integer(value: Int32) -> Int32: ... @native_call([Addr(Arg(0))]) def convert_real(value: Float64) -> Float64: ... +@bind("convert") @overload("convert_integer") def convert(value: Int32) -> Int32: ... +@bind("convert") @overload("convert_real") def convert(value: Float64) -> Float64: ... class accumulator: + @bind("add") @overload("accumulator_add_integer") def add(self, value: Int32) -> None: ... + @bind("add") @overload("accumulator_add_real") def add(self, value: Float64) -> None: ... ``` @@ -1732,9 +1778,14 @@ when it is needed to resolve a public overload declaration from the standalone that is otherwise part of the wrapper input. `@native_call` is not emitted merely to restate an unchanged native function name. -An overload declaration is only a Python dispatch link. It must not also carry -`@native_call`; the linked concrete procedure owns any native projection, -including argument reordering, `Pass()`, hidden values, and projected returns. +An overload declaration is a Python dispatch link. It must not also carry +`@native_call`; the linked concrete procedure owns argument reordering, +`Pass()`, hidden values, and projected returns. An overload-level `@bind` +changes only the final native call target. + +The same rule applies to class overloads. Their concrete link may describe a +private specific while `@bind("public_generic")` selects the callable +type-bound generic. The loader resolves only the decorator string. It never guesses a target by signature. The target must exist exactly once, each target may occur only once @@ -1742,16 +1793,26 @@ in one overload set, and the public declaration must agree with the concrete call signature and return type. Missing, duplicate, ambiguous, and incompatible links are deterministic errors. -When a module-level Python overload group is renamed, `generic=` preserves the -native Fortran generic name: +Without overload-level `@bind`, a module candidate calls the linked procedure's +resolved native name. With `@bind`, it calls the named native symbol instead. +This is required when a public generic is the only native entry point for a +private specific: ```python -from x2py.contracts import Int32, overload +from x2py.contracts import Int32, bind, overload, private -@overload("convert_integer", generic="convert") +@private +def convert_integer(value: Int32) -> Int32: ... + +@bind("convert") +@overload("convert_integer") def convert_number(value: Int32) -> Int32: ... ``` +`@private` controls Python visibility only. For edited standalone contracts, +x2py cannot infer whether the linked native procedure is accessible. A direct +call to a Fortran-private specific therefore fails during the native build. + Python method names recover the native generic for ordinary operators. When two distinct Fortran generics share one Python method, the decorator also carries the otherwise unrecoverable operator spelling: @@ -1763,11 +1824,9 @@ from x2py.contracts import Bool, overload def __eq__(self, other: value) -> Bool: ... ``` -For module overloads, the optional `generic=` argument names the native generic -when it differs from the Python overload-set name. For class methods it is -restricted to a compatible operator or assignment generic. It is emitted for -`.eqv.` and `.neqv.`, which would otherwise be indistinguishable from -`operator(==)` and `operator(/=)`. +For class methods, `generic=` is restricted to a compatible operator or +assignment generic. It is emitted for `.eqv.` and `.neqv.`, which would +otherwise be indistinguishable from `operator(==)` and `operator(/=)`. - -For lookup-style commands, use the -[verified examples cookbook](../examples/verified-cookbook.md). For -the full generated Python contract, use the -[Fortran wrapper guide](../guide/fortran-wrapper.md). - -## Before You Start - -x2py requires Python 3.10 or newer. Wrapper builds also need a working GNU -native toolchain, Python development headers, and NumPy development files. - - - -Install the checkout and inspect the CLI: - -```bash -python3 -m pip install -e . -python3 -m x2py --help -``` - -The examples below use repository fixtures and run from the repository root. -They use `python3`; replace that with your Python 3.10+ executable if needed. - -## What x2py Builds - -The current runtime wrapper backend is implemented for Fortran source inputs. -Given ordered Fortran sources, x2py performs this pipeline: - -```text -Fortran sources - -> compiler preprocessing and target-type probing - -> parser facts - -> semantic IR construction - -> generated native bridge and Python binding - -> compiled Python extension -``` - - - - - -## Step 1: Inspect A Small Fortran Source - -Start with this checked fixture: - - -```fortran -module m1 -contains -subroutine add1(n, x) - integer, intent(in) :: n - real(kind=8), intent(inout), dimension(n) :: x -end subroutine add1 -end module m1 -``` - -Ask x2py for the parser-level source facts: - - -```bash -python3 -m x2py parse tests/data/fortran/general/basic_subroutine.f90 -``` - -Expected output: - - -```text -File: tests/data/fortran/general/basic_subroutine.f90 - Modules: 1 - - module m1 (vars=0, uses=0) - Procedures: 1 - - subroutine add1(n:integer[0], x:real(8)[1]) -``` - -This output is intentionally compact. It says there is one module and one -subroutine, but it does not yet decide the Python wrapper behavior. - -## Step 2: Generate The Editable Contract - -Generate the semantic `.pyi` contract: - - -```bash -python3 -m x2py generate --pyi tests/data/fortran/general/basic_subroutine.f90 -``` - -Expected output: - - -```python -File: tests/data/fortran/general/basic_subroutine.f90 -Root contract: basic_subroutine/basic_subroutine.pyi -from . import m1 - -Module contract: m1.pyi -from x2py.contracts import Addr, Arg, Float64, Int32, native_call - -@native_call([Addr(Arg(0)), Arg(1)]) -def add1( - n: Int32, - x: Float64[n] -) -> None: ... -``` - -Read this as the native boundary x2py must preserve: - -- `n` is a read-only integer value in Python; the native call receives the - address of x2py's converted native slot for that value. -- `x` is a writable rank-one `Float64` array whose size is described by `n`. -- The subroutine returns `None` because it mutates the caller-provided array. - -The full `.pyi` syntax is documented in -[Semantic .pyi Format](../reference/semantic-pyi-format.md). - -## Step 3: Build A Real Extension - -Use a tiny runtime fixture for the first compiled wrapper: - - -```fortran -module fruntime_abi_f90 -contains - real(8) function scale(value, factor) result(output) - real(8), intent(in) :: value - real(8), intent(in) :: factor - output = value * factor - end function scale -end module fruntime_abi_f90 -``` - -From the command line, a build looks like this: - -```bash -python3 -m x2py tests/data/fortran/wrapper/fruntime_abi_f90.f90 \ - --out-dir build/fruntime_abi \ - --json -``` - -The command writes generated bridge, binding, runtime, object, and shared -library artifacts under the output directory. The JSON output reports the -module name and generated files. Recognizable wrapper inputs select the wrapper -build stage automatically when no inspection stage is selected. - -## Step 5: Import And Call The Extension - -This checked Python example builds into a temporary directory, imports the -generated extension from the returned shared-library path, and calls the native -function: - - -```python -from importlib.util import module_from_spec, spec_from_file_location -from pathlib import Path -from tempfile import TemporaryDirectory - -import numpy as np - -from x2py import build_fortran_extension - -source = Path("tests/data/fortran/wrapper/fruntime_abi_f90.f90") -with TemporaryDirectory() as output_dir: - build = build_fortran_extension(source, output_dir=output_dir) - spec = spec_from_file_location(build.module_name, build.shared_library) - module = module_from_spec(spec) - spec.loader.exec_module(module) - native_module = module.fruntime_abi_f90 - - print(build.module_name) - print(native_module.scale(np.float64(3.0), np.float64(2.5))) -``` - -Expected output: - - -```text -fruntime_abi_f90 -7.5 -``` - -The exact NumPy scalar types are part of the native ABI contract. Passing a -plain Python `float` where the wrapper requires `numpy.float64` raises -`TypeError` instead of silently changing the native conversion. - -## Common Beginner Mistakes - -| Symptom | Check | -| --- | --- | -| Importing the extension fails | Make sure the output directory is on `sys.path`, or load the shared library path returned by the Python API. | -| A Python number is rejected | Pass the exact NumPy scalar dtype required by the native signature. | -| Generated files are hard to inspect | Build with `--out-dir` and optionally `--verbose` to keep and print artifact paths. | - - - -## What You Learned - -You used x2py to: - -- read Fortran source facts; -- inspect the semantic `.pyi` contract; -- build the wrapper plan through the default build path; and -- build, import, and call a generated extension. - -Next: - -- Use the [verified examples cookbook](../examples/verified-cookbook.md) - for task-specific recipes. -- Use the [Fortran wrapper guide](../guide/fortran-wrapper.md) for the - complete generated Python behavior. -- Use [Semantic .pyi Format](../reference/semantic-pyi-format.md) when editing - wrapper contracts. diff --git a/docs/user/tutorials/index.md b/docs/user/tutorials/index.md index 589fb129a..1fa2b3566 100644 --- a/docs/user/tutorials/index.md +++ b/docs/user/tutorials/index.md @@ -4,21 +4,22 @@ audience: users prerequisites: getting started related: ../getting-started/index.md, ../examples/index.md status: planned-documentation +publication: draft --- # Tutorials -Tutorials are ordered from beginner to advanced and should be step-by-step, -runnable, and backed by checked fixtures or tests. +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. [Basic wrapper tutorial](basic-wrapper.md) -2. [Scientific library tutorial](scientific-library.md) -3. [Numerical solver tutorial](numerical-solver.md) -4. [Modern Fortran project tutorial](modern-fortran-project.md) -5. [Large Fortran codebase tutorial](large-fortran-codebase.md) -6. [Packaging tutorial](packaging.md) +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 diff --git a/docs/user/tutorials/large-fortran-codebase.md b/docs/user/tutorials/large-fortran-codebase.md index 6fd4bf1dd..141dbd54d 100644 --- a/docs/user/tutorials/large-fortran-codebase.md +++ b/docs/user/tutorials/large-fortran-codebase.md @@ -2,8 +2,9 @@ title: Large Fortran Codebase Tutorial audience: advanced users prerequisites: modern Fortran project tutorial, packaging -related: modern-fortran-project.md, ../guide/packaging.md +related: modern-fortran-project.md, ../guide/building-shared-library.md status: planned-documentation +publication: draft --- # Large Fortran Codebase Tutorial diff --git a/docs/user/tutorials/modern-fortran-project.md b/docs/user/tutorials/modern-fortran-project.md index a6ac5979a..b3a8c33e9 100644 --- a/docs/user/tutorials/modern-fortran-project.md +++ b/docs/user/tutorials/modern-fortran-project.md @@ -4,6 +4,7 @@ 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 diff --git a/docs/user/tutorials/numerical-solver.md b/docs/user/tutorials/numerical-solver.md index f4e264654..c6299616d 100644 --- a/docs/user/tutorials/numerical-solver.md +++ b/docs/user/tutorials/numerical-solver.md @@ -4,6 +4,7 @@ audience: users, advanced users prerequisites: basic wrapper tutorial, arrays related: scientific-library.md, ../guide/arrays.md status: planned-documentation +publication: draft --- # Numerical Solver Tutorial diff --git a/docs/user/tutorials/packaging.md b/docs/user/tutorials/packaging.md index 8cd71dba3..fea3f1dd6 100644 --- a/docs/user/tutorials/packaging.md +++ b/docs/user/tutorials/packaging.md @@ -2,8 +2,9 @@ title: Packaging Tutorial audience: users, packagers prerequisites: basic wrapper tutorial -related: ../guide/packaging.md, ../guide/distribution.md +related: ../guide/building-shared-library.md status: planned-documentation +publication: draft --- # Packaging Tutorial diff --git a/docs/user/tutorials/scientific-library.md b/docs/user/tutorials/scientific-library.md index 05fca4d1c..10050307a 100644 --- a/docs/user/tutorials/scientific-library.md +++ b/docs/user/tutorials/scientific-library.md @@ -4,6 +4,7 @@ audience: users prerequisites: basic wrapper tutorial related: numerical-solver.md, ../examples/index.md status: planned-documentation +publication: draft --- # Scientific Library Tutorial diff --git a/mkdocs.yml b/mkdocs.yml index 254dca3bd..180ddf634 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,7 +1,30 @@ site_name: x2py +site_url: https://pynumlab.github.io/x2py/ +repo_url: https://github.com/PyNumLab/x2py +repo_name: PyNumLab/x2py docs_dir: docs +theme: + name: readthedocs + collapse_navigation: false + include_homepage_in_sidebar: true + navigation_depth: 4 + sticky_navigation: true + titles_only: false +extra_css: + - stylesheets/site.css + - stylesheets/code-copy.css +extra_javascript: + - javascripts/code-copy.js +plugins: + - search +hooks: + - tools/mkdocs_publication.py +markdown_extensions: + - admonition + - attr_list + - toc: + permalink: true exclude_docs: | - maintainer/** old_docs/** nav: - Home: index.md @@ -17,34 +40,40 @@ nav: - User Guide: - Overview: user/guide/index.md - Data Types: user/guide/data-types.md + - Arrays: user/guide/arrays.md + - Strings: user/guide/strings.md - Wrapping Functions: user/guide/wrapping-functions.md - Wrapping Subroutines: user/guide/wrapping-subroutines.md - Wrapping Modules: user/guide/wrapping-modules.md - - Arrays: user/guide/arrays.md - Optional Arguments: user/guide/optional-arguments.md - - Generic Interfaces: user/guide/generic-interfaces.md + - Generic Interfaces (Overloading): user/guide/generic-interfaces.md + - Wrapping Derived Types: user/guide/wrapping-derived-types.md - Allocatables: user/guide/allocatables.md - Pointers: user/guide/pointers.md - - Wrapping Derived Types: user/guide/wrapping-derived-types.md - Memory Management: user/guide/memory-management.md - Callbacks: user/guide/callbacks.md - Enumerations: user/guide/enumerations.md - - Error Handling: user/guide/error-handling.md - - Packaging: user/guide/packaging.md - - Distribution: user/guide/distribution.md - - Fortran Wrapper Guide: user/guide/fortran-wrapper.md - - Editing Semantic .pyi Contracts: user/guide/editing-semantic-pyi-contracts.md + - Raw Addresses: user/guide/raw-addresses.md + - Error Handling & Diagnostics: user/guide/error-handling.md + - Building the Shared Library: user/guide/building-shared-library.md - Tutorials: - Overview: user/tutorials/index.md - - Basic Wrapper Tutorial: user/tutorials/basic-wrapper.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 - - Verified Examples Cookbook: user/examples/verified-cookbook.md + - BLAS Wrapper: user/examples/blas-wrapper.md + - LAPACK Wrapper: user/examples/lapack-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 CLI: user/examples/recipes/build-and-import-cli.md - Build and Import With the Python API: user/examples/recipes/build-and-import-python-api.md - - Generate an Editable Makefile: user/examples/recipes/generate-editable-makefile.md - - Build Multiple Fortran Sources: user/examples/recipes/build-multiple-fortran-sources.md - Inspect a Fortran API: user/examples/recipes/inspect-fortran-api.md # X2PY_C_DOCS: - Inspect a C API: user/examples/recipes/inspect-c-api.md - Work With Semantic .pyi Contracts: user/examples/recipes/semantic-pyi-contracts.md @@ -55,17 +84,30 @@ nav: - Overview: user/reference/index.md - CLI Commands: user/reference/cli-commands.md - Python API: user/reference/python-api.md + - Fortran Wrapper Reference: user/reference/fortran-wrapper.md - Semantic IR: user/reference/semantic-ir.md - Semantic .pyi Format: user/reference/semantic-pyi-format.md - - Callbacks: user/reference/callbacks.md + - .pyi Contracts: + - Overview: user/reference/pyi-contracts/index.md + - Exports and Modules: user/reference/pyi-contracts/exports-and-modules.md + - Functions and Classes: user/reference/pyi-contracts/functions-and-classes.md + - Calls and Results: user/reference/pyi-contracts/calls-and-results.md - Diagnostic Codes: user/reference/diagnostic-codes.md - Generated Functions: user/reference/generated-functions.md - Generated Modules: user/reference/generated-modules.md - Generated Classes: user/reference/generated-classes.md - Configuration Files: user/reference/configuration-files.md - - Language Support: user/language-support/index.md + - Language Support: + - Overview: user/language-support/index.md + - Feature Matrix: user/language-support/feature-matrix.md - FAQ: user/faq/index.md - - Troubleshooting: user/troubleshooting/index.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 - Changelog: user/changelog/index.md - Developer Documentation: - Overview: developer/index.md @@ -86,5 +128,40 @@ nav: - Overview: developer/contributing/index.md - Contribution Guide: developer/contributing/contribution-guide.md - Pull Request Workflow: developer/contributing/pull-request-workflow.md - - Coding Standards: developer/contributing/coding-standards.md - Review Process: developer/contributing/review-process.md + - Maintainer Documentation: + - Overview: maintainer/README.md + - Documentation Architecture: maintainer/documentation-architecture.md + - CI/CD: maintainer/ci-cd.md + - Release Process: maintainer/release-process.md + - Design: + - Overview: maintainer/design/index.md + - Overall Architecture: maintainer/design/overall-architecture.md + - Parser Architecture: maintainer/design/parser-architecture.md + - Semantic Analysis: maintainer/design/semantic-analysis.md + - Runtime Model: maintainer/design/runtime-model.md + - Error Propagation Model: maintainer/design/error-propagation-model.md + - Memory Ownership Model: maintainer/design/memory-ownership-model.md + - Code Generation: maintainer/design/code-generation.md + - CPython Integration: maintainer/design/cpython-integration.md + - Multilanguage Runtime Architecture: maintainer/design/semantic-multilanguage-wrapper-runtime-architecture.md + - Wrapper Design Notes: maintainer/design/wrapper-design-notes.md + - Internal Architecture: + - Overview: maintainer/internal-architecture/index.md + - Pipeline Map: maintainer/internal-architecture/pipeline-map.md + - Wrapper Generation Pipeline: maintainer/internal-architecture/wrapper-generation-pipeline.md + - AST Design: maintainer/internal-architecture/ast-design.md + - Semantic Passes: maintainer/internal-architecture/semantic-passes.md + - Type System: maintainer/internal-architecture/type-system.md + - Runtime Layer: maintainer/internal-architecture/runtime-layer.md + - Ownership Tracking: maintainer/internal-architecture/ownership-tracking.md + - Dependency Analysis: maintainer/internal-architecture/dependency-analysis.md + - Error Handling Pipeline: maintainer/internal-architecture/error-handling-pipeline.md + - Symbol Tables: maintainer/internal-architecture/symbol-tables.md + - Roadmaps: + - Overview: maintainer/roadmap/index.md + - Documentation Content: maintainer/roadmap/documentation-content-checklist.md + - Semantic .pyi Wrapper: maintainer/roadmap/semantic-pyi-wrapper-checklist.md + - Native Array Handles: maintainer/roadmap/native-array-handle-checklist.md + - Wrapper Plan Migration: maintainer/roadmap/wrapper-plan-migration-checklist.md + - Test Suite Organization: maintainer/roadmap/test-suite-organization-checklist.md diff --git a/pyproject.toml b/pyproject.toml index 7d71c5ffc..2e77bcf08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,10 @@ pretty = [ "rich>=13.7", "rich-argparse>=1.4", ] +docs = [ + "mkdocs==1.6.1", + "mkdocs-material==9.7.6", +] qa = [ "bandit[toml]==1.9.4", "coverage[toml]>=7.10", diff --git a/tests/README.md b/tests/README.md index ea9a17e21..ea2e20b39 100644 --- a/tests/README.md +++ b/tests/README.md @@ -26,7 +26,7 @@ user-visible feature that a contributor is changing. | Shared Python utilities | `tests/utilities/` | `python3 -m pytest -q tests/utilities` | | NumPy and semantic type mapping | `tests/types/` | `python3 -m pytest -q tests/types` | | Runtime handles | `tests/runtime/handles/` | `python3 -m pytest -q tests/runtime/handles` | -| Documentation structure and examples | `tests/docs/` | `python3 -m pytest -q tests/docs` | +| Documentation structure, publication, and examples | `tests/docs/` | `python3 -m pytest -q tests/docs` | | Repository tools | `tests/tools/` | `python3 -m pytest -q tests/tools` | | Parked performance lane | `tests/benchmarks/` | `python3 -m pytest -q tests/benchmarks` | diff --git a/tests/_shared/ownership_policy_support.py b/tests/_shared/ownership_policy_support.py index bdae3f989..672d7de38 100644 --- a/tests/_shared/ownership_policy_support.py +++ b/tests/_shared/ownership_policy_support.py @@ -186,6 +186,7 @@ def _native_array_policy( python_setter="none", native_setter="none", output_projection="none", + result_allocation="not_applicable", release="native_owner", target_lifetime="module", destroy_behavior="none", diff --git a/tests/architecture/test_test_suite_layout.py b/tests/architecture/test_test_suite_layout.py index 913441405..89c194ed7 100644 --- a/tests/architecture/test_test_suite_layout.py +++ b/tests/architecture/test_test_suite_layout.py @@ -50,7 +50,7 @@ "test_print_pytest_failures.py", "test_warm_real_library_native_cache.py", } -DOCS_TEST_MODULES = {"test_examples.py", "test_structure.py"} +DOCS_TEST_MODULES = {"test_examples.py", "test_publication.py", "test_structure.py"} ARCHITECTURE_TEST_MODULES = { "test_dependency_boundaries.py", "test_package_structure.py", diff --git a/tests/cli/test_argument_contract.py b/tests/cli/test_argument_contract.py index 07b9269c4..5f371a539 100644 --- a/tests/cli/test_argument_contract.py +++ b/tests/cli/test_argument_contract.py @@ -576,8 +576,8 @@ def assert_group_order(help_text, *headings): assert "Name the Python extension and stable NAME.so library" in normalized_build_help assert "Print build paths and metadata as JSON" in normalized_build_help assert 'Native compiler flags (for example, "-O3 -fopenmp")' in normalized_build_help - assert "docs/user/guide/fortran-wrapper.md" in build_help - assert "See docs/user/guide/fortran-wrapper.md for native flags and libraries." in build_help + assert "docs/user/reference/cli-commands.md" in build_help + assert "See docs/user/reference/cli-commands.md for all build options." in build_help assert "Build from a semantic contract:" in build_help assert "Replay a build manifest:" in build_help assert "Manifest overrides: --out, --compiler, -I/--include-dir" in normalized_build_help diff --git a/tests/data/fortran/wrapper/fallocatable_views_f90.f90 b/tests/data/fortran/wrapper/fallocatable_views_f90.f90 index 9a6c7116a..a4a7187d9 100644 --- a/tests/data/fortran/wrapper/fallocatable_views_f90.f90 +++ b/tests/data/fortran/wrapper/fallocatable_views_f90.f90 @@ -90,10 +90,10 @@ function make_values(n) result(values) end do end function make_values - function make_matrix(n, m) result(values) + subroutine make_matrix(n, m, values) integer, intent(in) :: n integer, intent(in) :: m - real(8), allocatable :: values(:, :) + real(8), allocatable, intent(out) :: values(:, :) integer :: i integer :: j @@ -104,7 +104,7 @@ function make_matrix(n, m) result(values) values(i, j) = real(100 + i + 10 * j, kind=8) end do end do - end function make_matrix + end subroutine make_matrix subroutine allocate_values(self, n) class(buffer), intent(inout) :: self diff --git a/tests/data/fortran/wrapper/farray_results_f90.f90 b/tests/data/fortran/wrapper/farray_results_f90.f90 index 858ffff6b..f8350226a 100644 --- a/tests/data/fortran/wrapper/farray_results_f90.f90 +++ b/tests/data/fortran/wrapper/farray_results_f90.f90 @@ -177,4 +177,28 @@ function maybe_alloc_vector(n) result(values) end do end if end function maybe_alloc_vector + + function zero_alloc_matrix(cols) result(values) + integer, intent(in) :: cols + real(8), allocatable :: values(:, :) + + allocate(values(0, cols)) + end function zero_alloc_matrix + + function maybe_alloc_matrix(rows, cols) result(values) + integer, intent(in) :: rows + integer, intent(in) :: cols + real(8), allocatable :: values(:, :) + integer :: i + integer :: j + + if (rows > 0 .and. cols > 0) then + allocate(values(rows, cols)) + do j = 1, cols + do i = 1, rows + values(i, j) = real(100 * i + 10 * j, 8) + end do + end do + end if + end function maybe_alloc_matrix end module farray_results_f90 diff --git a/tests/data/fortran/wrapper/fstrings_f90.f90 b/tests/data/fortran/wrapper/fstrings_f90.f90 index ffbd8c1e3..13e7f78a0 100644 --- a/tests/data/fortran/wrapper/fstrings_f90.f90 +++ b/tests/data/fortran/wrapper/fstrings_f90.f90 @@ -61,11 +61,11 @@ function string_result_c_char() result(value) value = 'C-CHAR!!' end function string_result_c_char - function string_result_deferred(text) result(value) + subroutine string_result_deferred(text, value) character(len=*), intent(in) :: text - character(len=:), allocatable :: value + character(len=:), allocatable, intent(out) :: value value = trim(text) // '-deferred' - end function string_result_deferred + end subroutine string_result_deferred integer function fixed_array_extent(labels) character(len=8), intent(in) :: labels(:) diff --git a/tests/docs/test_publication.py b/tests/docs/test_publication.py new file mode 100644 index 000000000..2896169ff --- /dev/null +++ b/tests/docs/test_publication.py @@ -0,0 +1,77 @@ +"""Verify fail-closed MkDocs publication filtering.""" + +from __future__ import annotations + +from pathlib import Path + +from tools import mkdocs_publication + + +def test_root_and_lane_indexes_gate_reviewed_pages() -> None: + states = { + "index.md": "reviewed", + "user/index.md": "reviewed", + "user/ready.md": "reviewed", + "user/draft.md": "draft", + "developer/index.md": "draft", + "developer/ready.md": "reviewed", + "maintainer/README.md": None, + } + + assert mkdocs_publication._reviewed_paths(states) == { + "index.md", + "user/index.md", + "user/ready.md", + } + assert mkdocs_publication._reviewed_paths({**states, "index.md": "draft"}) == set() + + +def test_navigation_drops_drafts_and_empty_sections() -> None: + navigation = [ + {"Home": "index.md"}, + {"User": [{"Overview": "user/index.md"}, {"Draft": "user/draft.md"}]}, + {"Developer": [{"Draft": "developer/draft.md"}]}, + ] + + assert mkdocs_publication._filter_navigation(navigation, {"index.md", "user/index.md"}) == [ + {"Home": "index.md"}, + {"User": [{"Overview": "user/index.md"}]}, + ] + + +def test_production_links_to_unpublished_pages_point_to_draft_site_route(monkeypatch) -> None: + monkeypatch.setattr( + mkdocs_publication, + "_known_document_paths", + {"index.md", "user/index.md", "user/draft.md"}, + ) + monkeypatch.setattr(mkdocs_publication, "_published_paths", {"index.md", "user/index.md"}) + markdown = "[User](user/index.md) [Draft](user/draft.md) [Source](../README.md) [External](https://example.com)" + + assert mkdocs_publication._rewrite_unpublished_document_targets(markdown, "index.md") == ( + "[User](user/index.md) [Draft](user/draft/) [Source](../README.md) [External](https://example.com)" + ) + + +def test_repository_evidence_links_are_rewritten_to_github(tmp_path: Path, monkeypatch) -> None: + docs_dir = tmp_path / "docs" + page_dir = docs_dir / "user" + page_dir.mkdir(parents=True) + (page_dir / "index.md").write_text("# User\n", encoding="utf-8") + documentation_section = page_dir / "guide" + documentation_section.mkdir() + source_file = tmp_path / "tests" / "evidence.py" + source_file.parent.mkdir() + source_file.write_text("# evidence\n", encoding="utf-8") + monkeypatch.setattr(mkdocs_publication, "_docs_dir", docs_dir) + monkeypatch.setattr(mkdocs_publication, "_repository_url", "https://github.com/PyNumLab/x2py") + + markdown = ( + "[Page](index.md) [Section](guide/) [Evidence](../../tests/evidence.py#proof) [Missing](../../missing.py)" + ) + + assert mkdocs_publication._rewrite_repository_targets(markdown, "user/index.md") == ( + "[Page](index.md) [Section](guide/) " + "[Evidence](https://github.com/PyNumLab/x2py/blob/main/tests/evidence.py#proof) " + "[Missing](../../missing.py)" + ) diff --git a/tests/docs/test_structure.py b/tests/docs/test_structure.py index 9ec2ec6d2..6f0add66b 100644 --- a/tests/docs/test_structure.py +++ b/tests/docs/test_structure.py @@ -23,14 +23,22 @@ DOCS_ROOT / "index.md", *sorted((DOCS_ROOT / "user").rglob("*.md")), *sorted((DOCS_ROOT / "developer").rglob("*.md")), + *sorted((DOCS_ROOT / "maintainer").rglob("*.md")), +] +LEARNING_DOCUMENTATION_PATHS = [ + *sorted((DOCS_ROOT / "user").rglob("*.md")), + *sorted((DOCS_ROOT / "developer").rglob("*.md")), ] -PUBLISHED_DOCUMENTATION_PATHS = [ROOT / "README.md", *WEBSITE_DOCUMENTATION_PATHS] DEFERRED_C_PAGE_PATHS = [ ROOT / "docs/maintainer/design/cpython-integration.md", ROOT / "docs/developer/c-parser-reference.md", ROOT / "docs/user/examples/recipes/inspect-c-api.md", ] MARKDOWN_LINK = re.compile(r"\[[^\]]+\]\(([^)#]+)(?:#[^)]+)?\)") +NEXT_NAVIGATION = re.compile(r"^\s*(?:#{2,6}\s+Next|\*\*Next\*\*:?)\s*$", re.IGNORECASE) +NEXT_SECTION_BOUNDARY = re.compile(r"^\s*(?:#{2,6}\s+|---\s*$|\*\*[^*]+\*\*)") +ALLOWED_CONTEXTUAL_FORWARD_LINK_SOURCE_PREFIXES = ("user/getting-started/", "user/guide/") +ALLOWED_CONTEXTUAL_FORWARD_LINK_PREFIXES = ("user/reference/pyi-contracts/",) C_DOCS_START = "" C_DOCS_DISABLED = "" and hidden == "ordinary": + hidden = None + elif hidden == "deferred-c": assert "--" not in line, f"{path.relative_to(ROOT)}: invalid double hyphen in deferred comment" + elif hidden == "ordinary": + continue elif not line.lstrip().startswith(C_DOCS_DISABLED): visible.append(line) assert not hidden, f"{path.relative_to(ROOT)}: unclosed deferred documentation comment" return "\n".join(visible) +def _instructional_body_without_next(body: str) -> str: + instructional_lines: list[str] = [] + inside_next = False + + for line in body.splitlines(): + if NEXT_NAVIGATION.match(line): + inside_next = True + continue + if inside_next and NEXT_SECTION_BOUNDARY.match(line): + inside_next = False + if not inside_next: + instructional_lines.append(line) + + return "\n".join(instructional_lines) + + +def _next_navigation_items(body: str) -> list[tuple[int, str, bool]]: + items: list[tuple[int, str, bool]] = [] + current_line: int | None = None + current_parts: list[str] = [] + inside_next = False + + def flush_current() -> None: + nonlocal current_line, current_parts + if current_line is not None: + items.append((current_line, " ".join(current_parts), True)) + current_line = None + current_parts = [] + + for line_number, line in enumerate(body.splitlines(), start=1): + if NEXT_NAVIGATION.match(line): + flush_current() + inside_next = True + continue + if inside_next and NEXT_SECTION_BOUNDARY.match(line): + flush_current() + inside_next = False + continue + if not inside_next or not line.strip(): + continue + if line.startswith("- "): + flush_current() + current_line = line_number + current_parts = [line[2:].strip()] + elif current_line is not None and line.startswith(" "): + current_parts.append(line.strip()) + else: + flush_current() + items.append((line_number, line.strip(), False)) + + flush_current() + return items + + def _combined_text(relative_paths: list[str]) -> str: return "\n".join((ROOT / relative_path).read_text(encoding="utf-8") for relative_path in relative_paths) @@ -478,6 +548,20 @@ def _site_navigation_positions() -> dict[str, int]: return {path: index for index, path in enumerate(paths)} +def _user_guide_index_order() -> list[str]: + _, body = _front_matter(DOCS_ROOT / "user/guide/index.md") + guide_root = (DOCS_ROOT / "user/guide").resolve() + paths: list[str] = [] + for target in MARKDOWN_LINK.findall(body): + resolved = (guide_root / target).resolve() + if resolved.parent != guide_root or resolved.name == "index.md": + continue + relative_path = resolved.relative_to(DOCS_ROOT).as_posix() + if relative_path not in paths: + paths.append(relative_path) + return paths + + @cache def _x2py_cli_help() -> str: commands = [ @@ -539,12 +623,22 @@ def test_documentation_page_metadata(path: Path) -> None: assert metadata[key], f"{path.relative_to(ROOT)}: metadata field {key!r} is empty" assert metadata["status"] in ALLOWED_STATUSES, f"{path.relative_to(ROOT)}: unknown status {metadata['status']!r}" + assert metadata["publication"] in ALLOWED_PUBLICATION_STATES, ( + f"{path.relative_to(ROOT)}: unknown publication state {metadata['publication']!r}" + ) if metadata["status"] in TODO_STATUSES: assert "## TODO" in body, f"{path.relative_to(ROOT)}: unfinished pages must include a TODO section" assert "TODO:" in body, f"{path.relative_to(ROOT)}: TODO section must contain explicit TODO markers" -@pytest.mark.parametrize("path", PUBLISHED_DOCUMENTATION_PATHS, ids=lambda path: str(path.relative_to(ROOT))) +@pytest.mark.parametrize( + "path", + [ + ROOT / "README.md", + *(path for path in WEBSITE_DOCUMENTATION_PATHS if _front_matter(path)[0].get("publication") == "reviewed"), + ], + ids=lambda path: str(path.relative_to(ROOT)), +) def test_deferred_c_documentation_is_not_visible(path: Path) -> None: visible = _visible_documentation_source(path) for allowed_text in VISIBLE_C_DOCUMENTATION_EXCEPTIONS.get(str(path.relative_to(ROOT)), ()): @@ -569,7 +663,7 @@ def test_deferred_c_pages_are_not_in_site_navigation() -> None: def test_readme_quick_start_shows_input_source_before_wrapper_build() -> None: readme = _visible_documentation_source(ROOT / "README.md") - quick_start = readme.split("## Quick Start", maxsplit=1)[1].split( + quick_start = readme.split("## Installation & Quick Start", maxsplit=1)[1].split( "The runtime wrapper mechanism is:", maxsplit=1, )[0] @@ -591,8 +685,7 @@ def test_readme_quick_start_shows_input_source_before_wrapper_build() -> None: assert command in top_help help_index = quick_start.index("python3 -m x2py --help") - source_index = quick_start.index("") - fortran_block_index = quick_start.index("```fortran", source_index) + fortran_block_index = quick_start.index("```fortran") source_build_command_index = quick_start.index( "python3 -m x2py scale.f90", fortran_block_index, @@ -666,7 +759,7 @@ def test_readme_quick_start_shows_input_source_before_wrapper_build() -> None: verbose_output_index = quick_start.index("generated Python binding", verbose_c_flag_index) module_lesson_index = quick_start.index("first wrapped module", verbose_output_index) - assert help_index < source_index < fortran_block_index < source_build_command_index + assert help_index < fortran_block_index < source_build_command_index assert source_build_command_index < default_source_build_tree_index < named_source_build_command_index assert named_source_build_command_index < source_build_tree_index < explicit_source_build_command_index assert explicit_source_build_command_index < explicit_source_build_tree_index < pyi_generation_command_index @@ -705,7 +798,14 @@ def test_required_documentation_area_exists(relative_path: str) -> None: def test_documentation_root_uses_three_audience_lanes() -> None: directories = {path.name for path in DOCS_ROOT.iterdir() if path.is_dir()} root_pages = {path.name for path in DOCS_ROOT.glob("*.md")} - assert directories == {"user", "developer", "maintainer", "old_docs"} + assert directories == { + "user", + "developer", + "maintainer", + "javascripts", + "stylesheets", + "old_docs", + } assert root_pages == {"index.md"} @@ -727,7 +827,7 @@ def test_documentation_lane_has_consistent_audience(lane: str, audience_terms: t assert metadata["audience"] == "maintainers" -@pytest.mark.parametrize("path", WEBSITE_DOCUMENTATION_PATHS, ids=lambda path: str(path.relative_to(ROOT))) +@pytest.mark.parametrize("path", LEARNING_DOCUMENTATION_PATHS, ids=lambda path: str(path.relative_to(ROOT))) def test_website_documentation_does_not_link_to_maintainer_lane(path: Path) -> None: maintainer_root = (DOCS_ROOT / "maintainer").resolve() for target in MARKDOWN_LINK.findall(_visible_documentation_source(path)): @@ -770,11 +870,24 @@ def test_required_roadmap_page_exists(relative_path: str) -> None: assert (DOCS_ROOT / relative_path).is_file() -def test_maintainer_documentation_is_excluded_from_site_build() -> None: +def test_site_navigation_includes_all_publishable_lanes_and_excludes_archive() -> None: site_configuration = (ROOT / "mkdocs.yml").read_text(encoding="utf-8") - assert "maintainer/**" in site_configuration assert "old_docs/**" in site_configuration - assert not any(path.startswith("maintainer/") for path in _site_navigation_positions()) + positions = _site_navigation_positions() + assert "user/index.md" in positions + assert "developer/index.md" in positions + assert "maintainer/README.md" in positions + + +def test_user_guide_navigation_follows_index_reading_order() -> None: + positions = _site_navigation_positions() + navigation_order = [ + path + for path, _ in sorted(positions.items(), key=lambda item: item[1]) + if path.startswith("user/guide/") and path != "user/guide/index.md" + ] + + assert navigation_order == _user_guide_index_order() @pytest.mark.parametrize("relative_path", REQUIRED_GETTING_STARTED_PAGES) @@ -817,12 +930,15 @@ def test_user_guide_page_is_completed_in_documentation_checklist(relative_path: @pytest.mark.parametrize( "relative_path", - [*REQUIRED_GETTING_STARTED_PAGES[1:], *REQUIRED_USER_GUIDE_PAGES[1:]], + [*REQUIRED_GETTING_STARTED_PAGES[1:], *REQUIRED_USER_GUIDE_PAGES[1:], *EXAMPLE_DOCUMENTATION_PAGES], ) -def test_sequential_user_page_does_not_link_forward(relative_path: str) -> None: +def test_sequential_user_pages_do_not_link_forward_from_instructional_prose(relative_path: str) -> None: path = DOCS_ROOT / relative_path _, body = _front_matter(path) + body = _instructional_body_without_next(body) positions = _site_navigation_positions() + if relative_path not in positions: + pytest.skip(f"{relative_path}: not active in site navigation") source_position = positions[relative_path] for target in MARKDOWN_LINK.findall(body): target_path = (path.parent / target).resolve() @@ -831,64 +947,250 @@ def test_sequential_user_page_does_not_link_forward(relative_path: str) -> None: target_relative = target_path.relative_to(DOCS_ROOT).as_posix() if target_relative not in positions: continue + if relative_path.startswith(ALLOWED_CONTEXTUAL_FORWARD_LINK_SOURCE_PREFIXES) and target_relative.startswith( + ALLOWED_CONTEXTUAL_FORWARD_LINK_PREFIXES + ): + continue assert positions[target_relative] <= source_position, f"{relative_path}: forward link to {target_relative}" -@pytest.mark.parametrize("relative_path", REQUIRED_USER_GUIDE_PAGES[:-2]) +@pytest.mark.parametrize( + "relative_path", + [*REQUIRED_GETTING_STARTED_PAGES[1:], *REQUIRED_USER_GUIDE_PAGES[1:]], +) +def test_next_sections_use_linked_bullet_destinations(relative_path: str) -> None: + _, body = _front_matter(DOCS_ROOT / relative_path) + for line_number, item, is_bullet in _next_navigation_items(body): + assert is_bullet, f"{relative_path}:{line_number}: Next content must be a bullet item" + assert MARKDOWN_LINK.search(item), f"{relative_path}:{line_number}: Next item must include a Markdown link" + + +@pytest.mark.parametrize("relative_path", REQUIRED_USER_GUIDE_PAGES) def test_user_guide_commands_do_not_expose_fixture_paths(relative_path: str) -> None: page = (DOCS_ROOT / relative_path).read_text(encoding="utf-8") assert "python3 -m x2py tests/" not in page -def test_getting_started_overview_uses_standalone_example_and_current_evidence() -> None: +@pytest.mark.parametrize( + "relative_path", + [ + "index.md", + "user/index.md", + *REQUIRED_GETTING_STARTED_PAGES, + *REQUIRED_USER_GUIDE_PAGES, + ], +) +def test_reviewed_user_pages_do_not_expose_internal_evidence(relative_path: str) -> None: + page = _visible_documentation_source(DOCS_ROOT / relative_path) + assert "## Evidence" not in page + assert "## Runtime Evidence" not in page + assert "Runtime tests:" not in page + assert "../../../tests/" not in page + assert "../../tests/" not in page + assert "../tests/" not in page + + +@pytest.mark.parametrize( + "relative_path", + [ + "index.md", + "user/index.md", + *REQUIRED_GETTING_STARTED_PAGES, + *REQUIRED_USER_GUIDE_PAGES, + ], +) +def test_reviewed_user_pages_do_not_contain_editorial_notes(relative_path: str) -> None: + page = _visible_documentation_source(DOCS_ROOT / relative_path).casefold() + for phrase in ( + "i kept your", + "let me know if you want", + "original content had", + "restore/polish", + ): + assert phrase not in page + + +def test_getting_started_overview_uses_standalone_example() -> None: overview = (DOCS_ROOT / "user/getting-started/index.md").read_text(encoding="utf-8") assert "scale.scale(np.float64(3.0), np.float64(2.5))" in overview - assert "build_from_source/test_build_modes.py" in overview + + +def test_documentation_homepage_demonstrates_x2py_before_getting_started() -> None: + page = (DOCS_ROOT / "index.md").read_text(encoding="utf-8") + introduction_index = page.index("x2py turns supported Fortran source") + try_heading_index = page.index("## Try it in 30 seconds {#try-x2py}") + source_index = page.index("```fortran", try_heading_index) + build_index = page.index("python3 -m x2py scale.f90") + call_index = page.index("result = scale.scale(np.float64(3.0), np.float64(2.5))") + output_index = page.index("7.5", call_index) + docstring_index = page.index("scale(value, factor) -> float64", output_index) + getting_started_index = page.index("Getting Started](user/getting-started/index.md)") + + assert introduction_index < try_heading_index < source_index < build_index < call_index < output_index + assert output_index < docstring_index < getting_started_index + assert "value : float64\nfactor : float64" in page + assert "result : float64" in page + assert "If an argument has an incompatible Python type or dtype." in page + assert "developer/index.md" not in page + assert "maintainer/README.md" not in page + assert "user/guide/" not in page + + +def test_documentation_links_to_documentation_stay_on_the_website() -> None: + github_documentation_prefixes = ( + "https://github.com/PyNumLab/x2py/blob/main/docs/", + "https://github.com/PyNumLab/x2py/tree/main/docs/", + ) + + for path in DOC_PATHS: + prose_lines: list[str] = [] + fence: str | None = None + for line in _visible_documentation_source(path).splitlines(): + marker = re.match(r"^\s*(`{3,}|~{3,})", line) + if fence is not None: + if line.strip() == fence: + fence = None + continue + if marker is not None: + fence = marker.group(1) + continue + prose_lines.append(line) + + for target in MARKDOWN_LINK.findall("\n".join(prose_lines)): + if "/" not in target and "." not in target: + continue + assert not target.startswith(github_documentation_prefixes), ( + f"{path.relative_to(ROOT)}: documentation link points to GitHub: {target}" + ) + if target.startswith(("http://", "https://", "mailto:")): + continue + resolved = (path.parent / target).resolve() + assert resolved != (ROOT / "README.md").resolve(), ( + f"{path.relative_to(ROOT)}: documentation workflow points to the repository README" + ) + if resolved.is_relative_to(DOCS_ROOT.resolve()): + assert resolved.is_file(), ( + f"{path.relative_to(ROOT)}: documentation link must target a website page or asset: {target}" + ) def test_first_wrapped_function_shows_contract_and_mentions_later_support_boundaries() -> None: page = (DOCS_ROOT / "user/getting-started/first-wrapped-function.md").read_text(encoding="utf-8") - source_index = page.index("[README Quick Start](../../../README.md#quick-start)") - build_index = page.index("python3 -m x2py scale.f90 \\") + source_index = page.index("scale.f90") + build_index = page.index("python3 -m x2py scale.f90") command_index = page.index("python3 -m x2py generate --pyi scale.f90") contract_index = page.index( "@external\n@native_call([Addr(Arg(0)), Addr(Arg(1))])\ndef scale(\n" " value: Float64,\n factor: Float64\n) -> Float64: ..." ) - - assert source_index < build_index < command_index < contract_index - assert "" not in page + docstring_index = page.index("## Inspect the Generated Docstring") + call_index = page.index("## Call the Function") + + assert source_index < command_index < contract_index < build_index + assert build_index < docstring_index < call_index + assert "editable description of the\nPython interface" in page + assert "scale(value, factor) -> float64" in page + assert "assert result == 7.5" in page + assert "isinstance(result, float)" not in page assert "## Current Limitations" not in page - assert "language feature matrix later" in page def test_first_wrapped_module_shows_local_input_and_generated_contract() -> None: page = (DOCS_ROOT / "user/getting-started/first-wrapped-module.md").read_text(encoding="utf-8") - source_index = page.index("Create `module_state.f90` with this module:") - build_index = page.index("python3 -m x2py module_state.f90 \\") + source_index = page.index("module_state.f90") + build_index = page.index("python3 -m x2py module_state.f90") + docstring_index = page.index("## Inspect the Generated Docstring") + usage_index = page.index("## Usage Example") inspect_index = page.index("python3 -m x2py generate --pyi module_state.f90") - contract_index = page.index("nmax: Final[Int32] = 12") - - assert source_index < build_index < inspect_index < contract_index + contract_index = page.index("## Key Rules") + + assert source_index < build_index < docstring_index < usage_index < inspect_index < contract_index + assert "print(mod.__doc__)" in page + assert "module_state\n\nModule Attributes" in page + assert "summarize() -> int32" in page + assert "scaled_counter() -> float64" in page + assert "next_local() -> int32" in page + assert "nmax : int32\n Read-only constant." in page + assert "counter : int32" in page + assert "scale : float64" in page + assert "saved_counter : int32" in page + assert "Assignment writes through to native storage." not in page assert "fmodule_vars_f90" not in page assert "## Current Limitations" not in page - assert "language feature matrix later collects support boundaries" in page def test_beginner_workflow_reuses_scale_example_without_renaming_it() -> None: page = (DOCS_ROOT / "user/getting-started/beginner-workflow.md").read_text(encoding="utf-8") - source_reference_index = page.index("[README Quick Start](../../../README.md#quick-start)") - layout_index = page.index("src/\n scale.f90") + source_reference_index = page.index("scale.f90") + layout_index = page.index("src/") contract_index = page.index("python3 -m x2py generate --pyi src/scale.f90") - build_index = page.index("python3 -m x2py src/scale.f90 \\\n --out-dir build/scale") + build_index = page.index("python3 -m x2py src/scale.f90") smoke_index = page.index("result = scale.scale(np.float64(3.0), np.float64(2.5))") - advanced_index = page.index("## Advanced Next Step: Edit The Semantic Contract") + editing_index = page.index("## 4. Optionally Edit the Contract") + edited_contract_index = page.index("contracts/scale/__init__.pyi", editing_index) + diagnosis_index = page.index("## 5. Diagnose a Failure") - assert source_reference_index < layout_index < contract_index < build_index < smoke_index < advanced_index + assert source_reference_index < layout_index < contract_index < build_index + assert build_index < smoke_index < editing_index < edited_contract_index < diagnosis_index assert "scale_api" not in page +def test_user_guide_teaches_small_contract_edits_in_context() -> None: + arrays = _visible_documentation_source(DOCS_ROOT / "user/guide/arrays.md") + strings = _visible_documentation_source(DOCS_ROOT / "user/guide/strings.md") + functions = _visible_documentation_source(DOCS_ROOT / "user/guide/wrapping-functions.md") + modules = _visible_documentation_source(DOCS_ROOT / "user/guide/wrapping-modules.md") + generics = _visible_documentation_source(DOCS_ROOT / "user/guide/generic-interfaces.md") + derived = _visible_documentation_source(DOCS_ROOT / "user/guide/wrapping-derived-types.md") + errors = _visible_documentation_source(DOCS_ROOT / "user/guide/error-handling.md") + + assert "Edit the semantic `.pyi` and add `ORDER_C`" in arrays + assert "Edit the declarations in `contracts/strings/strings_api.pyi`" in strings + assert '@bind("scale")' in functions + assert "## Shape the Module API With the Contract" in modules + assert "nmax: Final[Int32] = 12" in modules + assert "counter: Int32 = 9" in modules + assert "scale: Float64 = 2.0" in modules + assert "saved_counter: private[Int32]" in modules + assert "It does not turn a writable Fortran variable into a read-only" in modules + assert "## Flatten Module Namespaces" in modules + assert "from .module1 import *" in modules + assert "from .module2 import *" in modules + assert "library.func1()" in modules + assert "library.module1` and `library.module2` are no longer exported" in modules + assert "the wrapper build fails and asks for an explicit" in modules + assert "## Extend an Overload Set" in generics + assert '@overload("convert_logical")' in generics + assert "## Custom Constructor" in derived + assert '@bind("initialize_point")' in derived + assert "### Expose a Module Procedure as a Method" in derived + assert "def move(self, dx: Float64, dy: Float64)" in derived + assert '@raises(status="status", message="message", success=0)' in errors + + +def test_user_guide_keeps_generated_docstrings_with_new_overload_and_class_features() -> None: + modules = _visible_documentation_source(DOCS_ROOT / "user/guide/wrapping-modules.md") + generics = _visible_documentation_source(DOCS_ROOT / "user/guide/generic-interfaces.md") + derived = _visible_documentation_source(DOCS_ROOT / "user/guide/wrapping-derived-types.md") + + assert "## Inspect the Module" not in modules + assert "print(mod.__doc__)" not in modules + + assert "## Inspect the Overloads" in generics + assert "print(conversions.__doc__)" in generics + assert "print(conversions.convert.__doc__)" in generics + assert "convert(value: int32) -> int32" in generics + assert "convert(value: float64) -> float64" in generics + + assert "## Inspect the Class" in derived + assert "print(points.point.__doc__)" in derived + assert "print(points.point.__init__.__doc__)" in derived + assert "`points.point.move.__doc__`" in derived + assert "print(points.point.__add__.__doc__)" in derived + assert "__add__(right: point) -> point" in derived + + def test_getting_started_pages_keep_advanced_stage_flags_out_of_beginner_path() -> None: content = "\n".join( _visible_documentation_source(DOCS_ROOT / relative_path) for relative_path in REQUIRED_GETTING_STARTED_PAGES @@ -899,18 +1201,16 @@ def test_getting_started_pages_keep_advanced_stage_flags_out_of_beginner_path() assert "--json" not in content -def test_user_guide_uses_automatic_wrapper_stage_selection() -> None: +def test_user_guide_shows_direct_shared_library_build() -> None: content = "\n".join( _visible_documentation_source(DOCS_ROOT / relative_path) for relative_path in REQUIRED_USER_GUIDE_PAGES ) - assert "python3 -m x2py generate --makefile src/scale.f90" in content - assert "python3 -m x2py contracts/solver/__init__.pyi \\\n --native-fortran-sources solver.f90" in content - assert "python3 -m x2py generate --makefile mesh.f90 solver.f90 --out-dir build" in content + assert "python3 -m x2py src/scale.f90 --out-dir build/scale" in content -def test_fortran_wrapper_guide_shows_every_common_shared_library_build_input() -> None: - content = _visible_documentation_source(DOCS_ROOT / "user/guide/fortran-wrapper.md") +def test_fortran_wrapper_reference_shows_every_common_shared_library_build_input() -> None: + content = _visible_documentation_source(DOCS_ROOT / "user/reference/fortran-wrapper.md") example = content.split("For example, this command supplies every common build input", maxsplit=1)[1].split( "`--compiler` selects", maxsplit=1 )[0] @@ -936,10 +1236,14 @@ def test_array_handle_docs_keep_views_copies_and_handles_distinct() -> None: assert "Reading the Python attribute" in allocatables assert "returns an `Allocatable[T[...]]` handle, not `ndarray | None`." in allocatables assert "never creates an automatic detached snapshot" in allocatables - assert "A borrowed view is a NumPy array that points at storage Python does not own." in allocatables - assert "Pointer-array handle results remain blocked" in pointers - assert "Any NumPy view returned by `p.to_numpy()` is tied to the pointer target" in pointers - assert "plain and `Aliased` derived module variables remain live native-owned objects" in memory + assert "A NumPy view reflects current native storage." in allocatables + assert "A pointer-array function result becomes a returned `PointerArray`." in pointers + assert "has persistent descriptor storage, but the target can belong to another" in pointers + assert "`associate(other)` makes two pointer handles refer to the same target" in pointers + assert "If `p2` is unassociated, `p1` becomes" in pointers + assert "Do Not Return A Pointer To Expired Local Storage" in pointers + assert "Derived module variables remain live objects" in memory + assert "Fortran module owns their storage" in memory @pytest.mark.parametrize("heading", CLI_HELP_GROUP_HEADINGS) @@ -1092,3 +1396,36 @@ def test_old_top_level_documentation_was_moved(relative_path: str) -> None: def test_static_site_seed_configuration_exists() -> None: assert (ROOT / "mkdocs.yml").is_file() + + +def test_site_theme_keeps_sidebar_open_and_code_blocks_copyable() -> None: + site_configuration = (ROOT / "mkdocs.yml").read_text(encoding="utf-8") + assert "name: readthedocs" in site_configuration + assert "collapse_navigation: false" in site_configuration + assert "navigation_depth: 4" in site_configuration + assert "stylesheets/site.css" in site_configuration + assert "stylesheets/code-copy.css" in site_configuration + assert "javascripts/code-copy.js" in site_configuration + + script = (DOCS_ROOT / "javascripts" / "code-copy.js").read_text(encoding="utf-8") + layout_stylesheet = (DOCS_ROOT / "stylesheets" / "site.css").read_text(encoding="utf-8") + stylesheet = (DOCS_ROOT / "stylesheets" / "code-copy.css").read_text(encoding="utf-8") + assert ".wy-nav-content" in layout_stylesheet + assert "max-width: 1200px" in layout_stylesheet + assert "margin: 0" in layout_stylesheet + assert ".wy-nav-side" in layout_stylesheet + assert "padding-bottom: 0" in layout_stylesheet + assert ".wy-side-scroll" in layout_stylesheet + assert "overflow-y: auto" in layout_stylesheet + assert "scrollbar-width: thin" in layout_stylesheet + assert ".wy-side-scroll::-webkit-scrollbar-thumb" in layout_stylesheet + assert ".rst-versions" in layout_stylesheet + assert "display: none" in layout_stylesheet + assert ".rst-content pre" in layout_stylesheet + assert "width: 100%" in layout_stylesheet + assert "max-width: 56rem" in layout_stylesheet + assert "padding-right: 3.25rem" in layout_stylesheet + assert 'document.querySelectorAll("pre code")' in script + assert "navigator.clipboard.writeText" in script + assert 'button.setAttribute("aria-label", "Copy code to clipboard")' in script + assert ".x2py-code-copy" in stylesheet diff --git a/tests/parser/c/fixtures/general/name_reuse.json b/tests/parser/c/fixtures/general/name_reuse.json index 4813368b3..a9799ea22 100644 --- a/tests/parser/c/fixtures/general/name_reuse.json +++ b/tests/parser/c/fixtures/general/name_reuse.json @@ -131,14 +131,16 @@ { "name": "same_name", "type": { - "model": "CBool", + "model": "CTypedef", "qualifiers": [], - "source_text": "_Bool same_name" + "source_text": "bool same_name", + "name": "bool", + "type": null, + "source_location": null, + "declaration_locations": [] }, "declared_type": { - "model": "CBool", - "qualifiers": [], - "source_text": "_Bool same_name" + "reference": "bool" }, "source_location": null, "callback_policy": null @@ -220,13 +222,13 @@ "filename": "general/name_reuse.h", "line": 18, "column": 1, - "source_line": "void do_work_l(" + "source_line": "void do_work_l(bool same_name, struct same_name *shared);" }, "start": { "filename": "general/name_reuse.h", "line": 18, "column": 1, - "source_line": "void do_work_l(" + "source_line": "void do_work_l(bool same_name, struct same_name *shared);" }, "end": null, "declaration_locations": [] @@ -365,9 +367,13 @@ { "name": "convert_to_logical", "result_type": { - "model": "CBool", + "model": "CTypedef", "qualifiers": [], - "source_text": "_Bool" + "source_text": "bool", + "name": "bool", + "type": null, + "source_location": null, + "declaration_locations": [] }, "parameters": [ { @@ -423,13 +429,13 @@ "filename": "general/name_reuse.h", "line": 21, "column": 1, - "source_line": "_Bool " + "source_line": "bool convert_to_logical(const char *same_name);" }, "start": { "filename": "general/name_reuse.h", "line": 21, "column": 1, - "source_line": "_Bool " + "source_line": "bool convert_to_logical(const char *same_name);" }, "end": null, "declaration_locations": [] @@ -485,9 +491,13 @@ { "name": "same_name_l", "type": { - "model": "CBool", + "model": "CTypedef", "qualifiers": [], - "source_text": "extern _Bool same_name_l" + "source_text": "extern bool same_name_l", + "name": "bool", + "type": null, + "source_location": null, + "declaration_locations": [] }, "storage": [ "extern" @@ -498,7 +508,7 @@ "filename": "general/name_reuse.h", "line": 12, "column": 1, - "source_line": "extern " + "source_line": "extern bool same_name_l;" }, "callback_policy": null, "declaration_locations": [] @@ -693,14 +703,10 @@ { "name": "same_name", "type": { - "model": "CBool", - "qualifiers": [], - "source_text": "_Bool same_name" + "reference": "bool" }, "declared_type": { - "model": "CBool", - "qualifiers": [], - "source_text": "_Bool same_name" + "reference": "bool" }, "source_location": null, "callback_policy": null @@ -750,13 +756,13 @@ "filename": "general/name_reuse.h", "line": 18, "column": 1, - "source_line": "void do_work_l(" + "source_line": "void do_work_l(bool same_name, struct same_name *shared);" }, "start": { "filename": "general/name_reuse.h", "line": 18, "column": 1, - "source_line": "void do_work_l(" + "source_line": "void do_work_l(bool same_name, struct same_name *shared);" }, "end": null, "declaration_locations": [] @@ -895,9 +901,7 @@ "convert_to_logical": { "name": "convert_to_logical", "result_type": { - "model": "CBool", - "qualifiers": [], - "source_text": "_Bool" + "reference": "bool" }, "parameters": [ { @@ -953,13 +957,13 @@ "filename": "general/name_reuse.h", "line": 21, "column": 1, - "source_line": "_Bool " + "source_line": "bool convert_to_logical(const char *same_name);" }, "start": { "filename": "general/name_reuse.h", "line": 21, "column": 1, - "source_line": "_Bool " + "source_line": "bool convert_to_logical(const char *same_name);" }, "end": null, "declaration_locations": [] @@ -1015,9 +1019,7 @@ "same_name_l": { "name": "same_name_l", "type": { - "model": "CBool", - "qualifiers": [], - "source_text": "extern _Bool same_name_l" + "reference": "bool" }, "storage": [ "extern" @@ -1028,7 +1030,7 @@ "filename": "general/name_reuse.h", "line": 12, "column": 1, - "source_line": "extern " + "source_line": "extern bool same_name_l;" }, "callback_policy": null, "declaration_locations": [] diff --git a/tests/parser/c/generate_c_parser_goldens.py b/tests/parser/c/generate_c_parser_goldens.py index 73a4aa105..6bead4ad3 100644 --- a/tests/parser/c/generate_c_parser_goldens.py +++ b/tests/parser/c/generate_c_parser_goldens.py @@ -106,6 +106,25 @@ def _is_project_location(location: object) -> bool: return isinstance(location, dict) and _is_project_filename(location.get("filename")) +def _project_source_line(filename: object, line: object) -> str | None: + if not _is_project_filename(filename) or not isinstance(line, int) or line <= 0: + return None + source = _C_DATA_DIR / filename + if not source.is_file(): + return None + try: + return source.read_text(encoding="utf-8").splitlines()[line - 1] + except IndexError: + return None + + +def _stable_project_location(location: dict) -> dict: + source_line = _project_source_line(location.get("filename"), location.get("line")) + if source_line is None: + return location + return {**location, "source_line": source_line} + + def _has_project_source_location(declaration: object) -> bool: return isinstance(declaration, dict) and _is_project_location(declaration.get("source_location")) @@ -202,8 +221,30 @@ def _system_declaration_reference(declaration: dict) -> str | None: return None +def _stable_bool_payload(qualifiers: object = None) -> dict: + return {"model": "CBool", "qualifiers": list(qualifiers or []), "source_text": "bool"} + + +def _stable_bool_type_payload(declaration: dict) -> dict | None: + if declaration.get("reference") == "bool": + return _stable_bool_payload() + if declaration.get("model") == "CBool": + return _stable_bool_payload(declaration.get("qualifiers")) + if ( + declaration.get("model") == "CTypedef" + and declaration.get("name") == "bool" + and not _is_project_location(declaration.get("source_location")) + ): + return _stable_bool_payload(declaration.get("qualifiers")) + return None + + def _stable_payload_value(value, symbols: dict[str, set[str]]): if isinstance(value, dict): + bool_payload = _stable_bool_type_payload(value) + if bool_payload is not None: + return bool_payload + reference = _system_declaration_reference(value) if reference is not None: return {"reference": _stable_payload_value(reference, symbols)} @@ -250,6 +291,9 @@ def _stable_payload_value(value, symbols: dict[str, set[str]]): "source_line": None, } continue + if _is_project_filename(filename): + stable[key] = _stable_project_location(nested) + continue stable[key] = _stable_payload_value(nested, symbols) return stable if isinstance(value, list): diff --git a/tests/parsing/c/test_c_cli_skeleton.py b/tests/parsing/c/test_c_cli_skeleton.py index f68800675..df5b34866 100644 --- a/tests/parsing/c/test_c_cli_skeleton.py +++ b/tests/parsing/c/test_c_cli_skeleton.py @@ -365,7 +365,7 @@ def test_cli_c_rejects_fortran_only_parse_flags(tmp_path: Path): assert "Fortran-only" in res.stderr -def test_cli_c_no_color_and_debug_traceback_flags_are_accepted(tmp_path: Path): +def test_cli_c_no_color_and_debug_flags_are_accepted(tmp_path: Path): header = tmp_path / "api.h" header.write_text("int run(void);\n", encoding="utf-8") cmd = [ @@ -377,7 +377,7 @@ def test_cli_c_no_color_and_debug_traceback_flags_are_accepted(tmp_path: Path): "--language", "c", "--no-color", - "--debug-traceback", + "--debug", ] res = subprocess.run(cmd, capture_output=True, text=True, check=True) diff --git a/tests/pipeline/preprocessing/test_cli.py b/tests/pipeline/preprocessing/test_cli.py index 575e07f0d..6f303326e 100644 --- a/tests/pipeline/preprocessing/test_cli.py +++ b/tests/pipeline/preprocessing/test_cli.py @@ -22,7 +22,8 @@ def test_cli_help_documents_exact_compiler_and_preprocessing_examples(): assert "Compiler used for preprocessing" in res.stdout assert "default: gfortran; cc with --language c" in " ".join(res.stdout.split()) assert "--compile-commands PATH" in res.stdout - assert "-D NAME[=VALUE]" in res.stdout + assert "-D" in res.stdout + assert "--define NAME[=VALUE]" in res.stdout def test_cli_c_default_compiler_mode_accepts_include_dirs(tmp_path: Path): diff --git a/tests/pyi/fixtures/general/f77_subroutine/f77_subroutine.pyi b/tests/pyi/fixtures/general/f77_subroutine/f77_subroutine.pyi index 876cee85c..7cd60e206 100644 --- a/tests/pyi/fixtures/general/f77_subroutine/f77_subroutine.pyi +++ b/tests/pyi/fixtures/general/f77_subroutine/f77_subroutine.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Float64, Int32, external, native_call +from x2py.contracts import Addr, Arg, Float64, Int32, Returns, external, native_call @external @native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2), Arg(3)]) @@ -7,4 +7,4 @@ def daxpy( a: Float64, x: Float64[n], y: Float64[n] -) -> None: ... +) -> tuple[Returns["n", Int32], Returns["a", Float64]]: ... diff --git a/tests/pyi/fixtures/general/modern_pyi_example/modern_math_physics.pyi b/tests/pyi/fixtures/general/modern_pyi_example/modern_math_physics.pyi index be33224e7..d3515ca3d 100644 --- a/tests/pyi/fixtures/general/modern_pyi_example/modern_math_physics.pyi +++ b/tests/pyi/fixtures/general/modern_pyi_example/modern_math_physics.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Float64, Int32, Return, Returns, native_call +from x2py.contracts import Addr, Arg, Float64, Int32, native_call class particle: def __init__( @@ -19,14 +19,15 @@ class vector3: counter: Int32 -@native_call([Return('p', 0), Addr(Arg(0)), Addr(Arg(1)), Addr(Arg(2)), Addr(Arg(3)), Addr(Arg(4))]) +@native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2)), Addr(Arg(3)), Addr(Arg(4)), Addr(Arg(5))]) def init_particle( + p: particle, pid: Int32, mass: Float64, x: Float64, y: Float64, z: Float64 -) -> particle: ... +) -> None: ... @native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2)), Addr(Arg(3))]) def kinetic_energy( @@ -49,7 +50,7 @@ def dot3( def fill_identity3( a: Float64[3, 3] -) -> Returns["a", Float64[3, 3]]: ... +) -> None: ... def normalize_particle( p: particle diff --git a/tests/pyi/fixtures/general/scope_name_reuse_combinations/scope_name_reuse_combinations.pyi b/tests/pyi/fixtures/general/scope_name_reuse_combinations/scope_name_reuse_combinations.pyi index bba2d6927..0cd0dd7b1 100644 --- a/tests/pyi/fixtures/general/scope_name_reuse_combinations/scope_name_reuse_combinations.pyi +++ b/tests/pyi/fixtures/general/scope_name_reuse_combinations/scope_name_reuse_combinations.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Bool, Complex64, Float32, Int32, String, native_call, overload +from x2py.contracts import Addr, Arg, Bool, Complex64, Float32, Int32, Returns, String, native_call, overload class same_name: def __init__( @@ -22,7 +22,7 @@ same_name_s: String[8] @native_call([Addr(Arg(0))]) def do_work_i( same_name: Int32 -) -> None: ... +) -> Returns["same_name", Int32]: ... @native_call([Addr(Arg(0))]) def do_work_r( @@ -37,12 +37,12 @@ def do_work_l( @native_call([Addr(Arg(0))]) def host_one( same_name: Int32 -) -> None: ... +) -> Returns["same_name", Int32]: ... @native_call([Addr(Arg(0))]) def host_two( same_name: Float32 -) -> None: ... +) -> Returns["same_name", Float32]: ... @native_call([Addr(Arg(0))]) def convert_to_complex( @@ -61,7 +61,7 @@ def convert_to_logical( @overload("do_work_i") def do_work( same_name: Int32 -) -> None: ... +) -> Returns["same_name", Int32]: ... @overload("do_work_r") def do_work( diff --git a/tests/runtime/handles/test_array_actual_abi.py b/tests/runtime/handles/test_array_actual_abi.py index 1cd4c0c71..e36cb7af6 100644 --- a/tests/runtime/handles/test_array_actual_abi.py +++ b/tests/runtime/handles/test_array_actual_abi.py @@ -418,6 +418,85 @@ def test_array_actual_argument_abi_packer_uses_ndarray_data_pointer_and_shape_fi ) == (values.ctypes.data, 2, values.dtype.itemsize, 2, 3, 1, 2, 1, 1) +def test_array_actual_argument_abi_packer_flattens_contiguous_storage_shape(): + values = np.asfortranarray(np.arange(6, dtype=np.float64).reshape((2, 3), order="F")) + + assert _native_array_actual_argument_for_binding_positional( + values, + expected_dtype=np.float64, + expected_rank=-1, + expected_shape=None, + require_native_byte_order=True, + require_aligned=True, + require_contiguous=True, + flatten_storage=True, + ) == (values.ctypes.data, values.size) + + +def test_array_actual_argument_abi_packer_flattens_final_edge_after_checked_prefix(): + values = np.asfortranarray(np.arange(24, dtype=np.float64).reshape((2, 3, 4), order="F")) + + assert _native_array_actual_argument_for_binding_positional( + values, + expected_dtype=np.float64, + expected_rank=2, + expected_shape=(2, None), + expected_layout="F", + require_native_byte_order=True, + require_aligned=True, + require_contiguous=True, + flatten_storage=True, + flat_axis=1, + ) == (values.ctypes.data, 2, 12) + + +def test_array_actual_argument_abi_packer_flattens_leading_edge_before_checked_suffix(): + values = np.arange(24, dtype=np.float64).reshape((2, 3, 4), order="C") + + assert _native_array_actual_argument_for_binding_positional( + values, + expected_dtype=np.float64, + expected_rank=2, + expected_shape=(None, 4), + expected_layout="C", + require_native_byte_order=True, + require_aligned=True, + require_contiguous=True, + flatten_storage=True, + flat_axis=0, + ) == (values.ctypes.data, 6, 4) + + +def test_array_actual_argument_abi_packer_flattens_native_handle_shape(): + actual = _handoff(252) + handle = AllocatableArray( + dtype=np.dtype(np.float64), + rank=2, + ops={ + "descriptor": lambda _handle: _handoff(253), + "shape": lambda _handle: (2, 3), + "allocated": lambda _handle: True, + "layout": lambda _handle: "F", + "writeable": lambda _handle: True, + "native_byte_order": lambda _handle: True, + "aligned": lambda _handle: True, + "to_numpy": lambda _handle: pytest.fail("flat array-actual ABI packing must not call to_numpy"), + "array_actual": lambda _handle: actual, + }, + ) + + assert _native_array_actual_argument_for_binding_positional( + handle, + expected_dtype=np.float64, + expected_rank=-1, + expected_shape=None, + require_native_byte_order=True, + require_aligned=True, + require_contiguous=True, + flatten_storage=True, + ) == (actual.address, 6) + + def test_array_actual_argument_abi_packer_uses_allocatable_native_array_actual_without_numpy_conversion(): actual = _handoff(246) calls = [] diff --git a/tests/runtime/handles/test_descriptor_abi.py b/tests/runtime/handles/test_descriptor_abi.py index 2215f5b30..31c352fdd 100644 --- a/tests/runtime/handles/test_descriptor_abi.py +++ b/tests/runtime/handles/test_descriptor_abi.py @@ -288,7 +288,8 @@ def test_descriptor_argument_abi_packer_rejects_wrong_kind_and_unsupported_descr def test_projected_descriptor_handoff_requires_persistent_standard_descriptor_storage(): - direct = _NativeArrayDescriptorHandoff(0x1234) + owner = object() + direct = _NativeArrayDescriptorHandoff(owner) handle = AllocatableArray( dtype=np.dtype(np.float64), rank=1, @@ -307,7 +308,7 @@ def test_projected_descriptor_handoff_requires_persistent_standard_descriptor_st expected_dtype=np.float64, expected_rank=1, expected_shape=(2,), - ) == (direct.address,) + ) == (owner,) assert _native_array_descriptor_handoff_for_binding_positional( handle, "allocatable", @@ -315,7 +316,48 @@ def test_projected_descriptor_handoff_requires_persistent_standard_descriptor_st 1, (2,), False, - ) == (direct.address,) + ) == (owner,) + + +def test_owned_standard_descriptor_can_supply_fact_packed_read_only_handoff(): + owner = object() + direct = _NativeArrayDescriptorHandoff(owner) + record = { + "base_addr": 0x5678, + "elem_len": 8, + "rank": 1, + "dim": [{"lower_bound": 0, "extent": 2, "sm": 8}], + } + handle = PointerArray( + dtype=np.dtype(np.float64), + rank=1, + ops={ + "array_actual": lambda _handle: pytest.fail("descriptor handoff must not request array actual"), + "shape": lambda _handle: (2,), + "associated": lambda _handle: True, + "nullify": lambda _handle: None, + "descriptor": lambda _handle: direct, + "to_numpy": lambda _handle: record, + "destroy": lambda _handle: None, + }, + descriptor_ownership="owned", + to_numpy_policy="unsupported", + ) + + assert _native_array_descriptor_argument_for_binding( + handle, + descriptor_kind="pointer", + expected_dtype=np.float64, + expected_rank=1, + expected_shape=(2,), + ) == (0x5678, 8, 1, 0, 2, 8) + assert _native_array_descriptor_handoff_for_binding( + handle, + descriptor_kind="pointer", + expected_dtype=np.float64, + expected_rank=1, + expected_shape=(2,), + ) == (owner,) assert _native_array_descriptor_handoff_for_binding_positional( None, "allocatable", diff --git a/tests/runtime/handles/test_factories_and_lifecycle.py b/tests/runtime/handles/test_factories_and_lifecycle.py index 3ca2d06f8..f7a5fe502 100644 --- a/tests/runtime/handles/test_factories_and_lifecycle.py +++ b/tests/runtime/handles/test_factories_and_lifecycle.py @@ -67,6 +67,8 @@ def to_numpy(): ) assert isinstance(handle, AllocatableArray) + assert isinstance(handle.dtype, np.dtype) + assert handle.dtype == np.dtype("float64") assert handle.owner is owner assert handle.generation == 9 assert handle.shape == (3,) @@ -116,7 +118,7 @@ def test_generated_handle_factory_splats_shape_operations_to_scalar_extents(): def test_generated_owned_handle_factory_passes_persistent_owner_to_every_operation(): calls = [] - owner = 0x1234 + owner = object() value = np.arange(3, dtype=np.float64) def operation(name, result=None): @@ -133,7 +135,7 @@ def call(received_owner, *args): { "shape": operation("shape", (3,)), "array_actual": operation("array_actual", 0x5678), - "descriptor": operation("descriptor", 0x9ABC), + "descriptor": operation("descriptor", owner), "allocated": operation("allocated", True), "to_numpy": operation("to_numpy", value), "resize": operation("resize"), @@ -151,7 +153,7 @@ def call(received_owner, *args): descriptor_kind="allocatable", expected_dtype=np.float64, expected_rank=1, - ) == (0x9ABC,) + ) == (owner,) handle.resize((5,)) handle.close() diff --git a/tests/runtime/handles/test_handle_protocols.py b/tests/runtime/handles/test_handle_protocols.py index 021e00346..3c4685056 100644 --- a/tests/runtime/handles/test_handle_protocols.py +++ b/tests/runtime/handles/test_handle_protocols.py @@ -9,6 +9,8 @@ _handoff, _native_array_actual_for_binding, _native_array_descriptor_for_binding, + _native_array_handle_from_generated_ops, + _pointer_descriptor_for_array, _required_handoff_ops, np, pytest, @@ -36,7 +38,8 @@ def test_allocatable_handle_uses_common_metadata_shape_owner_and_numpy_dispatch( assert isinstance(handle, NativeArrayHandleBase) assert handle.descriptor_kind == "allocatable" - assert handle.dtype == "float64" + assert isinstance(handle.dtype, np.dtype) + assert handle.dtype == np.dtype("float64") assert handle.rank == 2 assert handle.shape == (2, 3) assert handle.to_numpy() is state.value @@ -355,6 +358,149 @@ def nullify(_handle): assert handle.to_numpy() is None +def test_pointer_associate_accepts_reassociation_and_an_unassociated_source(): + first_value = np.arange(3, dtype=np.float64) + second_value = np.arange(4, dtype=np.float64) + destination_state = {"descriptor": _pointer_descriptor_for_array(first_value)} + source_state = {"descriptor": _pointer_descriptor_for_array(second_value)} + + def pointer(state): + return PointerArray( + dtype="float64", + rank=1, + ops={ + "shape": lambda _handle: tuple(dimension["extent"] for dimension in state["descriptor"]["dim"]), + "array_actual": lambda _handle: _handoff(state["descriptor"]["base_addr"]), + "descriptor": lambda _handle: state["descriptor"], + "to_numpy": lambda _handle: state["descriptor"], + "associated": lambda _handle: state["descriptor"]["base_addr"] != 0, + "associate": lambda _handle, descriptor: state.update(descriptor=descriptor), + "nullify": lambda _handle: state.update( + descriptor={ + "base_addr": 0, + "elem_len": 8, + "rank": 1, + "dim": [{"lower_bound": 0, "extent": 0, "sm": 8}], + } + ), + }, + to_numpy_policy="descriptor_view", + ) + + destination = pointer(destination_state) + source = pointer(source_state) + + destination.associate(source) + assert destination.associated is True + assert destination.shape == (4,) + np.testing.assert_array_equal(destination.to_numpy(), second_value) + + source.nullify() + destination.associate(source) + assert destination.associated is False + + +def test_generated_pointer_associate_packs_standard_descriptor_facts(): + value = np.arange(6, dtype=np.float64)[::2] + source_state = {"descriptor": _pointer_descriptor_for_array(value)} + source = PointerArray( + dtype="float64", + rank=1, + ops={ + "shape": lambda _handle: value.shape, + "array_actual": lambda _handle: _handoff(value.ctypes.data), + "descriptor": lambda _handle: source_state["descriptor"], + "to_numpy": lambda _handle: source_state["descriptor"], + "associated": lambda _handle: True, + "associate": lambda _handle, descriptor: source_state.update(descriptor=descriptor), + "nullify": lambda _handle: None, + }, + to_numpy_policy="descriptor_view", + ) + received = [] + destination = _native_array_handle_from_generated_ops( + "pointer", + "float64", + 1, + { + "shape": lambda: None, + "array_actual": lambda: 1, + "descriptor": lambda: 1, + "associated": lambda: False, + "associate": lambda facts: received.append(facts), + "nullify": lambda: None, + }, + to_numpy_policy="unsupported", + ) + + destination.associate(source) + + assert received == [ + ( + int(value.ctypes.data), + 8, + 1, + 1, + 3, + 16, + ) + ] + + +@pytest.mark.parametrize( + ("other", "error", "message"), + [ + (object(), TypeError, "requires another PointerArray"), + ( + PointerArray( + dtype="int32", + rank=1, + ops={ + **_required_handoff_ops(), + "shape": lambda _handle: None, + "associated": lambda _handle: False, + "nullify": lambda _handle: None, + }, + to_numpy_policy="unsupported", + ), + TypeError, + "dtype", + ), + ( + PointerArray( + dtype="float64", + rank=2, + ops={ + **_required_handoff_ops(), + "shape": lambda _handle: None, + "associated": lambda _handle: False, + "nullify": lambda _handle: None, + }, + to_numpy_policy="unsupported", + ), + ValueError, + "rank", + ), + ], +) +def test_pointer_associate_rejects_incompatible_sources(other, error, message): + destination = PointerArray( + dtype="float64", + rank=1, + ops={ + **_required_handoff_ops(), + "shape": lambda _handle: None, + "associated": lambda _handle: False, + "associate": lambda _handle, _descriptor: None, + "nullify": lambda _handle: None, + }, + to_numpy_policy="unsupported", + ) + + with pytest.raises(error, match=message): + destination.associate(other) + + def test_pointer_allocation_operations_are_policy_gated_by_ops_table(): state = _ArrayState(shape=(1,), value=object()) handle = PointerArray( diff --git a/tests/runtime/test_contract_constructors.py b/tests/runtime/test_contract_constructors.py new file mode 100644 index 000000000..9fa6ce3cc --- /dev/null +++ b/tests/runtime/test_contract_constructors.py @@ -0,0 +1,249 @@ +"""Runtime constructors exposed by concrete x2py contract annotations.""" + +import numpy as np +import pytest + +import x2py.contracts as contracts +from x2py.runtime.handles import ( + AllocatableArray, + PointerArray, + _bind_contract_native_array_handle, + _native_array_descriptor_argument_for_binding, + _native_array_descriptor_handoff_for_binding, +) + + +def _pointer_descriptor(value): + return { + "base_addr": int(value.ctypes.data), + "elem_len": int(value.dtype.itemsize), + "rank": value.ndim, + "dim": [ + { + "lower_bound": 1, + "extent": int(extent), + "sm": int(stride), + } + for extent, stride in zip(value.shape, value.strides, strict=True) + ], + } + + +@pytest.mark.parametrize( + ("contract", "scalar_type"), + [ + (contracts.Bool, np.bool_), + (contracts.Int8, np.int8), + (contracts.Int16, np.int16), + (contracts.Int32, np.int32), + (contracts.Int64, np.int64), + (contracts.UInt8, np.uint8), + (contracts.UInt16, np.uint16), + (contracts.UInt32, np.uint32), + (contracts.UInt64, np.uint64), + (contracts.Float16, np.float16), + (contracts.Float32, np.float32), + (contracts.Float64, np.float64), + (contracts.Float128, np.longdouble), + (contracts.Complex64, np.complex64), + (contracts.Complex128, np.complex128), + (contracts.Complex256, np.clongdouble), + (contracts.SizeT, np.uintp), + ], +) +def test_concrete_primitive_default_constructors_return_zero_numpy_scalars(contract, scalar_type): + value = contract() + + assert isinstance(value, scalar_type) + assert value == scalar_type(0) + + +def test_contract_default_handle_constructors_preserve_dtype_rank_and_empty_state(): + allocatable = contracts.Allocatable[contracts.Float64[:]]() + pointer = contracts.Pointer[contracts.Int32[:, :]]() + + assert isinstance(allocatable, AllocatableArray) + assert allocatable.dtype == np.dtype(np.float64) + assert allocatable.rank == 1 + assert allocatable.owned is True + assert allocatable.allocated is False + assert allocatable.shape is None + assert allocatable.to_numpy() is None + + assert isinstance(pointer, PointerArray) + assert pointer.dtype == np.dtype(np.int32) + assert pointer.rank == 2 + assert pointer.owned is True + assert pointer.associated is False + assert pointer.shape is None + assert pointer.to_numpy() is None + + +def test_fresh_pointer_associate_copies_association_without_following_source_descriptor(): + value = np.arange(6, dtype=np.float64)[::2] + source_state = {"descriptor": _pointer_descriptor(value)} + + def source_nullify(_handle): + source_state["descriptor"] = { + "base_addr": 0, + "elem_len": 8, + "rank": 1, + "dim": [{"lower_bound": 0, "extent": 0, "sm": 8}], + } + + source = PointerArray( + dtype="float64", + rank=1, + ops={ + "shape": lambda _handle: value.shape, + "array_actual": lambda _handle: int(value.ctypes.data), + "descriptor": lambda _handle: source_state["descriptor"], + "to_numpy": lambda _handle: source_state["descriptor"], + "associated": lambda _handle: source_state["descriptor"]["base_addr"] != 0, + "associate": lambda _handle, descriptor: source_state.update(descriptor=descriptor), + "nullify": source_nullify, + }, + to_numpy_policy="descriptor_view", + ) + target = contracts.Pointer[contracts.Float64[:]]() + + target.associate(source) + assert target.associated is True + assert target.shape == (3,) + np.testing.assert_array_equal(target.to_numpy(), value) + + source.nullify() + assert source.associated is False + assert target.associated is True + np.testing.assert_array_equal(target.to_numpy(), value) + + target.associate(source) + assert target.associated is False + assert target.to_numpy() is None + + +def test_fresh_pointer_pending_association_is_applied_when_native_storage_attaches(): + value = np.arange(4, dtype=np.float64) + descriptor = _pointer_descriptor(value) + source = PointerArray( + dtype="float64", + rank=1, + ops={ + "shape": lambda _handle: value.shape, + "array_actual": lambda _handle: int(value.ctypes.data), + "descriptor": lambda _handle: descriptor, + "to_numpy": lambda _handle: descriptor, + "associated": lambda _handle: True, + "associate": lambda _handle, _descriptor: None, + "nullify": lambda _handle: None, + }, + to_numpy_policy="descriptor_view", + ) + target = contracts.Pointer[contracts.Float64[:]]() + target.associate(source) + owner = object() + received = [] + state = {"associated": False} + + def associate(received_owner, facts): + received.append((received_owner, facts)) + state["associated"] = True + + _bind_contract_native_array_handle( + target, + "pointer", + "float64", + 1, + { + "shape": lambda _owner: value.shape if state["associated"] else None, + "array_actual": lambda _owner: int(value.ctypes.data), + "descriptor": lambda received_owner: received_owner, + "associated": lambda _owner: state["associated"], + "associate": associate, + "nullify": lambda _owner: state.update(associated=False), + "destroy": lambda _owner: None, + }, + owner, + "owned", + "unsupported", + ) + + assert target.associated is True + assert received == [ + ( + owner, + ( + int(value.ctypes.data), + 8, + 1, + 1, + 4, + 8, + ), + ) + ] + + +def test_fresh_contract_handle_supplies_present_empty_read_only_descriptor_facts(): + handle = contracts.Allocatable[contracts.Float64[:]]() + + assert _native_array_descriptor_argument_for_binding( + handle, + descriptor_kind="allocatable", + expected_dtype=np.float64, + expected_rank=1, + ) == (0, 8, 1, 0, 0, 8) + + +def test_writable_contract_handle_adopts_generated_storage_and_closes_once(): + handle = contracts.Allocatable[contracts.Float64[:]]() + calls = [] + owner = object() + + def bind_default(value): + _bind_contract_native_array_handle( + value, + "allocatable", + "float64", + 1, + { + "shape": lambda received_owner: calls.append(("shape", received_owner)) or None, + "array_actual": lambda received_owner: 0x5678, + "descriptor": lambda received_owner: received_owner, + "allocated": lambda received_owner: False, + "destroy": lambda received_owner: calls.append(("destroy", received_owner)), + }, + owner, + "owned", + "unsupported", + ) + + assert _native_array_descriptor_handoff_for_binding( + handle, + descriptor_kind="allocatable", + expected_dtype=np.float64, + expected_rank=1, + bind_default=bind_default, + ) == (owner,) + assert handle.owner == owner + + handle.close() + handle.close() + assert calls == [("destroy", owner)] + + +def test_non_array_descriptor_and_ordinary_array_annotations_are_not_factories(): + with pytest.raises(TypeError, match="scalar allocatable contracts"): + contracts.Allocatable[contracts.Float64]() + with pytest.raises(TypeError, match="ordinary array contract annotations"): + contracts.Float64[:]() + with pytest.raises(TypeError, match="element contract 'String'"): + contracts.Pointer[contracts.String[:]]() + with pytest.raises(TypeError, match="positive array rank"): + contracts.Allocatable[contracts.Float64[()]]() + with pytest.raises(TypeError, match="positive array rank"): + contracts.Pointer[contracts.Float64[...]]() + with pytest.raises(TypeError, match="explicit native length and encoding"): + contracts.String() + with pytest.raises(TypeError, match="default constructor takes no arguments"): + contracts.Int32(3) diff --git a/tests/semantics/conversion/fortran/test_fortran_conversion_procedures_and_interfaces.py b/tests/semantics/conversion/fortran/test_fortran_conversion_procedures_and_interfaces.py index c09e04870..eaa33299e 100644 --- a/tests/semantics/conversion/fortran/test_fortran_conversion_procedures_and_interfaces.py +++ b/tests/semantics/conversion/fortran/test_fortran_conversion_procedures_and_interfaces.py @@ -22,6 +22,7 @@ pytest, semantic_models, ) +from x2py.semantics.metadata import PROJECTED_OUTPUT_METADATA def test_bind_c_name_and_value_calling_convention_reach_semantic_ir(): @@ -380,6 +381,193 @@ def test_scalar_descriptors_record_native_projection_kind(): ] +def test_pointer_array_output_visibility_follows_intent_and_optional_presence(): + source = """ +module pointer_output_mod +contains +subroutine create_values(values) + real(8), pointer, intent(out) :: values(:) +end subroutine create_values + +subroutine maybe_create_values(values) + real(8), pointer, optional, intent(out) :: values(:) +end subroutine maybe_create_values + +subroutine replace_values(values) + real(8), pointer, intent(inout) :: values(:) +end subroutine replace_values +end module pointer_output_mod +""" + + smod = fortran_module_to_semantic_module(parse_fortran_source(source)) + create = get_function(smod, "create_values") + maybe_create = get_function(smod, "maybe_create_values") + replace = get_function(smod, "replace_values") + + assert create.arguments[0].metadata[PROJECTED_OUTPUT_METADATA] is True + assert create.projection == [ + ProjectionMapping( + python_name="values", + native_name="values", + native_position=0, + python_position=None, + result_position=0, + ) + ] + assert maybe_create.projection == [ + ProjectionMapping( + python_name="values", + native_name="values", + native_position=0, + python_position=0, + result_position=0, + ) + ] + assert replace.projection == [ + ProjectionMapping( + python_name="values", + native_name="values", + native_position=0, + python_position=0, + ) + ] + + +def test_primitive_scalar_inout_stays_visible_and_projects_replacement_return(): + source = """ +module outputs +contains +subroutine scale_in_place(value, factor) + real(8), intent(inout) :: value + real(8), intent(in) :: factor + value = factor * value +end subroutine scale_in_place +end module outputs +""" + + smod = fortran_module_to_semantic_module(parse_fortran_source(source)) + scale = get_function(smod, "scale_in_place") + + assert scale.arguments[0].metadata[PROJECTED_OUTPUT_METADATA] is True + assert scale.projection == [ + ProjectionMapping( + python_name="value", + native_name="value", + native_position=0, + python_position=0, + result_position=0, + ), + ProjectionMapping( + python_name="factor", + native_name="factor", + native_position=1, + python_position=1, + ), + ] + + +def test_missing_intent_scalar_uses_conservative_replacement_projection(): + source = """ +real(4) function square(value) result(output) + real(4) :: value + output = value * value +end function square +""" + + smod = fortran_file_to_semantic_modules(parse_fortran_source(source))[0] + square = get_function(smod, "square") + + assert square.arguments[0].semantic_type.storage.mutable is True + assert square.arguments[0].metadata[PROJECTED_OUTPUT_METADATA] is True + assert square.projection == [ + ProjectionMapping( + python_name="value", + native_name="value", + native_position=0, + python_position=0, + result_position=1, + ) + ] + + +def test_ordinary_array_output_stays_visible_without_result_projection(): + source = """ +module outputs +contains +subroutine fill(values) + real(8), intent(out) :: values(:) +end subroutine fill +end module outputs +""" + + smod = fortran_module_to_semantic_module(parse_fortran_source(source)) + fill = get_function(smod, "fill") + + assert PROJECTED_OUTPUT_METADATA not in fill.arguments[0].metadata + assert fill.projection == [ + ProjectionMapping( + python_name="values", + native_name="values", + native_position=0, + python_position=0, + ) + ] + + +def test_scalar_derived_output_stays_visible_without_result_projection(): + source = """ +module outputs +type :: point + real(8) :: x +end type point +contains +subroutine fill(value) + type(point), intent(out) :: value +end subroutine fill +end module outputs +""" + + smod = fortran_module_to_semantic_module(parse_fortran_source(source)) + fill = get_function(smod, "fill") + + assert PROJECTED_OUTPUT_METADATA not in fill.arguments[0].metadata + assert fill.projection == [ + ProjectionMapping( + python_name="value", + native_name="value", + native_position=0, + python_position=0, + ) + ] + + +def test_optional_scalar_derived_output_stays_visible_without_result_projection(): + source = """ +module outputs +type :: point + real(8) :: x +end type point +contains +subroutine fill(value) + type(point), intent(out), optional :: value +end subroutine fill +end module outputs +""" + + smod = fortran_module_to_semantic_module(parse_fortran_source(source)) + fill = get_function(smod, "fill") + + assert PROJECTED_OUTPUT_METADATA not in fill.arguments[0].metadata + assert fill.projection == [ + ProjectionMapping( + python_name="value", + native_name="value", + native_position=0, + python_position=0, + ) + ] + + def test_function_result(): source = """ module func_mod @@ -586,7 +774,8 @@ def test_dummy_procedure_interfaces_become_complete_callable_contracts(): assert "@prototype\ndef transform_iface(" in emitted assert "callback: transform_iface" in emitted assert "@prototype\ndef value_iface(" in emitted - assert "value: Value(Int32)" in emitted + assert "value: Int32" in emitted + assert "ref: Addr(Float64)" in emitted assert "@prototype\ndef string_iface(" in emitted assert "read_label: String[8]" in emitted assert native_contract_issues(parse_pyi_text(emitted, module_name=module.name)) == [] diff --git a/tests/semantics/conversion/pyi/test_classes_and_overloads.py b/tests/semantics/conversion/pyi/test_classes_and_overloads.py index f4e414d34..732c8ea7b 100644 --- a/tests/semantics/conversion/pyi/test_classes_and_overloads.py +++ b/tests/semantics/conversion/pyi/test_classes_and_overloads.py @@ -167,22 +167,16 @@ def __init__(self) -> None: ... assert " def __init__(self) -> None: ..." in emit_module(module) -def test_convert_pyi_to_ir_bound_constructor_replaces_generated_keyword_initializer(): +def test_convert_pyi_to_ir_bound_constructor_uses_explicit_pass_position(): module = parse_pyi_text( """ class state: - @private - def init_state( - self, - seed: Addr(Int32), - scale: Addr(Float64) = ... - ) -> None: ... - @bind("init_state") + @native_call([Arg(0), Pass(), Arg(1)]) def __init__( self, - seed: Addr(Int32), - scale: Addr(Float64) = ... + left: state, + right: state ) -> None: ... id: Int32 = 7 @@ -193,41 +187,43 @@ def __init__( cls = module.classes[0] assert cls.origin.metadata[SUPPRESS_DEFAULT_CONSTRUCTOR_METADATA] is True - assert [method.name for method in cls.methods] == ["init_state", "__init__"] - target = cls.methods[0] - assert target.visibility == "private" - init = cls.methods[1] + assert [method.name for method in cls.methods] == ["__init__"] + init = cls.methods[0] assert init.native_name == "init_state" assert init.metadata[BIND_TARGET_METADATA] == "init_state" - assert [arg.name for arg in init.arguments] == ["seed", "scale"] + assert [arg.name for arg in init.arguments] == ["left", "self", "right"] + assert init.passed_object_position == 1 + assert [(item.native_position, item.python_position) for item in init.projection] == [ + (0, 0), + (1, 1), + (2, 2), + ] emitted = emit_module(module) - assert " @private\n def init_state(" in emitted - assert ' @bind("init_state")\n def __init__(' in emitted + assert ' @bind("init_state")\n @native_call([Arg(0), Pass(), Arg(1)])\n def __init__(' in emitted assert "def __init__(\n self,\n *," not in emitted assert parse_pyi_text(emitted, module_name="edited") == module -def test_convert_pyi_to_ir_bound_constructor_allows_public_target_method(): +def test_convert_pyi_to_ir_bound_constructor_does_not_require_class_method_target(): module = parse_pyi_text( """ -class state: - def init_state(self, seed: Int32) -> None: ... +def init_state(owner: state, seed: Int32) -> None: ... +class state: @bind("init_state") + @native_call([Pass(), Addr(Arg(0))]) def __init__(self, seed: Int32) -> None: ... """, module_name="edited", ) cls = module.classes[0] - assert [(method.name, method.visibility) for method in cls.methods] == [ - ("init_state", "public"), - ("__init__", "public"), - ] + assert [method.name for method in cls.methods] == ["__init__"] + assert cls.methods[0].native_name == module.functions[0].native_name == "init_state" emitted = emit_module(module) - assert " def init_state(" in emitted - assert ' @bind("init_state")\n def __init__(' in emitted + assert "def init_state(" in emitted + assert ' @bind("init_state")\n @native_call([Pass(), Addr(Arg(0))])\n def __init__(' in emitted @pytest.mark.parametrize( @@ -246,6 +242,7 @@ class state: def __init__(self, *, id: Int32 = 7) -> None: ... @bind("init_state") + @native_call([Pass(), Addr(Arg(0))]) def __init__(self, seed: Int32) -> None: ... """, "Direct constructor bindings replace the generated field constructor", @@ -254,19 +251,19 @@ def __init__(self, seed: Int32) -> None: ... """ class state: @bind("init_state") + @native_call([Addr(Arg(0))]) def __init__(self, seed: Int32) -> None: ... """, - "Bound constructor references missing class method 'init_state'", + "Bound constructor native_call requires exactly one Pass() entry", ), ( """ class state: - def init_state(self, seed: Int32, scale: Float64) -> None: ... - @bind("init_state") + @native_call([Pass(), Pass(), Addr(Arg(0))]) def __init__(self, seed: Int32) -> None: ... """, - "Bound constructor declaration is incompatible with class method 'init_state'", + "Bound constructor native_call requires exactly one Pass() entry", ), ], ) @@ -291,6 +288,7 @@ def hidden() -> None: ... def test_convert_pyi_to_ir_resolves_x2py_overload_by_explicit_specific_name(): module = parse_pyi_text( """ +@bind("convert_integer_native") def convert_integer(value: Int32) -> Int32: ... @overload("convert_integer") @@ -304,15 +302,16 @@ def convert(value: Int32) -> Int32: ... ("convert", ["convert_integer"]) ] assert module.overload_sets[0].procedures[0].metadata["overload_target"] == "convert_integer" + assert module.overload_sets[0].procedures[0].native_name == "convert_integer_native" -def test_convert_pyi_to_ir_renames_module_generic_and_round_trips_native_name(): +def test_convert_pyi_to_ir_applies_module_overload_bind_and_round_trips_native_name(): module = parse_pyi_text( """ -@bind("convert") def convert_integer(value: Int32) -> Int32: ... -@overload("convert_integer", generic="convert") +@bind("convert") +@overload("convert_integer") def convert_number(value: Int32) -> Int32: ... """, module_name="generic_mod", @@ -320,9 +319,48 @@ def convert_number(value: Int32) -> Int32: ... overload = module.overload_sets[0] assert overload.name == "convert_number" - assert overload.procedures[0].metadata["fortran_generic_name"] == "convert" + assert overload.procedures[0].native_name == "convert" + assert overload.procedures[0].metadata[BIND_TARGET_METADATA] == "convert" + emitted = emit_module(module) + assert '@bind("convert")\n@overload("convert_integer")' in emitted + + +def test_convert_pyi_to_ir_applies_class_overload_bind_and_round_trips_native_name(): + module = parse_pyi_text( + """ +def set_integer(self: item, value: Int32) -> None: ... + +class item: + @bind("set") + @overload("set_integer") + def set(self, value: Int32) -> None: ... +""", + module_name="generic_mod", + ) + + candidate = module.classes[0].overload_sets[0].procedures[0] + assert candidate.native_name == "set" + assert candidate.metadata[BIND_TARGET_METADATA] == "set" emitted = emit_module(module) - assert '@overload("convert_integer", generic="convert")' in emitted + assert ' @bind("set")\n @overload("set_integer")' in emitted + + +def test_convert_pyi_to_ir_applies_constructor_overload_bind(): + module = parse_pyi_text( + """ +def set_integer(self: item, value: Int32) -> None: ... + +class item: + @bind("set") + @overload("set_integer") + def __init__(self, value: Int32) -> None: ... +""", + module_name="generic_mod", + ) + + candidate = module.classes[0].overload_sets[0].procedures[0] + assert candidate.native_name == "set" + assert candidate.metadata[BIND_TARGET_METADATA] == "set" @pytest.mark.parametrize( @@ -338,6 +376,15 @@ def convert_number(value: Int32) -> Int32: ... ), ( """ +def convert_integer(value: Int32) -> Int32: ... + +@overload("convert_integer", generic="convert") +def convert_number(value: Int32) -> Int32: ... +""", + "generic is only valid for class overloads; use bind on a module overload", + ), + ( + """ def compare(left: item, right: item) -> Bool: ... class item: @overload("compare", generic="operator(.eqv.)") @@ -448,10 +495,11 @@ def assign_vector_real( assert from_pyi.classes[0].overload_sets[0].procedures[0].metadata["overload_kind"] == "assignment" -def test_type_bound_method_declarations_restore_root_target_metadata(): +def test_method_declarations_keep_module_procedure_targets_independent(): from_pyi = parse_pyi_text( """ class vector: + @native_call([Pass(), Arg(0)]) def scale( self, factor: Addr(Float64) @@ -481,21 +529,20 @@ def inspect(value: Annotated[vector, Polymorphic]) -> None: ... module_name="edited", ) functions = {func.name: func for func in from_pyi.functions} + methods = {method.name: method for method in from_pyi.classes[0].methods} - assert functions["scale"].metadata["fortran_type_bound_target"] is True - assert functions["scale"].metadata["fortran_passed_object_name"] == "self" - assert functions["scale"].metadata["fortran_passed_object_position"] == 0 - assert functions["scale"].arguments[0].semantic_type.metadata["fortran_polymorphic"] is True - assert functions["shift_vector"].metadata["fortran_type_bound_target"] is True - assert functions["shift_vector"].metadata["fortran_passed_object_name"] == "owner" - assert functions["shift_vector"].metadata["fortran_passed_object_position"] == 1 - assert functions["shift_vector"].arguments[1].semantic_type.metadata["fortran_polymorphic"] is True + assert methods["scale"].native_name == "scale" + assert methods["shift"].native_name == "shift_vector" + assert "fortran_type_bound_target" not in functions["scale"].metadata + assert "fortran_type_bound_target" not in functions["shift_vector"].metadata emitted = emit_module(from_pyi) assert "self: vector" in emitted assert "owner: vector" in emitted assert "value: Annotated[vector, Polymorphic]" in emitted - assert parse_pyi_text(emitted, module_name="edited") == from_pyi + reparsed = parse_pyi_text(emitted, module_name="edited") + assert "fortran_type_bound_target" not in reparsed.functions[0].metadata + assert emit_module(reparsed) == emitted def test_pyi_keyword_normalized_type_bound_method_keeps_native_binding_name(): diff --git a/tests/semantics/conversion/pyi/test_types_and_values.py b/tests/semantics/conversion/pyi/test_types_and_values.py index 0e76bb2e4..c3b44a9a3 100644 --- a/tests/semantics/conversion/pyi/test_types_and_values.py +++ b/tests/semantics/conversion/pyi/test_types_and_values.py @@ -203,7 +203,7 @@ def set_status( assert parse_pyi_text(emitted, module_name="status_api") == module -def test_convert_pyi_to_ir_uses_reference_default_and_explicit_value_callbacks(): +def test_convert_pyi_to_ir_uses_value_default_and_explicit_reference_callbacks(): module = parse_pyi_text( """ class particle: @@ -211,11 +211,13 @@ class particle: @prototype def callback_shape( - value: Value(Int32), + value: Int32, + scalar_ref: Addr(Float64), values: Float64[:], scalar_storage: Float64[()], - scalar: Float64, - count: Int32, + scalar_value: Float64, + count_ref: Addr(Int32), + derived_value: Value(particle), output: Float64[:], result_storage: Float64[()], ) -> None: ... @@ -235,17 +237,21 @@ def register( False, False, False, + True, False, + True, False, False, ] assert callback_arguments[0].semantic_type.storage is None - assert callback_arguments[1].semantic_type.storage.kind == "array" + assert callback_arguments[1].semantic_type.storage.kind == "reference" assert callback_arguments[2].semantic_type.storage.kind == "array" - assert callback_arguments[3].semantic_type.storage.kind == "reference" - assert callback_arguments[4].semantic_type.storage.mutable is True - assert callback_arguments[5].semantic_type.storage.mutable is True - assert callback_arguments[6].semantic_type.storage.mutable is True + assert callback_arguments[3].semantic_type.storage.kind == "array" + assert callback_arguments[4].semantic_type.storage is None + assert callback_arguments[5].semantic_type.storage.kind == "reference" + assert callback_arguments[6].semantic_type.storage is None + assert callback_arguments[7].semantic_type.storage.mutable is True + assert callback_arguments[8].semantic_type.storage.mutable is True @pytest.mark.parametrize( @@ -460,13 +466,34 @@ def invalid(value: String[:]) -> None: ... @pytest.mark.parametrize( "annotation", [ - "Addr(Float64)", "Addr(Float64[n])", "Addr[2](Float64)", + "Addr(String[8])", + "Addr(particle)", + "Addr(Allocatable[Float64])", + "Addr(Pointer[Float64])", + ], +) +def test_convert_pyi_to_ir_rejects_invalid_prototype_address_wrappers(annotation: str): + with pytest.raises(ValueError, match=r"Addr.*prototype"): + parse_pyi_text( + f"class particle:\n mass: Float64\n\n@prototype\ndef callback(value: {annotation}) -> None: ...", + module_name="callbacks", + ) + + +@pytest.mark.parametrize( + "annotation", + [ + "Value(Float64)", + "Value(Int32)", + "Value(String[8])", + "Value(Allocatable[Float64])", + "Value(Pointer[Float64])", ], ) -def test_convert_pyi_to_ir_rejects_unnecessary_prototype_address_wrappers(annotation: str): - with pytest.raises(ValueError, match=r"Addr\(\.\.\.\) is unnecessary inside prototype declarations"): +def test_convert_pyi_to_ir_rejects_redundant_or_invalid_prototype_value_wrappers(annotation: str): + with pytest.raises(ValueError, match=r"Value.*callback"): parse_pyi_text( f"@prototype\ndef callback(value: {annotation}) -> None: ...", module_name="callbacks", diff --git a/tests/semantics/fixtures/general/modern_pyi_example.json b/tests/semantics/fixtures/general/modern_pyi_example.json index 6900956e9..6deb77adf 100644 --- a/tests/semantics/fixtures/general/modern_pyi_example.json +++ b/tests/semantics/fixtures/general/modern_pyi_example.json @@ -55,9 +55,7 @@ }, "visibility": "public", "default_value": null, - "metadata": { - "projected_output": true - }, + "metadata": {}, "origin": { "source_language": "fortran", "native_name": "p", @@ -444,8 +442,8 @@ "python_name": "p", "native_name": "p", "native_position": 0, - "python_position": null, - "result_position": 0, + "python_position": 0, + "result_position": null, "value_kind": "", "value": null }, @@ -453,7 +451,7 @@ "python_name": "pid", "native_name": "pid", "native_position": 1, - "python_position": 0, + "python_position": 1, "result_position": null, "value_kind": "", "value": null @@ -462,7 +460,7 @@ "python_name": "mass", "native_name": "mass", "native_position": 2, - "python_position": 1, + "python_position": 2, "result_position": null, "value_kind": "", "value": null @@ -471,7 +469,7 @@ "python_name": "x", "native_name": "x", "native_position": 3, - "python_position": 2, + "python_position": 3, "result_position": null, "value_kind": "", "value": null @@ -480,7 +478,7 @@ "python_name": "y", "native_name": "y", "native_position": 4, - "python_position": 3, + "python_position": 4, "result_position": null, "value_kind": "", "value": null @@ -489,7 +487,7 @@ "python_name": "z", "native_name": "z", "native_position": 5, - "python_position": 4, + "python_position": 5, "result_position": null, "value_kind": "", "value": null @@ -1470,9 +1468,7 @@ }, "visibility": "public", "default_value": null, - "metadata": { - "projected_output": true - }, + "metadata": {}, "origin": { "source_language": "fortran", "native_name": "a", @@ -1514,7 +1510,7 @@ "native_name": "a", "native_position": 0, "python_position": 0, - "result_position": 0, + "result_position": null, "value_kind": "", "value": null } diff --git a/tests/semantics/fixtures/general/scope_name_reuse_combinations.json b/tests/semantics/fixtures/general/scope_name_reuse_combinations.json index 6a721408a..98c3e7243 100644 --- a/tests/semantics/fixtures/general/scope_name_reuse_combinations.json +++ b/tests/semantics/fixtures/general/scope_name_reuse_combinations.json @@ -55,7 +55,9 @@ }, "visibility": "public", "default_value": null, - "metadata": {}, + "metadata": { + "projected_output": true + }, "origin": { "source_language": "fortran", "native_name": "same_name", @@ -88,7 +90,7 @@ "native_name": "same_name", "native_position": 0, "python_position": 0, - "result_position": null, + "result_position": 0, "value_kind": "", "value": null } @@ -361,7 +363,9 @@ }, "visibility": "public", "default_value": null, - "metadata": {}, + "metadata": { + "projected_output": true + }, "origin": { "source_language": "fortran", "native_name": "same_name", @@ -394,7 +398,7 @@ "native_name": "same_name", "native_position": 0, "python_position": 0, - "result_position": null, + "result_position": 0, "value_kind": "", "value": null } @@ -463,7 +467,9 @@ }, "visibility": "public", "default_value": null, - "metadata": {}, + "metadata": { + "projected_output": true + }, "origin": { "source_language": "fortran", "native_name": "same_name", @@ -496,7 +502,7 @@ "native_name": "same_name", "native_position": 0, "python_position": 0, - "result_position": null, + "result_position": 0, "value_kind": "", "value": null } @@ -983,7 +989,9 @@ }, "visibility": "public", "default_value": null, - "metadata": {}, + "metadata": { + "projected_output": true + }, "origin": { "source_language": "fortran", "native_name": "same_name", @@ -1016,7 +1024,7 @@ "native_name": "same_name", "native_position": 0, "python_position": 0, - "result_position": null, + "result_position": 0, "value_kind": "", "value": null } diff --git a/tests/semantics/policy/test_native_array_ownership.py b/tests/semantics/policy/test_native_array_ownership.py index 66d3c57f1..0f2ebba53 100644 --- a/tests/semantics/policy/test_native_array_ownership.py +++ b/tests/semantics/policy/test_native_array_ownership.py @@ -100,6 +100,58 @@ def make_values() -> Allocatable[Float64[:]]: ... assert policy.output_projection == "projected_handle" +def test_hidden_pointer_handle_output_owns_descriptor_but_not_target_policy(): + module = parse_pyi_text( + """ +@native_call([Return("values", 0)]) +def select_values() -> Pointer[Float64[:]]: ... +""", + module_name="hidden_pointer_handle_result", + ) + complete_semantic_policies(module) + + argument = module.functions[0].arguments[0] + decision = argument.metadata[RESOLVED_OWNERSHIP_POLICY_METADATA] + policy = argument.metadata[RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA] + + assert decision.owner is OwnershipOwner.WRAPPER + assert decision.transfer is TransferMode.WRAPPER_INSTANCE + assert decision.destruction is DestructionPolicy.WRAPPER_DEALLOC + assert decision.codegen_action is CodegenAction.WRAPPER_INSTANCE + assert decision.native_barrier_action is NativeBarrierAction.PASS_NATIVE_DESCRIPTOR + assert policy.descriptor_kind == "pointer" + assert policy.handle_kind == "owned_result_descriptor" + assert policy.origin == "projected_result" + assert policy.owner_retention == "wrapper_owner_storage" + assert policy.descriptor_ownership == "owned" + assert policy.output_projection == "projected_handle" + assert policy.target_lifetime == "unknown" + assert policy.destroy_behavior == "handle_finalizer" + assert policy.to_numpy == "unsupported" + assert set(policy.operations) == {"associate", "associated", "nullify", "to_numpy"} + + +def test_visible_descriptor_writeback_completes_caller_handle_construction_lifecycle(): + module = parse_pyi_text( + """ +@native_call([Arg(0)]) +def replace_values( + values: Allocatable[Float64[:]], +) -> Returns["values", Allocatable[Float64[:]]]: ... +""", + module_name="caller_created_handle", + ) + complete_semantic_policies(module) + + policy = module.functions[0].arguments[0].metadata[RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA] + + assert policy.default_construction == "lazy_owned_descriptor" + assert policy.default_descriptor_ownership == "owned" + assert policy.default_release == "wrapper_dealloc" + assert policy.default_destroy_behavior == "handle_finalizer" + assert "destroy" in policy.default_operations + + @pytest.mark.parametrize( ("owner", "transfer", "destruction", "context"), [ @@ -218,7 +270,7 @@ def test_documented_transfer_and_destruction_modes_resolve_or_fail_closed(): ( "blocked", _array_type(pointer=True), - _hidden_output_context(projects_result=True, python_visible=False), + _writable_argument_context(), ), ] @@ -337,7 +389,7 @@ class box: assert module_policy.requires_pointer_c_descriptor_interop is True assert module_policy.target_lifetime == "module" assert module_policy.destroy_behavior == "none" - assert set(module_policy.operations) == {"associated", "nullify", "to_numpy"} + assert set(module_policy.operations) == {"associate", "associated", "nullify", "to_numpy"} assert "allocate" not in module_policy.operations assert "deallocate" not in module_policy.operations assert "resize" not in module_policy.operations @@ -350,7 +402,7 @@ class box: assert field_policy.requires_pointer_c_descriptor_interop is True assert field_policy.target_lifetime == "parent_wrapper" assert field_policy.destroy_behavior == "parent_wrapper_finalizer" - assert set(field_policy.operations) == {"associated", "nullify", "to_numpy"} + assert set(field_policy.operations) == {"associate", "associated", "nullify", "to_numpy"} def test_complete_pointer_policy_metadata_round_trips_without_overriding_container_ownership(): @@ -489,8 +541,8 @@ def consume( default_target = module.functions[0].arguments[0].metadata[RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA] unsafe_target = module.functions[0].arguments[1].metadata[RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA] - assert set(default_target.operations) == {"associated", "nullify", "to_numpy"} - assert set(unsafe_target.operations) == {"associated", "deallocate", "nullify", "to_numpy"} + assert set(default_target.operations) == {"associate", "associated", "nullify", "to_numpy"} + assert set(unsafe_target.operations) == {"associate", "associated", "deallocate", "nullify", "to_numpy"} assert "allocate" not in unsafe_target.operations assert "resize" not in unsafe_target.operations @@ -584,7 +636,7 @@ def make_target() -> Pointer[Float64[:]]: ... assert field_target.descriptor_interop == "pointer_c_descriptor" assert field_target.requires_pointer_c_descriptor_interop is True assert field_target.is_blocked is False - assert set(field_target.operations) == {"associated", "nullify", "to_numpy"} + assert set(field_target.operations) == {"associate", "associated", "nullify", "to_numpy"} assert argument_values.handle_kind == "argument_descriptor" assert argument_values.origin == "argument" @@ -596,6 +648,11 @@ def make_target() -> Pointer[Float64[:]]: ... assert argument_values.descriptor_interop == "none" assert argument_values.requires_pointer_c_descriptor_interop is False assert set(argument_values.operations) == {"allocated", "to_numpy"} + assert argument_values.default_construction == "fact_packed_empty" + assert argument_values.default_descriptor_ownership == "owned" + assert argument_values.default_release == "wrapper_dealloc" + assert argument_values.default_destroy_behavior == "handle_finalizer" + assert "destroy" in argument_values.default_operations assert optional_target.handle_kind == "optional_absent_handle" assert optional_target.optional_absent is True @@ -607,7 +664,9 @@ def make_target() -> Pointer[Float64[:]]: ... assert optional_target.blocker is None assert optional_target.descriptor_interop == "pointer_c_descriptor" assert optional_target.requires_pointer_c_descriptor_interop is True - assert set(optional_target.operations) == {"associated", "nullify", "to_numpy"} + assert set(optional_target.operations) == {"associate", "associated", "nullify", "to_numpy"} + assert optional_target.default_construction == "fact_packed_empty" + assert "destroy" in optional_target.default_operations assert "allocate" not in optional_target.operations assert "deallocate" not in optional_target.operations assert "resize" not in optional_target.operations @@ -623,6 +682,7 @@ def make_target() -> Pointer[Float64[:]]: ... assert managed_target.requires_pointer_c_descriptor_interop is True assert set(managed_target.operations) == { "allocate", + "associate", "associated", "deallocate", "nullify", @@ -644,15 +704,25 @@ def make_target() -> Pointer[Float64[:]]: ... assert allocatable_result.requires_pointer_c_descriptor_interop is False assert allocatable_result.requires_c_descriptor_interop is True assert set(allocatable_result.operations) == {"allocated", "deallocate", "resize", "to_numpy"} - - assert pointer_result.handle_kind == "unsupported" - assert pointer_result.owner_retention == "unknown" + assert allocatable_result.default_construction == "none" + assert allocatable_result.default_operations == () + + assert pointer_result.handle_kind == "owned_result_descriptor" + assert pointer_result.origin == "result" + assert pointer_result.owner == "wrapper" + assert pointer_result.owner_retention == "wrapper_owner_storage" + assert pointer_result.descriptor_ownership == "owned" + assert pointer_result.output_projection == "handle_result" + assert pointer_result.release == "wrapper_dealloc" assert pointer_result.target_lifetime == "unknown" - assert pointer_result.destroy_behavior == "blocked" - assert pointer_result.is_blocked is True - assert pointer_result.descriptor_interop == "none" - assert pointer_result.requires_pointer_c_descriptor_interop is False - assert "stable owner storage and target lifetime" in pointer_result.blocker + assert pointer_result.destroy_behavior == "handle_finalizer" + assert pointer_result.is_blocked is False + assert pointer_result.blocker is None + assert pointer_result.descriptor_interop == "pointer_c_descriptor" + assert pointer_result.requires_pointer_c_descriptor_interop is True + assert pointer_result.requires_c_descriptor_interop is True + assert set(pointer_result.operations) == {"associate", "associated", "nullify", "to_numpy"} + assert pointer_result.default_construction == "none" def test_aliased_does_not_change_allocatable_live_view_semantics(): diff --git a/tests/semantics/policy/test_policy_defaults_and_validation.py b/tests/semantics/policy/test_policy_defaults_and_validation.py index 647175e1f..d9d97d13c 100644 --- a/tests/semantics/policy/test_policy_defaults_and_validation.py +++ b/tests/semantics/policy/test_policy_defaults_and_validation.py @@ -58,6 +58,14 @@ def test_default_policy_decisions_cover_public_object_kinds(): assert scalar.transfer is TransferMode.BY_VALUE assert scalar.codegen_action is CodegenAction.DIRECT_VALUE + scalar_replacement = resolver.decide_semantic_type( + _scalar_type(), + _writable_argument_context(projects_result=True), + ) + assert scalar_replacement.owner is OwnershipOwner.PYTHON + assert scalar_replacement.transfer is TransferMode.COPY_RETURN + assert scalar_replacement.codegen_action is CodegenAction.COPY_IN_OUT + string = resolver.decide_semantic_type(_string_type(), OwnershipContext.result()) assert string.owner is OwnershipOwner.PYTHON assert string.transfer is TransferMode.COPY_RETURN diff --git a/tests/semantics/policy/test_wrapper_policy.py b/tests/semantics/policy/test_wrapper_policy.py index d2898476c..21d9c8959 100644 --- a/tests/semantics/policy/test_wrapper_policy.py +++ b/tests/semantics/policy/test_wrapper_policy.py @@ -2,6 +2,7 @@ import pytest +from tests._shared.ownership_policy_support import parse_pyi_text from tests.wrapper.fortran._support import wrapper_source from x2py.parsers.fortran.parser import parse_fortran_project from x2py.pipeline.build import _apply_source_python_exports, _fortran_source_for_pipeline, _merge_wrapper_modules @@ -24,12 +25,15 @@ ObjectKind, OwnershipOwner, PythonBarrierAction, - StorageMode, SetterAction, + StorageMode, TransferMode, ) from x2py.semantics.policy_completion import complete_semantic_policies from x2py.semantics.wrapper_policy import ( + RAW_STRING_ADDRESS_COPY_REASON, + STRING_STORAGE_COPY_REASON, + ArgumentConversionPhase, ArgumentHandoffMode, BridgeDataAction, CallbackABIKind, @@ -38,18 +42,15 @@ FunctionWrapperPolicy, ModuleGetterAction, ModuleVariablePolicy, + NativeArrayDescriptorKind, + NativeDescriptorHandoffABI, NativeStatusErrorPolicy, OptionalMode, PythonExceptionKind, - RAW_STRING_ADDRESS_COPY_REASON, - STRING_STORAGE_COPY_REASON, WritebackPhase, completed_function_wrapper_policy, ) -from tests._shared.ownership_policy_support import parse_pyi_text - - FMATH_CONTRACT = Path("tests/wrapper/fortran/scalars/contracts/fmath/__init__.pyi") @@ -73,11 +74,44 @@ def test_fmath_fixture_gets_completed_function_wrapper_policy(): assert all(isinstance(policy, FunctionWrapperPolicy) for policy in policies) assert all(policy.supported for policy in policies) assert all(policy.blockers == () for policy in policies) - assert all(policy.writeback_actions == () for policy in policies) + assert all(policy.writeback_actions for policy in policies) + assert all( + argument.conversion_phase is ArgumentConversionPhase.IMMEDIATE + for policy in policies + for argument in policy.arguments + ) assert all(policy.cleanup_actions == () for policy in policies) assert all(policy.release_actions == () for policy in policies) +def test_module_overload_bind_takes_precedence_per_candidate(): + module = parse_pyi_text( + """ +def convert_integer(value: Int32) -> Int32: ... + +@private +@bind("convert_real_specific") +def convert_real(value: Float64) -> Float64: ... + +@overload("convert_integer") +def convert(value: Int32) -> Int32: ... + +@bind("convert") +@overload("convert_real") +def convert(value: Float64) -> Float64: ... +""", + module_name="conversions", + ) + + complete_semantic_policies(module) + + policies = [ + procedure.metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + for procedure in module.overload_sets[0].procedures + ] + assert [policy.native_name for policy in policies] == ["convert_integer", "convert"] + + def test_optional_scalar_policy_completes_nullable_value_presence_before_planning(): module = pyi_file_to_semantic_module( Path("tests/wrapper/fortran/function_calls/contracts/foptional_fixed/__init__.pyi"), @@ -172,6 +206,7 @@ def test_scalar_copy_in_out_policy_completes_writeback_before_planning(): assert policy.supported is True assert policy.results == () assert policy.native_is_subroutine is True + assert policy.arguments[0].conversion_phase is ArgumentConversionPhase.IMMEDIATE assert tuple(action.phase for action in policy.writeback_actions) == tuple(WritebackPhase) assert {action.source_role for action in policy.writeback_actions} == {"scalar_writeback.bump.value:value"} assert {action.result_position for action in policy.writeback_actions} == {0} @@ -218,8 +253,42 @@ def with_scalar(n: Int32) -> tuple[Int32, Int32]: ... hidden = policy.results[1] assert hidden.native_barrier_action is NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS assert policy.native_call_slots[1].owner_path == hidden.owner_path - assert policy.native_call_slots[1].result_position == hidden.result_position - assert policy.native_call_slots[1].native_barrier_action is hidden.native_barrier_action + + +def test_rank_zero_scalar_storage_results_complete_as_numpy_array_policies(): + module = parse_pyi_text( + """ +def direct_storage_result() -> Float64[()]: ... + +@native_call([Return("out", 0)]) +def hidden_storage_result() -> Float64[()]: ... +""", + module_name="rank_zero_storage_results", + ) + complete_semantic_policies(module) + + policies = {function.name: completed_function_wrapper_policy(function) for function in module.functions} + direct_policy = policies["direct_storage_result"] + hidden_policy = policies["hidden_storage_result"] + direct = direct_policy.results[0] + hidden = hidden_policy.results[0] + + assert direct_policy.supported is True + assert hidden_policy.supported is True + assert direct.ownership.kind is ObjectKind.NUMPY_ARRAY + assert direct.array.rank == 0 + assert direct.array.category == "scalar_storage" + assert direct.codegen_action is CodegenAction.COPY_OUT + assert direct.native_barrier_action is NativeBarrierAction.NONE + assert direct.bridge_data_action is BridgeDataAction.COPY_REPRESENTATION + assert hidden.ownership.kind is ObjectKind.NUMPY_ARRAY + assert hidden.array.rank == 0 + assert hidden.array.category == "scalar_storage" + assert hidden.codegen_action is CodegenAction.COPY_OUT + assert hidden.native_barrier_action is NativeBarrierAction.PASS_STORAGE_ADDRESS + assert hidden.bridge_data_action is BridgeDataAction.COPY_REPRESENTATION + assert hidden_policy.native_call_slots[0].result_position == hidden.result_position + assert hidden_policy.native_call_slots[0].native_barrier_action is hidden.native_barrier_action def test_source_hidden_scalar_output_completes_call_local_address_before_planning(): @@ -233,7 +302,7 @@ def test_source_hidden_scalar_output_completes_call_local_address_before_plannin assert policy.native_call_slots[1].native_barrier_action is hidden.native_barrier_action -def test_source_callback_value_override_and_reference_default_are_completed(): +def test_source_callback_value_default_and_explicit_reference_are_completed(): module = _source_semantic_module("fcallback_all_f90.f90", module_name="fcallback_all_f90") function = next(item for item in module.functions if item.name == "apply_value_callback") policy = completed_function_wrapper_policy(function) @@ -251,6 +320,42 @@ def test_source_callback_value_override_and_reference_default_are_completed(): assert extent.adapter_action is CallbackTransferAction.COPY_IN +@pytest.mark.parametrize( + ("prototype", "blocker"), + [ + ( + "def callback_shape(value: Allocatable[Float64]) -> None: ...", + "callback argument 'value' uses unsupported allocatable, pointer, polymorphic, or assumed-type storage", + ), + ( + "def callback_shape(value: Float64 = ...) -> None: ...", + "callback argument 'value' cannot be optional", + ), + ( + "def callback_shape() -> Pointer[Float64]: ...", + "callback result uses unsupported allocatable, pointer, polymorphic, or assumed-type storage", + ), + ], +) +def test_callback_descriptor_and_optional_forms_are_blocked_before_codegen(prototype: str, blocker: str): + module = parse_pyi_text( + f""" +@prototype +{prototype} + +def apply(callback: callback_shape) -> None: ... +""", + module_name="unsupported_callback_shape", + ) + + complete_semantic_policies(module) + + policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + assert isinstance(policy, FunctionWrapperPolicy) + assert policy.supported is False + assert blocker in policy.blockers + + def test_external_declaration_mode_is_completed_from_native_abi_requirements(): module = parse_pyi_text( """ @@ -290,7 +395,49 @@ def create_allocatable() -> Float64 | None: ... assert decision.native_barrier_action is NativeBarrierAction.PASS_VALUE -def test_source_fmath_scalar_policy_accepts_storage_address_native_action(): +def test_direct_allocatable_scalar_function_result_is_blocked_before_codegen(): + module = parse_pyi_text( + """ +@native_call([Arg(0)], result=Allocatable(Return(0))) +def maybe_allocatable(flag: Int32) -> Float64 | None: ... +""", + module_name="direct_descriptor_result", + ) + + complete_semantic_policies(module) + + policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + assert isinstance(policy, FunctionWrapperPolicy) + assert policy.supported is False + assert ( + "direct allocatable scalar function results cannot preserve unallocated state; " + "use an allocatable hidden output projection" + ) in policy.blockers + + +def test_direct_high_rank_allocatable_function_result_is_supported_before_codegen(): + module = parse_pyi_text( + """ +@native_call([Addr(Arg(0)), Addr(Arg(1))]) +def make_matrix(n: Int32, m: Int32) -> Allocatable[Float64[:, :]]: ... +""", + module_name="direct_allocatable_matrix_result", + ) + + complete_semantic_policies(module) + + policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + assert isinstance(policy, FunctionWrapperPolicy) + assert policy.supported is True + assert policy.blockers == () + result = policy.results[0] + assert result.rank == 2 + assert result.native_array_handle is not None + assert result.native_array_handle.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE + assert result.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE + + +def test_source_fmath_scalar_policy_projects_conservative_replacements(): module = _source_semantic_module("fmath.f", module_name="fmath") function = next(item for item in module.functions if item.name == "ADD_R8") policies = [item.metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] for item in module.functions] @@ -305,19 +452,20 @@ def test_source_fmath_scalar_policy_accepts_storage_address_native_action(): assert policy.external is True assert [argument.name for argument in policy.arguments] == ["X", "Y"] assert [argument.codegen_action for argument in policy.arguments] == [ - CodegenAction.IN_PLACE_ARGUMENT, - CodegenAction.IN_PLACE_ARGUMENT, + CodegenAction.COPY_IN_OUT, + CodegenAction.COPY_IN_OUT, ] + assert all(argument.conversion_phase is ArgumentConversionPhase.IMMEDIATE for argument in policy.arguments) assert [argument.python_barrier_action for argument in policy.arguments] == [ PythonBarrierAction.SCALAR_VALUE, PythonBarrierAction.SCALAR_VALUE, ] assert [argument.native_barrier_action for argument in policy.arguments] == [ - NativeBarrierAction.PASS_STORAGE_ADDRESS, - NativeBarrierAction.PASS_STORAGE_ADDRESS, + NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS, + NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS, ] assert [argument.storage_mode for argument in policy.arguments] == [StorageMode.STACK, StorageMode.STACK] - assert all(policy.writeback_actions == () for policy in policies) + assert all(policy.writeback_actions for policy in policies) assert all(policy.cleanup_actions == () for policy in policies) assert all(policy.release_actions == () for policy in policies) assert [ @@ -327,14 +475,14 @@ def test_source_fmath_scalar_policy_accepts_storage_address_native_action(): ( "projection", "arg", - NativeBarrierAction.PASS_STORAGE_ADDRESS, - CodegenAction.IN_PLACE_ARGUMENT, + NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS, + CodegenAction.COPY_IN_OUT, ), ( "projection", "arg", - NativeBarrierAction.PASS_STORAGE_ADDRESS, - CodegenAction.IN_PLACE_ARGUMENT, + NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS, + CodegenAction.COPY_IN_OUT, ), ] @@ -380,12 +528,13 @@ def test_fmath_scalar_policy_records_address_projected_call_slots(): assert argument.rank == 0 assert argument.optional is False assert argument.ownership.kind is ObjectKind.SCALAR - assert argument.codegen_action is CodegenAction.CALL_LOCAL_INPUT + assert argument.codegen_action is CodegenAction.COPY_IN_OUT + assert argument.conversion_phase is ArgumentConversionPhase.IMMEDIATE assert argument.python_barrier_action is PythonBarrierAction.SCALAR_VALUE assert argument.native_barrier_action is NativeBarrierAction.PASS_CALL_LOCAL_ADDRESS - assert argument.storage_mode is StorageMode.ALIAS - assert argument.boundary_storage_mode is StorageMode.ALIAS - assert argument.projects_result is False + assert argument.storage_mode is StorageMode.STACK + assert argument.boundary_storage_mode is StorageMode.STACK + assert argument.projects_result is True assert argument.python_visible is True assert [(slot.native_position, slot.python_position) for slot in policy.native_call_slots] == [ @@ -645,6 +794,75 @@ def sum_values(values: Float64[:]) -> Float64: ... assert policy.native_call_slots[0].array == argument.array +def test_wrapper_policy_flattens_python_rank_for_rank_one_assumed_size_storage(): + module = parse_pyi_text( + """ +def sum_flat(n: Int32, values: Float64[Flat]) -> Float64: ... +""", + module_name="flat_array_argument", + ) + complete_semantic_policies(module) + policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] + + argument = policy.arguments[1] + assert argument.array is not None + assert argument.array.rank == 1 + assert argument.array.shape == (":",) + assert argument.array.category == "assumed_size" + assert argument.array.flatten_python_storage is True + assert argument.array.flat_axis == 0 + assert argument.native_array_actual is not None + assert argument.native_array_actual.rank == 1 + assert argument.native_array_actual.shape == (":",) + assert argument.native_array_actual.flatten_storage is True + assert argument.native_array_actual.flat_axis == 0 + assert policy.native_call_slots[1].array == argument.array + + +def test_wrapper_policy_flattens_remaining_axes_for_multidimensional_assumed_size_storage(): + module = parse_pyi_text( + """ +from x2py.contracts import Annotated, Flat, Float64, Int32, ORDER_C + +def sum_fortran(rows: Int32, values: Float64[rows, Flat]) -> Float64: ... +def sum_c(columns: Int32, values: Annotated[Float64[Flat, columns], ORDER_C]) -> Float64: ... +""", + module_name="flat_matrix_argument", + ) + complete_semantic_policies(module) + policies = { + function.name: function.metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] for function in module.functions + } + + fortran_argument = policies["sum_fortran"].arguments[1] + assert fortran_argument.array is not None + assert fortran_argument.array.rank == 2 + assert fortran_argument.array.shape == ("rows", ":") + assert fortran_argument.array.order == "ORDER_F" + assert fortran_argument.array.category == "assumed_size" + assert fortran_argument.array.flatten_python_storage is True + assert fortran_argument.array.flat_axis == 1 + assert fortran_argument.native_array_actual is not None + assert fortran_argument.native_array_actual.rank == 2 + assert fortran_argument.native_array_actual.shape == ("rows", ":") + assert fortran_argument.native_array_actual.flatten_storage is True + assert fortran_argument.native_array_actual.flat_axis == 1 + + c_argument = policies["sum_c"].arguments[1] + assert c_argument.array is not None + assert c_argument.array.rank == 2 + assert c_argument.array.shape == (":", "columns") + assert c_argument.array.order == "ORDER_C" + assert c_argument.array.category == "assumed_size" + assert c_argument.array.flatten_python_storage is True + assert c_argument.array.flat_axis == 0 + assert c_argument.native_array_actual is not None + assert c_argument.native_array_actual.rank == 2 + assert c_argument.native_array_actual.shape == (":", "columns") + assert c_argument.native_array_actual.flatten_storage is True + assert c_argument.native_array_actual.flat_axis == 0 + + def test_wrapper_policy_completes_required_raw_array_address_handoff(): module = parse_pyi_text( """ @@ -782,6 +1000,7 @@ def discard_name(name: String[8]) -> None: ... assert argument.ownership.transfer is TransferMode.COPY_RETURN assert argument.ownership.destruction is DestructionPolicy.PYTHON_REFCOUNT assert argument.codegen_action is CodegenAction.COPY_IN_OUT + assert argument.conversion_phase is ArgumentConversionPhase.DEFERRED_REPLACEMENT assert argument.character_length == 8 assert argument.projects_result is True # The native call mutates a binding-owned replacement, not the immutable diff --git a/tests/types/test_numpy.py b/tests/types/test_numpy.py index 2d2ff5646..970e8f81a 100644 --- a/tests/types/test_numpy.py +++ b/tests/types/test_numpy.py @@ -23,6 +23,7 @@ def test_semantic_dtype_to_numpy_dtype_dictionary_uses_resolved_widths(): "UInt16": "numpy.uint16", "UInt32": "numpy.uint32", "UInt64": "numpy.uint64", + "Float16": "numpy.float16", "Float32": "numpy.float32", "Float64": "numpy.float64", "Float128": "numpy.longdouble", @@ -48,6 +49,7 @@ def test_semantic_type_to_numpy_dtype_uses_dtype_not_name(): semantic_type = SemanticType("Int", dtype="Int64") assert semantic_type_to_numpy_dtype(semantic_type) == numpy.dtype(numpy.int64) + assert semantic_dtype_to_numpy_dtype("Float16") == numpy.dtype(numpy.float16) assert semantic_dtype_to_numpy_dtype("Float64") == numpy.dtype(numpy.float64) dtype_map = semantic_dtype_to_numpy_dtype_map() assert dtype_map["Int32"] == numpy.dtype(numpy.int32) diff --git a/tests/wrapper/CHECKLIST_COVERAGE.md b/tests/wrapper/CHECKLIST_COVERAGE.md index f497964b8..34430a4b0 100644 --- a/tests/wrapper/CHECKLIST_COVERAGE.md +++ b/tests/wrapper/CHECKLIST_COVERAGE.md @@ -57,7 +57,7 @@ recorded progression, not in the live ledger. | Function-call contracts rebuild from generated `.pyi` fixtures with the same optional-argument handling, scalar replacement writeback, hidden output projection, multiple-result ordering, allocatable nullable outputs, native-call projection metadata, native shared-library link inputs, and validation failures as source builds | `function_calls/test_optional_arguments.py::test_optional_arguments_drive_fortran_present_behavior`, `function_calls/test_optional_arguments.py::test_fixed_form_optional_arguments_drive_fortran_present_behavior`, `function_calls/test_optional_arguments.py::test_optional_array_descriptors_preserve_presence_and_storage_state`, `function_calls/test_scalar_writeback_plan.py::test_scalar_copy_in_out_returns_replacement`, `function_calls/test_output_arguments.py::test_output_arguments_and_multiple_results_follow_python_projection_rules`, `function_calls/test_native_call_examples.py::test_native_call_examples_cover_scalar_array_string_and_object_projection`, `function_calls/test_native_call_examples.py::test_native_call_examples_build_from_generated_pyi_and_native_shared_library` | | Array contracts rebuild from generated `.pyi` fixtures with the same dtype, rank, shape, order, stride, lower-bound, writeability, byte-order, alignment, zero-extent, assumed-rank dispatch, ordinary Python-owned result behavior, and allocatable result-handle behavior as source builds | `arrays/test_array_contracts.py::test_remaining_array_contracts_are_validated_before_fortran_calls`, `arrays/test_array_results.py::test_array_results_follow_data_buffer_and_descriptor_handle_contracts`, `arrays/test_array_results.py::test_owned_allocatable_results_preserve_handle_state`, `arrays/test_assumed_rank_arrays.py::test_assumed_rank_arguments_dispatch_to_runtime_rank`, `arrays/test_assumed_rank_arrays.py::test_assumed_rank_bridge_dispatches_each_runtime_rank_argument`, `arrays/test_multidimensional_arrays.py::test_rank2_contiguous_contract_requires_fortran_contiguous`, `arrays/test_multidimensional_arrays.py::test_rank2_assumed_shape_accepts_fortran_ordered_strided_views`, `arrays/test_multidimensional_arrays.py::test_rank2_assumed_shape_rejects_non_positive_strides`, `arrays/test_multidimensional_arrays.py::test_rank2_explicit_shape_requires_fortran_contiguous`, `arrays/test_multidimensional_arrays.py::test_rank3_contiguous_contract_requires_fortran_contiguous`, `arrays/test_multidimensional_arrays.py::test_rank3_assumed_shape_accepts_fortran_ordered_strided_views` | | Character contracts rebuild from generated `.pyi` fixtures with the same fixed-length buffers, assumed-length strings, nullable deferred scalar results, deferred-width native handles, copy-in/copy-out behavior, optional strings, Unicode handling, embedded-NUL validation, and raw fixed-width array addresses as source builds | `strings/test_character_arguments.py::test_legacy_fortran_character_arguments_and_results`, `strings/test_character_arguments.py::test_modern_fortran_character_arguments_and_results`, `strings/test_character_arguments.py::test_deferred_allocatable_string_results_use_canonical_plan`, `strings/test_character_arguments.py::test_deferred_character_array_handles_use_canonical_plan`, `strings/test_character_arguments.py::test_raw_fixed_width_character_arrays_use_canonical_plan`, `strings/test_character_edge_cases.py::test_fortran_character_edge_cases_follow_copy_in_copy_out_policy`, `tests/semantics/conversion/pyi/test_calls_and_projections.py::test_projected_replacement_without_native_call_keeps_writable_argument_storage`, `tests/semantics/conversion/pyi/test_calls_and_projections.py::test_native_call_projected_output_keeps_visible_storage_writable` | -| Derived-type contracts rebuild from generated `.pyi` fixtures with the same fields, methods, type-bound root targets, constructors, finalizers, borrowed child lifetime, scalar object boundaries, inheritance, polymorphic dispatch, complete scalar actual/dummy compatibility, descriptor-backed scalar module proxies, and wrapper-owned allocatable/pointer holders as source builds | `derived_types/test_derived_type_methods.py::test_modern_fortran_derived_type_exposes_class_and_type_bound_methods`, `derived_types/test_constructors_and_finalizers.py::test_fortran_default_constructor_keywords_and_finalization`, `derived_types/test_borrowed_finalizers.py::test_borrowed_child_wrapper_never_finalizes_native_component`, `derived_types/test_derived_layout.py::test_bind_c_derived_types_use_accessors_and_fortran_value_copy`, `derived_types/test_derived_type_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries`, `derived_types/test_phase8_derived_plan.py`, `derived_types/test_scalar_derived_actual_dummy_matrix.py`, `derived_types/test_inheritance.py::test_fortran_extension_types_generate_python_inheritance`, `derived_types/test_pointers.py::test_pointer_array_handles_block_on_unsupported_result_owner_policy`, `tests/wrapper_codegen/test_phase8_scalar_derived_actual_dummy_matrix.py`, `tests/semantics/conversion/pyi/test_classes_and_overloads.py::test_type_bound_method_declarations_restore_root_target_metadata` | +| Derived-type contracts rebuild from generated `.pyi` fixtures with the same fields, independent module-procedure and method surfaces, constructors, finalizers, borrowed child lifetime, scalar object boundaries, inheritance, polymorphic dispatch, complete scalar actual/dummy compatibility, descriptor-backed scalar module proxies, and wrapper-owned allocatable/pointer holders as source builds | `derived_types/test_derived_type_methods.py::test_modern_fortran_derived_type_exposes_class_and_type_bound_methods`, `derived_types/test_constructors_and_finalizers.py::test_fortran_default_constructor_keywords_and_finalization`, `derived_types/test_borrowed_finalizers.py::test_borrowed_child_wrapper_never_finalizes_native_component`, `derived_types/test_derived_layout.py::test_bind_c_derived_types_use_accessors_and_fortran_value_copy`, `derived_types/test_derived_type_boundaries.py::test_scalar_derived_types_cross_procedure_boundaries`, `derived_types/test_phase8_derived_plan.py`, `derived_types/test_scalar_derived_actual_dummy_matrix.py`, `derived_types/test_inheritance.py::test_fortran_extension_types_generate_python_inheritance`, `derived_types/test_pointers.py::test_pointer_array_results_use_owned_descriptors_without_owning_targets`, `tests/wrapper_codegen/test_phase8_scalar_derived_actual_dummy_matrix.py`, `tests/semantics/conversion/pyi/test_classes_and_overloads.py::test_method_declarations_keep_module_procedure_targets_independent` | | Callback contracts route through wrapper-plan generation without legacy lowering and rebuild from generated `.pyi` fixtures with the same primitive scalar values, array, character-storage, and derived callback conversions, call-scoped lifetime, nested same-thread entry, GIL handling, reference cleanup, and fatal exception behavior as source builds | `callbacks/test_all_callback_shapes.py::test_immediate_callbacks_cover_all_supported_argument_shapes`, `callbacks/test_scalar_callbacks.py::test_immediate_scalar_dummy_procedure_calls_python_callback`, `callbacks/test_scalar_callbacks.py::test_callback_exception_prints_traceback_and_aborts_host_process`, `callbacks/test_array_callbacks.py::test_immediate_dummy_procedure_converts_array_arguments_and_results`, `callbacks/test_derived_callbacks.py::test_immediate_dummy_procedure_converts_derived_arguments_and_results`, `tests/wrapper_codegen/test_phase10_callbacks.py`, `tests/semantics/conversion/pyi/test_types_and_values.py::test_convert_pyi_to_ir_preserves_prototype_argument_names_and_dimensions` | | Module-state contracts rebuild from generated `.pyi` fixtures with the same scalar module attributes, parameter behavior, saved native state, plain and `Aliased` live allocatable module views, borrowed field handles, owned allocatable result handles, same-handle allocatable descriptor mutation, explicit-copy independence, fresh extraction after state changes, rank-zero descriptor copying/nullability, and common-block encapsulation as source builds; isolated scalar and native-handle owners also replay legacy and wrapper-plan routes | `module_state/test_module_state.py::test_scalar_module_variables_use_attributes_and_parameters_have_no_native_setter`, `module_state/test_scalar_module_variable_plan.py::test_whole_scalar_module_variable_behavior_uses_canonical_plan`, `module_state/test_allocatable_views.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles`, `module_state/test_allocatable_views.py::test_plain_allocatable_module_array_exposes_current_live_view`, `module_state/test_allocatable_views.py::test_scalar_descriptor_module_variables_return_copied_optional_values`, `module_state/test_allocatable_replacement.py::test_allocatable_inout_arrays_mutate_and_return_the_same_handle`, `module_state/test_allocatable_replacement.py::test_projected_allocatable_descriptor_preserves_same_handle_identity`, `derived_types/test_pointers.py::test_module_native_array_handles_use_canonical_plan`, `module_state/test_common_blocks.py::test_common_block_storage_stays_internal_to_wrapped_fortran` | | Runtime behavior contracts rebuild from generated or edited `.pyi` fixtures with the same recursion/reentrancy behavior, `@hold_gil` GIL policy, `@raises(...)` status projection, and generated wrapper policy code as source-backed builds | `runtime_behavior/test_runtime_recursion.py::test_recursive_native_runtime_calls`, `runtime_behavior/test_runtime_policies.py::test_pyi_runtime_policies_release_gil_and_project_native_errors`, `runtime_behavior/test_runtime_policies.py::test_compiled_runtime_policies_release_gil_and_project_native_errors`, `runtime_behavior/test_openmp_runtime.py::test_openmp_enabled_procedure_builds_with_explicit_gnu_flags` | @@ -84,13 +84,14 @@ recorded progression, not in the live ledger. | Immutable writable scalar, string, array, and derived-type arguments use policy-selected native temporaries and return replacements without mutating the original Python-visible object | `edit_pyi_contracts/test_policy_dispatch_contracts.py::test_immutable_scalar_string_array_and_derived_policies_return_replacements`, `edit_pyi_contracts/modified_contracts/fnative_call_examples_immutable_kinds/fnative_call_examples_f90.pyi` | | Contradictory owner/transfer/destruction triples fail before bridge generation with the declaration and rejected triple in the diagnostic | `edit_pyi_contracts/test_policy_dispatch_contracts.py::test_contradictory_ownership_contract_fails_before_bridge_generation`, `edit_pyi_contracts/invalid_contracts/contradictory_ownership/fnative_call_examples_f90.pyi` | | Explicit editable ownership triples produce native-owned borrowed module handles, wrapper-owned borrowed component handles, and wrapper-owned result handles with distinct release boundaries; borrowed wrapper children retain their owner and finalization runs exactly once | `edit_pyi_contracts/test_ownership_contracts.py`, `edit_pyi_contracts/modified_contracts/fallocatable_views_explicit_ownership/fallocatable_views_f90.pyi`, `edit_pyi_contracts/modified_contracts/fborrowed_finalizer_explicit_ownership/fborrowed_finalizer_f90.pyi` | -| Edited contracts remove classes, methods, constructors, class members, and individual overload candidates; they can also add a renamed `@bind` declaration and a new overload group over existing native specifics | `edit_pyi_contracts/test_surface_edit_contracts.py`, `edit_pyi_contracts/modified_contracts/foverloads_pruned_surface/`, `edit_pyi_contracts/modified_contracts/foverloads_without_constructor_member/`, `edit_pyi_contracts/modified_contracts/foverloads_added_bindings/` | +| Edited contracts remove classes, methods, constructors, class members, and individual overload candidates; they can also add a renamed `@bind` declaration and a new overload group over existing native specifics. Missing overload binds for private module or type-bound specifics preserve the native compiler accessibility error | `edit_pyi_contracts/test_surface_edit_contracts.py`, `edit_pyi_contracts/modified_contracts/foverloads_pruned_surface/`, `edit_pyi_contracts/modified_contracts/foverloads_without_constructor_member/`, `edit_pyi_contracts/modified_contracts/foverloads_added_bindings/`, `edit_pyi_contracts/modified_contracts/foverloads_private_specific_without_bind/`, `edit_pyi_contracts/modified_contracts/foverloads_private_type_bound_specifics_without_bind/` | | Edited `.pyi` contracts can remove a public function and hide declarations with `@private` or `private[...]` while preserving unaffected runtime behavior | `edit_pyi_contracts/test_visibility_contracts.py::test_editable_contract_removes_and_hides_public_declarations`, `edit_pyi_contracts/modified_contracts/module_variables_visibility/fmodule_vars_f90.pyi` | -| Native `Addr(Arg(...))` projection remains primitive-scalar-only while array descriptor arguments complete native-array handle policy before lowering, descriptor-argument bridge pass-through uses the completed plan, and unsupported pointer result ownership remains an explicit policy failure | `tests/semantics/policy/test_accessor_and_storage_policy.py::test_policy_completion_rejects_addr_projection_for_array_descriptor_handles`, `tests/semantics/policy/test_native_array_ownership.py::test_native_array_handle_policies_complete_before_ir_lowering`, `derived_types/test_pointers.py::test_pointer_array_handles_block_on_unsupported_result_owner_policy` | +| Native `Addr(Arg(...))` projection remains primitive-scalar-only while array descriptor arguments and results complete native-array handle policy before lowering; returned pointer arrays own persistent descriptor storage without claiming ownership of their targets | `tests/semantics/policy/test_accessor_and_storage_policy.py::test_policy_completion_rejects_addr_projection_for_array_descriptor_handles`, `tests/semantics/policy/test_native_array_ownership.py::test_native_array_handle_policies_complete_before_ir_lowering`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_pointer_result_uses_owned_pointer_descriptor_without_target_deallocation`, `derived_types/test_pointers.py::test_pointer_array_results_use_owned_descriptors_without_owning_targets` | | Native array handle bridge and binding architecture dispatches from completed descriptor-kind and handle-kind policy pairs | `tests/semantics/policy/test_native_array_ownership.py::test_native_array_handle_dispatcher_routes_completed_policy_to_named_method`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_generated_artifacts_follow_one_typed_action_vocabulary`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_plan_edits_fail_central_validation` | -| Module native-array handles lower from typed borrowed-handle plans to private operation wrappers for state, shape, pointer/descriptor handoff, allocatable `.to_numpy()`/`deallocate()`/`resize(shape)`, pointer `nullify()`, and policy-gated pointer shape-changing operations | `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_module_variables_own_borrowed_handle_plans_and_operation_sets`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_generated_artifacts_follow_one_typed_action_vocabulary` | -| Generated native array handle construction uses typed operation sets and the runtime factory adapts generated operations to the handle protocol, including shape changes and pointer-address handoff | `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_module_variables_own_borrowed_handle_plans_and_operation_sets`, `tests/runtime/handles/test_factories_and_lifecycle.py::test_generated_handle_factory_adapts_private_operations_to_runtime_protocol`, `tests/runtime/handles/test_factories_and_lifecycle.py::test_generated_handle_factory_splats_shape_operations_to_scalar_extents`, `tests/runtime/handles/test_factories_and_lifecycle.py::test_generated_handle_factory_rejects_invalid_descriptor_kind_and_handoff_result` | -| Owned allocatable results use planned persistent descriptor ownership, collect bridge-local data before transfer, expose owner-addressed operations, and release through the shared handle finalizer path; source and generated-`.pyi` modes retain compiled behavior coverage | `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_numeric_owned_result_is_collected_before_persistent_descriptor_move`, `tests/semantics/policy/test_native_array_ownership.py::test_hidden_allocatable_handle_output_completes_as_owned_result_before_lowering`, `tests/runtime/handles/test_factories_and_lifecycle.py::test_generated_owned_handle_factory_passes_persistent_owner_to_every_operation`, `module_state/test_allocatable_views.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles`, `module_state/test_allocatable_replacement.py::test_allocatable_inout_arrays_mutate_and_return_the_same_handle` | +| Module native-array handles lower from typed borrowed-handle plans to private operation wrappers for state, shape, pointer/descriptor handoff, allocatable `.to_numpy()`/`deallocate()`/`resize(shape)`, pointer `associate(other)`/`nullify()`, and policy-gated pointer shape-changing operations | `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_module_variables_own_borrowed_handle_plans_and_operation_sets`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_module_pointer_association_uses_standard_descriptor_assignment`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_generated_artifacts_follow_one_typed_action_vocabulary`, `derived_types/test_pointers.py::test_module_and_derived_pointer_handles_track_native_association` | +| Generated and caller-created native array handles use typed operation sets. Contract constructors start empty; read-only calls pack empty facts, while writable calls lazily attach a versioned native record and persistent compiler descriptor to the same handle. Caller-created pointers can copy an associated or unassociated descriptor state before that attachment. ABI-compatible, separately built extensions validate and reuse the record for allocatable and pointer mutation | `tests/runtime/test_contract_constructors.py`, `tests/semantics/policy/test_native_array_ownership.py::test_visible_descriptor_writeback_completes_caller_handle_construction_lifecycle`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_keeps_datatype_specific_state_under_argument_and_result_plans`, `module_state/test_allocatable_replacement.py::test_allocatable_inout_arrays_mutate_and_return_the_same_handle`, `module_state/test_allocatable_replacement.py::test_caller_created_allocatable_crosses_separately_built_extensions`, `derived_types/test_pointers.py::test_caller_created_pointer_handle_tracks_native_output_association`, `derived_types/test_pointers.py::test_caller_created_pointer_crosses_separately_built_extensions` | +| Owned allocatable results use planned persistent descriptor ownership, collect bridge-local data before transfer, expose owner-addressed operations, and release through the shared handle finalizer path; source and generated-`.pyi` modes retain compiled behavior coverage | `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_numeric_owned_result_defaults_to_assignment_then_move_alloc`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_maybe_unallocated_owned_result_uses_collector_without_local_assignment`, `tests/semantics/policy/test_native_array_ownership.py::test_hidden_allocatable_handle_output_completes_as_owned_result_before_lowering`, `tests/runtime/handles/test_factories_and_lifecycle.py::test_generated_owned_handle_factory_passes_persistent_owner_to_every_operation`, `arrays/test_array_results.py::test_maybe_unallocated_allocatable_result_preserves_absent_state`, `module_state/test_allocatable_views.py::test_allocatable_module_fields_and_results_expose_lifetime_safe_handles`, `module_state/test_allocatable_views.py::test_maybe_unallocated_direct_allocatable_results_preserve_unallocated_state`, `module_state/test_allocatable_replacement.py::test_allocatable_inout_arrays_mutate_and_return_the_same_handle` | +| Pointer-valued functions and nonoptional pointer-array `intent(out)` dummies use the owned-result descriptor path, preserve associated and unassociated states, and destroy only wrapper descriptor storage; optional outputs and `intent(inout)` descriptors remain Python-visible | `tests/semantics/conversion/fortran/test_fortran_conversion_procedures_and_interfaces.py::test_pointer_array_output_visibility_follows_intent_and_optional_presence`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_pointer_result_uses_owned_pointer_descriptor_without_target_deallocation`, `derived_types/test_pointers.py::test_module_and_derived_pointer_handles_track_native_association`, `derived_types/test_pointers.py::test_pointer_array_results_use_owned_descriptors_without_owning_targets` | | Native array descriptor arguments record required and optional-presence roles in the typed plan; direct bridge and binding lowering consume those roles while runtime helpers validate descriptor kind, dtype, rank, and shape metadata | `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_keeps_datatype_specific_state_under_argument_and_result_plans`, `tests/wrapper_codegen/test_phase7_native_array_handles.py::test_phase7_generated_artifacts_follow_one_typed_action_vocabulary`, `tests/runtime/handles/test_descriptor_abi.py` | | Concrete-rank numeric array arguments retain the NumPy extraction path and use native handle actuals without converting through `to_numpy()`; optional and assumed-rank behavior remains covered at the public wrapper boundary | `tests/wrapper_codegen/test_phase6a_array_buffers.py::test_required_array_buffer_dispatches_through_named_binding_and_bridge_methods`, `tests/runtime/handles/test_array_actual_abi.py`, `tests/wrapper/fortran/arrays/test_array_contracts.py::test_remaining_array_contracts_are_validated_before_fortran_calls`, `tests/wrapper/fortran/arrays/test_assumed_rank_arrays.py::test_assumed_rank_arguments_dispatch_to_runtime_rank`, `tests/wrapper/fortran/function_calls/test_optional_arguments.py::test_optional_arguments_drive_fortran_present_behavior` | | Runtime normal-array argument packing uses the generated Bind-C array tuple shape for ndarray inputs and native handle array-actual handoff: pointer address, optional runtime rank, optional item size, extents, and optional upper bounds plus unit strides; allocated/associated handles pack without calling `to_numpy()`, and unallocated/unassociated handles block before generated handoff | `tests/runtime/handles/test_array_actual_abi.py::test_array_actual_argument_abi_packer_uses_ndarray_data_pointer_and_shape_fields`, `tests/runtime/handles/test_array_actual_abi.py::test_array_actual_argument_abi_packer_uses_allocatable_native_array_actual_without_numpy_conversion`, `tests/runtime/handles/test_array_actual_abi.py::test_array_actual_argument_abi_packer_uses_pointer_native_array_actual_dtype_metadata`, `tests/runtime/handles/test_array_actual_abi.py::test_array_actual_argument_abi_packer_rejects_absent_handles_before_generated_handoff` | @@ -103,7 +104,7 @@ recorded progression, not in the live ledger. | Default and keyword construction, explicit bound construction, exact constructor/method overloads, borrowed-child finalization, extension inheritance, and scalar polymorphic input run through the direct wrapper-plan class path | `derived_types/test_constructors_and_finalizers.py`, `derived_types/test_phase9_bound_constructors.py`, `naming/test_phase9_class_overloads.py`, `derived_types/test_borrowed_finalizers.py`, `derived_types/test_inheritance.py`, `derived_types/test_derived_type_methods.py` | | Reduced derived procedure boundaries replay passing legacy/source behavior through direct plans for required and optional wrapper inputs, exact-type rejection, in-place and caller-supplied output identity, ordinary, `sequence`, and `bind(C)` exact typed native value copies, direct and hidden owned results, checked allocation, conversion cleanup, and exactly-once owner finalization | `derived_types/test_phase8_derived_plan.py::test_scalar_derived_objects_use_canonical_plan`, `derived_types/test_phase8_derived_plan.py::test_value_copy_and_optional_derived_inputs_match_source_oracle`, `derived_types/test_scalar_derived_actual_dummy_matrix.py::test_sequence_derived_value_uses_the_same_typed_opaque_call_path`, `derived_types/test_phase8_derived_plan.py::test_borrowed_child_retains_owner_and_finalizes_exactly_once`, `tests/wrapper_codegen/test_phase8_derived_types.py::test_mixed_derived_results_check_allocation_and_own_every_failure_path_before_scalar_conversion` | | Plain non-target module objects use live typed member proxies while `Aliased` objects use direct addresses; both retain the module, reject module replacement, and expose live scalar, fixed-string, ordinary-array, nested-derived, allocatable-handle, and pointer-handle fields with completed getter/setter and parent-owner behavior. Detached whole-object `Snapshot[T]` is removed | `derived_types/test_phase8_derived_plan.py::test_plain_module_derived_proxy_reads_and_writes_live_members`, `derived_types/test_phase8_derived_plan.py::test_aliased_module_derived_object_uses_direct_live_field_handles`, `derived_types/test_phase8_derived_plan.py::test_fixed_string_fields_use_canonical_plan`, `derived_types/test_phase8_derived_plan.py::test_pointer_field_descriptor_views_use_canonical_plan` | -| Every wrapper build uses completed policy and the canonical wrapper-plan generator; dependency isolation is structural, while unsupported derived shapes retain exact policy blockers | `derived_types/test_phase8_derived_plan.py::test_scalar_derived_objects_use_canonical_plan`, `callbacks/test_all_callback_shapes.py::test_immediate_callbacks_cover_all_supported_argument_shapes`, `derived_types/test_phase9_bound_constructors.py::test_bound_constructor_replaces_field_initialization_and_reuses_method_plan`, `tests/wrapper_codegen/test_phase0b_contracts.py::test_wrapper_build_pipeline_imports_canonical_generator`, `tests/wrapper_codegen/test_phase8_derived_types.py::test_unsupported_derived_shapes_fail_on_exact_completed_policy_blockers` | +| Every wrapper build uses completed policy and the canonical wrapper-plan generator; dependency isolation is structural, while unsupported derived shapes retain exact policy blockers | `derived_types/test_phase8_derived_plan.py::test_scalar_derived_objects_use_canonical_plan`, `callbacks/test_all_callback_shapes.py::test_immediate_callbacks_cover_all_supported_argument_shapes`, `derived_types/test_phase9_bound_constructors.py::test_bound_constructor_replaces_field_initialization_with_direct_pass_projection`, `tests/wrapper_codegen/test_phase0b_contracts.py::test_wrapper_build_pipeline_imports_canonical_generator`, `tests/wrapper_codegen/test_phase8_derived_types.py::test_unsupported_derived_shapes_fail_on_exact_completed_policy_blockers` | ## Build From Source diff --git a/tests/wrapper/fortran/_support.py b/tests/wrapper/fortran/_support.py index d49edd4ad..3df7cacff 100644 --- a/tests/wrapper/fortran/_support.py +++ b/tests/wrapper/fortran/_support.py @@ -6,6 +6,7 @@ import sys from functools import cache from pathlib import Path +from tempfile import TemporaryDirectory from types import ModuleType import numpy as np @@ -52,7 +53,10 @@ def _assert_fmath_examples(module): for name, args, expected in cases: public_name = name.lower() - actual = getattr(module, public_name)(*args) + actual, *replacements = getattr(module, public_name)(*args) + assert len(replacements) == len(args), public_name + for replacement, argument in zip(replacements, args, strict=True): + np.testing.assert_equal(replacement, argument, err_msg=public_name) if isinstance(expected, bool): assert bool(actual) is expected, public_name elif isinstance(expected, int): @@ -110,6 +114,39 @@ def _compiler() -> str: return compiler +@cache +def _supports_maybe_unallocated_function_result() -> bool: + """Check the GNU extension used to inspect an allocatable function result.""" + source = """ +module probe +contains + function make_value() result(value) + real, allocatable :: value(:) + end function make_value + subroutine collect(value) + real, allocatable :: value(:) + end subroutine collect + subroutine call_collect() + call collect(make_value()) + end subroutine call_collect +end module probe +""" + with TemporaryDirectory() as directory: + result = subprocess.run( + [_compiler(), "-x", "f95", "-c", "-o", str(Path(directory) / "probe.o"), "-"], + input=source, + capture_output=True, + text=True, + check=False, + ) + return result.returncode == 0 + + +def _require_maybe_unallocated_function_result_support() -> None: + if not _supports_maybe_unallocated_function_result(): + pytest.skip("gfortran rejects allocatable function results as allocatable helper arguments") + + def _compile_native_object(source: Path, native_dir: Path) -> Path: native_dir.mkdir(parents=True, exist_ok=True) native_source = native_dir / source.name @@ -382,8 +419,9 @@ def _assert_fmath_array_examples(module, *, suffix="", strided=False): array_args = [_array_argument(scalar_arg, size, strided=strided) for scalar_arg in scalar_args] result = _array_result(expected, size, strided=strided) - getattr(module, wrapped_name)(np.int32(size), *array_args, result) + replacement_size = getattr(module, wrapped_name)(np.int32(size), *array_args, result) + assert replacement_size == np.int32(size), wrapped_name _assert_array_result(wrapped_name, result, expected, size) @@ -450,6 +488,9 @@ def _assert_modern_class_examples(module): value.shift(np.float64(1.5), np.float64(-2.0)) assert value.x == np.float64(7.5) assert value.y == np.float64(6.0) + module.scale(value, np.float64(0.5)) + assert value.x == np.float64(3.75) + assert value.y == np.float64(3.0) assert hasattr(module, "vector_store") store = module.vector_store() diff --git a/tests/wrapper/fortran/arrays/contracts/farray_contracts_f90/farray_contracts_f90.pyi b/tests/wrapper/fortran/arrays/contracts/farray_contracts_f90/farray_contracts_f90.pyi index 3df3d2218..bd7739ef9 100644 --- a/tests/wrapper/fortran/arrays/contracts/farray_contracts_f90/farray_contracts_f90.pyi +++ b/tests/wrapper/fortran/arrays/contracts/farray_contracts_f90/farray_contracts_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Flat, Float64, Int32, Returns, native_call +from x2py.contracts import Addr, Arg, Flat, Float64, Int32, native_call @native_call([Addr(Arg(0)), Arg(1)]) def sum_assumed_size( @@ -22,79 +22,79 @@ def bump_inout( def fill_out( values: Float64[::] -) -> Returns["values", Float64[::]]: ... +) -> None: ... def shift1( values: Float64[::], out: Float64[::] -) -> Returns["out", Float64[::]]: ... +) -> None: ... def shift2( values: Float64[::, ::], out: Float64[::, ::] -) -> Returns["out", Float64[::, ::]]: ... +) -> None: ... def shift3( values: Float64[::, ::, ::], out: Float64[::, ::, ::] -) -> Returns["out", Float64[::, ::, ::]]: ... +) -> None: ... def shift4( values: Float64[::, ::, ::, ::], out: Float64[::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::]]: ... +) -> None: ... def shift5( values: Float64[::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::]]: ... +) -> None: ... def shift6( values: Float64[::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift7( values: Float64[::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift8( values: Float64[::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift9( values: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift10( values: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift11( values: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift12( values: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift13( values: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift14( values: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... def shift15( values: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::], out: Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::] -) -> Returns["out", Float64[::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::, ::]]: ... +) -> None: ... diff --git a/tests/wrapper/fortran/arrays/contracts/farray_results_f90/farray_results_f90.pyi b/tests/wrapper/fortran/arrays/contracts/farray_results_f90/farray_results_f90.pyi index 651440ab0..c377ed726 100644 --- a/tests/wrapper/fortran/arrays/contracts/farray_results_f90/farray_results_f90.pyi +++ b/tests/wrapper/fortran/arrays/contracts/farray_results_f90/farray_results_f90.pyi @@ -58,3 +58,14 @@ def zero_alloc_vector() -> Allocatable[Float64[:]]: ... def maybe_alloc_vector( n: Int32 ) -> Allocatable[Float64[:]]: ... + +@native_call([Addr(Arg(0))]) +def zero_alloc_matrix( + cols: Int32 +) -> Allocatable[Float64[:, :]]: ... + +@native_call([Addr(Arg(0)), Addr(Arg(1))]) +def maybe_alloc_matrix( + rows: Int32, + cols: Int32 +) -> Allocatable[Float64[:, :]]: ... diff --git a/tests/wrapper/fortran/arrays/contracts/multid_arrays/multid_arrays.pyi b/tests/wrapper/fortran/arrays/contracts/multid_arrays/multid_arrays.pyi index 5f84b5ba7..60ba854e3 100644 --- a/tests/wrapper/fortran/arrays/contracts/multid_arrays/multid_arrays.pyi +++ b/tests/wrapper/fortran/arrays/contracts/multid_arrays/multid_arrays.pyi @@ -1,19 +1,19 @@ -from x2py.contracts import Addr, Arg, Float64, Int32, Returns, native_call +from x2py.contracts import Addr, Arg, Float64, Int32, native_call def scale2_contiguous( a: Float64[:, :], out: Float64[:, :] -) -> Returns["out", Float64[:, :]]: ... +) -> None: ... def scale2_strided( a: Float64[::, ::], out: Float64[::, ::] -) -> Returns["out", Float64[::, ::]]: ... +) -> None: ... def checksum2_strided( a: Float64[::, ::], checksum: Float64[1] -) -> Returns["checksum", Float64[1]]: ... +) -> None: ... @native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2), Arg(3)]) def scale2_explicit( @@ -21,19 +21,19 @@ def scale2_explicit( cols: Int32, a: Float64[rows, cols], out: Float64[rows, cols] -) -> Returns["out", Float64[rows, cols]]: ... +) -> None: ... def shift3_contiguous( a: Float64[:, :, :], out: Float64[:, :, :] -) -> Returns["out", Float64[:, :, :]]: ... +) -> None: ... def shift3_strided( a: Float64[::, ::, ::], out: Float64[::, ::, ::] -) -> Returns["out", Float64[::, ::, ::]]: ... +) -> None: ... def checksum3_strided( a: Float64[::, ::, ::], checksum: Float64[1] -) -> Returns["checksum", Float64[1]]: ... +) -> None: ... diff --git a/tests/wrapper/fortran/arrays/test_array_contracts.py b/tests/wrapper/fortran/arrays/test_array_contracts.py index 17d191a8e..6b2ef983a 100644 --- a/tests/wrapper/fortran/arrays/test_array_contracts.py +++ b/tests/wrapper/fortran/arrays/test_array_contracts.py @@ -151,6 +151,17 @@ def test_remaining_array_contracts_are_validated_before_fortran_calls( with pytest.raises(TypeError, match="writeable"): module.sum_in(readonly) + fortran_storage = np.asfortranarray(np.array([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]], dtype=np.float64)) + c_storage = np.array([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]], dtype=np.float64, order="C") + assert module.sum_assumed_size(np.int32(fortran_storage.size), fortran_storage) == np.float64(66.0) + assert module.sum_assumed_size(np.int32(c_storage.size), c_storage) == np.float64(66.0) + assert module.sum_assumed_size(np.int32(3), fortran_storage) == np.float64(13.0) + assert module.sum_assumed_size(np.int32(3), c_storage) == np.float64(6.0) + + noncontiguous_flat = fortran_storage[:, ::2] + with pytest.raises(TypeError, match="contiguous"): + module.sum_assumed_size(np.int32(noncontiguous_flat.size), noncontiguous_flat) + lower_bound_values = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float64) assert module.scale_lower(np.int32(4), lower_bound_values) is None np.testing.assert_allclose(lower_bound_values, np.array([2.0, 4.0, 6.0, 8.0], dtype=np.float64)) @@ -180,7 +191,7 @@ def test_remaining_array_contracts_are_validated_before_fortran_calls( empty_rank4 = np.empty((0, 1, 1, 1), dtype=np.float64, order="F") empty_rank4_out = np.empty_like(empty_rank4, order="F") - assert module.shift4(empty_rank4, empty_rank4_out) is empty_rank4_out + assert module.shift4(empty_rank4, empty_rank4_out) is None assert empty_rank4_out.shape == empty_rank4.shape zero_stride_empty = as_strided( @@ -195,12 +206,12 @@ def test_remaining_array_contracts_are_validated_before_fortran_calls( ) assert zero_stride_empty.flags.f_contiguous assert zero_stride_empty_out.flags.f_contiguous - assert module.shift4(zero_stride_empty, zero_stride_empty_out) is zero_stride_empty_out + assert module.shift4(zero_stride_empty, zero_stride_empty_out) is None for rank in range(1, _MAX_WRAPPER_TEST_RANK + 1): shape = (2, *([1] * (rank - 1))) source = np.asfortranarray(np.arange(np.prod(shape), dtype=np.float64).reshape(shape, order="F")) out = np.empty(shape, dtype=np.float64, order="F") - assert getattr(module, f"shift{rank}")(source, out) is out + assert getattr(module, f"shift{rank}")(source, out) is None np.testing.assert_allclose(out, source + rank) diff --git a/tests/wrapper/fortran/arrays/test_array_results.py b/tests/wrapper/fortran/arrays/test_array_results.py index 764825f72..384f33f7c 100644 --- a/tests/wrapper/fortran/arrays/test_array_results.py +++ b/tests/wrapper/fortran/arrays/test_array_results.py @@ -1,21 +1,22 @@ """Array-valued function result runtime wrapper tests.""" import gc -from pathlib import Path import shutil +from pathlib import Path import numpy as np import pytest -from x2py import build_pyi_extension -from x2py.runtime.handles import AllocatableArray from tests.wrapper.fortran._support import ( _build_source_or_generated_pyi_and_import, _compile_native_object, _import_from_build_dir, + _require_maybe_unallocated_function_result_support, _sole_native_module, wrapper_source, ) +from x2py import build_pyi_extension +from x2py.runtime.handles import AllocatableArray ARRAY_RESULTS_F90_SOURCE = wrapper_source("farray_results_f90.f90") CONTRACT_FIXTURES = Path(__file__).parent / "contracts" @@ -88,6 +89,21 @@ def test_array_results_follow_data_buffer_and_descriptor_handle_contracts( allocated = module.maybe_alloc_vector(np.int32(3)) assert isinstance(allocated, AllocatableArray) np.testing.assert_allclose(allocated.to_numpy(), np.array([5.0, 10.0, 15.0], dtype=np.float64)) + + allocated_matrix = module.maybe_alloc_matrix(np.int32(2), np.int32(3)) + assert isinstance(allocated_matrix, AllocatableArray) + assert allocated_matrix.allocated is True + np.testing.assert_allclose( + allocated_matrix.to_numpy(), + np.array([[110.0, 120.0, 130.0], [210.0, 220.0, 230.0]], dtype=np.float64), + ) + + zero_alloc_matrix = module.zero_alloc_matrix(np.int32(2)) + assert isinstance(zero_alloc_matrix, AllocatableArray) + assert zero_alloc_matrix.allocated is True + assert zero_alloc_matrix.shape == (0, 2) + assert zero_alloc_matrix.to_numpy().shape == (0, 2) + del module gc.collect() np.testing.assert_allclose(matrix, np.array([[12.0, 13.0, 14.0], [22.0, 23.0, 24.0]], dtype=np.float64)) @@ -142,7 +158,7 @@ def test_owned_allocatable_results_preserve_handle_state(tmp_path: Path): contract_package = tmp_path / "allocatable_results" shutil.copytree(CONTRACT_FIXTURES / "farray_results_f90", contract_package) (contract_package / "__init__.pyi").write_text( - "from .farray_results_f90 import maybe_alloc_vector, zero_alloc_vector\n", + "from .farray_results_f90 import maybe_alloc_matrix, maybe_alloc_vector, zero_alloc_matrix, zero_alloc_vector\n", encoding="utf-8", ) result = build_pyi_extension( @@ -165,5 +181,76 @@ def test_owned_allocatable_results_preserve_handle_state(tmp_path: Path): assert zero_sized.shape == (0,) assert zero_sized.to_numpy().shape == (0,) + allocated_matrix = module.maybe_alloc_matrix(np.int32(2), np.int32(3)) + assert isinstance(allocated_matrix, AllocatableArray) + assert allocated_matrix.allocated is True + np.testing.assert_allclose( + allocated_matrix.to_numpy(), + np.array([[110.0, 120.0, 130.0], [210.0, 220.0, 230.0]]), + ) + + zero_sized_matrix = module.zero_alloc_matrix(np.int32(2)) + assert isinstance(zero_sized_matrix, AllocatableArray) + assert zero_sized_matrix.allocated is True + assert zero_sized_matrix.shape == (0, 2) + assert zero_sized_matrix.to_numpy().shape == (0, 2) + allocated.close() zero_sized.close() + allocated_matrix.close() + zero_sized_matrix.close() + + +def test_maybe_unallocated_allocatable_result_preserves_absent_state(tmp_path: Path): + """Use an edited contract for direct allocatable results that may be unallocated.""" + _require_maybe_unallocated_function_result_support() + native_object = _compile_native_object(ARRAY_RESULTS_F90_SOURCE, tmp_path / "native") + contract_package = tmp_path / "maybe_unallocated_results" + shutil.copytree(CONTRACT_FIXTURES / "farray_results_f90", contract_package) + pyi_path = contract_package / "farray_results_f90.pyi" + contract_text = pyi_path.read_text(encoding="utf-8") + contract_text = contract_text.replace( + "Addr, Allocatable, Arg, Float64, Int32, native_call", + "Addr, Allocatable, Annotated, Arg, Float64, Int32, MaybeUnallocated, native_call", + 1, + ) + contract_text = contract_text.replace( + "def maybe_alloc_vector(\n n: Int32\n) -> Allocatable[Float64[:]]: ...", + "def maybe_alloc_vector(\n n: Int32\n) -> Annotated[Allocatable[Float64[:]], MaybeUnallocated]: ...", + 1, + ) + contract_text = contract_text.replace( + "def maybe_alloc_matrix(\n rows: Int32,\n cols: Int32\n) -> Allocatable[Float64[:, :]]: ...", + "def maybe_alloc_matrix(\n" + " rows: Int32,\n" + " cols: Int32\n" + ") -> Annotated[Allocatable[Float64[:, :]], MaybeUnallocated]: ...", + 1, + ) + pyi_path.write_text(contract_text, encoding="utf-8") + (contract_package / "__init__.pyi").write_text( + "from .farray_results_f90 import maybe_alloc_matrix, maybe_alloc_vector\n", + encoding="utf-8", + ) + result = build_pyi_extension( + contract_package / "__init__.pyi", + native_objects=[native_object], + native_include_dirs=[native_object.parent], + output_dir=tmp_path / "build", + ) + imported = _import_from_build_dir(result.module_name, result.output_dir) + module = imported if hasattr(imported, "maybe_alloc_vector") else _sole_native_module(imported) + + allocated_vector = module.maybe_alloc_vector(np.int32(3)) + assert isinstance(allocated_vector, AllocatableArray) + assert allocated_vector.allocated is True + np.testing.assert_allclose(allocated_vector.to_numpy(), np.array([5.0, 10.0, 15.0])) + + unallocated_matrix = module.maybe_alloc_matrix(np.int32(0), np.int32(3)) + assert isinstance(unallocated_matrix, AllocatableArray) + assert unallocated_matrix.allocated is False + assert unallocated_matrix.shape is None + assert unallocated_matrix.to_numpy() is None + + allocated_vector.close() + unallocated_matrix.close() diff --git a/tests/wrapper/fortran/arrays/test_multidimensional_arrays.py b/tests/wrapper/fortran/arrays/test_multidimensional_arrays.py index 602177d4a..051ecf48f 100644 --- a/tests/wrapper/fortran/arrays/test_multidimensional_arrays.py +++ b/tests/wrapper/fortran/arrays/test_multidimensional_arrays.py @@ -279,21 +279,21 @@ def test_dense_strided_and_projected_arrays_use_canonical_plan(tmp_path: Path): dense = _matrix() dense_out = np.zeros_like(dense, order="F") - assert module.scale2_contiguous(dense, dense_out) is dense_out + assert module.scale2_contiguous(dense, dense_out) is None np.testing.assert_allclose(dense_out, 2.0 * dense) explicit_out = np.zeros_like(dense, order="F") - assert module.scale2_explicit(np.int32(4), np.int32(3), dense, explicit_out) is explicit_out + assert module.scale2_explicit(np.int32(4), np.int32(3), dense, explicit_out) is None np.testing.assert_allclose(explicit_out, 4.0 * dense) strided = _strided_matrix() strided_out = _strided_matrix_output(strided.shape) - assert module.scale2_strided(strided, strided_out) is strided_out + assert module.scale2_strided(strided, strided_out) is None np.testing.assert_allclose(strided_out, 3.0 * strided) empty = _strided_matrix(0, 3) empty_out = _strided_matrix_output(empty.shape) - assert module.scale2_strided(empty, empty_out) is empty_out + assert module.scale2_strided(empty, empty_out) is None assert empty_out.shape == (0, 3) dense = _matrix() diff --git a/tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py b/tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py index 0a84925f7..4d33f9599 100644 --- a/tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py +++ b/tests/wrapper/fortran/build_from_pyi/test_pyi_wrapper_builds.py @@ -607,13 +607,16 @@ def test_mutable_module_variable_default_initializes_native_storage(tmp_path: Pa root = _generate_pyi(MODULE_VARIABLE_SOURCE, tmp_path / "contracts", MODULE_VARIABLES_GENERATED) leaf = root.parent / "fmodule_vars_f90.pyi" leaf.write_text( - leaf.read_text(encoding="utf-8").replace("counter: Int32", "counter: Int32 = 41"), + leaf.read_text(encoding="utf-8") + .replace("counter: Int32", "counter: Int32 = 41") + .replace("scale: Float64", "scale: Float64 = 2.5"), encoding="utf-8", ) root.write_text( "\n".join( [ "from .fmodule_vars_f90 import counter", + "from .fmodule_vars_f90 import scale", "from .fmodule_vars_f90 import summarize", "", ] @@ -625,9 +628,12 @@ def test_mutable_module_variable_default_initializes_native_storage(tmp_path: Pa module, _payload = _build_pyi_cli(root, native_object, tmp_path / "pyi_build") assert module.counter == np.int32(41) + assert module.scale == np.float64(2.5) assert module.summarize() == np.int32(53) module.counter = np.int32(5) + module.scale = np.float64(1.25) assert module.summarize() == np.int32(17) + assert module.scale == np.float64(1.25) def test_entry_rejects_colliding_wildcard_exports(tmp_path: Path): diff --git a/tests/wrapper/fortran/build_from_source/contracts/fdefault_output/__init__.pyi b/tests/wrapper/fortran/build_from_source/contracts/fdefault_output/__init__.pyi index 26ce38723..389a712cc 100644 --- a/tests/wrapper/fortran/build_from_source/contracts/fdefault_output/__init__.pyi +++ b/tests/wrapper/fortran/build_from_source/contracts/fdefault_output/__init__.pyi @@ -1,7 +1,7 @@ -from x2py.contracts import Addr, Arg, Int32, external, native_call +from x2py.contracts import Addr, Arg, Int32, Returns, external, native_call @external @native_call([Addr(Arg(0))]) def add_one( value: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["value", Int32]]: ... diff --git a/tests/wrapper/fortran/build_from_source/contracts/fmath/__init__.pyi b/tests/wrapper/fortran/build_from_source/contracts/fmath/__init__.pyi index f207260ef..200fd18dd 100644 --- a/tests/wrapper/fortran/build_from_source/contracts/fmath/__init__.pyi +++ b/tests/wrapper/fortran/build_from_source/contracts/fmath/__init__.pyi @@ -1,60 +1,60 @@ -from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, bind, external, native_call +from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, Returns, bind, external, native_call @bind("SQUARE_R4") @external @native_call([Addr(Arg(0))]) def square_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SQUARE_R8") @external @native_call([Addr(Arg(0))]) def square_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("SQUARE_I4") @external @native_call([Addr(Arg(0))]) def square_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("SQUARE_C4") @external @native_call([Addr(Arg(0))]) def square_c4( Z: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["Z", Complex64]]: ... @bind("SQUARE_C8") @external @native_call([Addr(Arg(0))]) def square_c8( Z: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["Z", Complex128]]: ... @bind("CUBE_R4") @external @native_call([Addr(Arg(0))]) def cube_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("CUBE_R8") @external @native_call([Addr(Arg(0))]) def cube_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("CUBE_I4") @external @native_call([Addr(Arg(0))]) def cube_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("ADD_R4") @external @@ -62,7 +62,7 @@ def cube_i4( def add_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("ADD_R8") @external @@ -70,7 +70,7 @@ def add_r4( def add_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("ADD_I4") @external @@ -78,7 +78,7 @@ def add_r8( def add_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("ADD_C4") @external @@ -86,7 +86,7 @@ def add_i4( def add_c4( X: Complex64, Y: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["X", Complex64], Returns["Y", Complex64]]: ... @bind("ADD_C8") @external @@ -94,7 +94,7 @@ def add_c4( def add_c8( X: Complex128, Y: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["X", Complex128], Returns["Y", Complex128]]: ... @bind("SUB_R4") @external @@ -102,7 +102,7 @@ def add_c8( def sub_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("SUB_R8") @external @@ -110,7 +110,7 @@ def sub_r4( def sub_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("SUB_I4") @external @@ -118,7 +118,7 @@ def sub_r8( def sub_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MUL_R4") @external @@ -126,7 +126,7 @@ def sub_i4( def mul_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MUL_R8") @external @@ -134,7 +134,7 @@ def mul_r4( def mul_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MUL_I4") @external @@ -142,7 +142,7 @@ def mul_r8( def mul_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("DIV_R4") @external @@ -150,7 +150,7 @@ def mul_i4( def div_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("DIV_R8") @external @@ -158,7 +158,7 @@ def div_r4( def div_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("POW_R4") @external @@ -166,7 +166,7 @@ def div_r8( def pow_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("POW_R8") @external @@ -174,133 +174,133 @@ def pow_r4( def pow_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("ABS_R4") @external @native_call([Addr(Arg(0))]) def abs_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ABS_R8") @external @native_call([Addr(Arg(0))]) def abs_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ABS_I4") @external @native_call([Addr(Arg(0))]) def abs_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("NEG_R4") @external @native_call([Addr(Arg(0))]) def neg_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("NEG_R8") @external @native_call([Addr(Arg(0))]) def neg_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("NEG_I4") @external @native_call([Addr(Arg(0))]) def neg_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("SIN_R4") @external @native_call([Addr(Arg(0))]) def sin_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SIN_R8") @external @native_call([Addr(Arg(0))]) def sin_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("COS_R4") @external @native_call([Addr(Arg(0))]) def cos_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("COS_R8") @external @native_call([Addr(Arg(0))]) def cos_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("TAN_R4") @external @native_call([Addr(Arg(0))]) def tan_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("TAN_R8") @external @native_call([Addr(Arg(0))]) def tan_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ASIN_R4") @external @native_call([Addr(Arg(0))]) def asin_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ASIN_R8") @external @native_call([Addr(Arg(0))]) def asin_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ACOS_R4") @external @native_call([Addr(Arg(0))]) def acos_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ACOS_R8") @external @native_call([Addr(Arg(0))]) def acos_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ATAN_R4") @external @native_call([Addr(Arg(0))]) def atan_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ATAN_R8") @external @native_call([Addr(Arg(0))]) def atan_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ATAN2_R4") @external @@ -308,7 +308,7 @@ def atan_r8( def atan2_r4( Y: Float32, X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["Y", Float32], Returns["X", Float32]]: ... @bind("ATAN2_R8") @external @@ -316,63 +316,63 @@ def atan2_r4( def atan2_r8( Y: Float64, X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["Y", Float64], Returns["X", Float64]]: ... @bind("EXP_R4") @external @native_call([Addr(Arg(0))]) def exp_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("EXP_R8") @external @native_call([Addr(Arg(0))]) def exp_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("LOG_R4") @external @native_call([Addr(Arg(0))]) def log_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("LOG_R8") @external @native_call([Addr(Arg(0))]) def log_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("LOG10_R4") @external @native_call([Addr(Arg(0))]) def log10_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("LOG10_R8") @external @native_call([Addr(Arg(0))]) def log10_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("SQRT_R4") @external @native_call([Addr(Arg(0))]) def sqrt_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SQRT_R8") @external @native_call([Addr(Arg(0))]) def sqrt_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("HYPOT_R4") @external @@ -380,7 +380,7 @@ def sqrt_r8( def hypot_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("HYPOT_R8") @external @@ -388,7 +388,7 @@ def hypot_r4( def hypot_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MIN_R4") @external @@ -396,7 +396,7 @@ def hypot_r8( def min_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MIN_R8") @external @@ -404,7 +404,7 @@ def min_r4( def min_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MIN_I4") @external @@ -412,7 +412,7 @@ def min_r8( def min_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MAX_R4") @external @@ -420,7 +420,7 @@ def min_i4( def max_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MAX_R8") @external @@ -428,7 +428,7 @@ def max_r4( def max_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MAX_I4") @external @@ -436,7 +436,7 @@ def max_r8( def max_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("SIGN_R4") @external @@ -444,7 +444,7 @@ def max_i4( def sign_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("SIGN_R8") @external @@ -452,7 +452,7 @@ def sign_r4( def sign_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MOD_I4") @external @@ -460,7 +460,7 @@ def sign_r8( def mod_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MOD_R4") @external @@ -468,7 +468,7 @@ def mod_i4( def mod_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MOD_R8") @external @@ -476,35 +476,35 @@ def mod_r4( def mod_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("DEG2RAD_R4") @external @native_call([Addr(Arg(0))]) def deg2rad_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("DEG2RAD_R8") @external @native_call([Addr(Arg(0))]) def deg2rad_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("RAD2DEG_R4") @external @native_call([Addr(Arg(0))]) def rad2deg_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("RAD2DEG_R8") @external @native_call([Addr(Arg(0))]) def rad2deg_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("DIST2_R4") @external @@ -512,7 +512,7 @@ def rad2deg_r8( def dist2_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("DIST2_R8") @external @@ -520,7 +520,7 @@ def dist2_r4( def dist2_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("DOT2_R4") @external @@ -530,7 +530,7 @@ def dot2_r4( X2: Float32, Y1: Float32, Y2: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X1", Float32], Returns["X2", Float32], Returns["Y1", Float32], Returns["Y2", Float32]]: ... @bind("DOT2_R8") @external @@ -540,7 +540,7 @@ def dot2_r8( X2: Float64, Y1: Float64, Y2: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X1", Float64], Returns["X2", Float64], Returns["Y1", Float64], Returns["Y2", Float64]]: ... @bind("DOT3_R4") @external @@ -552,7 +552,7 @@ def dot3_r4( Y1: Float32, Y2: Float32, Y3: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X1", Float32], Returns["X2", Float32], Returns["X3", Float32], Returns["Y1", Float32], Returns["Y2", Float32], Returns["Y3", Float32]]: ... @bind("DOT3_R8") @external @@ -564,81 +564,81 @@ def dot3_r8( Y1: Float64, Y2: Float64, Y3: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X1", Float64], Returns["X2", Float64], Returns["X3", Float64], Returns["Y1", Float64], Returns["Y2", Float64], Returns["Y3", Float64]]: ... @bind("CONJ_C4") @external @native_call([Addr(Arg(0))]) def conj_c4( Z: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["Z", Complex64]]: ... @bind("CONJ_C8") @external @native_call([Addr(Arg(0))]) def conj_c8( Z: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["Z", Complex128]]: ... @bind("REAL_C4") @external @native_call([Addr(Arg(0))]) def real_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("REAL_C8") @external @native_call([Addr(Arg(0))]) def real_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("AIMAG_C4") @external @native_call([Addr(Arg(0))]) def aimag_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("AIMAG_C8") @external @native_call([Addr(Arg(0))]) def aimag_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("ABS_C4") @external @native_call([Addr(Arg(0))]) def abs_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("ABS_C8") @external @native_call([Addr(Arg(0))]) def abs_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("IS_POSITIVE_R4") @external @native_call([Addr(Arg(0))]) def is_positive_r4( X: Float32 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Float32]]: ... @bind("IS_POSITIVE_R8") @external @native_call([Addr(Arg(0))]) def is_positive_r8( X: Float64 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Float64]]: ... @bind("IS_EVEN_I4") @external @native_call([Addr(Arg(0))]) def is_even_i4( X: Int32 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Int32]]: ... diff --git a/tests/wrapper/fortran/build_from_source/test_compiler_verbose.py b/tests/wrapper/fortran/build_from_source/test_compiler_verbose.py index 9b5012d0a..90b4a620b 100644 --- a/tests/wrapper/fortran/build_from_source/test_compiler_verbose.py +++ b/tests/wrapper/fortran/build_from_source/test_compiler_verbose.py @@ -2,6 +2,7 @@ import sys from pathlib import Path +import x2py.compiling.compilers as compiler_module from x2py.compiling.objects import ObjectFile from x2py.compiling.compilers import Compiler from x2py.compiling.compiler_profiles import available_compilers, vendors @@ -89,6 +90,79 @@ def test_python_sysconfig_profile_flags_do_not_override_wrapper_profile(monkeypa assert "-g" not in command +def test_supported_optional_profile_flags_are_used_when_executing(monkeypatch, tmp_path: Path): + compiler = Compiler("GNU") + monkeypatch.setattr(compiler, "_executable", lambda _language, _tools: "gfortran") + monkeypatch.setattr(compiler, "_supports_optional_flag", lambda _executable, flag: flag == "-ftrampoline-impl=heap") + monkeypatch.setattr(Compiler, "run_command", staticmethod(lambda command, _verbose=False: tuple(command))) + object_file = ObjectFile( + source=tmp_path / "bridge.f90", + object_path=tmp_path / "bridge.o", + language="fortran", + ) + + compiler.compile_object(object_file) + + assert "-ftrampoline-impl=heap" in compiler.command_log[0] + + +def test_unsupported_optional_profile_flags_are_omitted(monkeypatch, tmp_path: Path): + compiler = Compiler("GNU") + monkeypatch.setattr(compiler, "_executable", lambda _language, _tools: "gfortran") + monkeypatch.setattr(compiler, "_supports_optional_flag", lambda _executable, _flag: False) + monkeypatch.setattr(Compiler, "run_command", staticmethod(lambda command, _verbose=False: tuple(command))) + object_file = ObjectFile( + source=tmp_path / "bridge.f90", + object_path=tmp_path / "bridge.o", + language="fortran", + ) + + compiler.compile_object(object_file) + + assert "-ftrampoline-impl=heap" not in compiler.command_log[0] + + +def test_optional_profile_flag_probe_reads_the_selected_compiler_help(monkeypatch): + calls = [] + + def completed(command, **kwargs): + calls.append((command, kwargs)) + return type( + "Completed", + (), + { + "returncode": 0, + "stdout": " -ftrampoline-impl= stack\n", + "stderr": "", + }, + )() + + Compiler._supports_optional_flag.cache_clear() + monkeypatch.setattr(compiler_module.subprocess, "run", completed) + + assert Compiler._supports_optional_flag("gfortran-test", "-ftrampoline-impl=heap") is True + assert calls == [ + ( + ("gfortran-test", "-Q", "--help=common"), + { + "capture_output": True, + "text": True, + "check": False, + }, + ) + ] + + +def test_optional_profile_flag_probe_fails_closed_when_the_compiler_cannot_start(monkeypatch): + def unavailable(*_args, **_kwargs): + raise OSError("missing compiler") + + Compiler._supports_optional_flag.cache_clear() + monkeypatch.setattr(compiler_module.subprocess, "run", unavailable) + + assert Compiler._supports_optional_flag("missing-gfortran", "-ftrampoline-impl=heap") is False + + def test_link_keeps_the_declared_object_and_link_argument_order(monkeypatch, tmp_path: Path): compiler = Compiler("GNU", execute_commands=False) monkeypatch.setattr(compiler, "_executable", lambda _language, _tools: "gfortran") diff --git a/tests/wrapper/fortran/callbacks/contracts/fcallback_all_f90/fcallback_all_f90.pyi b/tests/wrapper/fortran/callbacks/contracts/fcallback_all_f90/fcallback_all_f90.pyi index 612630adc..ba374014d 100644 --- a/tests/wrapper/fortran/callbacks/contracts/fcallback_all_f90/fcallback_all_f90.pyi +++ b/tests/wrapper/fortran/callbacks/contracts/fcallback_all_f90/fcallback_all_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Float64, Int32, Return, Returns, String, Value, native_call, prototype +from x2py.contracts import Addr, Arg, Float64, Int32, Return, Returns, String, native_call, prototype class point_t: def __init__( @@ -13,19 +13,19 @@ class point_t: @prototype def value_callback( - value: Value(Int32) + value: Int32 ) -> Int32: ... @prototype def scalar_storage_callback( - value: Float64, - output: Float64, - missing: Float64 + value: Addr(Float64), + output: Addr(Float64), + missing: Addr(Float64) ) -> None: ... @prototype def array_storage_callback( - count: Int32, + count: Addr(Int32), values: Float64[count], output: Float64[count] ) -> None: ... @@ -48,12 +48,12 @@ def apply_value_callback( value: Int32 ) -> Int32: ... -@native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2)), Return('output', 0)]) +@native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2)), Return('output', 2)]) def apply_scalar_storage_callback( callback: scalar_storage_callback, value: Float64, missing: Float64 -) -> Float64: ... +) -> tuple[Returns["value", Float64], Returns["missing", Float64], Float64]: ... @native_call([Arg(0), Addr(Arg(1)), Arg(2), Arg(3)]) def apply_array_storage_callback( @@ -61,7 +61,7 @@ def apply_array_storage_callback( count: Int32, values: Float64[count], output: Float64[count] -) -> Returns["output", Float64[count]]: ... +) -> None: ... @native_call([Arg(0), Arg(1), Return('write_label', 1)]) def apply_string_storage_callback( @@ -69,8 +69,8 @@ def apply_string_storage_callback( update_label: String[8] ) -> tuple[Returns["update_label", String[8]], String[8]]: ... -@native_call([Arg(0), Arg(1), Return('output', 0)]) def apply_point_callback( callback: point_callback, - value: point_t -) -> point_t: ... + value: point_t, + output: point_t +) -> None: ... diff --git a/tests/wrapper/fortran/callbacks/contracts/fcallback_array_f90/fcallback_array_f90.pyi b/tests/wrapper/fortran/callbacks/contracts/fcallback_array_f90/fcallback_array_f90.pyi index dad878c74..e84c5ac80 100644 --- a/tests/wrapper/fortran/callbacks/contracts/fcallback_array_f90/fcallback_array_f90.pyi +++ b/tests/wrapper/fortran/callbacks/contracts/fcallback_array_f90/fcallback_array_f90.pyi @@ -1,14 +1,14 @@ -from x2py.contracts import Addr, Arg, Float64, Int32, Returns, native_call, prototype +from x2py.contracts import Addr, Arg, Float64, Int32, native_call, prototype @prototype def reduce_callback( - count: Int32, + count: Addr(Int32), values: Float64[count] ) -> Float64: ... @prototype def transform_callback( - count: Int32, + count: Addr(Int32), values: Float64[count] ) -> Float64[count]: ... @@ -25,4 +25,4 @@ def apply_transform( count: Int32, values: Float64[count], output: Float64[count] -) -> Returns["output", Float64[count]]: ... +) -> None: ... diff --git a/tests/wrapper/fortran/callbacks/contracts/fcallback_derived_f90/fcallback_derived_f90.pyi b/tests/wrapper/fortran/callbacks/contracts/fcallback_derived_f90/fcallback_derived_f90.pyi index 86124cf2a..566665cef 100644 --- a/tests/wrapper/fortran/callbacks/contracts/fcallback_derived_f90/fcallback_derived_f90.pyi +++ b/tests/wrapper/fortran/callbacks/contracts/fcallback_derived_f90/fcallback_derived_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Arg, Float64, Return, native_call, prototype +from x2py.contracts import Float64, prototype class point_t: def __init__( @@ -16,8 +16,8 @@ def point_callback( value: point_t ) -> point_t: ... -@native_call([Arg(0), Arg(1), Return('output', 0)]) def apply_point( callback: point_callback, - value: point_t -) -> point_t: ... + value: point_t, + output: point_t +) -> None: ... diff --git a/tests/wrapper/fortran/callbacks/contracts/fcallback_scalar_f90/fcallback_scalar_f90.pyi b/tests/wrapper/fortran/callbacks/contracts/fcallback_scalar_f90/fcallback_scalar_f90.pyi index 522d6b7aa..07b89ba71 100644 --- a/tests/wrapper/fortran/callbacks/contracts/fcallback_scalar_f90/fcallback_scalar_f90.pyi +++ b/tests/wrapper/fortran/callbacks/contracts/fcallback_scalar_f90/fcallback_scalar_f90.pyi @@ -2,17 +2,17 @@ from x2py.contracts import Addr, Arg, Float64, native_call, prototype @prototype def scalar_callback( - value: Float64 + value: Addr(Float64) ) -> Float64: ... @prototype def notify_callback( - value: Float64 + value: Addr(Float64) ) -> None: ... @prototype def callback( - value: Float64 + value: Addr(Float64) ) -> Float64: ... @native_call([Arg(0), Addr(Arg(1))]) diff --git a/tests/wrapper/fortran/callbacks/test_all_callback_shapes.py b/tests/wrapper/fortran/callbacks/test_all_callback_shapes.py index 61acfedf0..649f8931c 100644 --- a/tests/wrapper/fortran/callbacks/test_all_callback_shapes.py +++ b/tests/wrapper/fortran/callbacks/test_all_callback_shapes.py @@ -43,7 +43,7 @@ def array_callback(count, input_values, output_values): output_values[:count] = input_values[:count] + 1.5 result = module.apply_array_storage_callback(array_callback, np.int32(3), values, output) - assert result is output + assert result is None np.testing.assert_allclose(output, np.array([2.5, 3.5, 4.5], dtype=np.float64)) def string_callback(read_label, write_label, update_label): @@ -61,10 +61,14 @@ def string_callback(read_label, write_label, update_label): assert module.apply_string_storage_callback(string_callback, "OLD ") == ("UPDATED!", "WRITTEN!") point = module.point_t(x=np.float64(2.0), y=np.float64(5.0)) - shifted = module.apply_point_callback( - lambda value: module.point_t(x=value.x + 1.0, y=value.y * 2.0), - point, + shifted = module.point_t() + assert ( + module.apply_point_callback( + lambda value: module.point_t(x=value.x + 1.0, y=value.y * 2.0), + point, + shifted, + ) + is None ) - assert isinstance(shifted, module.point_t) assert shifted.x == np.float64(3.0) assert shifted.y == np.float64(10.0) diff --git a/tests/wrapper/fortran/callbacks/test_array_callbacks.py b/tests/wrapper/fortran/callbacks/test_array_callbacks.py index 5b63b27c6..0f5ea2171 100644 --- a/tests/wrapper/fortran/callbacks/test_array_callbacks.py +++ b/tests/wrapper/fortran/callbacks/test_array_callbacks.py @@ -35,5 +35,5 @@ def test_immediate_dummy_procedure_converts_array_arguments_and_results( values, transformed, ) - assert result is transformed + assert result is None np.testing.assert_array_equal(transformed, np.array([2.0, 4.0, 6.0], dtype=np.float64)) diff --git a/tests/wrapper/fortran/callbacks/test_derived_callbacks.py b/tests/wrapper/fortran/callbacks/test_derived_callbacks.py index 7b7ab12c7..122380150 100644 --- a/tests/wrapper/fortran/callbacks/test_derived_callbacks.py +++ b/tests/wrapper/fortran/callbacks/test_derived_callbacks.py @@ -26,11 +26,13 @@ def test_immediate_dummy_procedure_converts_derived_arguments_and_results( pyi_parity_build_mode, ) point = module.point_t(x=np.float64(2.0), y=np.float64(5.0)) + output = module.point_t() result = module.apply_point( lambda value: module.point_t(x=value.x + 1.0, y=value.y * 2.0), point, + output, ) - assert isinstance(result, module.point_t) - assert result.x == np.float64(3.0) - assert result.y == np.float64(10.0) + assert result is None + assert output.x == np.float64(3.0) + assert output.y == np.float64(10.0) diff --git a/tests/wrapper/fortran/derived_types/contracts/fbound_constructor_phase9/fclasses_f90.pyi b/tests/wrapper/fortran/derived_types/contracts/fbound_constructor_phase9/fclasses_f90.pyi index 8bab34c05..0d4bea6c3 100644 --- a/tests/wrapper/fortran/derived_types/contracts/fbound_constructor_phase9/fclasses_f90.pyi +++ b/tests/wrapper/fortran/derived_types/contracts/fbound_constructor_phase9/fclasses_f90.pyi @@ -2,16 +2,13 @@ from x2py.contracts import Addr, Arg, Float64, Pass, bind, native_call class vector: - @bind("shift") + @bind("shift_vector") + @native_call([Addr(Arg(0)), Pass(), Addr(Arg(1))]) def __init__(self, dx: Float64, dy: Float64) -> None: ... x: Float64 y: Float64 - @bind("shift_vector") - @native_call([Addr(Arg(0)), Pass(), Addr(Arg(1))]) - def shift(self, dx: Float64, dy: Float64) -> None: ... - @native_call([Addr(Arg(0)), Arg(1), Addr(Arg(2))]) def shift_vector( diff --git a/tests/wrapper/fortran/derived_types/contracts/fclasses_f90/fclasses_f90.pyi b/tests/wrapper/fortran/derived_types/contracts/fclasses_f90/fclasses_f90.pyi index 1aa56ee3b..b331d0cd2 100644 --- a/tests/wrapper/fortran/derived_types/contracts/fclasses_f90/fclasses_f90.pyi +++ b/tests/wrapper/fortran/derived_types/contracts/fclasses_f90/fclasses_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Allocatable, Arg, Float64, Int64, Pass, bind, native_call +from x2py.contracts import Addr, Allocatable, Annotated, Arg, Float64, Int64, Pass, Polymorphic, bind, native_call class vector: def __init__( @@ -66,41 +66,41 @@ class vector_store: @native_call([Arg(0), Addr(Arg(1))]) def scale( - self: vector, + self: Annotated[vector, Polymorphic], factor: Float64 ) -> None: ... @native_call([Addr(Arg(0)), Arg(1), Addr(Arg(2))]) def shift_vector( dx: Float64, - owner: vector, + owner: Annotated[vector, Polymorphic], dy: Float64 ) -> None: ... def magnitude( - self: vector + self: Annotated[vector, Polymorphic] ) -> Float64: ... @native_call([Arg(0), Addr(Arg(1))]) def allocate_values( - self: vector_store, + self: Annotated[vector_store, Polymorphic], n: Int64 ) -> None: ... def set_values( - self: vector_store, + self: Annotated[vector_store, Polymorphic], source: Float64[::] ) -> None: ... @native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2))]) def allocate_matrix( - self: vector_store, + self: Annotated[vector_store, Polymorphic], rows: Int64, cols: Int64 ) -> None: ... def set_matrix( - self: vector_store, + self: Annotated[vector_store, Polymorphic], source: Float64[::, ::] ) -> None: ... diff --git a/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_f90/fderived_boundary_f90.pyi b/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_f90/fderived_boundary_f90.pyi index 55381ca3d..7c3bf696c 100644 --- a/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_f90/fderived_boundary_f90.pyi +++ b/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_f90/fderived_boundary_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Float64, Return, native_call +from x2py.contracts import Addr, Arg, Float64, native_call class point: def __init__( @@ -32,11 +32,12 @@ def move_point( dy: Float64 ) -> None: ... -@native_call([Return('p', 0), Addr(Arg(0)), Addr(Arg(1))]) +@native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2))]) def make_point_out( + p: point, x: Float64, y: Float64 -) -> point: ... +) -> None: ... @native_call([Addr(Arg(0)), Addr(Arg(1))]) def make_point( diff --git a/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_phase8_opaque/fderived_boundary_f90.pyi b/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_phase8_opaque/fderived_boundary_f90.pyi index 0307647d2..94b6b20fa 100644 --- a/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_phase8_opaque/fderived_boundary_f90.pyi +++ b/tests/wrapper/fortran/derived_types/contracts/fderived_boundary_phase8_opaque/fderived_boundary_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Float64, Return, native_call +from x2py.contracts import Addr, Arg, Float64, native_call class point: x: Float64 @@ -15,11 +15,12 @@ def move_point( dy: Float64 ) -> None: ... -@native_call([Return('p', 0), Addr(Arg(0)), Addr(Arg(1))]) +@native_call([Arg(0), Addr(Arg(1)), Addr(Arg(2))]) def make_point_out( + p: point, x: Float64, y: Float64 -) -> point: ... +) -> None: ... @native_call([Addr(Arg(0)), Addr(Arg(1))]) def make_point( diff --git a/tests/wrapper/fortran/derived_types/contracts/finheritance_f90/finheritance_f90.pyi b/tests/wrapper/fortran/derived_types/contracts/finheritance_f90/finheritance_f90.pyi index 39ae37d17..57f826f6b 100644 --- a/tests/wrapper/fortran/derived_types/contracts/finheritance_f90/finheritance_f90.pyi +++ b/tests/wrapper/fortran/derived_types/contracts/finheritance_f90/finheritance_f90.pyi @@ -44,21 +44,21 @@ class box(base_shape): def area(self) -> Float64: ... def base_area( - self: base_shape + self: Annotated[base_shape, Polymorphic] ) -> Float64: ... @native_call([Arg(0), Addr(Arg(1))]) def base_set_size( - self: base_shape, + self: Annotated[base_shape, Polymorphic], value: Float64 ) -> None: ... def circle_area( - self: circle + self: Annotated[circle, Polymorphic] ) -> Float64: ... def box_area( - self: box + self: Annotated[box, Polymorphic] ) -> Float64: ... def describe_shape( diff --git a/tests/wrapper/fortran/derived_types/contracts/fpointers_f90/fpointers_f90.pyi b/tests/wrapper/fortran/derived_types/contracts/fpointers_f90/fpointers_f90.pyi index 82651fd70..c227cc6c8 100644 --- a/tests/wrapper/fortran/derived_types/contracts/fpointers_f90/fpointers_f90.pyi +++ b/tests/wrapper/fortran/derived_types/contracts/fpointers_f90/fpointers_f90.pyi @@ -19,4 +19,4 @@ def sum_pointer( def pointer_to_values( values: Annotated[Float64[::], Aliased], use_values: Int32 -) -> Annotated[Pointer[Float64[:]], PointerAssociation("runtime"), Ownership("python"), Transfer("snapshot_copy"), Destruction("python_refcount")]: ... +) -> Annotated[Pointer[Float64[:]], PointerAssociation("runtime")]: ... diff --git a/tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py b/tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py index 5c7c08e7d..7e9277db0 100644 --- a/tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py +++ b/tests/wrapper/fortran/derived_types/test_derived_type_boundaries.py @@ -42,8 +42,8 @@ def test_scalar_derived_types_cross_procedure_boundaries( assert point.x == np.float64(5.0) assert point.y == np.float64(7.0) - out_point = module.make_point_out(np.float64(8.0), np.float64(9.0)) - assert isinstance(out_point, module.point) + out_point = module.point() + assert module.make_point_out(out_point, np.float64(8.0), np.float64(9.0)) is None assert out_point.x == np.float64(8.0) assert out_point.y == np.float64(9.0) diff --git a/tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py b/tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py index 918a95946..d8d6fa6c5 100644 --- a/tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py +++ b/tests/wrapper/fortran/derived_types/test_phase8_derived_plan.py @@ -266,10 +266,10 @@ def _exercise_point_boundary(module): assert point.x == np.float64(6.0) assert point.y == np.float64(8.0) - hidden = module.make_point_out(np.float64(10.0), np.float64(11.0)) - assert isinstance(hidden, module.point) - assert hidden.x == np.float64(10.0) - assert hidden.y == np.float64(11.0) + output = module.make_point(np.float64(0.0), np.float64(0.0)) + assert module.make_point_out(output, np.float64(10.0), np.float64(11.0)) is None + assert output.x == np.float64(10.0) + assert output.y == np.float64(11.0) with pytest.raises(TypeError, match="Expected"): point.x = 12.0 @@ -289,9 +289,8 @@ def test_scalar_derived_objects_use_canonical_plan(tmp_path: Path): assert "@x.setter\\n def x(self, value):" in generated_c assert "bind_c_x2py_field_point_x_get" in generated_fortran assert "bind_c_x2py_field_point_x_set" in generated_fortran - assert "p = c_null_ptr" in generated_fortran + assert "call native_make_point_out(p, x, y)" in generated_fortran assert "result = c_null_ptr" in generated_fortran - assert "allocate(p_value, stat=x2py_allocation_status)" in generated_fortran assert "allocate(result_value, stat=x2py_allocation_status)" in generated_fortran @@ -542,7 +541,8 @@ def test_value_copy_and_optional_derived_inputs_match_source_oracle(tmp_path: Pa assert source_module.update_point(source_point) is None assert source_point.x == np.float64(11.0) assert source_point.y == np.float64(22.0) - source_filled = source_module.fill_point() + source_filled = source_module.point() + assert source_module.fill_point(source_filled) is None assert source_filled.x == np.float64(31.0) assert source_filled.y == np.float64(32.0) diff --git a/tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py b/tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py index 247a86b56..3203ab888 100644 --- a/tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py +++ b/tests/wrapper/fortran/derived_types/test_phase9_bound_constructors.py @@ -15,7 +15,7 @@ CONTRACT = Path(__file__).parent / "contracts" / "fbound_constructor_phase9" / "__init__.pyi" -def test_bound_constructor_replaces_field_initialization_and_reuses_method_plan( +def test_bound_constructor_replaces_field_initialization_with_direct_pass_projection( tmp_path: Path, ): native_object = _compile_native_object(SOURCE, tmp_path / "native") @@ -30,15 +30,12 @@ def test_bound_constructor_replaces_field_initialization_and_reuses_method_plan( assert "_x2py_class_" not in module.__doc__ assert "Constructor\n-----------\nvector(dx, dy) -> vector" in module.vector.__doc__ assert "Fields\n------\nx : float64\ny : float64" in module.vector.__doc__ - assert "Methods\n-------\nshift(dx, dy) -> None" in module.vector.__doc__ assert "vector(dx, dy) -> vector" in module.vector.__init__.__doc__ assert "dx : float64" in module.vector.__init__.__doc__ - assert "shift(dx, dy) -> None" in module.vector.shift.__doc__ - assert "Updates the wrapped native instance in place." in module.vector.shift.__doc__ - assert "owner" not in module.vector.shift.__doc__ + assert "shift_vector(dx, owner, dy) -> None" in module.shift_vector.__doc__ assert "Assignment writes through to native storage." in module.vector.x.__doc__ value = module.vector(np.float64(2.0), np.float64(3.0)) assert (value.x, value.y) == (np.float64(2.0), np.float64(3.0)) - value.shift(np.float64(1.0), np.float64(-1.0)) + module.shift_vector(np.float64(1.0), value, np.float64(-1.0)) assert (value.x, value.y) == (np.float64(3.0), np.float64(2.0)) diff --git a/tests/wrapper/fortran/derived_types/test_pointers.py b/tests/wrapper/fortran/derived_types/test_pointers.py index 367a6b817..3e96aa58b 100644 --- a/tests/wrapper/fortran/derived_types/test_pointers.py +++ b/tests/wrapper/fortran/derived_types/test_pointers.py @@ -17,10 +17,51 @@ wrapper_source, ) from x2py import build_pyi_extension +from x2py.contracts import Float64, Pointer from x2py.runtime.handles import AllocatableArray, PointerArray POINTERS_F90_SOURCE = wrapper_source("fpointers_f90.f90") CONTRACT_FIXTURES = Path(__file__).parent / "contracts" +POINTER_CROSS_A_SOURCE = """\ +module fpointer_cross_a + real(8), target :: storage_a(2) = [1.0_8, 2.0_8] +contains + subroutine select_a(values) + real(8), pointer, intent(inout) :: values(:) + values => storage_a + end subroutine select_a + + function total_a(values) result(total) + real(8), pointer, intent(in) :: values(:) + real(8) :: total + if (associated(values)) then + total = sum(values) + else + total = -1.0_8 + end if + end function total_a +end module fpointer_cross_a +""" +POINTER_CROSS_B_SOURCE = """\ +module fpointer_cross_b + real(8), target :: storage_b(3) = [10.0_8, 20.0_8, 30.0_8] +contains + subroutine select_b(values) + real(8), pointer, intent(inout) :: values(:) + values => storage_b + end subroutine select_b + + function total_b(values) result(total) + real(8), pointer, intent(in) :: values(:) + real(8) :: total + if (associated(values)) then + total = sum(values) + else + total = -1.0_8 + end if + end function total_b +end module fpointer_cross_b +""" POINTER_HANDLE_SOURCE = """\ module fpointer_handles_f90 implicit none @@ -47,6 +88,16 @@ module_values => module_storage(2:4) end subroutine associate_module_contiguous + subroutine select_module_values(values) + real(8), pointer, intent(out) :: values(:) + values => module_storage(2:4) + end subroutine select_module_values + + subroutine select_no_values(values) + real(8), pointer, intent(out) :: values(:) + nullify(values) + end subroutine select_no_values + subroutine allocate_module_values() if (allocated(module_allocatable)) deallocate(module_allocatable) allocate(module_allocatable(3)) @@ -93,6 +144,58 @@ """ +def _build_pointer_cross_extension( + source_text: str, + module_name: str, + select_name: str, + total_name: str, + workdir: Path, +): + source = workdir / f"{module_name}.f90" + source.write_text(source_text, encoding="utf-8") + native_object = _compile_native_object(source, workdir / "native") + contract_dir = workdir / "contracts" + contract_dir.mkdir() + (contract_dir / "__init__.pyi").write_text( + f"from .{module_name} import {select_name}, {total_name}\n", + encoding="utf-8", + ) + pointer_type = """Annotated[ + Pointer[Float64[:]], + PointerAssociation("runtime"), + PointerPolicy( + nullable=True, + transfer="call_local", + target_owner="module", + lifetime="module", + deallocation="never", + shape_source="pointer_bounds", + contiguity="contiguous", + reassociation="native", + aliasing="borrowed", + mutability="view", + ), +]""" + (contract_dir / f"{module_name}.pyi").write_text( + f"""from x2py.contracts import Annotated, Float64, Pointer, PointerAssociation, PointerPolicy, Returns + +def {select_name}( + values: {pointer_type}, +) -> Returns["values", {pointer_type}]: ... + +def {total_name}(values: Pointer[Float64[:]]) -> Float64: ... +""", + encoding="utf-8", + ) + result = build_pyi_extension( + contract_dir / "__init__.pyi", + native_objects=[native_object], + native_include_dirs=[native_object.parent], + output_dir=workdir / "build", + ) + return _sole_native_module(_import_from_build_dir(result.module_name, result.output_dir)) + + def _pointer_handle_module(build_mode: str, tmp_path: Path): filename = "fpointer_handles_f90.f90" expected_sources = { @@ -173,6 +276,36 @@ def test_module_and_derived_pointer_handles_track_native_association( assert module_handle.shape == (3,) assert module.sum_values(module_handle) == np.float64(9.0) + selected = module.select_module_values() + assert isinstance(selected, PointerArray) + assert selected.owned is True + assert selected.associated is True + assert selected.shape == (3,) + assert module.sum_pointer_descriptor(selected) == np.float64(9.0) + + no_values = module.select_no_values() + assert isinstance(no_values, PointerArray) + assert no_values.owned is True + assert no_values.associated is False + assert no_values.shape is None + + module_handle.associate(no_values) + assert module_handle.associated is False + module_handle.associate(selected) + assert module_handle.associated is True + assert module.sum_pointer_descriptor(module_handle) == np.float64(9.0) + selected.nullify() + assert module_handle.associated is True + module_handle.associate(selected) + assert module_handle.associated is False + module.associate_module_contiguous() + + selected.close() + no_values.close() + assert selected.closed is True + assert no_values.closed is True + assert module.sum_values(module_handle) == np.float64(9.0) + module_handle.nullify() assert module_handle.associated is False assert module_handle.shape is None @@ -188,6 +321,15 @@ def test_module_and_derived_pointer_handles_track_native_association( assert field_handle.shape == (3,) assert module.sum_values(field_handle) == np.float64(24.0) + module.associate_module_contiguous() + field_handle.associate(module_handle) + assert module.sum_pointer_descriptor(field_handle) == np.float64(9.0) + module_handle.nullify() + assert field_handle.associated is True + field_handle.associate(module_handle) + assert field_handle.associated is False + owner.associate_values() + owner_id = id(owner) del owner gc.collect() @@ -197,6 +339,53 @@ def test_module_and_derived_pointer_handles_track_native_association( assert field_handle.associated is False +def test_caller_created_pointer_crosses_separately_built_extensions(tmp_path: Path): + first_dir = tmp_path / "first" + second_dir = tmp_path / "second" + first_dir.mkdir() + second_dir.mkdir() + first = _build_pointer_cross_extension( + POINTER_CROSS_A_SOURCE, + "fpointer_cross_a", + "select_a", + "total_a", + first_dir, + ) + second = _build_pointer_cross_extension( + POINTER_CROSS_B_SOURCE, + "fpointer_cross_b", + "select_b", + "total_b", + second_dir, + ) + values = Pointer[Float64[:]]() + + assert first.select_a(values) is values + np.testing.assert_array_equal(values.to_numpy(), np.array([1.0, 2.0])) + assert second.total_b(values) == np.float64(3.0) + + assert second.select_b(values) is values + np.testing.assert_array_equal(values.to_numpy(), np.array([10.0, 20.0, 30.0])) + assert first.total_a(values) == np.float64(60.0) + + first_values = Pointer[Float64[:]]() + second_values = Pointer[Float64[:]]() + assert first.select_a(first_values) is first_values + assert second.select_b(second_values) is second_values + + first_values.associate(second_values) + assert first.total_a(first_values) == np.float64(60.0) + second_values.nullify() + assert first_values.associated is True + first_values.associate(second_values) + assert first_values.associated is False + + first_values.close() + second_values.close() + values.close() + assert values.closed is True + + def test_pointer_descriptor_views_preserve_slice_shape_strides_and_parent_lifetime(tmp_path: Path): module = _pointer_descriptor_view_module(tmp_path) @@ -307,22 +496,105 @@ def sum_allocatable_descriptor(values: Allocatable[Float64[:]]) -> Float64: ... assert allocatable_handle.allocated is False -def test_pointer_array_handles_block_on_unsupported_result_owner_policy( +def test_caller_created_pointer_handle_tracks_native_output_association(tmp_path: Path): + source = tmp_path / "native" / "fpointer_handles_f90.f90" + source.parent.mkdir() + source.write_text(POINTER_HANDLE_SOURCE, encoding="utf-8") + native_object = _compile_native_object(source, tmp_path / "native_build") + contract = tmp_path / "contracts" / "fpointer_handles_f90.pyi" + contract.parent.mkdir() + pointer_type = """Annotated[ + Pointer[Float64[:]], + PointerAssociation("runtime"), + PointerPolicy( + nullable=True, + transfer="call_local", + target_owner="module", + lifetime="module", + deallocation="never", + shape_source="pointer_bounds", + contiguity="contiguous", + reassociation="native", + aliasing="borrowed", + mutability="view", + ), +]""" + contract.write_text( + f"""from x2py.contracts import Annotated, Float64, Pointer, PointerAssociation, PointerPolicy, Returns + +def select_module_values( + values: {pointer_type}, +) -> Returns["values", {pointer_type}]: ... + +def sum_pointer_descriptor(values: Pointer[Float64[:]]) -> Float64: ... +""", + encoding="utf-8", + ) + result = build_pyi_extension( + contract, + native_objects=[native_object], + native_include_dirs=[native_object.parent], + output_dir=tmp_path / "build", + ) + module = _sole_native_module(_import_from_build_dir(result.module_name, result.output_dir)) + + handle = Pointer[Float64[:]]() + assert handle.associated is False + assert module.sum_pointer_descriptor(handle) == np.float64(-1.0) + assert module.select_module_values(handle) is handle + assert handle.associated is True + assert handle.shape == (3,) + np.testing.assert_allclose(handle.to_numpy(), np.array([2.0, 3.0, 4.0])) + assert module.sum_pointer_descriptor(handle) == np.float64(9.0) + + source = Pointer[Float64[:]]() + assert module.select_module_values(source) is source + alias = Pointer[Float64[:]]() + alias.associate(source) + assert alias.associated is True + assert module.sum_pointer_descriptor(alias) == np.float64(9.0) + source.nullify() + assert alias.associated is True + alias.associate(source) + assert alias.associated is False + + source.close() + alias.close() + handle.close() + assert handle.closed is True + + +def test_pointer_array_results_use_owned_descriptors_without_owning_targets( pyi_parity_build_mode: str, tmp_path: Path, ): - with pytest.raises((subprocess.CalledProcessError, ValueError)) as exc_info: - _build_source_or_generated_pyi_and_import( - POINTERS_F90_SOURCE, - tmp_path, - { - "bind_c_fpointers_f90_wrapper.f90", - "fpointers_f90_wrapper.c", - "fpointers_f90_wrapper.h", - }, - CONTRACT_FIXTURES / "fpointers_f90", - pyi_parity_build_mode, - ) - - error = exc_info.value.stderr if isinstance(exc_info.value, subprocess.CalledProcessError) else str(exc_info.value) - assert "pointer handle results need stable owner storage and target lifetime policy before wrapping" in error + module = _build_source_or_generated_pyi_and_import( + POINTERS_F90_SOURCE, + tmp_path, + { + "bind_c_fpointers_f90_wrapper.f90", + "fpointers_f90_wrapper.c", + "fpointers_f90_wrapper.h", + }, + CONTRACT_FIXTURES / "fpointers_f90", + pyi_parity_build_mode, + ) + values = np.array([1.0, 2.0, 3.0], dtype=np.float64) + + selected = module.pointer_to_values(values, np.int32(1)) + assert isinstance(selected, PointerArray) + assert selected.owned is True + assert selected.associated is True + assert selected.shape == (3,) + assert module.sum_pointer(selected) == np.float64(6.0) + + absent = module.pointer_to_values(values, np.int32(0)) + assert isinstance(absent, PointerArray) + assert absent.associated is False + assert absent.shape is None + + selected.close() + absent.close() + assert selected.closed is True + assert absent.closed is True + np.testing.assert_array_equal(values, np.array([1.0, 2.0, 3.0], dtype=np.float64)) diff --git a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_added_bindings/foverloads_f90.pyi b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_added_bindings/foverloads_f90.pyi index 058adc3de..5b0f87809 100644 --- a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_added_bindings/foverloads_f90.pyi +++ b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_added_bindings/foverloads_f90.pyi @@ -15,12 +15,14 @@ def convert_real_specific( value: Float64 ) -> Float64: ... -@overload("convert_int", generic="convert") +@bind("convert") +@overload("convert_int") def convert_number( value: Int32 ) -> Int32: ... -@overload("convert_real_specific", generic="convert") +@bind("convert") +@overload("convert_real_specific") def convert_number( value: Float64 ) -> Float64: ... diff --git a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_specific_without_bind/__init__.pyi b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_specific_without_bind/__init__.pyi new file mode 100644 index 000000000..3a44ee390 --- /dev/null +++ b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_specific_without_bind/__init__.pyi @@ -0,0 +1,2 @@ +# Intentional difference: overload candidates call inaccessible private specifics. +from . import foverloads_f90 diff --git a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_specific_without_bind/foverloads_f90.pyi b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_specific_without_bind/foverloads_f90.pyi new file mode 100644 index 000000000..b495d8faf --- /dev/null +++ b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_specific_without_bind/foverloads_f90.pyi @@ -0,0 +1,25 @@ +# Intentional difference: @private controls Python visibility, while the missing +# overload-level @bind attempts to call each native specific directly. +from x2py.contracts import Addr, Arg, Float64, Int32, native_call, overload, private + +@private +@native_call([Addr(Arg(0))]) +def convert_integer( + value: Int32 +) -> Int32: ... + +@private +@native_call([Addr(Arg(0))]) +def convert_real( + value: Float64 +) -> Float64: ... + +@overload("convert_integer") +def convert( + value: Int32 +) -> Int32: ... + +@overload("convert_real") +def convert( + value: Float64 +) -> Float64: ... diff --git a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_type_bound_specifics_without_bind/__init__.pyi b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_type_bound_specifics_without_bind/__init__.pyi new file mode 100644 index 000000000..0e2fc200d --- /dev/null +++ b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_type_bound_specifics_without_bind/__init__.pyi @@ -0,0 +1,2 @@ +# Intentional difference: class overloads call inaccessible private specifics. +from . import foverloads_f90 diff --git a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_type_bound_specifics_without_bind/foverloads_f90.pyi b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_type_bound_specifics_without_bind/foverloads_f90.pyi new file mode 100644 index 000000000..72b1d5af6 --- /dev/null +++ b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_private_type_bound_specifics_without_bind/foverloads_f90.pyi @@ -0,0 +1,41 @@ +# Intentional difference: the class overloads omit @bind("add") and therefore +# attempt to call each native specific directly. +from x2py.contracts import Addr, Arg, Float64, Int32, Pass, bind, native_call, overload, private + + +class accumulator: + def __init__(self, *, total: Float64 = 0.0) -> None: ... + + total: Float64 = 0.0 + + @private + @bind("accumulator_add_integer") + @native_call([Pass(), Addr(Arg(0))]) + def add_integer(self, value: Int32) -> None: ... + + @private + @bind("accumulator_add_real") + @native_call([Pass(), Addr(Arg(0))]) + def add_real(self, value: Float64) -> None: ... + + @overload("accumulator_add_integer") + def add(self, value: Int32) -> None: ... + + @overload("accumulator_add_real") + def add(self, value: Float64) -> None: ... + + +@private +@native_call([Arg(0), Addr(Arg(1))]) +def accumulator_add_integer( + self: accumulator, + value: Int32, +) -> None: ... + + +@private +@native_call([Arg(0), Addr(Arg(1))]) +def accumulator_add_real( + self: accumulator, + value: Float64, +) -> None: ... diff --git a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_pruned_surface/foverloads_f90.pyi b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_pruned_surface/foverloads_f90.pyi index bd601606e..6a1c27505 100644 --- a/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_pruned_surface/foverloads_f90.pyi +++ b/tests/wrapper/fortran/edit_pyi_contracts/modified_contracts/foverloads_pruned_surface/foverloads_f90.pyi @@ -1,6 +1,6 @@ # Intentional difference: remove class sample, remove accumulator.add, and # remove the complex overload candidate while leaving the integer/real generic. -from x2py.contracts import Addr, Arg, Float64, Int32, native_call, overload, private +from x2py.contracts import Addr, Arg, Float64, Int32, bind, native_call, overload, private class accumulator: def __init__( @@ -23,11 +23,13 @@ def convert_real( value: Float64 ) -> Float64: ... +@bind("convert") @overload("convert_integer") def convert( value: Int32 ) -> Int32: ... +@bind("convert") @overload("convert_real") def convert( value: Float64 diff --git a/tests/wrapper/fortran/edit_pyi_contracts/test_surface_edit_contracts.py b/tests/wrapper/fortran/edit_pyi_contracts/test_surface_edit_contracts.py index 5bafc977f..99c6266bb 100644 --- a/tests/wrapper/fortran/edit_pyi_contracts/test_surface_edit_contracts.py +++ b/tests/wrapper/fortran/edit_pyi_contracts/test_surface_edit_contracts.py @@ -60,3 +60,34 @@ def test_editable_contract_adds_renamed_binding_and_overload_group(tmp_path: Pat assert module.convert_number(np.float64(6.0)) == np.float64(6.5) with pytest.raises(TypeError): module.convert_number(np.complex128(1.0 + 0.0j)) + + +def test_private_native_specific_without_overload_bind_fails_fortran_accessibility(tmp_path: Path): + native_object = _compile_native_object(NATIVE_SOURCE, tmp_path / "native") + + with pytest.raises(RuntimeError) as exc_info: + _build_contract( + "foverloads_private_specific_without_bind", + native_object, + tmp_path / "inaccessible", + ) + + error = str(exc_info.value).casefold() + assert "convert_integer" in error + assert "not found in module" in error + + +def test_private_type_bound_specifics_without_overload_bind_fail_fortran_accessibility(tmp_path: Path): + native_object = _compile_native_object(NATIVE_SOURCE, tmp_path / "native") + + with pytest.raises(RuntimeError) as exc_info: + _build_contract( + "foverloads_private_type_bound_specifics_without_bind", + native_object, + tmp_path / "inaccessible_type_bound", + ) + + error = str(exc_info.value).casefold() + assert "accumulator_add_integer" in error + assert "accumulator_add_real" in error + assert "not found in module" in error diff --git a/tests/wrapper/fortran/external_routines/contracts/fixed_external/__init__.pyi b/tests/wrapper/fortran/external_routines/contracts/fixed_external/__init__.pyi index 7a4a6b86d..1bb863e40 100644 --- a/tests/wrapper/fortran/external_routines/contracts/fixed_external/__init__.pyi +++ b/tests/wrapper/fortran/external_routines/contracts/fixed_external/__init__.pyi @@ -1,7 +1,7 @@ -from x2py.contracts import Addr, Arg, Int32, external, native_call +from x2py.contracts import Addr, Arg, Int32, Returns, external, native_call @external @native_call([Addr(Arg(0))]) def fixed_add( value: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["value", Int32]]: ... diff --git a/tests/wrapper/fortran/external_routines/test_external_procedures.py b/tests/wrapper/fortran/external_routines/test_external_procedures.py index ecc9be01a..db43736d5 100644 --- a/tests/wrapper/fortran/external_routines/test_external_procedures.py +++ b/tests/wrapper/fortran/external_routines/test_external_procedures.py @@ -178,7 +178,7 @@ def bundled_external_module(pyi_parity_build_mode: str, tmp_path: Path): def test_fixed_form_standalone_external_runtime_parity(fixed_external_module): - assert fixed_external_module.fixed_add(np.int32(4)) == np.int32(5) + assert fixed_external_module.fixed_add(np.int32(4)) == (np.int32(5), np.int32(4)) def test_free_form_standalone_external_runtime_parity(free_external_module): diff --git a/tests/wrapper/fortran/function_calls/contracts/fnative_call_examples_f90/fnative_call_examples_f90.pyi b/tests/wrapper/fortran/function_calls/contracts/fnative_call_examples_f90/fnative_call_examples_f90.pyi index 43a16203d..1404d104c 100644 --- a/tests/wrapper/fortran/function_calls/contracts/fnative_call_examples_f90/fnative_call_examples_f90.pyi +++ b/tests/wrapper/fortran/function_calls/contracts/fnative_call_examples_f90/fnative_call_examples_f90.pyi @@ -20,7 +20,7 @@ def scalar_status( def fill_vector( n: Int32, values: Float64[n] -) -> Returns["values", Float64[n]]: ... +) -> None: ... @native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2), Arg(3)]) def shift_matrix( @@ -28,7 +28,7 @@ def shift_matrix( m: Int32, values: Float64[n, m], out: Float64[n, m] -) -> Returns["out", Float64[n, m]]: ... +) -> None: ... @native_call([Arg(0), Return('status', 0)]) def scale_with_status( @@ -42,13 +42,14 @@ def fixed_inout( @native_call([Return('label', 0)]) def make_label() -> String[6]: ... -@native_call([Addr(Arg(0)), Arg(1), Return('status', 2), Return('label', 3)]) +@native_call([Addr(Arg(0)), Arg(1), Return('status', 1), Return('label', 2)]) def summarize_mixed( n: Int32, values: Float64[n] -) -> tuple[Float64, Returns["values", Float64[n]], Int32, String[6]]: ... +) -> tuple[Float64, Int32, String[6]]: ... -@native_call([Addr(Arg(0)), Return('point', 0)]) +@native_call([Addr(Arg(0)), Arg(1)]) def make_point( - scale: Int32 -) -> summary_point: ... + scale: Int32, + point: summary_point +) -> None: ... diff --git a/tests/wrapper/fortran/function_calls/contracts/foptional_f90/foptional_f90.pyi b/tests/wrapper/fortran/function_calls/contracts/foptional_f90/foptional_f90.pyi index 8478f4a22..d4da9f74f 100644 --- a/tests/wrapper/fortran/function_calls/contracts/foptional_f90/foptional_f90.pyi +++ b/tests/wrapper/fortran/function_calls/contracts/foptional_f90/foptional_f90.pyi @@ -28,7 +28,7 @@ def mutate_optional( def fill_optional( n: Int32, values: Float64[::] = ... -) -> Returns["values", Float64[::]] | None: ... +) -> None: ... @native_call([Addr(Arg(0)), Arg(1)]) def optional_status( diff --git a/tests/wrapper/fortran/function_calls/contracts/foutputs_f90/foutputs_f90.pyi b/tests/wrapper/fortran/function_calls/contracts/foutputs_f90/foutputs_f90.pyi index 3422a4b1c..2238b82b3 100644 --- a/tests/wrapper/fortran/function_calls/contracts/foutputs_f90/foutputs_f90.pyi +++ b/tests/wrapper/fortran/function_calls/contracts/foutputs_f90/foutputs_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Allocatable, Arg, Float64, Int32, Return, Returns, String, native_call +from x2py.contracts import Addr, Allocatable, Arg, Float64, Int32, Return, String, native_call class output_point: def __init__( @@ -20,14 +20,14 @@ def scalar_status( def fill_vector( n: Int32, values: Float64[n] -) -> Returns["values", Float64[n]]: ... +) -> None: ... @native_call([Addr(Arg(0)), Addr(Arg(1)), Arg(2)]) def fill_matrix( n: Int32, m: Int32, values: Float64[n, m] -) -> Returns["values", Float64[n, m]]: ... +) -> None: ... @native_call([Addr(Arg(0)), Return('values', 0)]) def build_alloc( @@ -39,11 +39,11 @@ def with_scalar( n: Int32 ) -> tuple[Int32, Int32]: ... -@native_call([Addr(Arg(0)), Arg(1), Return('status', 2), Return('built', 3)]) +@native_call([Addr(Arg(0)), Arg(1), Return('status', 1), Return('built', 2)]) def mixed_outputs( n: Int32, values: Float64[n] -) -> tuple[Float64, Returns["values", Float64[n]], Int32, Allocatable[Float64[:]]]: ... +) -> tuple[Float64, Int32, Allocatable[Float64[:]]]: ... def increment( values: Float64[::] @@ -57,7 +57,8 @@ def increment_with_status( @native_call([Return('label', 0)]) def make_label() -> String[8]: ... -@native_call([Addr(Arg(0)), Return('point', 0)]) +@native_call([Addr(Arg(0)), Arg(1)]) def make_point( - scale: Int32 -) -> output_point: ... + scale: Int32, + point: output_point +) -> None: ... diff --git a/tests/wrapper/fortran/function_calls/test_native_call_examples.py b/tests/wrapper/fortran/function_calls/test_native_call_examples.py index c5efaa65e..c203330f8 100644 --- a/tests/wrapper/fortran/function_calls/test_native_call_examples.py +++ b/tests/wrapper/fortran/function_calls/test_native_call_examples.py @@ -29,14 +29,12 @@ def _assert_native_call_examples(module) -> None: assert module.scalar_status(np.int32(4)) == np.int32(15) vector = np.empty(4, dtype=np.float64) - returned_vector = module.fill_vector(np.int32(4), vector) - assert returned_vector is vector + assert module.fill_vector(np.int32(4), vector) is None np.testing.assert_allclose(vector, np.array([1.5, 3.0, 4.5, 6.0], dtype=np.float64)) matrix = np.array([[1.0, 3.0, 5.0], [2.0, 4.0, 6.0]], dtype=np.float64, order="F") shifted = np.empty((2, 3), dtype=np.float64, order="F") - returned_matrix = module.shift_matrix(np.int32(2), np.int32(3), matrix, shifted) - assert returned_matrix is shifted + assert module.shift_matrix(np.int32(2), np.int32(3), matrix, shifted) is None np.testing.assert_allclose(shifted, matrix + 10.0) inout = np.array([2.0, 5.0, 7.0], dtype=np.float64) @@ -49,15 +47,14 @@ def _assert_native_call_examples(module) -> None: assert module.make_label() == "done " mixed_values = np.empty(3, dtype=np.float64) - total, returned_values, status, label = module.summarize_mixed(np.int32(3), mixed_values) + total, status, label = module.summarize_mixed(np.int32(3), mixed_values) assert total == np.float64(3.75) - assert returned_values is mixed_values assert status == np.int32(23) assert label == "mix " np.testing.assert_allclose(mixed_values, np.array([11.0, 12.0, 13.0], dtype=np.float64)) - point = module.make_point(np.int32(7)) - assert isinstance(point, module.summary_point) + point = module.summary_point() + assert module.make_point(np.int32(7), point) is None assert point.total == np.float64(7.5) assert point.code == np.int32(107) diff --git a/tests/wrapper/fortran/function_calls/test_optional_arguments.py b/tests/wrapper/fortran/function_calls/test_optional_arguments.py index 730358c79..e0d076367 100644 --- a/tests/wrapper/fortran/function_calls/test_optional_arguments.py +++ b/tests/wrapper/fortran/function_calls/test_optional_arguments.py @@ -247,8 +247,7 @@ def test_optional_arguments_drive_fortran_present_behavior( np.testing.assert_allclose(mutable, np.array([5.5, 6.5], dtype=np.float64)) output = np.empty(3, dtype=np.float64) - returned_output = module.fill_optional(np.int32(3), output) - assert returned_output is output + assert module.fill_optional(np.int32(3), output) is None np.testing.assert_allclose(output, np.array([11.0, 12.0, 13.0], dtype=np.float64)) assert module.fill_optional(np.int32(3)) is None assert module.fill_optional(np.int32(3), None) is None diff --git a/tests/wrapper/fortran/function_calls/test_output_arguments.py b/tests/wrapper/fortran/function_calls/test_output_arguments.py index f4504222e..eddb57964 100644 --- a/tests/wrapper/fortran/function_calls/test_output_arguments.py +++ b/tests/wrapper/fortran/function_calls/test_output_arguments.py @@ -38,11 +38,11 @@ def test_output_arguments_and_multiple_results_follow_python_projection_rules( assert "scalar_status(n) -> int32" in module.scalar_status.__doc__ assert "status : int32" in module.scalar_status.__doc__ - assert "fill_vector(n, values) -> ndarray[float64]" in module.fill_vector.__doc__ + assert "fill_vector(n, values) -> None" in module.fill_vector.__doc__ assert "Parameters\n----------" in module.fill_vector.__doc__ - assert "Returns\n-------" in module.fill_vector.__doc__ + assert "Returns\n-------\nNone" in module.fill_vector.__doc__ assert "Raises\n------" in module.fill_vector.__doc__ - assert "Native code may update this value; the updated value is returned." in module.fill_vector.__doc__ + assert "Native code may update the supplied storage in place." in module.fill_vector.__doc__ assert "Direction:" not in module.fill_vector.__doc__ assert "Initial contents are ignored." not in module.fill_vector.__doc__ assert "Ownership: Caller-owned" in module.fill_vector.__doc__ @@ -50,18 +50,17 @@ def test_output_arguments_and_multiple_results_follow_python_projection_rules( assert "Descriptor ownership: owned" in module.build_alloc.__doc__ assert "Unallocated state remains inside the returned handle." in module.build_alloc.__doc__ assert "make_label() -> str" in module.make_label.__doc__ - assert "make_point(scale) -> output_point" in module.make_point.__doc__ + assert "make_point(scale, point) -> None" in module.make_point.__doc__ + assert "point : output_point" in module.make_point.__doc__ assert module.scalar_status(np.int32(5)) == np.int32(15) vector = np.empty(4, dtype=np.float64) - returned_vector = module.fill_vector(np.int32(4), vector) - assert returned_vector is vector + assert module.fill_vector(np.int32(4), vector) is None np.testing.assert_allclose(vector, np.array([2.0, 4.0, 6.0, 8.0], dtype=np.float64)) matrix = np.empty((2, 3), dtype=np.float64, order="F") - returned_matrix = module.fill_matrix(np.int32(2), np.int32(3), matrix) - assert returned_matrix is matrix + assert module.fill_matrix(np.int32(2), np.int32(3), matrix) is None np.testing.assert_allclose( matrix, np.array([[11.0, 21.0, 31.0], [12.0, 22.0, 32.0]], dtype=np.float64), @@ -81,12 +80,11 @@ def test_output_arguments_and_multiple_results_follow_python_projection_rules( mixed_vector = np.empty(3, dtype=np.float64) mixed_result = module.mixed_outputs(np.int32(3), mixed_vector) assert mixed_result[0] == np.float64(3.5) - assert mixed_result[1] is mixed_vector - assert mixed_result[2] == np.int32(23) - np.testing.assert_allclose(mixed_result[1], np.array([101.0, 102.0, 103.0], dtype=np.float64)) - assert isinstance(mixed_result[3], AllocatableArray) + assert mixed_result[1] == np.int32(23) + np.testing.assert_allclose(mixed_vector, np.array([101.0, 102.0, 103.0], dtype=np.float64)) + assert isinstance(mixed_result[2], AllocatableArray) np.testing.assert_allclose( - mixed_result[3].to_numpy(), + mixed_result[2].to_numpy(), np.array([201.0, 202.0, 203.0], dtype=np.float64), ) @@ -98,8 +96,8 @@ def test_output_arguments_and_multiple_results_follow_python_projection_rules( assert module.make_label() == "RESULT!!" - point = module.make_point(np.int32(6)) - assert isinstance(point, module.output_point) + point = module.output_point() + assert module.make_point(np.int32(6), point) is None assert point.x == np.float64(6.25) assert point.tag == np.int32(46) diff --git a/tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py b/tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py index b4cf19289..32dd8e5f9 100644 --- a/tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py +++ b/tests/wrapper/fortran/function_calls/test_scalar_writeback_plan.py @@ -3,6 +3,8 @@ from __future__ import annotations from pathlib import Path +import subprocess +import sys import numpy as np import pytest @@ -12,7 +14,7 @@ _import_from_build_dir, _sole_native_module, ) -from x2py import build_pyi_extension +from x2py import build_fortran_extension, build_pyi_extension def test_scalar_copy_in_out_returns_replacement(tmp_path: Path): @@ -65,3 +67,90 @@ def bump( assert replacement == np.int32(5) with pytest.raises(TypeError): module.bump("bad") + + +def test_source_generated_scalar_inout_contract_returns_replacement_and_keeps_namespace(tmp_path: Path): + source = tmp_path / "outputs.f90" + source.write_text( + """ +module outputs + implicit none +contains + subroutine scale_in_place(value, factor) + real(8), intent(inout) :: value + real(8), intent(in) :: factor + value = factor * value + end subroutine scale_in_place +end module outputs +""", + encoding="utf-8", + ) + + source_result = build_fortran_extension(source, output_dir=tmp_path / "source_build") + source_module = _import_from_build_dir(source_result.module_name, source_result.output_dir) + assert not hasattr(source_module, "scale_in_place") + assert source_module.outputs.scale_in_place(np.float64(4.0), np.float64(2.5)) == np.float64(10.0) + + contract_package = tmp_path / "contracts" / "outputs" + subprocess.run( + [ + sys.executable, + "-m", + "x2py", + "generate", + "--pyi", + str(source), + "--out", + str(contract_package), + ], + capture_output=True, + text=True, + check=True, + ) + entry = contract_package / "__init__.pyi" + leaf = contract_package / "outputs.pyi" + assert entry.read_text(encoding="utf-8") == "from . import outputs\n" + leaf_text = leaf.read_text(encoding="utf-8") + assert ( + 'def scale_in_place(\n value: Float64,\n factor: Float64\n) -> Returns["value", Float64]: ...' + in leaf_text + ) + + native_object = _compile_native_object(source, tmp_path / "native") + package_result = build_pyi_extension( + entry, + native_objects=[native_object], + native_include_dirs=[native_object.parent], + output_dir=tmp_path / "package_build", + ) + sys.modules.pop("outputs.outputs", None) + package_module = _import_from_build_dir(package_result.module_name, package_result.output_dir) + + assert package_result.module_name == "outputs" + assert not hasattr(package_module, "scale_in_place") + assert package_module.outputs.scale_in_place(np.float64(5.0), np.float64(3.0)) == np.float64(15.0) + + leaf_result = build_pyi_extension( + leaf, + native_objects=[native_object], + native_include_dirs=[native_object.parent], + output_name="leaf_outputs", + output_dir=tmp_path / "leaf_build", + ) + leaf_module = _import_from_build_dir(leaf_result.module_name, leaf_result.output_dir) + + assert leaf_module.scale_in_place(np.float64(6.0), np.float64(4.0)) == np.float64(24.0) + assert not hasattr(leaf_module, "outputs") + + entry.write_text("from .outputs import *\n", encoding="utf-8") + flat_result = build_pyi_extension( + entry, + native_objects=[native_object], + native_include_dirs=[native_object.parent], + output_name="flat_outputs", + output_dir=tmp_path / "flat_build", + ) + flat_module = _import_from_build_dir(flat_result.module_name, flat_result.output_dir) + + assert flat_module.scale_in_place(np.float64(7.0), np.float64(5.0)) == np.float64(35.0) + assert not hasattr(flat_module, "outputs") diff --git a/tests/wrapper/fortran/layout_rules/test_wrapper_guide_layout.py b/tests/wrapper/fortran/layout_rules/test_wrapper_guide_layout.py index 1b9c5950d..ca26972e5 100644 --- a/tests/wrapper/fortran/layout_rules/test_wrapper_guide_layout.py +++ b/tests/wrapper/fortran/layout_rules/test_wrapper_guide_layout.py @@ -398,18 +398,18 @@ def test_wrapper_plan_migration_matrix_tracks_collected_wrapper_nodes(): assert _wrapper_plan_migration_summary_counts() == expected_summary -def test_wrapper_language_suite_and_user_guide_link_current_subject_paths(): +def test_wrapper_language_suite_and_reference_link_current_subject_paths(): root_test_modules = sorted(path.name for path in WRAPPER_SUITE_ROOT.glob("test_*.py")) assert root_test_modules == [] assert (WRAPPER_SUITE_ROOT / "README.md").is_file() assert "fortran/README.md" in (WRAPPER_SUITE_ROOT / "README.md").read_text(encoding="utf-8") - guide = (DOCS_ROOT / "user/guide/fortran-wrapper.md").read_text(encoding="utf-8") + reference = (DOCS_ROOT / "user/reference/fortran-wrapper.md").read_text(encoding="utf-8") runtime_paths = [test_path for test_path in SUBJECT_TEST_PATHS if not test_path.startswith("layout_rules/")] - missing = [test_path for test_path in runtime_paths if test_path not in guide] + missing = [test_path for test_path in runtime_paths if test_path not in reference] assert missing == [] - assert "- [x]" not in guide - assert "- [ ]" not in guide + assert "- [x]" not in reference + assert "- [ ]" not in reference def test_stale_wrapper_paths_are_rejected_after_stage_one_moves(): diff --git a/tests/wrapper/fortran/module_state/contracts/fallocatable_views_f90/fallocatable_views_f90.pyi b/tests/wrapper/fortran/module_state/contracts/fallocatable_views_f90/fallocatable_views_f90.pyi index bdab7e9b8..cc9de5201 100644 --- a/tests/wrapper/fortran/module_state/contracts/fallocatable_views_f90/fallocatable_views_f90.pyi +++ b/tests/wrapper/fortran/module_state/contracts/fallocatable_views_f90/fallocatable_views_f90.pyi @@ -53,7 +53,7 @@ def make_values( n: Int32 ) -> Allocatable[Float64[:]]: ... -@native_call([Addr(Arg(0)), Addr(Arg(1))]) +@native_call([Addr(Arg(0)), Addr(Arg(1)), Return('values', 0)]) def make_matrix( n: Int32, m: Int32 diff --git a/tests/wrapper/fortran/module_state/test_allocatable_replacement.py b/tests/wrapper/fortran/module_state/test_allocatable_replacement.py index 974224b4a..499eb4ef5 100644 --- a/tests/wrapper/fortran/module_state/test_allocatable_replacement.py +++ b/tests/wrapper/fortran/module_state/test_allocatable_replacement.py @@ -10,9 +10,11 @@ import pytest from x2py import build_pyi_extension +from x2py.contracts import Allocatable, Float64 from x2py.runtime.handles import AllocatableArray from tests.wrapper.fortran._support import ( WRAPPER_TEST_ROOT, + _build_text_and_import, _compile_native_object, _import_from_build_dir, _build_source_or_generated_pyi_and_import, @@ -23,6 +25,48 @@ ALLOCATABLE_INOUT_F90_SOURCE = wrapper_source("fallocatable_inout_f90.f90") ALLOCATABLE_FACTORY_F90_SOURCE = wrapper_source("fallocatable_views_f90.f90") CONTRACT_FIXTURES = Path(__file__).parent / "contracts" +ALLOCATABLE_CROSS_A_SOURCE = """\ +module fallocatable_cross_a +contains + subroutine select_a(values) + real(8), allocatable, intent(inout) :: values(:) + if (allocated(values)) deallocate(values) + allocate(values(2)) + values = [1.0_8, 2.0_8] + end subroutine select_a + + function total_a(values) result(total) + real(8), allocatable, intent(in) :: values(:) + real(8) :: total + if (allocated(values)) then + total = sum(values) + else + total = -1.0_8 + end if + end function total_a +end module fallocatable_cross_a +""" +ALLOCATABLE_CROSS_B_SOURCE = """\ +module fallocatable_cross_b +contains + subroutine select_b(values) + real(8), allocatable, intent(inout) :: values(:) + if (allocated(values)) deallocate(values) + allocate(values(3)) + values = [10.0_8, 20.0_8, 30.0_8] + end subroutine select_b + + function total_b(values) result(total) + real(8), allocatable, intent(in) :: values(:) + real(8) :: total + if (allocated(values)) then + total = sum(values) + else + total = -1.0_8 + end if + end function total_b +end module fallocatable_cross_b +""" def _allocatable_replacement_build_dir(tmp_path: Path, build_mode: str) -> Path: @@ -85,6 +129,13 @@ def test_allocatable_inout_arrays_mutate_and_return_the_same_handle( assert returned is values np.testing.assert_allclose(values.to_numpy(), np.array([1.0, 2.0], dtype=np.float64)) + fresh = Allocatable[Float64[:]]() + assert fresh.allocated is False + assert module.replace_values(fresh, np.int32(3)) is fresh + np.testing.assert_allclose(fresh.to_numpy(), np.array([3.0, 6.0, 9.0], dtype=np.float64)) + fresh.close() + assert fresh.closed is True + del values gc.collect() @@ -147,6 +198,45 @@ def build_values(n: Int32) -> Allocatable[Float64[:]]: ... values.close() +def test_caller_created_allocatable_crosses_separately_built_extensions(tmp_path: Path): + first_dir = tmp_path / "first" + second_dir = tmp_path / "second" + first_dir.mkdir() + second_dir.mkdir() + first = _build_text_and_import( + ALLOCATABLE_CROSS_A_SOURCE, + "fallocatable_cross_a.f90", + first_dir, + { + "bind_c_fallocatable_cross_a_wrapper.f90", + "fallocatable_cross_a_wrapper.c", + "fallocatable_cross_a_wrapper.h", + }, + ) + second = _build_text_and_import( + ALLOCATABLE_CROSS_B_SOURCE, + "fallocatable_cross_b.f90", + second_dir, + { + "bind_c_fallocatable_cross_b_wrapper.f90", + "fallocatable_cross_b_wrapper.c", + "fallocatable_cross_b_wrapper.h", + }, + ) + values = Allocatable[Float64[:]]() + + assert first.select_a(values) is values + np.testing.assert_array_equal(values.to_numpy(), np.array([1.0, 2.0])) + assert second.total_b(values) == np.float64(3.0) + + assert second.select_b(values) is values + np.testing.assert_array_equal(values.to_numpy(), np.array([10.0, 20.0, 30.0])) + assert first.total_a(values) == np.float64(60.0) + + values.close() + assert values.closed is True + + @pytest.mark.skipif(shutil.which("valgrind") is None, reason="Valgrind is required for native ownership checks") def test_allocatable_replacement_has_no_native_memory_errors(pyi_parity_build_mode: str, tmp_path: Path): _build_source_or_generated_pyi_and_import( diff --git a/tests/wrapper/fortran/module_state/test_allocatable_views.py b/tests/wrapper/fortran/module_state/test_allocatable_views.py index bd69a7f77..b65237bb3 100644 --- a/tests/wrapper/fortran/module_state/test_allocatable_views.py +++ b/tests/wrapper/fortran/module_state/test_allocatable_views.py @@ -13,6 +13,7 @@ _build_text_and_import, _compile_native_object, _import_from_build_dir, + _require_maybe_unallocated_function_result_support, _sole_native_module, wrapper_source, ) @@ -148,15 +149,15 @@ value => target_scale end subroutine create_pointer - function maybe_allocatable(flag) result(value) + subroutine maybe_allocatable(flag, value) integer(4), intent(in) :: flag - real(8), allocatable :: value + real(8), allocatable, intent(out) :: value if (flag /= 0) then allocate(value) value = 3.5_8 end if - end function maybe_allocatable + end subroutine maybe_allocatable function maybe_pointer(flag) result(value) integer(4), intent(in) :: flag @@ -268,6 +269,29 @@ def _scalar_descriptor_module(build_mode: str, tmp_path: Path): return _sole_native_module(_import_from_build_dir(result.module_name, result.output_dir)) +def _maybe_unallocated_direct_result_module(tmp_path: Path): + native_object = _compile_native_object(ALLOCATABLE_VIEW_F90_SOURCE, tmp_path / "native") + contract_dir = tmp_path / "contracts" / "fallocatable_views_f90" + contract_dir.mkdir(parents=True) + (contract_dir / "__init__.pyi").write_text("from . import fallocatable_views_f90\n", encoding="utf-8") + contract_text = (CONTRACT_FIXTURES / "fallocatable_views_f90" / "fallocatable_views_f90.pyi").read_text( + encoding="utf-8" + ) + contract_text = contract_text.replace("Int32, Pass", "Int32, MaybeUnallocated, Pass") + contract_text = contract_text.replace( + "def make_values(\n n: Int32\n) -> Allocatable[Float64[:]]: ...", + "def make_values(\n n: Int32\n) -> Annotated[Allocatable[Float64[:]], MaybeUnallocated]: ...", + ) + (contract_dir / "fallocatable_views_f90.pyi").write_text(contract_text, encoding="utf-8") + result = build_pyi_extension( + contract_dir / "__init__.pyi", + native_objects=[native_object], + native_include_dirs=[native_object.parent], + output_dir=tmp_path / "pyi_build", + ) + return _sole_native_module(_import_from_build_dir(result.module_name, result.output_dir)) + + def test_allocatable_module_fields_and_results_expose_lifetime_safe_handles( pyi_parity_build_mode: str, tmp_path: Path, @@ -340,14 +364,12 @@ def test_allocatable_module_fields_and_results_expose_lifetime_safe_handles( made_values = module.make_values(np.int32(3)) np.testing.assert_allclose(made_values.to_numpy(), np.array([3.0, 6.0, 9.0], dtype=np.float64)) - assert module.make_values(np.int32(0)).allocated is False made_matrix = module.make_matrix(np.int32(2), np.int32(2)) np.testing.assert_allclose( made_matrix.to_numpy(), np.array([[111.0, 121.0], [112.0, 122.0]], dtype=np.float64), ) - assert module.make_matrix(np.int32(2), np.int32(0)).allocated is False retained_result_view = made_values.to_numpy() del made_values @@ -385,6 +407,21 @@ def test_allocatable_module_fields_and_results_expose_lifetime_safe_handles( _ = built_values.allocated +def test_maybe_unallocated_direct_allocatable_results_preserve_unallocated_state(tmp_path: Path): + _require_maybe_unallocated_function_result_support() + module = _maybe_unallocated_direct_result_module(tmp_path) + + made_values = module.make_values(np.int32(3)) + np.testing.assert_allclose(made_values.to_numpy(), np.array([3.0, 6.0, 9.0], dtype=np.float64)) + assert module.make_values(np.int32(0)).allocated is False + + made_matrix = module.make_matrix(np.int32(2), np.int32(2)) + np.testing.assert_allclose( + made_matrix.to_numpy(), + np.array([[111.0, 121.0], [112.0, 122.0]], dtype=np.float64), + ) + + def test_scalar_descriptor_module_variables_return_copied_optional_values( pyi_parity_build_mode: str, tmp_path: Path, diff --git a/tests/wrapper/fortran/module_state/test_module_state.py b/tests/wrapper/fortran/module_state/test_module_state.py index 350005493..0e1e67d4f 100644 --- a/tests/wrapper/fortran/module_state/test_module_state.py +++ b/tests/wrapper/fortran/module_state/test_module_state.py @@ -41,6 +41,17 @@ def test_scalar_module_variables_use_attributes_and_parameters_have_no_native_se pyi_parity_build_mode, ) + module_docstring = module.__doc__ + assert module_docstring.startswith("fmodule_vars_f90\n\nModule Attributes") + assert "fmodule_vars_f90.fmodule_vars_f90" not in module_docstring + assert module_docstring.index("Module Attributes") < module_docstring.index("Functions") + assert module_docstring.index("Functions") < module_docstring.index("Classes") + assert "nmax : int32\n Read-only constant." in module_docstring + assert "counter : int32" in module_docstring + assert "scale : float64" in module_docstring + assert "saved_counter : int32" in module_docstring + assert "Assignment writes through to native storage." not in module_docstring + assert module.nmax == np.int32(12) assert isinstance(module.black, module.rgb_color) assert module.black.r == np.int32(0) diff --git a/tests/wrapper/fortran/multiple_files/test_multi_source_builds.py b/tests/wrapper/fortran/multiple_files/test_multi_source_builds.py index 70252b0c1..634eb6191 100644 --- a/tests/wrapper/fortran/multiple_files/test_multi_source_builds.py +++ b/tests/wrapper/fortran/multiple_files/test_multi_source_builds.py @@ -206,6 +206,34 @@ def test_multi_file_modules_build_one_merged_extension(tmp_path: Path): assert "use second_api" in bridge +def test_generated_child_modules_are_importable_submodules(tmp_path: Path): + _module, payload = _build_sources_and_import( + [ + ("first_api.f90", _source_text(FIRST_API_SOURCE)), + ("second_api.f90", _source_text(SECOND_API_SOURCE)), + ], + tmp_path, + ) + + module_name = str(payload["module_name"]) + for name in (module_name, f"{module_name}.first_api", f"{module_name}.second_api"): + sys.modules.pop(name, None) + sys.path.insert(0, str(tmp_path)) + try: + from first_api.first_api import add_one + from first_api.second_api import double_value + + root = importlib.import_module("first_api") + assert sys.modules["first_api.first_api"] is root.first_api + assert sys.modules["first_api.second_api"] is root.second_api + assert add_one(np.int32(4)) == 5 + assert double_value(np.int32(4)) == 10 + finally: + sys.path.remove(str(tmp_path)) + for name in (f"{module_name}.second_api", f"{module_name}.first_api", module_name): + sys.modules.pop(name, None) + + def test_multi_file_standalone_procedures_build_one_merged_extension(tmp_path: Path): module, payload = _build_sources_and_import( [ @@ -216,8 +244,8 @@ def test_multi_file_standalone_procedures_build_one_merged_extension(tmp_path: P ) assert payload["module_name"] == "standalone_api" - assert module.add_one(np.int32(4)) == 5 - assert module.double_value(np.int32(4)) == 8 + assert module.add_one(np.int32(4)) == (5, 4) + assert module.double_value(np.int32(4)) == (8, 4) def test_multi_source_pyi_out_writes_one_flat_combined_package(tmp_path: Path): diff --git a/tests/wrapper/fortran/naming/contracts/fclass_overloads_phase9/foverloads_f90.pyi b/tests/wrapper/fortran/naming/contracts/fclass_overloads_phase9/foverloads_f90.pyi index 1a093ac8f..1076408b9 100644 --- a/tests/wrapper/fortran/naming/contracts/fclass_overloads_phase9/foverloads_f90.pyi +++ b/tests/wrapper/fortran/naming/contracts/fclass_overloads_phase9/foverloads_f90.pyi @@ -16,9 +16,11 @@ class accumulator: @native_call([Pass(), Addr(Arg(0))]) def add_real(self, value: Float64) -> None: ... + @bind("add") @overload("accumulator_add_integer") def add(self, value: Int32) -> None: ... + @bind("add") @overload("accumulator_add_real") def add(self, value: Float64) -> None: ... diff --git a/tests/wrapper/fortran/naming/contracts/fconstructor_overloads_phase9/foverloads_f90.pyi b/tests/wrapper/fortran/naming/contracts/fconstructor_overloads_phase9/foverloads_f90.pyi index f79788b17..42b6cc1da 100644 --- a/tests/wrapper/fortran/naming/contracts/fconstructor_overloads_phase9/foverloads_f90.pyi +++ b/tests/wrapper/fortran/naming/contracts/fconstructor_overloads_phase9/foverloads_f90.pyi @@ -4,9 +4,11 @@ from x2py.contracts import Addr, Arg, Float64, Int32, Pass, bind, native_call, o class accumulator: def __init__(self, *, total: Float64 = 0.0) -> None: ... + @bind("add") @overload("accumulator_add_integer") def __init__(self, value: Int32) -> None: ... + @bind("add") @overload("accumulator_add_real") def __init__(self, value: Float64) -> None: ... @@ -22,9 +24,11 @@ class accumulator: @native_call([Pass(), Addr(Arg(0))]) def add_real(self, value: Float64) -> None: ... + @bind("add") @overload("accumulator_add_integer") def add(self, value: Int32) -> None: ... + @bind("add") @overload("accumulator_add_real") def add(self, value: Float64) -> None: ... diff --git a/tests/wrapper/fortran/naming/contracts/foperators_f90/foperators_f90.pyi b/tests/wrapper/fortran/naming/contracts/foperators_f90/foperators_f90.pyi index f7688635d..bd7ad1c79 100644 --- a/tests/wrapper/fortran/naming/contracts/foperators_f90/foperators_f90.pyi +++ b/tests/wrapper/fortran/naming/contracts/foperators_f90/foperators_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Bool, Float64, Int32, Pass, Returns, bind, native_call, overload, private +from x2py.contracts import Addr, Annotated, Arg, Bool, Float64, Int32, Pass, Polymorphic, Returns, bind, native_call, overload, private class vector: def __init__( @@ -430,15 +430,17 @@ def assign_vector_real( @private @native_call([Arg(0), Addr(Arg(1))]) def counter_add_integer( - self: counter, + self: Annotated[counter, Polymorphic], right: Int32 ) -> counter: ... +@bind("convert") @overload("convert_integer") def convert( value: Int32 ) -> Int32: ... +@bind("convert") @overload("convert_real") def convert( value: Float64 diff --git a/tests/wrapper/fortran/naming/contracts/foverloads_f90/foverloads_f90.pyi b/tests/wrapper/fortran/naming/contracts/foverloads_f90/foverloads_f90.pyi index 677fd7887..482c2e0bc 100644 --- a/tests/wrapper/fortran/naming/contracts/foverloads_f90/foverloads_f90.pyi +++ b/tests/wrapper/fortran/naming/contracts/foverloads_f90/foverloads_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Complex128, Float64, Int32, Pass, bind, native_call, overload, private +from x2py.contracts import Addr, Annotated, Arg, Complex128, Float64, Int32, Pass, Polymorphic, bind, native_call, overload, private class accumulator: def __init__( @@ -25,12 +25,14 @@ class accumulator: value: Float64 ) -> None: ... + @bind("add") @overload("accumulator_add_integer") def add( self, value: Int32 ) -> None: ... + @bind("add") @overload("accumulator_add_real") def add( self, @@ -88,47 +90,54 @@ def inspect_sample( @private @native_call([Arg(0), Addr(Arg(1))]) def accumulator_add_integer( - self: accumulator, + self: Annotated[accumulator, Polymorphic], value: Int32 ) -> None: ... @private @native_call([Arg(0), Addr(Arg(1))]) def accumulator_add_real( - self: accumulator, + self: Annotated[accumulator, Polymorphic], value: Float64 ) -> None: ... +@bind("convert") @overload("convert_integer") def convert( value: Int32 ) -> Int32: ... +@bind("convert") @overload("convert_real") def convert( value: Float64 ) -> Float64: ... +@bind("convert") @overload("convert_complex") def convert( value: Complex128 ) -> Complex128: ... +@bind("summarize") @overload("summarize_scalar") def summarize( value: Float64 ) -> Float64: ... +@bind("summarize") @overload("summarize_vector") def summarize( values: Float64[::] ) -> Float64: ... +@bind("inspect") @overload("inspect_accumulator") def inspect( value: accumulator ) -> Float64: ... +@bind("inspect") @overload("inspect_sample") def inspect( value: sample diff --git a/tests/wrapper/fortran/naming/contracts/foverloads_fixed/foverloads_fixed.pyi b/tests/wrapper/fortran/naming/contracts/foverloads_fixed/foverloads_fixed.pyi index e3d88ceb3..f1d2e93e0 100644 --- a/tests/wrapper/fortran/naming/contracts/foverloads_fixed/foverloads_fixed.pyi +++ b/tests/wrapper/fortran/naming/contracts/foverloads_fixed/foverloads_fixed.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Float64, Int32, native_call, overload, private +from x2py.contracts import Addr, Arg, Float64, Int32, bind, native_call, overload, private @private @native_call([Addr(Arg(0))]) @@ -12,11 +12,13 @@ def convert_real( value: Float64 ) -> Float64: ... +@bind("convert") @overload("convert_integer") def convert( value: Int32 ) -> Int32: ... +@bind("convert") @overload("convert_real") def convert( value: Float64 diff --git a/tests/wrapper/fortran/naming/test_defined_operators.py b/tests/wrapper/fortran/naming/test_defined_operators.py index 027f266b8..1c09a8fb3 100644 --- a/tests/wrapper/fortran/naming/test_defined_operators.py +++ b/tests/wrapper/fortran/naming/test_defined_operators.py @@ -44,6 +44,10 @@ def offset(value): left = vector(5.0) right = vector(2.0) + assert "__add__(*args, **kwargs)" in module.vector.__doc__ + assert "__add__(right: vector) -> vector" in module.vector.__add__.__doc__ + assert "add_vectors" not in module.vector.__add__.__doc__ + assert module.convert(np.int32(2)) == np.int32(12) assert module.convert(np.float64(2.0)) == np.float64(2.5) assert (left + right).value == np.float64(7.0) diff --git a/tests/wrapper/fortran/real_libraries/contracts/blas/__init__.pyi b/tests/wrapper/fortran/real_libraries/contracts/blas/__init__.pyi index c3dd7454b..e0a615266 100644 --- a/tests/wrapper/fortran/real_libraries/contracts/blas/__init__.pyi +++ b/tests/wrapper/fortran/real_libraries/contracts/blas/__init__.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Flat, Float32, Float64, Int32, String, bind, external, native_call +from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Flat, Float32, Float64, Int32, Returns, String, bind, external, native_call @bind("CAXPY") @external @@ -10,7 +10,7 @@ def caxpy( INCX: Int32, CY: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["CA", Complex64], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("CCOPY") @external @@ -21,7 +21,7 @@ def ccopy( INCX: Int32, CY: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("CDOTC") @external @@ -32,7 +32,7 @@ def cdotc( INCX: Int32, CY: Complex64[Flat], INCY: Int32 -) -> Complex64: ... +) -> tuple[Complex64, Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("CDOTU") @external @@ -43,7 +43,7 @@ def cdotu( INCX: Int32, CY: Complex64[Flat], INCY: Int32 -) -> Complex64: ... +) -> tuple[Complex64, Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("CGBMV") @external @@ -62,7 +62,7 @@ def cgbmv( BETA: Complex64, Y: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex64], Returns["INCY", Int32]]: ... @bind("CGEMM") @external @@ -81,7 +81,7 @@ def cgemm( BETA: Complex64, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex64], Returns["LDC", Int32]]: ... @bind("CGEMMTR") @external @@ -100,7 +100,7 @@ def cgemmtr( BETA: Complex64, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex64], Returns["LDC", Int32]]: ... @bind("CGEMV") @external @@ -117,7 +117,7 @@ def cgemv( BETA: Complex64, Y: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex64], Returns["INCY", Int32]]: ... @bind("CGERC") @external @@ -132,7 +132,7 @@ def cgerc( INCY: Int32, A: Complex64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("CGERU") @external @@ -147,7 +147,7 @@ def cgeru( INCY: Int32, A: Complex64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("CHBMV") @external @@ -164,7 +164,7 @@ def chbmv( BETA: Complex64, Y: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex64], Returns["INCY", Int32]]: ... @bind("CHEMM") @external @@ -182,7 +182,7 @@ def chemm( BETA: Complex64, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex64], Returns["LDC", Int32]]: ... @bind("CHEMV") @external @@ -198,7 +198,7 @@ def chemv( BETA: Complex64, Y: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex64], Returns["INCY", Int32]]: ... @bind("CHER") @external @@ -211,7 +211,7 @@ def cher( INCX: Int32, A: Complex64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["LDA", Int32]]: ... @bind("CHER2") @external @@ -226,7 +226,7 @@ def cher2( INCY: Int32, A: Complex64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("CHER2K") @external @@ -244,7 +244,7 @@ def cher2k( BETA: Float32, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float32], Returns["LDC", Int32]]: ... @bind("CHERK") @external @@ -260,7 +260,7 @@ def cherk( BETA: Float32, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["BETA", Float32], Returns["LDC", Int32]]: ... @bind("CHPMV") @external @@ -275,7 +275,7 @@ def chpmv( BETA: Complex64, Y: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["BETA", Complex64], Returns["INCY", Int32]]: ... @bind("CHPR") @external @@ -287,7 +287,7 @@ def chpr( X: Complex64[Flat], INCX: Int32, AP: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32]]: ... @bind("CHPR2") @external @@ -301,7 +301,7 @@ def chpr2( Y: Complex64[Flat], INCY: Int32, AP: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("CROTG") @external @@ -311,7 +311,7 @@ def crotg( b: Complex64, c: Float32, s: Complex64 -) -> None: ... +) -> tuple[Returns["a", Complex64], Returns["b", Complex64], Returns["c", Float32], Returns["s", Complex64]]: ... @bind("CSCAL") @external @@ -321,7 +321,7 @@ def cscal( CA: Complex64, CX: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["CA", Complex64], Returns["INCX", Int32]]: ... @bind("CSROT") @external @@ -334,7 +334,7 @@ def csrot( INCY: Int32, C: Float32, S: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Float32], Returns["S", Float32]]: ... @bind("CSSCAL") @external @@ -344,7 +344,7 @@ def csscal( SA: Float32, CX: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SA", Float32], Returns["INCX", Int32]]: ... @bind("CSWAP") @external @@ -355,7 +355,7 @@ def cswap( INCX: Int32, CY: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("CSYMM") @external @@ -373,7 +373,7 @@ def csymm( BETA: Complex64, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex64], Returns["LDC", Int32]]: ... @bind("CSYR2K") @external @@ -391,7 +391,7 @@ def csyr2k( BETA: Complex64, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex64], Returns["LDC", Int32]]: ... @bind("CSYRK") @external @@ -407,7 +407,7 @@ def csyrk( BETA: Complex64, C: Complex64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["BETA", Complex64], Returns["LDC", Int32]]: ... @bind("CTBMV") @external @@ -422,7 +422,7 @@ def ctbmv( LDA: Int32, X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("CTBSV") @external @@ -437,7 +437,7 @@ def ctbsv( LDA: Int32, X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("CTPMV") @external @@ -450,7 +450,7 @@ def ctpmv( AP: Complex64[Flat], X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("CTPSV") @external @@ -463,7 +463,7 @@ def ctpsv( AP: Complex64[Flat], X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("CTRMM") @external @@ -480,7 +480,7 @@ def ctrmm( LDA: Int32, B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("CTRMV") @external @@ -494,7 +494,7 @@ def ctrmv( LDA: Int32, X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("CTRSM") @external @@ -511,7 +511,7 @@ def ctrsm( LDA: Int32, B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("CTRSV") @external @@ -525,7 +525,7 @@ def ctrsv( LDA: Int32, X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("DASUM") @external @@ -534,7 +534,7 @@ def dasum( N: Int32, DX: Float64[Flat], INCX: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("DAXPY") @external @@ -546,14 +546,14 @@ def daxpy( INCX: Int32, DY: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["DA", Float64], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("DCABS1") @external @native_call([Addr(Arg(0))]) def dcabs1( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("DCOPY") @external @@ -564,7 +564,7 @@ def dcopy( INCX: Int32, DY: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("DDOT") @external @@ -575,7 +575,7 @@ def ddot( INCX: Int32, DY: Float64[Flat], INCY: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("DGBMV") @external @@ -594,7 +594,7 @@ def dgbmv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DGEMM") @external @@ -613,7 +613,7 @@ def dgemm( BETA: Float64, C: Float64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float64], Returns["LDC", Int32]]: ... @bind("DGEMMTR") @external @@ -632,7 +632,7 @@ def dgemmtr( BETA: Float64, C: Float64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float64], Returns["LDC", Int32]]: ... @bind("DGEMV") @external @@ -649,7 +649,7 @@ def dgemv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DGER") @external @@ -664,7 +664,7 @@ def dger( INCY: Int32, A: Float64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("DNRM2") @external @@ -673,7 +673,7 @@ def dnrm2( n: Int32, x: Float64[Flat], incx: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["n", Int32], Returns["incx", Int32]]: ... @bind("DROT") @external @@ -686,7 +686,7 @@ def drot( INCY: Int32, C: Float64, S: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Float64], Returns["S", Float64]]: ... @bind("DROTG") @external @@ -696,7 +696,7 @@ def drotg( b: Float64, c: Float64, s: Float64 -) -> None: ... +) -> tuple[Returns["a", Float64], Returns["b", Float64], Returns["c", Float64], Returns["s", Float64]]: ... @bind("DROTM") @external @@ -708,7 +708,7 @@ def drotm( DY: Float64[Flat], INCY: Int32, DPARAM: Float64[5] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("DROTMG") @external @@ -719,7 +719,7 @@ def drotmg( DX1: Float64, DY1: Float64, DPARAM: Float64[5] -) -> None: ... +) -> tuple[Returns["DD1", Float64], Returns["DD2", Float64], Returns["DX1", Float64], Returns["DY1", Float64]]: ... @bind("DSBMV") @external @@ -736,7 +736,7 @@ def dsbmv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DSCAL") @external @@ -746,7 +746,7 @@ def dscal( DA: Float64, DX: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["DA", Float64], Returns["INCX", Int32]]: ... @bind("DSDOT") @external @@ -757,7 +757,7 @@ def dsdot( INCX: Int32, SY: Float32[Flat], INCY: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("DSPMV") @external @@ -772,7 +772,7 @@ def dspmv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DSPR") @external @@ -784,7 +784,7 @@ def dspr( X: Float64[Flat], INCX: Int32, AP: Float64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32]]: ... @bind("DSPR2") @external @@ -798,7 +798,7 @@ def dspr2( Y: Float64[Flat], INCY: Int32, AP: Float64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("DSWAP") @external @@ -809,7 +809,7 @@ def dswap( INCX: Int32, DY: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("DSYMM") @external @@ -827,7 +827,7 @@ def dsymm( BETA: Float64, C: Float64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float64], Returns["LDC", Int32]]: ... @bind("DSYMV") @external @@ -843,7 +843,7 @@ def dsymv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DSYR") @external @@ -856,7 +856,7 @@ def dsyr( INCX: Int32, A: Float64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["LDA", Int32]]: ... @bind("DSYR2") @external @@ -871,7 +871,7 @@ def dsyr2( INCY: Int32, A: Float64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("DSYR2K") @external @@ -889,7 +889,7 @@ def dsyr2k( BETA: Float64, C: Float64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float64], Returns["LDC", Int32]]: ... @bind("DSYRK") @external @@ -905,7 +905,7 @@ def dsyrk( BETA: Float64, C: Float64[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["BETA", Float64], Returns["LDC", Int32]]: ... @bind("DTBMV") @external @@ -920,7 +920,7 @@ def dtbmv( LDA: Int32, X: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("DTBSV") @external @@ -935,7 +935,7 @@ def dtbsv( LDA: Int32, X: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("DTPMV") @external @@ -948,7 +948,7 @@ def dtpmv( AP: Float64[Flat], X: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("DTPSV") @external @@ -961,7 +961,7 @@ def dtpsv( AP: Float64[Flat], X: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("DTRMM") @external @@ -978,7 +978,7 @@ def dtrmm( LDA: Int32, B: Float64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("DTRMV") @external @@ -992,7 +992,7 @@ def dtrmv( LDA: Int32, X: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("DTRSM") @external @@ -1009,7 +1009,7 @@ def dtrsm( LDA: Int32, B: Float64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("DTRSV") @external @@ -1023,7 +1023,7 @@ def dtrsv( LDA: Int32, X: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("DZASUM") @external @@ -1032,7 +1032,7 @@ def dzasum( N: Int32, ZX: Complex128[Flat], INCX: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("DZNRM2") @external @@ -1041,7 +1041,7 @@ def dznrm2( n: Int32, x: Complex128[Flat], incx: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["n", Int32], Returns["incx", Int32]]: ... @bind("ICAMAX") @external @@ -1050,7 +1050,7 @@ def icamax( N: Int32, CX: Complex64[Flat], INCX: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("IDAMAX") @external @@ -1059,7 +1059,7 @@ def idamax( N: Int32, DX: Float64[Flat], INCX: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("ISAMAX") @external @@ -1068,7 +1068,7 @@ def isamax( N: Int32, SX: Float32[Flat], INCX: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("IZAMAX") @external @@ -1077,7 +1077,7 @@ def izamax( N: Int32, ZX: Complex128[Flat], INCX: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("LSAME") @external @@ -1093,7 +1093,7 @@ def sasum( N: Int32, SX: Float32[Flat], INCX: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("SAXPY") @external @@ -1105,14 +1105,14 @@ def saxpy( INCX: Int32, SY: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SA", Float32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("SCABS1") @external @native_call([Addr(Arg(0))]) def scabs1( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("SCASUM") @external @@ -1121,7 +1121,7 @@ def scasum( N: Int32, CX: Complex64[Flat], INCX: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("SCNRM2") @external @@ -1130,7 +1130,7 @@ def scnrm2( n: Int32, x: Complex64[Flat], incx: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["n", Int32], Returns["incx", Int32]]: ... @bind("SCOPY") @external @@ -1141,7 +1141,7 @@ def scopy( INCX: Int32, SY: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("SDOT") @external @@ -1152,7 +1152,7 @@ def sdot( INCX: Int32, SY: Float32[Flat], INCY: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("SDSDOT") @external @@ -1164,7 +1164,7 @@ def sdsdot( INCX: Int32, SY: Float32[Flat], INCY: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["SB", Float32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("SGBMV") @external @@ -1183,7 +1183,7 @@ def sgbmv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SGEMM") @external @@ -1202,7 +1202,7 @@ def sgemm( BETA: Float32, C: Float32[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float32], Returns["LDC", Int32]]: ... @bind("SGEMMTR") @external @@ -1221,7 +1221,7 @@ def sgemmtr( BETA: Float32, C: Float32[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float32], Returns["LDC", Int32]]: ... @bind("SGEMV") @external @@ -1238,7 +1238,7 @@ def sgemv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SGER") @external @@ -1253,7 +1253,7 @@ def sger( INCY: Int32, A: Float32[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("SNRM2") @external @@ -1262,7 +1262,7 @@ def snrm2( n: Int32, x: Float32[Flat], incx: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["n", Int32], Returns["incx", Int32]]: ... @bind("SROT") @external @@ -1275,7 +1275,7 @@ def srot( INCY: Int32, C: Float32, S: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Float32], Returns["S", Float32]]: ... @bind("SROTG") @external @@ -1285,7 +1285,7 @@ def srotg( b: Float32, c: Float32, s: Float32 -) -> None: ... +) -> tuple[Returns["a", Float32], Returns["b", Float32], Returns["c", Float32], Returns["s", Float32]]: ... @bind("SROTM") @external @@ -1297,7 +1297,7 @@ def srotm( SY: Float32[Flat], INCY: Int32, SPARAM: Float32[5] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("SROTMG") @external @@ -1308,7 +1308,7 @@ def srotmg( SX1: Float32, SY1: Float32, SPARAM: Float32[5] -) -> None: ... +) -> tuple[Returns["SD1", Float32], Returns["SD2", Float32], Returns["SX1", Float32], Returns["SY1", Float32]]: ... @bind("SSBMV") @external @@ -1325,7 +1325,7 @@ def ssbmv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SSCAL") @external @@ -1335,7 +1335,7 @@ def sscal( SA: Float32, SX: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SA", Float32], Returns["INCX", Int32]]: ... @bind("SSPMV") @external @@ -1350,7 +1350,7 @@ def sspmv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SSPR") @external @@ -1362,7 +1362,7 @@ def sspr( X: Float32[Flat], INCX: Int32, AP: Float32[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32]]: ... @bind("SSPR2") @external @@ -1376,7 +1376,7 @@ def sspr2( Y: Float32[Flat], INCY: Int32, AP: Float32[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("SSWAP") @external @@ -1387,7 +1387,7 @@ def sswap( INCX: Int32, SY: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("SSYMM") @external @@ -1405,7 +1405,7 @@ def ssymm( BETA: Float32, C: Float32[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float32], Returns["LDC", Int32]]: ... @bind("SSYMV") @external @@ -1421,7 +1421,7 @@ def ssymv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SSYR") @external @@ -1434,7 +1434,7 @@ def ssyr( INCX: Int32, A: Float32[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["LDA", Int32]]: ... @bind("SSYR2") @external @@ -1449,7 +1449,7 @@ def ssyr2( INCY: Int32, A: Float32[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("SSYR2K") @external @@ -1467,7 +1467,7 @@ def ssyr2k( BETA: Float32, C: Float32[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float32], Returns["LDC", Int32]]: ... @bind("SSYRK") @external @@ -1483,7 +1483,7 @@ def ssyrk( BETA: Float32, C: Float32[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["BETA", Float32], Returns["LDC", Int32]]: ... @bind("STBMV") @external @@ -1498,7 +1498,7 @@ def stbmv( LDA: Int32, X: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("STBSV") @external @@ -1513,7 +1513,7 @@ def stbsv( LDA: Int32, X: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("STPMV") @external @@ -1526,7 +1526,7 @@ def stpmv( AP: Float32[Flat], X: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("STPSV") @external @@ -1539,7 +1539,7 @@ def stpsv( AP: Float32[Flat], X: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("STRMM") @external @@ -1556,7 +1556,7 @@ def strmm( LDA: Int32, B: Float32[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("STRMV") @external @@ -1570,7 +1570,7 @@ def strmv( LDA: Int32, X: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("STRSM") @external @@ -1587,7 +1587,7 @@ def strsm( LDA: Int32, B: Float32[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("STRSV") @external @@ -1601,7 +1601,7 @@ def strsv( LDA: Int32, X: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("XERBLA") @external @@ -1609,7 +1609,7 @@ def strsv( def xerbla( SRNAME: String, INFO: Int32 -) -> None: ... +) -> Returns["INFO", Int32]: ... @bind("XERBLA_ARRAY") @external @@ -1618,7 +1618,7 @@ def xerbla_array( SRNAME_ARRAY: String[1][SRNAME_LEN], SRNAME_LEN: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["SRNAME_LEN", Int32], Returns["INFO", Int32]]: ... @bind("ZAXPY") @external @@ -1630,7 +1630,7 @@ def zaxpy( INCX: Int32, ZY: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ZA", Complex128], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("ZCOPY") @external @@ -1641,7 +1641,7 @@ def zcopy( INCX: Int32, ZY: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("ZDOTC") @external @@ -1652,7 +1652,7 @@ def zdotc( INCX: Int32, ZY: Complex128[Flat], INCY: Int32 -) -> Complex128: ... +) -> tuple[Complex128, Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("ZDOTU") @external @@ -1663,7 +1663,7 @@ def zdotu( INCX: Int32, ZY: Complex128[Flat], INCY: Int32 -) -> Complex128: ... +) -> tuple[Complex128, Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("ZDROT") @external @@ -1676,7 +1676,7 @@ def zdrot( INCY: Int32, C: Float64, S: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Float64], Returns["S", Float64]]: ... @bind("ZDSCAL") @external @@ -1686,7 +1686,7 @@ def zdscal( DA: Float64, ZX: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["DA", Float64], Returns["INCX", Int32]]: ... @bind("ZGBMV") @external @@ -1705,7 +1705,7 @@ def zgbmv( BETA: Complex128, Y: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex128], Returns["INCY", Int32]]: ... @bind("ZGEMM") @external @@ -1724,7 +1724,7 @@ def zgemm( BETA: Complex128, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex128], Returns["LDC", Int32]]: ... @bind("ZGEMMTR") @external @@ -1743,7 +1743,7 @@ def zgemmtr( BETA: Complex128, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex128], Returns["LDC", Int32]]: ... @bind("ZGEMV") @external @@ -1760,7 +1760,7 @@ def zgemv( BETA: Complex128, Y: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex128], Returns["INCY", Int32]]: ... @bind("ZGERC") @external @@ -1775,7 +1775,7 @@ def zgerc( INCY: Int32, A: Complex128[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("ZGERU") @external @@ -1790,7 +1790,7 @@ def zgeru( INCY: Int32, A: Complex128[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("ZHBMV") @external @@ -1807,7 +1807,7 @@ def zhbmv( BETA: Complex128, Y: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex128], Returns["INCY", Int32]]: ... @bind("ZHEMM") @external @@ -1825,7 +1825,7 @@ def zhemm( BETA: Complex128, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex128], Returns["LDC", Int32]]: ... @bind("ZHEMV") @external @@ -1841,7 +1841,7 @@ def zhemv( BETA: Complex128, Y: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex128], Returns["INCY", Int32]]: ... @bind("ZHER") @external @@ -1854,7 +1854,7 @@ def zher( INCX: Int32, A: Complex128[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["LDA", Int32]]: ... @bind("ZHER2") @external @@ -1869,7 +1869,7 @@ def zher2( INCY: Int32, A: Complex128[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["INCY", Int32], Returns["LDA", Int32]]: ... @bind("ZHER2K") @external @@ -1887,7 +1887,7 @@ def zher2k( BETA: Float64, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Float64], Returns["LDC", Int32]]: ... @bind("ZHERK") @external @@ -1903,7 +1903,7 @@ def zherk( BETA: Float64, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["BETA", Float64], Returns["LDC", Int32]]: ... @bind("ZHPMV") @external @@ -1918,7 +1918,7 @@ def zhpmv( BETA: Complex128, Y: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["BETA", Complex128], Returns["INCY", Int32]]: ... @bind("ZHPR") @external @@ -1930,7 +1930,7 @@ def zhpr( X: Complex128[Flat], INCX: Int32, AP: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32]]: ... @bind("ZHPR2") @external @@ -1944,7 +1944,7 @@ def zhpr2( Y: Complex128[Flat], INCY: Int32, AP: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("ZROTG") @external @@ -1954,7 +1954,7 @@ def zrotg( b: Complex128, c: Float64, s: Complex128 -) -> None: ... +) -> tuple[Returns["a", Complex128], Returns["b", Complex128], Returns["c", Float64], Returns["s", Complex128]]: ... @bind("ZSCAL") @external @@ -1964,7 +1964,7 @@ def zscal( ZA: Complex128, ZX: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ZA", Complex128], Returns["INCX", Int32]]: ... @bind("ZSWAP") @external @@ -1975,7 +1975,7 @@ def zswap( INCX: Int32, ZY: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32]]: ... @bind("ZSYMM") @external @@ -1993,7 +1993,7 @@ def zsymm( BETA: Complex128, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex128], Returns["LDC", Int32]]: ... @bind("ZSYR2K") @external @@ -2011,7 +2011,7 @@ def zsyr2k( BETA: Complex128, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32], Returns["BETA", Complex128], Returns["LDC", Int32]]: ... @bind("ZSYRK") @external @@ -2027,7 +2027,7 @@ def zsyrk( BETA: Complex128, C: Complex128[LDC, Flat], LDC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["BETA", Complex128], Returns["LDC", Int32]]: ... @bind("ZTBMV") @external @@ -2042,7 +2042,7 @@ def ztbmv( LDA: Int32, X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("ZTBSV") @external @@ -2057,7 +2057,7 @@ def ztbsv( LDA: Int32, X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("ZTPMV") @external @@ -2070,7 +2070,7 @@ def ztpmv( AP: Complex128[Flat], X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("ZTPSV") @external @@ -2083,7 +2083,7 @@ def ztpsv( AP: Complex128[Flat], X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("ZTRMM") @external @@ -2100,7 +2100,7 @@ def ztrmm( LDA: Int32, B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("ZTRMV") @external @@ -2114,7 +2114,7 @@ def ztrmv( LDA: Int32, X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... @bind("ZTRSM") @external @@ -2131,7 +2131,7 @@ def ztrsm( LDA: Int32, B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("ZTRSV") @external @@ -2145,4 +2145,4 @@ def ztrsv( LDA: Int32, X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INCX", Int32]]: ... diff --git a/tests/wrapper/fortran/real_libraries/contracts/lapack/LA_XISNAN.pyi b/tests/wrapper/fortran/real_libraries/contracts/lapack/LA_XISNAN.pyi index a386f0a79..beeeea9d9 100644 --- a/tests/wrapper/fortran/real_libraries/contracts/lapack/LA_XISNAN.pyi +++ b/tests/wrapper/fortran/real_libraries/contracts/lapack/LA_XISNAN.pyi @@ -1,23 +1,23 @@ -from x2py.contracts import Addr, Arg, Bool, Float32, Float64, bind, native_call, overload +from x2py.contracts import Addr, Arg, Bool, Float32, Float64, Returns, bind, native_call, overload @bind("SISNAN") @native_call([Addr(Arg(0))]) def sisnan( x: Float32 -) -> Bool: ... +) -> tuple[Bool, Returns["x", Float32]]: ... @bind("DISNAN") @native_call([Addr(Arg(0))]) def disnan( x: Float64 -) -> Bool: ... +) -> tuple[Bool, Returns["x", Float64]]: ... @overload("SISNAN") def la_isnan( x: Float32 -) -> Bool: ... +) -> tuple[Bool, Returns["x", Float32]]: ... @overload("DISNAN") def la_isnan( x: Float64 -) -> Bool: ... +) -> tuple[Bool, Returns["x", Float64]]: ... diff --git a/tests/wrapper/fortran/real_libraries/contracts/lapack/__init__.pyi b/tests/wrapper/fortran/real_libraries/contracts/lapack/__init__.pyi index 415845176..def93ada4 100644 --- a/tests/wrapper/fortran/real_libraries/contracts/lapack/__init__.pyi +++ b/tests/wrapper/fortran/real_libraries/contracts/lapack/__init__.pyi @@ -35,7 +35,7 @@ def cbbcsd( RWORK: Float32[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("CBDSQR") @external @@ -56,7 +56,7 @@ def cbdsqr( LDC: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NCVT", Int32], Returns["NRU", Int32], Returns["NCC", Int32], Returns["LDVT", Int32], Returns["LDU", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CGBBRD") @external @@ -81,7 +81,7 @@ def cgbbrd( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NCC", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["LDPT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CGBCON") @external @@ -99,7 +99,7 @@ def cgbcon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CGBEQU") @external @@ -117,7 +117,7 @@ def cgbequ( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CGBEQUB") @external @@ -135,7 +135,7 @@ def cgbequb( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CGBRFS") @external @@ -160,7 +160,7 @@ def cgbrfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CGBRFSX") @external @@ -193,7 +193,7 @@ def cgbrfsx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CGBSV") @external @@ -209,7 +209,7 @@ def cgbsv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CGBSVX") @external @@ -239,7 +239,7 @@ def cgbsvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CGBSVXX") @external @@ -274,7 +274,7 @@ def cgbsvxx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CGBTF2") @external @@ -288,7 +288,7 @@ def cgbtf2( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("CGBTRF") @external @@ -302,7 +302,7 @@ def cgbtrf( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("CGBTRS") @external @@ -319,7 +319,7 @@ def cgbtrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CGEBAK") @external @@ -335,7 +335,7 @@ def cgebak( V: Complex64[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("CGEBAL") @external @@ -349,7 +349,7 @@ def cgebal( IHI: Int32, SCALE: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("CGEBD2") @external @@ -365,7 +365,7 @@ def cgebd2( TAUP: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGEBRD") @external @@ -382,7 +382,7 @@ def cgebrd( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGECON") @external @@ -397,11 +397,11 @@ def cgecon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CGEDMD") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Addr(Arg(15)), Arg(16), Arg(17), Addr(Arg(18)), Arg(19), Addr(Arg(20)), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Return('INFO', 10)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Addr(Arg(15)), Arg(16), Arg(17), Addr(Arg(18)), Arg(19), Addr(Arg(20)), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Return('INFO', 1)]) def cgedmd( JOBS: String[1], JOBZ: String[1], @@ -432,11 +432,11 @@ def cgedmd( LRWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Int32, Returns["EIGS", Complex64[Flat]], Returns["Z", Complex64[LDZ, Flat]], Returns["RES", Float32[Flat]], Returns["B", Complex64[LDB, Flat]], Returns["W", Complex64[LDW, Flat]], Returns["S", Complex64[LDS, Flat]], Returns["ZWORK", Complex64[Flat]], Returns["RWORK", Float32[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("CGEDMDQ") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 2), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Arg(29), Addr(Arg(30)), Arg(31), Addr(Arg(32)), Return('INFO', 12)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 0), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Arg(29), Addr(Arg(30)), Arg(31), Addr(Arg(32)), Return('INFO', 1)]) def cgedmdq( JOBS: String[1], JOBZ: String[1], @@ -471,7 +471,7 @@ def cgedmdq( LWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Returns["X", Complex64[LDX, Flat]], Returns["Y", Complex64[LDY, Flat]], Int32, Returns["EIGS", Complex64[Flat]], Returns["Z", Complex64[LDZ, Flat]], Returns["RES", Float32[Flat]], Returns["B", Complex64[LDB, Flat]], Returns["V", Complex64[LDV, Flat]], Returns["S", Complex64[LDS, Flat]], Returns["ZWORK", Complex64[Flat]], Returns["WORK", Float32[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("CGEEQU") @external @@ -487,7 +487,7 @@ def cgeequ( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CGEEQUB") @external @@ -503,7 +503,7 @@ def cgeequb( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CGEES") @external @@ -524,7 +524,7 @@ def cgees( RWORK: Float32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEESX") @external @@ -548,7 +548,7 @@ def cgeesx( RWORK: Float32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["RCONDE", Float32], Returns["RCONDV", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEEV") @external @@ -568,7 +568,7 @@ def cgeev( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEEVX") @external @@ -596,7 +596,7 @@ def cgeevx( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEHD2") @external @@ -610,7 +610,7 @@ def cgehd2( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGEHRD") @external @@ -625,7 +625,7 @@ def cgehrd( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEJSV") @external @@ -652,7 +652,7 @@ def cgejsv( LRWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGELQ") @external @@ -667,7 +667,7 @@ def cgelq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGELQ2") @external @@ -680,7 +680,7 @@ def cgelq2( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGELQF") @external @@ -694,7 +694,7 @@ def cgelqf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGELQT") @external @@ -709,7 +709,7 @@ def cgelqt( LDT: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CGELQT3") @external @@ -722,7 +722,7 @@ def cgelqt3( T: Complex64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CGELS") @external @@ -739,7 +739,7 @@ def cgels( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGELSD") @external @@ -760,7 +760,7 @@ def cgelsd( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGELSS") @external @@ -780,7 +780,7 @@ def cgelss( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGELST") @external @@ -797,7 +797,7 @@ def cgelst( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGELSY") @external @@ -817,7 +817,7 @@ def cgelsy( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEMLQ") @external @@ -837,7 +837,7 @@ def cgemlq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEMLQT") @external @@ -857,7 +857,7 @@ def cgemlqt( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CGEMQR") @external @@ -877,7 +877,7 @@ def cgemqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEMQRT") @external @@ -897,7 +897,7 @@ def cgemqrt( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CGEQL2") @external @@ -910,7 +910,7 @@ def cgeql2( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGEQLF") @external @@ -924,7 +924,7 @@ def cgeqlf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEQP3") @external @@ -940,7 +940,7 @@ def cgeqp3( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEQP3RK") @external @@ -964,7 +964,7 @@ def cgeqp3rk( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float32], Returns["RELTOL", Float32], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float32], Returns["RELMAXC2NRMK", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEQR") @external @@ -979,7 +979,7 @@ def cgeqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEQR2") @external @@ -992,7 +992,7 @@ def cgeqr2( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGEQR2P") @external @@ -1005,7 +1005,7 @@ def cgeqr2p( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGEQRF") @external @@ -1019,7 +1019,7 @@ def cgeqrf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEQRFP") @external @@ -1033,7 +1033,7 @@ def cgeqrfp( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGEQRT") @external @@ -1048,7 +1048,7 @@ def cgeqrt( LDT: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CGEQRT2") @external @@ -1061,7 +1061,7 @@ def cgeqrt2( T: Complex64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CGEQRT3") @external @@ -1074,7 +1074,7 @@ def cgeqrt3( T: Complex64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CGERFS") @external @@ -1097,7 +1097,7 @@ def cgerfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CGERFSX") @external @@ -1128,7 +1128,7 @@ def cgerfsx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CGERQ2") @external @@ -1141,7 +1141,7 @@ def cgerq2( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGERQF") @external @@ -1155,7 +1155,7 @@ def cgerqf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGESC2") @external @@ -1168,7 +1168,7 @@ def cgesc2( IPIV: Int32[Flat], JPIV: Int32[Flat], SCALE: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float32]]: ... @bind("CGESDD") @external @@ -1189,7 +1189,7 @@ def cgesdd( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGESV") @external @@ -1203,7 +1203,7 @@ def cgesv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CGESVD") @external @@ -1224,7 +1224,7 @@ def cgesvd( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGESVDQ") @external @@ -1252,7 +1252,7 @@ def cgesvdq( RWORK: Float32[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["NUMRANK", Int32], Returns["LIWORK", Int32], Returns["LCWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGESVDX") @external @@ -1280,7 +1280,7 @@ def cgesvdx( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["NS", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGESVJ") @external @@ -1302,7 +1302,7 @@ def cgesvj( RWORK: Float32[LRWORK], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGESVX") @external @@ -1330,7 +1330,7 @@ def cgesvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CGESVXX") @external @@ -1363,7 +1363,7 @@ def cgesvxx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CGETC2") @external @@ -1375,7 +1375,7 @@ def cgetc2( IPIV: Int32[Flat], JPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGETF2") @external @@ -1387,7 +1387,7 @@ def cgetf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGETRF") @external @@ -1399,7 +1399,7 @@ def cgetrf( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGETRF2") @external @@ -1411,7 +1411,7 @@ def cgetrf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CGETRI") @external @@ -1424,7 +1424,7 @@ def cgetri( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGETRS") @external @@ -1439,7 +1439,7 @@ def cgetrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CGETSLS") @external @@ -1456,7 +1456,7 @@ def cgetsls( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGETSQRHRT") @external @@ -1474,7 +1474,7 @@ def cgetsqrhrt( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB1", Int32], Returns["NB1", Int32], Returns["NB2", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGBAK") @external @@ -1491,7 +1491,7 @@ def cggbak( V: Complex64[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("CGGBAL") @external @@ -1509,7 +1509,7 @@ def cggbal( RSCALE: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("CGGES") @external @@ -1536,7 +1536,7 @@ def cgges( RWORK: Float32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGES3") @external @@ -1563,7 +1563,7 @@ def cgges3( RWORK: Float32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGESX") @external @@ -1595,7 +1595,7 @@ def cggesx( LIWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGEV") @external @@ -1618,7 +1618,7 @@ def cggev( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGEV3") @external @@ -1641,7 +1641,7 @@ def cggev3( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGEVX") @external @@ -1676,7 +1676,7 @@ def cggevx( IWORK: Int32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float32], Returns["BBNRM", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGGLM") @external @@ -1695,7 +1695,7 @@ def cggglm( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGHD3") @external @@ -1717,7 +1717,7 @@ def cgghd3( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGHRD") @external @@ -1737,7 +1737,7 @@ def cgghrd( Z: Complex64[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CGGLSE") @external @@ -1756,7 +1756,7 @@ def cgglse( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGQRF") @external @@ -1774,7 +1774,7 @@ def cggqrf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGRQF") @external @@ -1792,7 +1792,7 @@ def cggrqf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGSVD3") @external @@ -1823,7 +1823,7 @@ def cggsvd3( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGGSVP3") @external @@ -1855,7 +1855,7 @@ def cggsvp3( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float32], Returns["TOLB", Float32], Returns["K", Int32], Returns["L", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGSVJ0") @external @@ -1878,7 +1878,7 @@ def cgsvj0( WORK: Complex64[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float32], Returns["SFMIN", Float32], Returns["TOL", Float32], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGSVJ1") @external @@ -1902,7 +1902,7 @@ def cgsvj1( WORK: Complex64[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float32], Returns["SFMIN", Float32], Returns["TOL", Float32], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CGTCON") @external @@ -1919,7 +1919,7 @@ def cgtcon( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CGTRFS") @external @@ -1945,7 +1945,7 @@ def cgtrfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CGTSV") @external @@ -1959,7 +1959,7 @@ def cgtsv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CGTSVX") @external @@ -1987,7 +1987,7 @@ def cgtsvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CGTTRF") @external @@ -2000,7 +2000,7 @@ def cgttrf( DU2: Complex64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CGTTRS") @external @@ -2017,7 +2017,7 @@ def cgttrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CGTTS2") @external @@ -2033,7 +2033,7 @@ def cgtts2( IPIV: Int32[Flat], B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["ITRANS", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("CHB2ST_KERNELS") @external @@ -2054,7 +2054,7 @@ def chb2st_kernels( TAU: Complex64[Flat], LDVT: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["WANTZ", Bool], Returns["TTYPE", Int32], Returns["ST", Int32], Returns["ED", Int32], Returns["SWEEP", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["IB", Int32], Returns["LDA", Int32], Returns["LDVT", Int32]]: ... @bind("CHBEV") @external @@ -2072,7 +2072,7 @@ def chbev( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHBEV_2STAGE") @external @@ -2091,7 +2091,7 @@ def chbev_2stage( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHBEVD") @external @@ -2113,7 +2113,7 @@ def chbevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHBEVD_2STAGE") @external @@ -2135,7 +2135,7 @@ def chbevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHBEVX") @external @@ -2164,7 +2164,7 @@ def chbevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHBEVX_2STAGE") @external @@ -2194,7 +2194,7 @@ def chbevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHBGST") @external @@ -2214,7 +2214,7 @@ def chbgst( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CHBGV") @external @@ -2235,7 +2235,7 @@ def chbgv( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHBGVD") @external @@ -2260,7 +2260,7 @@ def chbgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHBGVX") @external @@ -2292,7 +2292,7 @@ def chbgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDQ", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHBTRD") @external @@ -2310,7 +2310,7 @@ def chbtrd( LDQ: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("CHECON") @external @@ -2325,7 +2325,7 @@ def checon( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CHECON_3") @external @@ -2341,7 +2341,7 @@ def checon_3( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CHECON_ROOK") @external @@ -2356,7 +2356,7 @@ def checon_rook( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CHEEQUB") @external @@ -2371,7 +2371,7 @@ def cheequb( AMAX: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CHEEV") @external @@ -2387,7 +2387,7 @@ def cheev( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEEV_2STAGE") @external @@ -2403,7 +2403,7 @@ def cheev_2stage( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEEVD") @external @@ -2422,7 +2422,7 @@ def cheevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEEVD_2STAGE") @external @@ -2441,7 +2441,7 @@ def cheevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEEVR") @external @@ -2470,7 +2470,7 @@ def cheevr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEEVR_2STAGE") @external @@ -2499,7 +2499,7 @@ def cheevr_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEEVX") @external @@ -2526,7 +2526,7 @@ def cheevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEEVX_2STAGE") @external @@ -2553,7 +2553,7 @@ def cheevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEGS2") @external @@ -2567,7 +2567,7 @@ def chegs2( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHEGST") @external @@ -2581,7 +2581,7 @@ def chegst( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHEGV") @external @@ -2600,7 +2600,7 @@ def chegv( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEGV_2STAGE") @external @@ -2619,7 +2619,7 @@ def chegv_2stage( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEGVD") @external @@ -2641,7 +2641,7 @@ def chegvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHEGVX") @external @@ -2671,7 +2671,7 @@ def chegvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHERFS") @external @@ -2694,7 +2694,7 @@ def cherfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CHERFSX") @external @@ -2724,7 +2724,7 @@ def cherfsx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CHESV") @external @@ -2741,7 +2741,7 @@ def chesv( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHESV_AA") @external @@ -2758,7 +2758,7 @@ def chesv_aa( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHESV_AA_2STAGE") @external @@ -2778,7 +2778,7 @@ def chesv_aa_2stage( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHESV_RK") @external @@ -2796,7 +2796,7 @@ def chesv_rk( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHESV_ROOK") @external @@ -2813,7 +2813,7 @@ def chesv_rook( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHESVX") @external @@ -2839,7 +2839,7 @@ def chesvx( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHESVXX") @external @@ -2871,7 +2871,7 @@ def chesvxx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CHESWAPR") @external @@ -2883,7 +2883,7 @@ def cheswapr( LDA: Int32, I1: Int32, I2: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["I1", Int32], Returns["I2", Int32]]: ... @bind("CHETD2") @external @@ -2897,7 +2897,7 @@ def chetd2( E: Float32[Flat], TAU: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CHETF2") @external @@ -2909,7 +2909,7 @@ def chetf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CHETF2_RK") @external @@ -2922,7 +2922,7 @@ def chetf2_rk( E: Complex64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CHETF2_ROOK") @external @@ -2934,7 +2934,7 @@ def chetf2_rook( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CHETRD") @external @@ -2950,7 +2950,7 @@ def chetrd( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRD_2STAGE") @external @@ -2969,7 +2969,7 @@ def chetrd_2stage( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LHOUS2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRD_HB2ST") @external @@ -2989,7 +2989,7 @@ def chetrd_hb2st( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LHOUS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRD_HE2HB") @external @@ -3006,7 +3006,7 @@ def chetrd_he2hb( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDA", Int32], Returns["LDAB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRF") @external @@ -3020,7 +3020,7 @@ def chetrf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRF_AA") @external @@ -3034,7 +3034,7 @@ def chetrf_aa( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRF_AA_2STAGE") @external @@ -3051,7 +3051,7 @@ def chetrf_aa_2stage( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRF_RK") @external @@ -3066,7 +3066,7 @@ def chetrf_rk( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRF_ROOK") @external @@ -3080,7 +3080,7 @@ def chetrf_rook( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRI") @external @@ -3093,7 +3093,7 @@ def chetri( IPIV: Int32[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CHETRI2") @external @@ -3107,7 +3107,7 @@ def chetri2( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRI2X") @external @@ -3121,7 +3121,7 @@ def chetri2x( WORK: Complex64[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("CHETRI_3") @external @@ -3136,7 +3136,7 @@ def chetri_3( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRI_3X") @external @@ -3151,7 +3151,7 @@ def chetri_3x( WORK: Complex64[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("CHETRI_ROOK") @external @@ -3164,7 +3164,7 @@ def chetri_rook( IPIV: Int32[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CHETRS") @external @@ -3179,7 +3179,7 @@ def chetrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHETRS2") @external @@ -3195,7 +3195,7 @@ def chetrs2( LDB: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHETRS_3") @external @@ -3211,7 +3211,7 @@ def chetrs_3( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHETRS_AA") @external @@ -3228,7 +3228,7 @@ def chetrs_aa( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHETRS_AA_2STAGE") @external @@ -3246,7 +3246,7 @@ def chetrs_aa_2stage( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHETRS_ROOK") @external @@ -3261,7 +3261,7 @@ def chetrs_rook( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHFRK") @external @@ -3277,7 +3277,7 @@ def chfrk( LDA: Int32, BETA: Float32, C: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["BETA", Float32]]: ... @bind("CHGEQZ") @external @@ -3303,14 +3303,14 @@ def chgeqz( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHLA_TRANSTYPE") @external @native_call([Addr(Arg(0))]) def chla_transtype( TRANS: Int32 -) -> String[1]: ... +) -> tuple[String[1], Returns["TRANS", Int32]]: ... @bind("CHPCON") @external @@ -3324,7 +3324,7 @@ def chpcon( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CHPEV") @external @@ -3340,7 +3340,7 @@ def chpev( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHPEVD") @external @@ -3360,7 +3360,7 @@ def chpevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHPEVX") @external @@ -3385,7 +3385,7 @@ def chpevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHPGST") @external @@ -3397,7 +3397,7 @@ def chpgst( AP: Complex64[Flat], BP: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CHPGV") @external @@ -3415,7 +3415,7 @@ def chpgv( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHPGVD") @external @@ -3437,7 +3437,7 @@ def chpgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CHPGVX") @external @@ -3464,7 +3464,7 @@ def chpgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CHPRFS") @external @@ -3485,7 +3485,7 @@ def chprfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CHPSV") @external @@ -3499,7 +3499,7 @@ def chpsv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHPSVX") @external @@ -3522,7 +3522,7 @@ def chpsvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CHPTRD") @external @@ -3535,7 +3535,7 @@ def chptrd( E: Float32[Flat], TAU: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CHPTRF") @external @@ -3546,7 +3546,7 @@ def chptrf( AP: Complex64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CHPTRI") @external @@ -3558,7 +3558,7 @@ def chptri( IPIV: Int32[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CHPTRS") @external @@ -3572,7 +3572,7 @@ def chptrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CHSEIN") @external @@ -3597,7 +3597,7 @@ def chsein( IFAILL: Int32[Flat], IFAILR: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("CHSEQR") @external @@ -3616,7 +3616,7 @@ def chseqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLA_GBAMV") @external @@ -3635,7 +3635,7 @@ def cla_gbamv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Float32], Returns["LDAB", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("CLA_GBRCOND_C") @external @@ -3655,7 +3655,7 @@ def cla_gbrcond_c( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("CLA_GBRCOND_X") @external @@ -3674,7 +3674,7 @@ def cla_gbrcond_x( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["INFO", Int32]]: ... @bind("CLA_GBRFSX_EXTENDED") @external @@ -3711,7 +3711,7 @@ def cla_gbrfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("CLA_GBRPVGRW") @external @@ -3725,7 +3725,7 @@ def cla_gbrpvgrw( LDAB: Int32, AFB: Complex64[LDAFB, Flat], LDAFB: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NCOLS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32]]: ... @bind("CLA_GEAMV") @external @@ -3742,7 +3742,7 @@ def cla_geamv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("CLA_GERCOND_C") @external @@ -3760,7 +3760,7 @@ def cla_gercond_c( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("CLA_GERCOND_X") @external @@ -3777,7 +3777,7 @@ def cla_gercond_x( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("CLA_GERFSX_EXTENDED") @external @@ -3812,7 +3812,7 @@ def cla_gerfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("CLA_GERPVGRW") @external @@ -3824,7 +3824,7 @@ def cla_gerpvgrw( LDA: Int32, AF: Complex64[LDAF, Flat], LDAF: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("CLA_HEAMV") @external @@ -3840,7 +3840,7 @@ def cla_heamv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["UPLO", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("CLA_HERCOND_C") @external @@ -3858,7 +3858,7 @@ def cla_hercond_c( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("CLA_HERCOND_X") @external @@ -3875,7 +3875,7 @@ def cla_hercond_x( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("CLA_HERFSX_EXTENDED") @external @@ -3910,7 +3910,7 @@ def cla_herfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("CLA_HERPVGRW") @external @@ -3925,7 +3925,7 @@ def cla_herpvgrw( LDAF: Int32, IPIV: Int32[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["INFO", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("CLA_LIN_BERR") @external @@ -3937,7 +3937,7 @@ def cla_lin_berr( RES: Complex64[N, NRHS], AYB: Float32[N, NRHS], BERR: Float32[NRHS] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NZ", Int32], Returns["NRHS", Int32]]: ... @bind("CLA_PORCOND_C") @external @@ -3954,7 +3954,7 @@ def cla_porcond_c( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("CLA_PORCOND_X") @external @@ -3970,7 +3970,7 @@ def cla_porcond_x( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("CLA_PORFSX_EXTENDED") @external @@ -4004,7 +4004,7 @@ def cla_porfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("CLA_PORPVGRW") @external @@ -4017,7 +4017,7 @@ def cla_porpvgrw( AF: Complex64[LDAF, Flat], LDAF: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("CLA_SYAMV") @external @@ -4033,7 +4033,7 @@ def cla_syamv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["UPLO", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("CLA_SYRCOND_C") @external @@ -4051,7 +4051,7 @@ def cla_syrcond_c( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("CLA_SYRCOND_X") @external @@ -4068,7 +4068,7 @@ def cla_syrcond_x( INFO: Int32, WORK: Complex64[Flat], RWORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("CLA_SYRFSX_EXTENDED") @external @@ -4103,7 +4103,7 @@ def cla_syrfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("CLA_SYRPVGRW") @external @@ -4118,7 +4118,7 @@ def cla_syrpvgrw( LDAF: Int32, IPIV: Int32[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["INFO", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("CLA_WWADDW") @external @@ -4128,7 +4128,7 @@ def cla_wwaddw( X: Complex64[Flat], Y: Complex64[Flat], W: Complex64[Flat] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CLABRD") @external @@ -4147,7 +4147,7 @@ def clabrd( LDX: Int32, Y: Complex64[LDY, Flat], LDY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LDY", Int32]]: ... @bind("CLACGV") @external @@ -4156,7 +4156,7 @@ def clacgv( N: Int32, X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("CLACN2") @external @@ -4168,7 +4168,7 @@ def clacn2( EST: Float32, KASE: Int32, ISAVE: Int32[3] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float32], Returns["KASE", Int32]]: ... @bind("CLACON") @external @@ -4179,7 +4179,7 @@ def clacon( X: Complex64[N], EST: Float32, KASE: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float32], Returns["KASE", Int32]]: ... @bind("CLACP2") @external @@ -4192,7 +4192,7 @@ def clacp2( LDA: Int32, B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("CLACPY") @external @@ -4205,7 +4205,7 @@ def clacpy( LDA: Int32, B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("CLACRM") @external @@ -4220,7 +4220,7 @@ def clacrm( C: Complex64[LDC, Flat], LDC: Int32, RWORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32]]: ... @bind("CLACRT") @external @@ -4233,7 +4233,7 @@ def clacrt( INCY: Int32, C: Complex64, S: Complex64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Complex64], Returns["S", Complex64]]: ... @bind("CLADIV") @external @@ -4241,7 +4241,7 @@ def clacrt( def cladiv( X: Complex64, Y: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["X", Complex64], Returns["Y", Complex64]]: ... @bind("CLAED0") @external @@ -4258,7 +4258,7 @@ def claed0( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["QSIZ", Int32], Returns["N", Int32], Returns["LDQ", Int32], Returns["LDQS", Int32], Returns["INFO", Int32]]: ... @bind("CLAED7") @external @@ -4286,7 +4286,7 @@ def claed7( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["CUTPNT", Int32], Returns["QSIZ", Int32], Returns["TLVLS", Int32], Returns["CURLVL", Int32], Returns["CURPBM", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["INFO", Int32]]: ... @bind("CLAED8") @external @@ -4313,7 +4313,7 @@ def claed8( GIVCOL: Int32[2, Flat], GIVNUM: Float32[2, Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["N", Int32], Returns["QSIZ", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["CUTPNT", Int32], Returns["LDQ2", Int32], Returns["GIVPTR", Int32], Returns["INFO", Int32]]: ... @bind("CLAEIN") @external @@ -4332,7 +4332,7 @@ def claein( EPS3: Float32, SMLNUM: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["RIGHTV", Bool], Returns["NOINIT", Bool], Returns["N", Int32], Returns["LDH", Int32], Returns["W", Complex64], Returns["LDB", Int32], Returns["EPS3", Float32], Returns["SMLNUM", Float32], Returns["INFO", Int32]]: ... @bind("CLAESY") @external @@ -4346,7 +4346,7 @@ def claesy( EVSCAL: Complex64, CS1: Complex64, SN1: Complex64 -) -> None: ... +) -> tuple[Returns["A", Complex64], Returns["B", Complex64], Returns["C", Complex64], Returns["RT1", Complex64], Returns["RT2", Complex64], Returns["EVSCAL", Complex64], Returns["CS1", Complex64], Returns["SN1", Complex64]]: ... @bind("CLAEV2") @external @@ -4359,7 +4359,7 @@ def claev2( RT2: Float32, CS1: Float32, SN1: Complex64 -) -> None: ... +) -> tuple[Returns["A", Complex64], Returns["B", Complex64], Returns["C", Complex64], Returns["RT1", Float32], Returns["RT2", Float32], Returns["CS1", Float32], Returns["SN1", Complex64]]: ... @bind("CLAG2Z") @external @@ -4372,7 +4372,7 @@ def clag2z( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDSA", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CLAGS2") @external @@ -4391,7 +4391,7 @@ def clags2( SNV: Complex64, CSQ: Float32, SNQ: Complex64 -) -> None: ... +) -> tuple[Returns["UPPER", Bool], Returns["A1", Float32], Returns["A2", Complex64], Returns["A3", Float32], Returns["B1", Float32], Returns["B2", Complex64], Returns["B3", Float32], Returns["CSU", Float32], Returns["SNU", Complex64], Returns["CSV", Float32], Returns["SNV", Complex64], Returns["CSQ", Float32], Returns["SNQ", Complex64]]: ... @bind("CLAGTM") @external @@ -4409,7 +4409,7 @@ def clagtm( BETA: Float32, B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["ALPHA", Float32], Returns["LDX", Int32], Returns["BETA", Float32], Returns["LDB", Int32]]: ... @bind("CLAHEF") @external @@ -4425,7 +4425,7 @@ def clahef( W: Complex64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("CLAHEF_AA") @external @@ -4441,7 +4441,7 @@ def clahef_aa( H: Complex64[LDH, Flat], LDH: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["J1", Int32], Returns["M", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDH", Int32]]: ... @bind("CLAHEF_RK") @external @@ -4458,7 +4458,7 @@ def clahef_rk( W: Complex64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("CLAHEF_ROOK") @external @@ -4474,7 +4474,7 @@ def clahef_rook( W: Complex64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("CLAHQR") @external @@ -4493,7 +4493,7 @@ def clahqr( Z: Complex64[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CLAHR2") @external @@ -4509,7 +4509,7 @@ def clahr2( LDT: Int32, Y: Complex64[LDY, NB], LDY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDY", Int32]]: ... @bind("CLAIC1") @external @@ -4524,7 +4524,7 @@ def claic1( SESTPR: Float32, S: Complex64, C: Complex64 -) -> None: ... +) -> tuple[Returns["JOB", Int32], Returns["J", Int32], Returns["SEST", Float32], Returns["GAMMA", Complex64], Returns["SESTPR", Float32], Returns["S", Complex64], Returns["C", Complex64]]: ... @bind("CLALS0") @external @@ -4554,7 +4554,7 @@ def clals0( S: Float32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["K", Int32], Returns["C", Float32], Returns["S", Float32], Returns["INFO", Int32]]: ... @bind("CLALSA") @external @@ -4586,7 +4586,7 @@ def clalsa( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["LDU", Int32], Returns["LDGCOL", Int32], Returns["INFO", Int32]]: ... @bind("CLALSD") @external @@ -4606,7 +4606,7 @@ def clalsd( RWORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["INFO", Int32]]: ... @bind("CLAMSWLQ") @external @@ -4628,7 +4628,7 @@ def clamswlq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLAMTSQR") @external @@ -4650,7 +4650,7 @@ def clamtsqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLANGB") @external @@ -4663,7 +4663,7 @@ def clangb( AB: Complex64[LDAB, Flat], LDAB: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32]]: ... @bind("CLANGE") @external @@ -4675,7 +4675,7 @@ def clange( A: Complex64[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("CLANGT") @external @@ -4686,7 +4686,7 @@ def clangt( DL: Complex64[Flat], D: Complex64[Flat], DU: Complex64[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("CLANHB") @external @@ -4699,7 +4699,7 @@ def clanhb( AB: Complex64[LDAB, Flat], LDAB: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("CLANHE") @external @@ -4711,7 +4711,7 @@ def clanhe( A: Complex64[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("CLANHF") @external @@ -4723,7 +4723,7 @@ def clanhf( N: Int32, A: Complex64[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("CLANHP") @external @@ -4734,7 +4734,7 @@ def clanhp( N: Int32, AP: Complex64[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("CLANHS") @external @@ -4745,7 +4745,7 @@ def clanhs( A: Complex64[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("CLANHT") @external @@ -4755,7 +4755,7 @@ def clanht( N: Int32, D: Float32[Flat], E: Complex64[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("CLANSB") @external @@ -4768,7 +4768,7 @@ def clansb( AB: Complex64[LDAB, Flat], LDAB: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("CLANSP") @external @@ -4779,7 +4779,7 @@ def clansp( N: Int32, AP: Complex64[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("CLANSY") @external @@ -4791,7 +4791,7 @@ def clansy( A: Complex64[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("CLANTB") @external @@ -4805,7 +4805,7 @@ def clantb( AB: Complex64[LDAB, Flat], LDAB: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("CLANTP") @external @@ -4817,7 +4817,7 @@ def clantp( N: Int32, AP: Complex64[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("CLANTR") @external @@ -4831,7 +4831,7 @@ def clantr( A: Complex64[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("CLAPLL") @external @@ -4843,7 +4843,7 @@ def clapll( Y: Complex64[Flat], INCY: Int32, SSMIN: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["SSMIN", Float32]]: ... @bind("CLAPMR") @external @@ -4855,7 +4855,7 @@ def clapmr( X: Complex64[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("CLAPMT") @external @@ -4867,7 +4867,7 @@ def clapmt( X: Complex64[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("CLAQGB") @external @@ -4885,7 +4885,7 @@ def claqgb( COLCND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQGE") @external @@ -4901,7 +4901,7 @@ def claqge( COLCND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQHB") @external @@ -4916,7 +4916,7 @@ def claqhb( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQHE") @external @@ -4930,7 +4930,7 @@ def claqhe( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQHP") @external @@ -4943,7 +4943,7 @@ def claqhp( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQP2") @external @@ -4959,7 +4959,7 @@ def claqp2( VN1: Float32[Flat], VN2: Float32[Flat], WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["LDA", Int32]]: ... @bind("CLAQP2RK") @external @@ -4985,7 +4985,7 @@ def claqp2rk( VN2: Float32[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float32], Returns["RELTOL", Float32], Returns["KP1", Int32], Returns["MAXC2NRM", Float32], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float32], Returns["RELMAXC2NRMK", Float32], Returns["INFO", Int32]]: ... @bind("CLAQP3RK") @external @@ -5015,7 +5015,7 @@ def claqp3rk( LDF: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["NB", Int32], Returns["ABSTOL", Float32], Returns["RELTOL", Float32], Returns["KP1", Int32], Returns["MAXC2NRM", Float32], Returns["LDA", Int32], Returns["DONE", Bool], Returns["KB", Int32], Returns["MAXC2NRMK", Float32], Returns["RELMAXC2NRMK", Float32], Returns["LDF", Int32], Returns["INFO", Int32]]: ... @bind("CLAQPS") @external @@ -5035,7 +5035,7 @@ def claqps( AUXV: Complex64[Flat], F: Complex64[LDF, Flat], LDF: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDF", Int32]]: ... @bind("CLAQR0") @external @@ -5056,7 +5056,7 @@ def claqr0( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLAQR1") @external @@ -5068,7 +5068,7 @@ def claqr1( S1: Complex64, S2: Complex64, V: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["S1", Complex64], Returns["S2", Complex64]]: ... @bind("CLAQR2") @external @@ -5099,7 +5099,7 @@ def claqr2( LDWV: Int32, WORK: Complex64[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("CLAQR3") @external @@ -5130,7 +5130,7 @@ def claqr3( LDWV: Int32, WORK: Complex64[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("CLAQR4") @external @@ -5151,7 +5151,7 @@ def claqr4( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLAQR5") @external @@ -5181,7 +5181,7 @@ def claqr5( NH: Int32, WH: Complex64[LDWH, Flat], LDWH: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["KACC22", Int32], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NSHFTS", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LDV", Int32], Returns["LDU", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["NH", Int32], Returns["LDWH", Int32]]: ... @bind("CLAQSB") @external @@ -5196,7 +5196,7 @@ def claqsb( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQSP") @external @@ -5209,7 +5209,7 @@ def claqsp( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQSY") @external @@ -5223,11 +5223,11 @@ def claqsy( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("CLAQZ0") @external -@native_call([Arg(0), Arg(1), Arg(2), Addr(Arg(3)), Addr(Arg(4)), Addr(Arg(5)), Arg(6), Addr(Arg(7)), Arg(8), Addr(Arg(9)), Arg(10), Arg(11), Arg(12), Addr(Arg(13)), Arg(14), Addr(Arg(15)), Arg(16), Addr(Arg(17)), Arg(18), Addr(Arg(19)), Return('INFO', 1)]) +@native_call([Arg(0), Arg(1), Arg(2), Addr(Arg(3)), Addr(Arg(4)), Addr(Arg(5)), Arg(6), Addr(Arg(7)), Arg(8), Addr(Arg(9)), Arg(10), Arg(11), Arg(12), Addr(Arg(13)), Arg(14), Addr(Arg(15)), Arg(16), Addr(Arg(17)), Arg(18), Addr(Arg(19)), Return('INFO', 0)]) def claqz0( WANTS: String[1], WANTQ: String[1], @@ -5249,7 +5249,7 @@ def claqz0( LWORK: Int32, RWORK: Float32[Flat], REC: Int32 -) -> tuple[Returns["RWORK", Float32[Flat]], Int32]: ... +) -> Int32: ... @bind("CLAQZ1") @external @@ -5361,7 +5361,7 @@ def clar1v( RESID: Float32, RQCORR: Float32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["B1", Int32], Returns["BN", Int32], Returns["LAMBDA", Float32], Returns["PIVMIN", Float32], Returns["GAPTOL", Float32], Returns["WANTNC", Bool], Returns["NEGCNT", Int32], Returns["ZTZ", Float32], Returns["MINGMA", Float32], Returns["R", Int32], Returns["NRMINV", Float32], Returns["RESID", Float32], Returns["RQCORR", Float32]]: ... @bind("CLAR2V") @external @@ -5375,7 +5375,7 @@ def clar2v( C: Float32[Flat], S: Complex64[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCC", Int32]]: ... @bind("CLARCM") @external @@ -5390,7 +5390,7 @@ def clarcm( C: Complex64[LDC, Flat], LDC: Int32, RWORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32]]: ... @bind("CLARF") @external @@ -5405,7 +5405,7 @@ def clarf( C: Complex64[LDC, Flat], LDC: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex64], Returns["LDC", Int32]]: ... @bind("CLARF1F") @external @@ -5420,7 +5420,7 @@ def clarf1f( C: Complex64[LDC, Flat], LDC: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex64], Returns["LDC", Int32]]: ... @bind("CLARF1L") @external @@ -5435,7 +5435,7 @@ def clarf1l( C: Complex64[LDC, Flat], LDC: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex64], Returns["LDC", Int32]]: ... @bind("CLARFB") @external @@ -5456,7 +5456,7 @@ def clarfb( LDC: Int32, WORK: Complex64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("CLARFB_GETT") @external @@ -5474,7 +5474,7 @@ def clarfb_gett( LDB: Int32, WORK: Complex64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("CLARFG") @external @@ -5485,7 +5485,7 @@ def clarfg( X: Complex64[Flat], INCX: Int32, TAU: Complex64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["TAU", Complex64]]: ... @bind("CLARFGP") @external @@ -5496,7 +5496,7 @@ def clarfgp( X: Complex64[Flat], INCX: Int32, TAU: Complex64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["TAU", Complex64]]: ... @bind("CLARFT") @external @@ -5511,7 +5511,7 @@ def clarft( TAU: Complex64[Flat], T: Complex64[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("CLARFX") @external @@ -5525,7 +5525,7 @@ def clarfx( C: Complex64[LDC, Flat], LDC: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["TAU", Complex64], Returns["LDC", Int32]]: ... @bind("CLARFY") @external @@ -5539,7 +5539,7 @@ def clarfy( C: Complex64[LDC, Flat], LDC: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex64], Returns["LDC", Int32]]: ... @bind("CLARGV") @external @@ -5552,7 +5552,7 @@ def clargv( INCY: Int32, C: Float32[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("CLARNV") @external @@ -5562,7 +5562,7 @@ def clarnv( ISEED: Int32[4], N: Int32, X: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["IDIST", Int32], Returns["N", Int32]]: ... @bind("CLARRV") @external @@ -5593,7 +5593,7 @@ def clarrv( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["PIVMIN", Float32], Returns["M", Int32], Returns["DOL", Int32], Returns["DOU", Int32], Returns["MINRGP", Float32], Returns["RTOL1", Float32], Returns["RTOL2", Float32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CLARSCL2") @external @@ -5604,7 +5604,7 @@ def clarscl2( D: Float32[Flat], X: Complex64[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("CLARTG") @external @@ -5615,7 +5615,7 @@ def clartg( c: Float32, s: Complex64, r: Complex64 -) -> None: ... +) -> tuple[Returns["f", Complex64], Returns["g", Complex64], Returns["c", Float32], Returns["s", Complex64], Returns["r", Complex64]]: ... @bind("CLARTV") @external @@ -5629,7 +5629,7 @@ def clartv( C: Float32[Flat], S: Complex64[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("CLARZ") @external @@ -5645,7 +5645,7 @@ def clarz( C: Complex64[LDC, Flat], LDC: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["INCV", Int32], Returns["TAU", Complex64], Returns["LDC", Int32]]: ... @bind("CLARZB") @external @@ -5667,7 +5667,7 @@ def clarzb( LDC: Int32, WORK: Complex64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("CLARZT") @external @@ -5682,7 +5682,7 @@ def clarzt( TAU: Complex64[Flat], T: Complex64[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("CLASCL") @external @@ -5698,7 +5698,7 @@ def clascl( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["KL", Int32], Returns["KU", Int32], Returns["CFROM", Float32], Returns["CTO", Float32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CLASCL2") @external @@ -5709,7 +5709,7 @@ def clascl2( D: Float32[Flat], X: Complex64[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("CLASET") @external @@ -5722,7 +5722,7 @@ def claset( BETA: Complex64, A: Complex64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["BETA", Complex64], Returns["LDA", Int32]]: ... @bind("CLASR") @external @@ -5737,7 +5737,7 @@ def clasr( S: Float32[Flat], A: Complex64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("CLASSQ") @external @@ -5748,7 +5748,7 @@ def classq( incx: Int32, scale: Float32, sumsq: Float32 -) -> None: ... +) -> tuple[Returns["n", Int32], Returns["incx", Int32], Returns["scale", Float32], Returns["sumsq", Float32]]: ... @bind("CLASWLQ") @external @@ -5765,7 +5765,7 @@ def claswlq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLASWP") @external @@ -5778,7 +5778,7 @@ def claswp( K2: Int32, IPIV: Int32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["K1", Int32], Returns["K2", Int32], Returns["INCX", Int32]]: ... @bind("CLASYF") @external @@ -5794,7 +5794,7 @@ def clasyf( W: Complex64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("CLASYF_AA") @external @@ -5810,7 +5810,7 @@ def clasyf_aa( H: Complex64[LDH, Flat], LDH: Int32, WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["J1", Int32], Returns["M", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDH", Int32]]: ... @bind("CLASYF_RK") @external @@ -5827,7 +5827,7 @@ def clasyf_rk( W: Complex64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("CLASYF_ROOK") @external @@ -5843,7 +5843,7 @@ def clasyf_rook( W: Complex64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("CLATBS") @external @@ -5861,7 +5861,7 @@ def clatbs( SCALE: Float32, CNORM: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("CLATDF") @external @@ -5876,7 +5876,7 @@ def clatdf( RDSCAL: Float32, IPIV: Int32[Flat], JPIV: Int32[Flat] -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["RDSUM", Float32], Returns["RDSCAL", Float32]]: ... @bind("CLATPS") @external @@ -5892,7 +5892,7 @@ def clatps( SCALE: Float32, CNORM: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("CLATRD") @external @@ -5907,7 +5907,7 @@ def clatrd( TAU: Complex64[Flat], W: Complex64[LDW, Flat], LDW: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDW", Int32]]: ... @bind("CLATRS") @external @@ -5924,7 +5924,7 @@ def clatrs( SCALE: Float32, CNORM: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("CLATRS3") @external @@ -5945,7 +5945,7 @@ def clatrs3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLATRZ") @external @@ -5958,7 +5958,7 @@ def clatrz( LDA: Int32, TAU: Complex64[Flat], WORK: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32]]: ... @bind("CLATSQR") @external @@ -5975,7 +5975,7 @@ def clatsqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CLAUNHR_COL_GETRFNP") @external @@ -5987,7 +5987,7 @@ def claunhr_col_getrfnp( LDA: Int32, D: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CLAUNHR_COL_GETRFNP2") @external @@ -5999,7 +5999,7 @@ def claunhr_col_getrfnp2( LDA: Int32, D: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CLAUU2") @external @@ -6010,7 +6010,7 @@ def clauu2( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CLAUUM") @external @@ -6021,7 +6021,7 @@ def clauum( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CPBCON") @external @@ -6037,7 +6037,7 @@ def cpbcon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPBEQU") @external @@ -6052,7 +6052,7 @@ def cpbequ( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CPBRFS") @external @@ -6075,7 +6075,7 @@ def cpbrfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CPBSTF") @external @@ -6087,7 +6087,7 @@ def cpbstf( AB: Complex64[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("CPBSV") @external @@ -6102,7 +6102,7 @@ def cpbsv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPBSVX") @external @@ -6129,7 +6129,7 @@ def cpbsvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPBTF2") @external @@ -6141,7 +6141,7 @@ def cpbtf2( AB: Complex64[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("CPBTRF") @external @@ -6153,7 +6153,7 @@ def cpbtrf( AB: Complex64[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("CPBTRS") @external @@ -6168,7 +6168,7 @@ def cpbtrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPFTRF") @external @@ -6179,7 +6179,7 @@ def cpftrf( N: Int32, A: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CPFTRI") @external @@ -6190,7 +6190,7 @@ def cpftri( N: Int32, A: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CPFTRS") @external @@ -6204,7 +6204,7 @@ def cpftrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPOCON") @external @@ -6219,7 +6219,7 @@ def cpocon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPOEQU") @external @@ -6232,7 +6232,7 @@ def cpoequ( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CPOEQUB") @external @@ -6245,7 +6245,7 @@ def cpoequb( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CPORFS") @external @@ -6267,7 +6267,7 @@ def cporfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CPORFSX") @external @@ -6296,7 +6296,7 @@ def cporfsx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CPOSV") @external @@ -6310,7 +6310,7 @@ def cposv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPOSVX") @external @@ -6336,7 +6336,7 @@ def cposvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPOSVXX") @external @@ -6367,7 +6367,7 @@ def cposvxx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CPOTF2") @external @@ -6378,7 +6378,7 @@ def cpotf2( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CPOTRF") @external @@ -6389,7 +6389,7 @@ def cpotrf( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CPOTRF2") @external @@ -6400,7 +6400,7 @@ def cpotrf2( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CPOTRI") @external @@ -6411,7 +6411,7 @@ def cpotri( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CPOTRS") @external @@ -6425,7 +6425,7 @@ def cpotrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPPCON") @external @@ -6439,7 +6439,7 @@ def cppcon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPPEQU") @external @@ -6452,7 +6452,7 @@ def cppequ( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CPPRFS") @external @@ -6472,7 +6472,7 @@ def cpprfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CPPSV") @external @@ -6485,7 +6485,7 @@ def cppsv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPPSVX") @external @@ -6509,7 +6509,7 @@ def cppsvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPPTRF") @external @@ -6519,7 +6519,7 @@ def cpptrf( N: Int32, AP: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CPPTRI") @external @@ -6529,7 +6529,7 @@ def cpptri( N: Int32, AP: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CPPTRS") @external @@ -6542,7 +6542,7 @@ def cpptrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPSTF2") @external @@ -6557,7 +6557,7 @@ def cpstf2( TOL: Float32, WORK: Float32[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float32], Returns["INFO", Int32]]: ... @bind("CPSTRF") @external @@ -6572,7 +6572,7 @@ def cpstrf( TOL: Float32, WORK: Float32[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float32], Returns["INFO", Int32]]: ... @bind("CPTCON") @external @@ -6585,7 +6585,7 @@ def cptcon( RCOND: Float32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPTEQR") @external @@ -6599,7 +6599,7 @@ def cpteqr( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CPTRFS") @external @@ -6621,7 +6621,7 @@ def cptrfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CPTSV") @external @@ -6634,7 +6634,7 @@ def cptsv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPTSVX") @external @@ -6657,7 +6657,7 @@ def cptsvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CPTTRF") @external @@ -6667,7 +6667,7 @@ def cpttrf( D: Float32[Flat], E: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CPTTRS") @external @@ -6681,7 +6681,7 @@ def cpttrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CPTTS2") @external @@ -6694,7 +6694,7 @@ def cptts2( E: Complex64[Flat], B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["IUPLO", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("CROT") @external @@ -6707,7 +6707,7 @@ def crot( INCY: Int32, C: Float32, S: Complex64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Float32], Returns["S", Complex64]]: ... @bind("CRSCL") @external @@ -6717,7 +6717,7 @@ def crscl( A: Complex64, X: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["A", Complex64], Returns["INCX", Int32]]: ... @bind("CSPCON") @external @@ -6731,7 +6731,7 @@ def cspcon( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CSPMV") @external @@ -6746,7 +6746,7 @@ def cspmv( BETA: Complex64, Y: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["BETA", Complex64], Returns["INCY", Int32]]: ... @bind("CSPR") @external @@ -6758,7 +6758,7 @@ def cspr( X: Complex64[Flat], INCX: Int32, AP: Complex64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32]]: ... @bind("CSPRFS") @external @@ -6779,7 +6779,7 @@ def csprfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CSPSV") @external @@ -6793,7 +6793,7 @@ def cspsv( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CSPSVX") @external @@ -6816,7 +6816,7 @@ def cspsvx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CSPTRF") @external @@ -6827,7 +6827,7 @@ def csptrf( AP: Complex64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CSPTRI") @external @@ -6839,7 +6839,7 @@ def csptri( IPIV: Int32[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CSPTRS") @external @@ -6853,7 +6853,7 @@ def csptrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CSRSCL") @external @@ -6863,7 +6863,7 @@ def csrscl( SA: Float32, SX: Complex64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SA", Float32], Returns["INCX", Int32]]: ... @bind("CSTEDC") @external @@ -6882,7 +6882,7 @@ def cstedc( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSTEGR") @external @@ -6908,7 +6908,7 @@ def cstegr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSTEIN") @external @@ -6927,7 +6927,7 @@ def cstein( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CSTEMR") @external @@ -6954,7 +6954,7 @@ def cstemr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["NZC", Int32], Returns["TRYRAC", Bool], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSTEQR") @external @@ -6968,7 +6968,7 @@ def csteqr( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("CSYCON") @external @@ -6983,7 +6983,7 @@ def csycon( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CSYCON_3") @external @@ -6999,7 +6999,7 @@ def csycon_3( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CSYCON_ROOK") @external @@ -7014,7 +7014,7 @@ def csycon_rook( RCOND: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CSYCONV") @external @@ -7028,7 +7028,7 @@ def csyconv( IPIV: Int32[Flat], E: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYCONVF") @external @@ -7042,7 +7042,7 @@ def csyconvf( E: Complex64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYCONVF_ROOK") @external @@ -7056,7 +7056,7 @@ def csyconvf_rook( E: Complex64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYEQUB") @external @@ -7071,7 +7071,7 @@ def csyequb( AMAX: Float32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("CSYMV") @external @@ -7087,7 +7087,7 @@ def csymv( BETA: Complex64, Y: Complex64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex64], Returns["INCY", Int32]]: ... @bind("CSYR") @external @@ -7100,7 +7100,7 @@ def csyr( INCX: Int32, A: Complex64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex64], Returns["INCX", Int32], Returns["LDA", Int32]]: ... @bind("CSYRFS") @external @@ -7123,7 +7123,7 @@ def csyrfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CSYRFSX") @external @@ -7153,7 +7153,7 @@ def csyrfsx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CSYSV") @external @@ -7170,7 +7170,7 @@ def csysv( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYSV_AA") @external @@ -7187,7 +7187,7 @@ def csysv_aa( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYSV_AA_2STAGE") @external @@ -7207,7 +7207,7 @@ def csysv_aa_2stage( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYSV_RK") @external @@ -7225,7 +7225,7 @@ def csysv_rk( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYSV_ROOK") @external @@ -7242,7 +7242,7 @@ def csysv_rook( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYSVX") @external @@ -7268,7 +7268,7 @@ def csysvx( LWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYSVXX") @external @@ -7300,7 +7300,7 @@ def csysvxx( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("CSYSWAPR") @external @@ -7312,7 +7312,7 @@ def csyswapr( LDA: Int32, I1: Int32, I2: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["I1", Int32], Returns["I2", Int32]]: ... @bind("CSYTF2") @external @@ -7324,7 +7324,7 @@ def csytf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYTF2_RK") @external @@ -7337,7 +7337,7 @@ def csytf2_rk( E: Complex64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYTF2_ROOK") @external @@ -7349,7 +7349,7 @@ def csytf2_rook( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRF") @external @@ -7363,7 +7363,7 @@ def csytrf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRF_AA") @external @@ -7377,7 +7377,7 @@ def csytrf_aa( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRF_AA_2STAGE") @external @@ -7394,7 +7394,7 @@ def csytrf_aa_2stage( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRF_RK") @external @@ -7409,7 +7409,7 @@ def csytrf_rk( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRF_ROOK") @external @@ -7423,7 +7423,7 @@ def csytrf_rook( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRI") @external @@ -7436,7 +7436,7 @@ def csytri( IPIV: Int32[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRI2") @external @@ -7450,7 +7450,7 @@ def csytri2( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRI2X") @external @@ -7464,7 +7464,7 @@ def csytri2x( WORK: Complex64[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRI_3") @external @@ -7479,7 +7479,7 @@ def csytri_3( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRI_3X") @external @@ -7494,7 +7494,7 @@ def csytri_3x( WORK: Complex64[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRI_ROOK") @external @@ -7507,7 +7507,7 @@ def csytri_rook( IPIV: Int32[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRS") @external @@ -7522,7 +7522,7 @@ def csytrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRS2") @external @@ -7538,7 +7538,7 @@ def csytrs2( LDB: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRS_3") @external @@ -7554,7 +7554,7 @@ def csytrs_3( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRS_AA") @external @@ -7571,7 +7571,7 @@ def csytrs_aa( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRS_AA_2STAGE") @external @@ -7589,7 +7589,7 @@ def csytrs_aa_2stage( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CSYTRS_ROOK") @external @@ -7604,7 +7604,7 @@ def csytrs_rook( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CTBCON") @external @@ -7621,7 +7621,7 @@ def ctbcon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CTBRFS") @external @@ -7644,7 +7644,7 @@ def ctbrfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CTBTRS") @external @@ -7661,7 +7661,7 @@ def ctbtrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CTFSM") @external @@ -7678,7 +7678,7 @@ def ctfsm( A: Complex64[Flat], B: Complex64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex64], Returns["LDB", Int32]]: ... @bind("CTFTRI") @external @@ -7690,7 +7690,7 @@ def ctftri( N: Int32, A: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CTFTTP") @external @@ -7702,7 +7702,7 @@ def ctfttp( ARF: Complex64[Flat], AP: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CTFTTR") @external @@ -7715,7 +7715,7 @@ def ctfttr( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CTGEVC") @external @@ -7738,7 +7738,7 @@ def ctgevc( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDS", Int32], Returns["LDP", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("CTGEX2") @external @@ -7757,7 +7757,7 @@ def ctgex2( LDZ: Int32, J1: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["J1", Int32], Returns["INFO", Int32]]: ... @bind("CTGEXC") @external @@ -7777,7 +7777,7 @@ def ctgexc( IFST: Int32, ILST: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["INFO", Int32]]: ... @bind("CTGSEN") @external @@ -7807,7 +7807,7 @@ def ctgsen( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["M", Int32], Returns["PL", Float32], Returns["PR", Float32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("CTGSJA") @external @@ -7838,7 +7838,7 @@ def ctgsja( WORK: Complex64[Flat], NCYCLE: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float32], Returns["TOLB", Float32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["NCYCLE", Int32], Returns["INFO", Int32]]: ... @bind("CTGSNA") @external @@ -7864,7 +7864,7 @@ def ctgsna( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CTGSY2") @external @@ -7890,7 +7890,7 @@ def ctgsy2( RDSUM: Float32, RDSCAL: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float32], Returns["RDSUM", Float32], Returns["RDSCAL", Float32], Returns["INFO", Int32]]: ... @bind("CTGSYL") @external @@ -7918,7 +7918,7 @@ def ctgsyl( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float32], Returns["DIF", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CTPCON") @external @@ -7933,7 +7933,7 @@ def ctpcon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CTPLQT") @external @@ -7951,7 +7951,7 @@ def ctplqt( LDT: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CTPLQT2") @external @@ -7967,7 +7967,7 @@ def ctplqt2( T: Complex64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CTPMLQT") @external @@ -7990,7 +7990,7 @@ def ctpmlqt( LDB: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CTPMQRT") @external @@ -8013,7 +8013,7 @@ def ctpmqrt( LDB: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CTPQRT") @external @@ -8031,7 +8031,7 @@ def ctpqrt( LDT: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CTPQRT2") @external @@ -8047,7 +8047,7 @@ def ctpqrt2( T: Complex64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CTPRFB") @external @@ -8071,7 +8071,7 @@ def ctprfb( LDB: Int32, WORK: Complex64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("CTPRFS") @external @@ -8092,7 +8092,7 @@ def ctprfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CTPTRI") @external @@ -8103,7 +8103,7 @@ def ctptri( N: Int32, AP: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CTPTRS") @external @@ -8118,7 +8118,7 @@ def ctptrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CTPTTF") @external @@ -8130,7 +8130,7 @@ def ctpttf( AP: Complex64[Flat], ARF: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("CTPTTR") @external @@ -8142,7 +8142,7 @@ def ctpttr( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CTRCON") @external @@ -8158,7 +8158,7 @@ def ctrcon( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("CTREVC") @external @@ -8179,7 +8179,7 @@ def ctrevc( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("CTREVC3") @external @@ -8202,7 +8202,7 @@ def ctrevc3( RWORK: Float32[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("CTREXC") @external @@ -8217,7 +8217,7 @@ def ctrexc( IFST: Int32, ILST: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["INFO", Int32]]: ... @bind("CTRRFS") @external @@ -8239,7 +8239,7 @@ def ctrrfs( WORK: Complex64[Flat], RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("CTRSEN") @external @@ -8260,7 +8260,7 @@ def ctrsen( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["M", Int32], Returns["S", Float32], Returns["SEP", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CTRSNA") @external @@ -8284,7 +8284,7 @@ def ctrsna( LDWORK: Int32, RWORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LDWORK", Int32], Returns["INFO", Int32]]: ... @bind("CTRSYL") @external @@ -8303,7 +8303,7 @@ def ctrsyl( LDC: Int32, SCALE: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("CTRSYL3") @external @@ -8324,7 +8324,7 @@ def ctrsyl3( SWORK: Float32[LDSWORK, Flat], LDSWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float32], Returns["LDSWORK", Int32], Returns["INFO", Int32]]: ... @bind("CTRTI2") @external @@ -8336,7 +8336,7 @@ def ctrti2( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CTRTRI") @external @@ -8348,7 +8348,7 @@ def ctrtri( A: Complex64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CTRTRS") @external @@ -8364,7 +8364,7 @@ def ctrtrs( B: Complex64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("CTRTTF") @external @@ -8377,7 +8377,7 @@ def ctrttf( LDA: Int32, ARF: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CTRTTP") @external @@ -8389,7 +8389,7 @@ def ctrttp( LDA: Int32, AP: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CTZRZF") @external @@ -8403,7 +8403,7 @@ def ctzrzf( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNBDB") @external @@ -8431,7 +8431,7 @@ def cunbdb( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNBDB1") @external @@ -8452,7 +8452,7 @@ def cunbdb1( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNBDB2") @external @@ -8473,7 +8473,7 @@ def cunbdb2( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNBDB3") @external @@ -8494,7 +8494,7 @@ def cunbdb3( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNBDB4") @external @@ -8516,7 +8516,7 @@ def cunbdb4( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNBDB5") @external @@ -8536,7 +8536,7 @@ def cunbdb5( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNBDB6") @external @@ -8556,7 +8556,7 @@ def cunbdb6( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNCSD") @external @@ -8594,7 +8594,7 @@ def cuncsd( LRWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNCSD2BY1") @external @@ -8623,7 +8623,7 @@ def cuncsd2by1( LRWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNG2L") @external @@ -8637,7 +8637,7 @@ def cung2l( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CUNG2R") @external @@ -8651,7 +8651,7 @@ def cung2r( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CUNGBR") @external @@ -8667,7 +8667,7 @@ def cungbr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGHR") @external @@ -8682,7 +8682,7 @@ def cunghr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGL2") @external @@ -8696,7 +8696,7 @@ def cungl2( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CUNGLQ") @external @@ -8711,7 +8711,7 @@ def cunglq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGQL") @external @@ -8726,7 +8726,7 @@ def cungql( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGQR") @external @@ -8741,7 +8741,7 @@ def cungqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGR2") @external @@ -8755,7 +8755,7 @@ def cungr2( TAU: Complex64[Flat], WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("CUNGRQ") @external @@ -8770,7 +8770,7 @@ def cungrq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGTR") @external @@ -8784,7 +8784,7 @@ def cungtr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGTSQR") @external @@ -8801,7 +8801,7 @@ def cungtsqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNGTSQR_ROW") @external @@ -8818,7 +8818,7 @@ def cungtsqr_row( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNHR_COL") @external @@ -8833,7 +8833,7 @@ def cunhr_col( LDT: Int32, D: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("CUNM22") @external @@ -8852,7 +8852,7 @@ def cunm22( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LDQ", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNM2L") @external @@ -8870,7 +8870,7 @@ def cunm2l( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CUNM2R") @external @@ -8888,7 +8888,7 @@ def cunm2r( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CUNMBR") @external @@ -8908,7 +8908,7 @@ def cunmbr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNMHR") @external @@ -8928,7 +8928,7 @@ def cunmhr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNML2") @external @@ -8946,7 +8946,7 @@ def cunml2( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CUNMLQ") @external @@ -8965,7 +8965,7 @@ def cunmlq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNMQL") @external @@ -8984,7 +8984,7 @@ def cunmql( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNMQR") @external @@ -9003,7 +9003,7 @@ def cunmqr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNMR2") @external @@ -9021,7 +9021,7 @@ def cunmr2( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CUNMR3") @external @@ -9040,7 +9040,7 @@ def cunmr3( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("CUNMRQ") @external @@ -9059,7 +9059,7 @@ def cunmrq( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNMRZ") @external @@ -9079,7 +9079,7 @@ def cunmrz( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUNMTR") @external @@ -9098,7 +9098,7 @@ def cunmtr( WORK: Complex64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("CUPGTR") @external @@ -9112,7 +9112,7 @@ def cupgtr( LDQ: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("CUPMTR") @external @@ -9129,7 +9129,7 @@ def cupmtr( LDC: Int32, WORK: Complex64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DBBCSD") @external @@ -9164,7 +9164,7 @@ def dbbcsd( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DBDSDC") @external @@ -9184,7 +9184,7 @@ def dbdsdc( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["INFO", Int32]]: ... @bind("DBDSQR") @external @@ -9205,7 +9205,7 @@ def dbdsqr( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NCVT", Int32], Returns["NRU", Int32], Returns["NCC", Int32], Returns["LDVT", Int32], Returns["LDU", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DBDSVDX") @external @@ -9228,7 +9228,7 @@ def dbdsvdx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["NS", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DDISNA") @external @@ -9240,7 +9240,7 @@ def ddisna( D: Float64[Flat], SEP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DGBBRD") @external @@ -9264,7 +9264,7 @@ def dgbbrd( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NCC", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["LDPT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DGBCON") @external @@ -9282,7 +9282,7 @@ def dgbcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DGBEQU") @external @@ -9300,7 +9300,7 @@ def dgbequ( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DGBEQUB") @external @@ -9318,7 +9318,7 @@ def dgbequb( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DGBRFS") @external @@ -9343,7 +9343,7 @@ def dgbrfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DGBRFSX") @external @@ -9376,7 +9376,7 @@ def dgbrfsx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DGBSV") @external @@ -9392,7 +9392,7 @@ def dgbsv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DGBSVX") @external @@ -9422,7 +9422,7 @@ def dgbsvx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DGBSVXX") @external @@ -9457,7 +9457,7 @@ def dgbsvxx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DGBTF2") @external @@ -9471,7 +9471,7 @@ def dgbtf2( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("DGBTRF") @external @@ -9485,7 +9485,7 @@ def dgbtrf( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("DGBTRS") @external @@ -9502,7 +9502,7 @@ def dgbtrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DGEBAK") @external @@ -9518,7 +9518,7 @@ def dgebak( V: Float64[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("DGEBAL") @external @@ -9532,7 +9532,7 @@ def dgebal( IHI: Int32, SCALE: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("DGEBD2") @external @@ -9548,7 +9548,7 @@ def dgebd2( TAUP: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGEBRD") @external @@ -9565,7 +9565,7 @@ def dgebrd( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGECON") @external @@ -9580,11 +9580,11 @@ def dgecon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DGEDMD") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Arg(15), Addr(Arg(16)), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Addr(Arg(21)), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Return('INFO', 10)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Arg(15), Addr(Arg(16)), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Addr(Arg(21)), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Return('INFO', 1)]) def dgedmd( JOBS: String[1], JOBZ: String[1], @@ -9614,11 +9614,11 @@ def dgedmd( LWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Int32, Returns["REIG", Float64[Flat]], Returns["IMEIG", Float64[Flat]], Returns["Z", Float64[LDZ, Flat]], Returns["RES", Float64[Flat]], Returns["B", Float64[LDB, Flat]], Returns["W", Float64[LDW, Flat]], Returns["S", Float64[LDS, Flat]], Returns["WORK", Float64[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("DGEDMDQ") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 2), Arg(17), Arg(18), Arg(19), Addr(Arg(20)), Arg(21), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Arg(28), Addr(Arg(29)), Arg(30), Addr(Arg(31)), Return('INFO', 12)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 0), Arg(17), Arg(18), Arg(19), Addr(Arg(20)), Arg(21), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Arg(28), Addr(Arg(29)), Arg(30), Addr(Arg(31)), Return('INFO', 1)]) def dgedmdq( JOBS: String[1], JOBZ: String[1], @@ -9652,7 +9652,7 @@ def dgedmdq( LWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Returns["X", Float64[LDX, Flat]], Returns["Y", Float64[LDY, Flat]], Int32, Returns["REIG", Float64[Flat]], Returns["IMEIG", Float64[Flat]], Returns["Z", Float64[LDZ, Flat]], Returns["RES", Float64[Flat]], Returns["B", Float64[LDB, Flat]], Returns["V", Float64[LDV, Flat]], Returns["S", Float64[LDS, Flat]], Returns["WORK", Float64[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("DGEEQU") @external @@ -9668,7 +9668,7 @@ def dgeequ( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DGEEQUB") @external @@ -9684,7 +9684,7 @@ def dgeequb( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DGEES") @external @@ -9705,7 +9705,7 @@ def dgees( LWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEESX") @external @@ -9731,7 +9731,7 @@ def dgeesx( LIWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["RCONDE", Float64], Returns["RCONDV", Float64], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEEV") @external @@ -9751,7 +9751,7 @@ def dgeev( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEEVX") @external @@ -9780,7 +9780,7 @@ def dgeevx( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEHD2") @external @@ -9794,7 +9794,7 @@ def dgehd2( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGEHRD") @external @@ -9809,7 +9809,7 @@ def dgehrd( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEJSV") @external @@ -9834,7 +9834,7 @@ def dgejsv( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGELQ") @external @@ -9849,7 +9849,7 @@ def dgelq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGELQ2") @external @@ -9862,7 +9862,7 @@ def dgelq2( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGELQF") @external @@ -9876,7 +9876,7 @@ def dgelqf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGELQT") @external @@ -9891,7 +9891,7 @@ def dgelqt( LDT: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DGELQT3") @external @@ -9904,7 +9904,7 @@ def dgelqt3( T: Float64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DGELS") @external @@ -9921,7 +9921,7 @@ def dgels( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGELSD") @external @@ -9941,7 +9941,7 @@ def dgelsd( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGELSS") @external @@ -9960,7 +9960,7 @@ def dgelss( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGELST") @external @@ -9977,7 +9977,7 @@ def dgelst( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGELSY") @external @@ -9996,7 +9996,7 @@ def dgelsy( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEMLQ") @external @@ -10016,7 +10016,7 @@ def dgemlq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEMLQT") @external @@ -10036,7 +10036,7 @@ def dgemlqt( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DGEMQR") @external @@ -10056,7 +10056,7 @@ def dgemqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEMQRT") @external @@ -10076,7 +10076,7 @@ def dgemqrt( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DGEQL2") @external @@ -10089,7 +10089,7 @@ def dgeql2( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGEQLF") @external @@ -10103,7 +10103,7 @@ def dgeqlf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEQP3") @external @@ -10118,7 +10118,7 @@ def dgeqp3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEQP3RK") @external @@ -10141,7 +10141,7 @@ def dgeqp3rk( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float64], Returns["RELTOL", Float64], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float64], Returns["RELMAXC2NRMK", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEQR") @external @@ -10156,7 +10156,7 @@ def dgeqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEQR2") @external @@ -10169,7 +10169,7 @@ def dgeqr2( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGEQR2P") @external @@ -10182,7 +10182,7 @@ def dgeqr2p( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGEQRF") @external @@ -10196,7 +10196,7 @@ def dgeqrf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEQRFP") @external @@ -10210,7 +10210,7 @@ def dgeqrfp( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGEQRT") @external @@ -10225,7 +10225,7 @@ def dgeqrt( LDT: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DGEQRT2") @external @@ -10238,7 +10238,7 @@ def dgeqrt2( T: Float64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DGEQRT3") @external @@ -10251,7 +10251,7 @@ def dgeqrt3( T: Float64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DGERFS") @external @@ -10274,7 +10274,7 @@ def dgerfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DGERFSX") @external @@ -10305,7 +10305,7 @@ def dgerfsx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DGERQ2") @external @@ -10318,7 +10318,7 @@ def dgerq2( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGERQF") @external @@ -10332,7 +10332,7 @@ def dgerqf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGESC2") @external @@ -10345,7 +10345,7 @@ def dgesc2( IPIV: Int32[Flat], JPIV: Int32[Flat], SCALE: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float64]]: ... @bind("DGESDD") @external @@ -10365,7 +10365,7 @@ def dgesdd( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGESV") @external @@ -10379,7 +10379,7 @@ def dgesv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DGESVD") @external @@ -10399,7 +10399,7 @@ def dgesvd( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGESVDQ") @external @@ -10427,7 +10427,7 @@ def dgesvdq( RWORK: Float64[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["NUMRANK", Int32], Returns["LIWORK", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGESVDX") @external @@ -10454,7 +10454,7 @@ def dgesvdx( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["NS", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGESVJ") @external @@ -10474,7 +10474,7 @@ def dgesvj( WORK: Float64[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGESVX") @external @@ -10502,7 +10502,7 @@ def dgesvx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DGESVXX") @external @@ -10535,7 +10535,7 @@ def dgesvxx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DGETC2") @external @@ -10547,7 +10547,7 @@ def dgetc2( IPIV: Int32[Flat], JPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGETF2") @external @@ -10559,7 +10559,7 @@ def dgetf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGETRF") @external @@ -10571,7 +10571,7 @@ def dgetrf( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGETRF2") @external @@ -10583,7 +10583,7 @@ def dgetrf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DGETRI") @external @@ -10596,7 +10596,7 @@ def dgetri( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGETRS") @external @@ -10611,7 +10611,7 @@ def dgetrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DGETSLS") @external @@ -10628,7 +10628,7 @@ def dgetsls( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGETSQRHRT") @external @@ -10646,7 +10646,7 @@ def dgetsqrhrt( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB1", Int32], Returns["NB1", Int32], Returns["NB2", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGBAK") @external @@ -10663,7 +10663,7 @@ def dggbak( V: Float64[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("DGGBAL") @external @@ -10681,7 +10681,7 @@ def dggbal( RSCALE: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("DGGES") @external @@ -10708,7 +10708,7 @@ def dgges( LWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGES3") @external @@ -10735,7 +10735,7 @@ def dgges3( LWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGESX") @external @@ -10767,7 +10767,7 @@ def dggesx( LIWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGEV") @external @@ -10790,7 +10790,7 @@ def dggev( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGEV3") @external @@ -10813,7 +10813,7 @@ def dggev3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGEVX") @external @@ -10848,7 +10848,7 @@ def dggevx( IWORK: Int32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float64], Returns["BBNRM", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGGLM") @external @@ -10867,7 +10867,7 @@ def dggglm( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGHD3") @external @@ -10889,7 +10889,7 @@ def dgghd3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGHRD") @external @@ -10909,7 +10909,7 @@ def dgghrd( Z: Float64[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DGGLSE") @external @@ -10928,7 +10928,7 @@ def dgglse( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGQRF") @external @@ -10946,7 +10946,7 @@ def dggqrf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGRQF") @external @@ -10964,7 +10964,7 @@ def dggrqf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGSVD3") @external @@ -10994,7 +10994,7 @@ def dggsvd3( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGGSVP3") @external @@ -11025,7 +11025,7 @@ def dggsvp3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float64], Returns["TOLB", Float64], Returns["K", Int32], Returns["L", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGSVJ0") @external @@ -11048,7 +11048,7 @@ def dgsvj0( WORK: Float64[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float64], Returns["SFMIN", Float64], Returns["TOL", Float64], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGSVJ1") @external @@ -11072,7 +11072,7 @@ def dgsvj1( WORK: Float64[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float64], Returns["SFMIN", Float64], Returns["TOL", Float64], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DGTCON") @external @@ -11090,7 +11090,7 @@ def dgtcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DGTRFS") @external @@ -11116,7 +11116,7 @@ def dgtrfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DGTSV") @external @@ -11130,7 +11130,7 @@ def dgtsv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DGTSVX") @external @@ -11158,7 +11158,7 @@ def dgtsvx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DGTTRF") @external @@ -11171,7 +11171,7 @@ def dgttrf( DU2: Float64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DGTTRS") @external @@ -11188,7 +11188,7 @@ def dgttrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DGTTS2") @external @@ -11204,7 +11204,7 @@ def dgtts2( IPIV: Int32[Flat], B: Float64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["ITRANS", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("DHGEQZ") @external @@ -11230,7 +11230,7 @@ def dhgeqz( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DHSEIN") @external @@ -11255,7 +11255,7 @@ def dhsein( IFAILL: Int32[Flat], IFAILR: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("DHSEQR") @external @@ -11275,7 +11275,7 @@ def dhseqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DISNAN") @external @@ -11301,7 +11301,7 @@ def dla_gbamv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Float64], Returns["LDAB", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DLA_GBRCOND") @external @@ -11321,7 +11321,7 @@ def dla_gbrcond( INFO: Int32, WORK: Float64[Flat], IWORK: Int32[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("DLA_GBRFSX_EXTENDED") @external @@ -11358,7 +11358,7 @@ def dla_gbrfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("DLA_GBRPVGRW") @external @@ -11372,7 +11372,7 @@ def dla_gbrpvgrw( LDAB: Int32, AFB: Float64[LDAFB, Flat], LDAFB: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NCOLS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32]]: ... @bind("DLA_GEAMV") @external @@ -11389,7 +11389,7 @@ def dla_geamv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DLA_GERCOND") @external @@ -11407,7 +11407,7 @@ def dla_gercond( INFO: Int32, WORK: Float64[Flat], IWORK: Int32[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("DLA_GERFSX_EXTENDED") @external @@ -11442,7 +11442,7 @@ def dla_gerfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("DLA_GERPVGRW") @external @@ -11454,7 +11454,7 @@ def dla_gerpvgrw( LDA: Int32, AF: Float64[LDAF, Flat], LDAF: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("DLA_LIN_BERR") @external @@ -11466,7 +11466,7 @@ def dla_lin_berr( RES: Float64[N, NRHS], AYB: Float64[N, NRHS], BERR: Float64[NRHS] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NZ", Int32], Returns["NRHS", Int32]]: ... @bind("DLA_PORCOND") @external @@ -11483,7 +11483,7 @@ def dla_porcond( INFO: Int32, WORK: Float64[Flat], IWORK: Int32[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("DLA_PORFSX_EXTENDED") @external @@ -11517,7 +11517,7 @@ def dla_porfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("DLA_PORPVGRW") @external @@ -11530,7 +11530,7 @@ def dla_porpvgrw( AF: Float64[LDAF, Flat], LDAF: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("DLA_SYAMV") @external @@ -11546,7 +11546,7 @@ def dla_syamv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["UPLO", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("DLA_SYRCOND") @external @@ -11564,7 +11564,7 @@ def dla_syrcond( INFO: Int32, WORK: Float64[Flat], IWORK: Int32[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("DLA_SYRFSX_EXTENDED") @external @@ -11599,7 +11599,7 @@ def dla_syrfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("DLA_SYRPVGRW") @external @@ -11614,7 +11614,7 @@ def dla_syrpvgrw( LDAF: Int32, IPIV: Int32[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INFO", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("DLA_WWADDW") @external @@ -11624,7 +11624,7 @@ def dla_wwaddw( X: Float64[Flat], Y: Float64[Flat], W: Float64[Flat] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DLABAD") @external @@ -11632,7 +11632,7 @@ def dla_wwaddw( def dlabad( SMALL: Float64, LARGE: Float64 -) -> None: ... +) -> tuple[Returns["SMALL", Float64], Returns["LARGE", Float64]]: ... @bind("DLABRD") @external @@ -11651,7 +11651,7 @@ def dlabrd( LDX: Int32, Y: Float64[LDY, Flat], LDY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LDY", Int32]]: ... @bind("DLACN2") @external @@ -11664,7 +11664,7 @@ def dlacn2( EST: Float64, KASE: Int32, ISAVE: Int32[3] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float64], Returns["KASE", Int32]]: ... @bind("DLACON") @external @@ -11676,7 +11676,7 @@ def dlacon( ISGN: Int32[Flat], EST: Float64, KASE: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float64], Returns["KASE", Int32]]: ... @bind("DLACPY") @external @@ -11689,7 +11689,7 @@ def dlacpy( LDA: Int32, B: Float64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("DLADIV") @external @@ -11701,7 +11701,7 @@ def dladiv( D: Float64, P: Float64, Q: Float64 -) -> None: ... +) -> tuple[Returns["A", Float64], Returns["B", Float64], Returns["C", Float64], Returns["D", Float64], Returns["P", Float64], Returns["Q", Float64]]: ... @bind("DLADIV1") @external @@ -11713,7 +11713,7 @@ def dladiv1( D: Float64, P: Float64, Q: Float64 -) -> None: ... +) -> tuple[Returns["A", Float64], Returns["B", Float64], Returns["C", Float64], Returns["D", Float64], Returns["P", Float64], Returns["Q", Float64]]: ... @bind("DLADIV2") @external @@ -11725,7 +11725,7 @@ def dladiv2( D: Float64, R: Float64, T: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["A", Float64], Returns["B", Float64], Returns["C", Float64], Returns["D", Float64], Returns["R", Float64], Returns["T", Float64]]: ... @bind("DLAE2") @external @@ -11736,7 +11736,7 @@ def dlae2( C: Float64, RT1: Float64, RT2: Float64 -) -> None: ... +) -> tuple[Returns["A", Float64], Returns["B", Float64], Returns["C", Float64], Returns["RT1", Float64], Returns["RT2", Float64]]: ... @bind("DLAEBZ") @external @@ -11762,7 +11762,7 @@ def dlaebz( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["NITMAX", Int32], Returns["N", Int32], Returns["MMAX", Int32], Returns["MINP", Int32], Returns["NBMIN", Int32], Returns["ABSTOL", Float64], Returns["RELTOL", Float64], Returns["PIVMIN", Float64], Returns["MOUT", Int32], Returns["INFO", Int32]]: ... @bind("DLAED0") @external @@ -11780,7 +11780,7 @@ def dlaed0( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["QSIZ", Int32], Returns["N", Int32], Returns["LDQ", Int32], Returns["LDQS", Int32], Returns["INFO", Int32]]: ... @bind("DLAED1") @external @@ -11796,7 +11796,7 @@ def dlaed1( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["CUTPNT", Int32], Returns["INFO", Int32]]: ... @bind("DLAED2") @external @@ -11819,7 +11819,7 @@ def dlaed2( INDXP: Int32[Flat], COLTYP: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["INFO", Int32]]: ... @bind("DLAED3") @external @@ -11839,7 +11839,7 @@ def dlaed3( W: Float64[Flat], S: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["INFO", Int32]]: ... @bind("DLAED4") @external @@ -11853,7 +11853,7 @@ def dlaed4( RHO: Float64, DLAM: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["I", Int32], Returns["RHO", Float64], Returns["DLAM", Float64], Returns["INFO", Int32]]: ... @bind("DLAED5") @external @@ -11865,7 +11865,7 @@ def dlaed5( DELTA: Float64[2], RHO: Float64, DLAM: Float64 -) -> None: ... +) -> tuple[Returns["I", Int32], Returns["RHO", Float64], Returns["DLAM", Float64]]: ... @bind("DLAED6") @external @@ -11879,7 +11879,7 @@ def dlaed6( FINIT: Float64, TAU: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["KNITER", Int32], Returns["ORGATI", Bool], Returns["RHO", Float64], Returns["FINIT", Float64], Returns["TAU", Float64], Returns["INFO", Int32]]: ... @bind("DLAED7") @external @@ -11907,7 +11907,7 @@ def dlaed7( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["N", Int32], Returns["QSIZ", Int32], Returns["TLVLS", Int32], Returns["CURLVL", Int32], Returns["CURPBM", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["CUTPNT", Int32], Returns["INFO", Int32]]: ... @bind("DLAED8") @external @@ -11935,7 +11935,7 @@ def dlaed8( INDXP: Int32[Flat], INDX: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["K", Int32], Returns["N", Int32], Returns["QSIZ", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["CUTPNT", Int32], Returns["LDQ2", Int32], Returns["GIVPTR", Int32], Returns["INFO", Int32]]: ... @bind("DLAED9") @external @@ -11954,7 +11954,7 @@ def dlaed9( S: Float64[LDS, Flat], LDS: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["KSTART", Int32], Returns["KSTOP", Int32], Returns["N", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["LDS", Int32], Returns["INFO", Int32]]: ... @bind("DLAEDA") @external @@ -11974,7 +11974,7 @@ def dlaeda( Z: Float64[Flat], ZTEMP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["TLVLS", Int32], Returns["CURLVL", Int32], Returns["CURPBM", Int32], Returns["INFO", Int32]]: ... @bind("DLAEIN") @external @@ -11996,7 +11996,7 @@ def dlaein( SMLNUM: Float64, BIGNUM: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["RIGHTV", Bool], Returns["NOINIT", Bool], Returns["N", Int32], Returns["LDH", Int32], Returns["WR", Float64], Returns["WI", Float64], Returns["LDB", Int32], Returns["EPS3", Float64], Returns["SMLNUM", Float64], Returns["BIGNUM", Float64], Returns["INFO", Int32]]: ... @bind("DLAEV2") @external @@ -12009,7 +12009,7 @@ def dlaev2( RT2: Float64, CS1: Float64, SN1: Float64 -) -> None: ... +) -> tuple[Returns["A", Float64], Returns["B", Float64], Returns["C", Float64], Returns["RT1", Float64], Returns["RT2", Float64], Returns["CS1", Float64], Returns["SN1", Float64]]: ... @bind("DLAEXC") @external @@ -12026,7 +12026,7 @@ def dlaexc( N2: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["J1", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["INFO", Int32]]: ... @bind("DLAG2") @external @@ -12042,7 +12042,7 @@ def dlag2( WR1: Float64, WR2: Float64, WI: Float64 -) -> None: ... +) -> tuple[Returns["LDA", Int32], Returns["LDB", Int32], Returns["SAFMIN", Float64], Returns["SCALE1", Float64], Returns["SCALE2", Float64], Returns["WR1", Float64], Returns["WR2", Float64], Returns["WI", Float64]]: ... @bind("DLAG2S") @external @@ -12055,7 +12055,7 @@ def dlag2s( SA: Float32[LDSA, Flat], LDSA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDSA", Int32], Returns["INFO", Int32]]: ... @bind("DLAGS2") @external @@ -12074,7 +12074,7 @@ def dlags2( SNV: Float64, CSQ: Float64, SNQ: Float64 -) -> None: ... +) -> tuple[Returns["UPPER", Bool], Returns["A1", Float64], Returns["A2", Float64], Returns["A3", Float64], Returns["B1", Float64], Returns["B2", Float64], Returns["B3", Float64], Returns["CSU", Float64], Returns["SNU", Float64], Returns["CSV", Float64], Returns["SNV", Float64], Returns["CSQ", Float64], Returns["SNQ", Float64]]: ... @bind("DLAGTF") @external @@ -12089,7 +12089,7 @@ def dlagtf( D: Float64[Flat], IN: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LAMBDA", Float64], Returns["TOL", Float64], Returns["INFO", Int32]]: ... @bind("DLAGTM") @external @@ -12107,7 +12107,7 @@ def dlagtm( BETA: Float64, B: Float64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["ALPHA", Float64], Returns["LDX", Int32], Returns["BETA", Float64], Returns["LDB", Int32]]: ... @bind("DLAGTS") @external @@ -12123,7 +12123,7 @@ def dlagts( Y: Float64[Flat], TOL: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["JOB", Int32], Returns["N", Int32], Returns["TOL", Float64], Returns["INFO", Int32]]: ... @bind("DLAGV2") @external @@ -12140,7 +12140,7 @@ def dlagv2( SNL: Float64, CSR: Float64, SNR: Float64 -) -> None: ... +) -> tuple[Returns["LDA", Int32], Returns["LDB", Int32], Returns["CSL", Float64], Returns["SNL", Float64], Returns["CSR", Float64], Returns["SNR", Float64]]: ... @bind("DLAHQR") @external @@ -12160,7 +12160,7 @@ def dlahqr( Z: Float64[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DLAHR2") @external @@ -12176,7 +12176,7 @@ def dlahr2( LDT: Int32, Y: Float64[LDY, NB], LDY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDY", Int32]]: ... @bind("DLAIC1") @external @@ -12191,7 +12191,7 @@ def dlaic1( SESTPR: Float64, S: Float64, C: Float64 -) -> None: ... +) -> tuple[Returns["JOB", Int32], Returns["J", Int32], Returns["SEST", Float64], Returns["GAMMA", Float64], Returns["SESTPR", Float64], Returns["S", Float64], Returns["C", Float64]]: ... @bind("DLAISNAN") @external @@ -12223,7 +12223,7 @@ def dlaln2( SCALE: Float64, XNORM: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["LTRANS", Bool], Returns["NA", Int32], Returns["NW", Int32], Returns["SMIN", Float64], Returns["CA", Float64], Returns["LDA", Int32], Returns["D1", Float64], Returns["D2", Float64], Returns["LDB", Int32], Returns["WR", Float64], Returns["WI", Float64], Returns["LDX", Int32], Returns["SCALE", Float64], Returns["XNORM", Float64], Returns["INFO", Int32]]: ... @bind("DLALS0") @external @@ -12253,7 +12253,7 @@ def dlals0( S: Float64, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["K", Int32], Returns["C", Float64], Returns["S", Float64], Returns["INFO", Int32]]: ... @bind("DLALSA") @external @@ -12285,7 +12285,7 @@ def dlalsa( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["LDU", Int32], Returns["LDGCOL", Int32], Returns["INFO", Int32]]: ... @bind("DLALSD") @external @@ -12304,7 +12304,7 @@ def dlalsd( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["INFO", Int32]]: ... @bind("DLAMRG") @external @@ -12316,7 +12316,7 @@ def dlamrg( DTRD1: Int32, DTRD2: Int32, INDEX: Int32[Flat] -) -> None: ... +) -> tuple[Returns["N1", Int32], Returns["N2", Int32], Returns["DTRD1", Int32], Returns["DTRD2", Int32]]: ... @bind("DLAMSWLQ") @external @@ -12338,7 +12338,7 @@ def dlamswlq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DLAMTSQR") @external @@ -12360,7 +12360,7 @@ def dlamtsqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DLANEG") @external @@ -12372,7 +12372,7 @@ def dlaneg( SIGMA: Float64, PIVMIN: Float64, R: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["SIGMA", Float64], Returns["PIVMIN", Float64], Returns["R", Int32]]: ... @bind("DLANGB") @external @@ -12385,7 +12385,7 @@ def dlangb( AB: Float64[LDAB, Flat], LDAB: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32]]: ... @bind("DLANGE") @external @@ -12397,7 +12397,7 @@ def dlange( A: Float64[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("DLANGT") @external @@ -12408,7 +12408,7 @@ def dlangt( DL: Float64[Flat], D: Float64[Flat], DU: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("DLANHS") @external @@ -12419,7 +12419,7 @@ def dlanhs( A: Float64[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("DLANSB") @external @@ -12432,7 +12432,7 @@ def dlansb( AB: Float64[LDAB, Flat], LDAB: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("DLANSF") @external @@ -12444,7 +12444,7 @@ def dlansf( N: Int32, A: Float64[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("DLANSP") @external @@ -12455,7 +12455,7 @@ def dlansp( N: Int32, AP: Float64[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("DLANST") @external @@ -12465,7 +12465,7 @@ def dlanst( N: Int32, D: Float64[Flat], E: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("DLANSY") @external @@ -12477,7 +12477,7 @@ def dlansy( A: Float64[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("DLANTB") @external @@ -12491,7 +12491,7 @@ def dlantb( AB: Float64[LDAB, Flat], LDAB: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("DLANTP") @external @@ -12503,7 +12503,7 @@ def dlantp( N: Int32, AP: Float64[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("DLANTR") @external @@ -12517,7 +12517,7 @@ def dlantr( A: Float64[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("DLANV2") @external @@ -12533,7 +12533,7 @@ def dlanv2( RT2I: Float64, CS: Float64, SN: Float64 -) -> None: ... +) -> tuple[Returns["A", Float64], Returns["B", Float64], Returns["C", Float64], Returns["D", Float64], Returns["RT1R", Float64], Returns["RT1I", Float64], Returns["RT2R", Float64], Returns["RT2I", Float64], Returns["CS", Float64], Returns["SN", Float64]]: ... @bind("DLAORHR_COL_GETRFNP") @external @@ -12545,7 +12545,7 @@ def dlaorhr_col_getrfnp( LDA: Int32, D: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DLAORHR_COL_GETRFNP2") @external @@ -12557,7 +12557,7 @@ def dlaorhr_col_getrfnp2( LDA: Int32, D: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DLAPLL") @external @@ -12569,7 +12569,7 @@ def dlapll( Y: Float64[Flat], INCY: Int32, SSMIN: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["SSMIN", Float64]]: ... @bind("DLAPMR") @external @@ -12581,7 +12581,7 @@ def dlapmr( X: Float64[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("DLAPMT") @external @@ -12593,7 +12593,7 @@ def dlapmt( X: Float64[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("DLAPY2") @external @@ -12601,7 +12601,7 @@ def dlapmt( def dlapy2( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("DLAPY3") @external @@ -12610,7 +12610,7 @@ def dlapy3( X: Float64, Y: Float64, Z: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64], Returns["Z", Float64]]: ... @bind("DLAQGB") @external @@ -12628,7 +12628,7 @@ def dlaqgb( COLCND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64]]: ... @bind("DLAQGE") @external @@ -12644,7 +12644,7 @@ def dlaqge( COLCND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64]]: ... @bind("DLAQP2") @external @@ -12660,7 +12660,7 @@ def dlaqp2( VN1: Float64[Flat], VN2: Float64[Flat], WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["LDA", Int32]]: ... @bind("DLAQP2RK") @external @@ -12686,7 +12686,7 @@ def dlaqp2rk( VN2: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float64], Returns["RELTOL", Float64], Returns["KP1", Int32], Returns["MAXC2NRM", Float64], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float64], Returns["RELMAXC2NRMK", Float64], Returns["INFO", Int32]]: ... @bind("DLAQP3RK") @external @@ -12716,7 +12716,7 @@ def dlaqp3rk( LDF: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["NB", Int32], Returns["ABSTOL", Float64], Returns["RELTOL", Float64], Returns["KP1", Int32], Returns["MAXC2NRM", Float64], Returns["LDA", Int32], Returns["DONE", Bool], Returns["KB", Int32], Returns["MAXC2NRMK", Float64], Returns["RELMAXC2NRMK", Float64], Returns["LDF", Int32], Returns["INFO", Int32]]: ... @bind("DLAQPS") @external @@ -12736,7 +12736,7 @@ def dlaqps( AUXV: Float64[Flat], F: Float64[LDF, Flat], LDF: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDF", Int32]]: ... @bind("DLAQR0") @external @@ -12758,7 +12758,7 @@ def dlaqr0( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DLAQR1") @external @@ -12772,7 +12772,7 @@ def dlaqr1( SR2: Float64, SI2: Float64, V: Float64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["SR1", Float64], Returns["SI1", Float64], Returns["SR2", Float64], Returns["SI2", Float64]]: ... @bind("DLAQR2") @external @@ -12804,7 +12804,7 @@ def dlaqr2( LDWV: Int32, WORK: Float64[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("DLAQR3") @external @@ -12836,7 +12836,7 @@ def dlaqr3( LDWV: Int32, WORK: Float64[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("DLAQR4") @external @@ -12858,7 +12858,7 @@ def dlaqr4( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DLAQR5") @external @@ -12889,7 +12889,7 @@ def dlaqr5( NH: Int32, WH: Float64[LDWH, Flat], LDWH: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["KACC22", Int32], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NSHFTS", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LDV", Int32], Returns["LDU", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["NH", Int32], Returns["LDWH", Int32]]: ... @bind("DLAQSB") @external @@ -12904,7 +12904,7 @@ def dlaqsb( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("DLAQSP") @external @@ -12917,7 +12917,7 @@ def dlaqsp( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("DLAQSY") @external @@ -12931,7 +12931,7 @@ def dlaqsy( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("DLAQTR") @external @@ -12948,7 +12948,7 @@ def dlaqtr( X: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["LTRAN", Bool], Returns["LREAL", Bool], Returns["N", Int32], Returns["LDT", Int32], Returns["W", Float64], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("DLAQZ0") @external @@ -12990,7 +12990,7 @@ def dlaqz1( BETA1: Float64, BETA2: Float64, V: Float64[Flat] -) -> Returns["V", Float64[Flat]]: ... +) -> None: ... @bind("DLAQZ2") @external @@ -13103,7 +13103,7 @@ def dlar1v( RESID: Float64, RQCORR: Float64, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["B1", Int32], Returns["BN", Int32], Returns["LAMBDA", Float64], Returns["PIVMIN", Float64], Returns["GAPTOL", Float64], Returns["WANTNC", Bool], Returns["NEGCNT", Int32], Returns["ZTZ", Float64], Returns["MINGMA", Float64], Returns["R", Int32], Returns["NRMINV", Float64], Returns["RESID", Float64], Returns["RQCORR", Float64]]: ... @bind("DLAR2V") @external @@ -13117,7 +13117,7 @@ def dlar2v( C: Float64[Flat], S: Float64[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCC", Int32]]: ... @bind("DLARF") @external @@ -13132,7 +13132,7 @@ def dlarf( C: Float64[LDC, Flat], LDC: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float64], Returns["LDC", Int32]]: ... @bind("DLARF1F") @external @@ -13147,7 +13147,7 @@ def dlarf1f( C: Float64[LDC, Flat], LDC: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float64], Returns["LDC", Int32]]: ... @bind("DLARF1L") @external @@ -13162,7 +13162,7 @@ def dlarf1l( C: Float64[LDC, Flat], LDC: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float64], Returns["LDC", Int32]]: ... @bind("DLARFB") @external @@ -13183,7 +13183,7 @@ def dlarfb( LDC: Int32, WORK: Float64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("DLARFB_GETT") @external @@ -13201,7 +13201,7 @@ def dlarfb_gett( LDB: Int32, WORK: Float64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("DLARFG") @external @@ -13212,7 +13212,7 @@ def dlarfg( X: Float64[Flat], INCX: Int32, TAU: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["TAU", Float64]]: ... @bind("DLARFGP") @external @@ -13223,7 +13223,7 @@ def dlarfgp( X: Float64[Flat], INCX: Int32, TAU: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float64], Returns["INCX", Int32], Returns["TAU", Float64]]: ... @bind("DLARFT") @external @@ -13238,7 +13238,7 @@ def dlarft( TAU: Float64[Flat], T: Float64[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("DLARFX") @external @@ -13252,7 +13252,7 @@ def dlarfx( C: Float64[LDC, Flat], LDC: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["TAU", Float64], Returns["LDC", Int32]]: ... @bind("DLARFY") @external @@ -13266,7 +13266,7 @@ def dlarfy( C: Float64[LDC, Flat], LDC: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float64], Returns["LDC", Int32]]: ... @bind("DLARGV") @external @@ -13279,7 +13279,7 @@ def dlargv( INCY: Int32, C: Float64[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("DLARMM") @external @@ -13288,7 +13288,7 @@ def dlarmm( ANORM: Float64, BNORM: Float64, CNORM: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["ANORM", Float64], Returns["BNORM", Float64], Returns["CNORM", Float64]]: ... @bind("DLARNV") @external @@ -13298,7 +13298,7 @@ def dlarnv( ISEED: Int32[4], N: Int32, X: Float64[Flat] -) -> None: ... +) -> tuple[Returns["IDIST", Int32], Returns["N", Int32]]: ... @bind("DLARRA") @external @@ -13313,7 +13313,7 @@ def dlarra( NSPLIT: Int32, ISPLIT: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SPLTOL", Float64], Returns["TNRM", Float64], Returns["NSPLIT", Int32], Returns["INFO", Int32]]: ... @bind("DLARRB") @external @@ -13336,7 +13336,7 @@ def dlarrb( SPDIAM: Float64, TWIST: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["IFIRST", Int32], Returns["ILAST", Int32], Returns["RTOL1", Float64], Returns["RTOL2", Float64], Returns["OFFSET", Int32], Returns["PIVMIN", Float64], Returns["SPDIAM", Float64], Returns["TWIST", Int32], Returns["INFO", Int32]]: ... @bind("DLARRC") @external @@ -13353,7 +13353,7 @@ def dlarrc( LCNT: Int32, RCNT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["PIVMIN", Float64], Returns["EIGCNT", Int32], Returns["LCNT", Int32], Returns["RCNT", Int32], Returns["INFO", Int32]]: ... @bind("DLARRD") @external @@ -13384,7 +13384,7 @@ def dlarrd( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["RELTOL", Float64], Returns["PIVMIN", Float64], Returns["NSPLIT", Int32], Returns["M", Int32], Returns["WL", Float64], Returns["WU", Float64], Returns["INFO", Int32]]: ... @bind("DLARRE") @external @@ -13415,7 +13415,7 @@ def dlarre( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["RTOL1", Float64], Returns["RTOL2", Float64], Returns["SPLTOL", Float64], Returns["NSPLIT", Int32], Returns["M", Int32], Returns["PIVMIN", Float64], Returns["INFO", Int32]]: ... @bind("DLARRF") @external @@ -13439,7 +13439,7 @@ def dlarrf( LPLUS: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["CLSTRT", Int32], Returns["CLEND", Int32], Returns["SPDIAM", Float64], Returns["CLGAPL", Float64], Returns["CLGAPR", Float64], Returns["PIVMIN", Float64], Returns["SIGMA", Float64], Returns["INFO", Int32]]: ... @bind("DLARRJ") @external @@ -13459,7 +13459,7 @@ def dlarrj( PIVMIN: Float64, SPDIAM: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["IFIRST", Int32], Returns["ILAST", Int32], Returns["RTOL", Float64], Returns["OFFSET", Int32], Returns["PIVMIN", Float64], Returns["SPDIAM", Float64], Returns["INFO", Int32]]: ... @bind("DLARRK") @external @@ -13476,7 +13476,7 @@ def dlarrk( W: Float64, WERR: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["IW", Int32], Returns["GL", Float64], Returns["GU", Float64], Returns["PIVMIN", Float64], Returns["RELTOL", Float64], Returns["W", Float64], Returns["WERR", Float64], Returns["INFO", Int32]]: ... @bind("DLARRR") @external @@ -13486,7 +13486,7 @@ def dlarrr( D: Float64[Flat], E: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DLARRV") @external @@ -13517,7 +13517,7 @@ def dlarrv( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["PIVMIN", Float64], Returns["M", Int32], Returns["DOL", Int32], Returns["DOU", Int32], Returns["MINRGP", Float64], Returns["RTOL1", Float64], Returns["RTOL2", Float64], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DLARSCL2") @external @@ -13528,7 +13528,7 @@ def dlarscl2( D: Float64[Flat], X: Float64[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("DLARTG") @external @@ -13539,7 +13539,7 @@ def dlartg( c: Float64, s: Float64, r: Float64 -) -> None: ... +) -> tuple[Returns["f", Float64], Returns["g", Float64], Returns["c", Float64], Returns["s", Float64], Returns["r", Float64]]: ... @bind("DLARTGP") @external @@ -13550,7 +13550,7 @@ def dlartgp( CS: Float64, SN: Float64, R: Float64 -) -> None: ... +) -> tuple[Returns["F", Float64], Returns["G", Float64], Returns["CS", Float64], Returns["SN", Float64], Returns["R", Float64]]: ... @bind("DLARTGS") @external @@ -13561,7 +13561,7 @@ def dlartgs( SIGMA: Float64, CS: Float64, SN: Float64 -) -> None: ... +) -> tuple[Returns["X", Float64], Returns["Y", Float64], Returns["SIGMA", Float64], Returns["CS", Float64], Returns["SN", Float64]]: ... @bind("DLARTV") @external @@ -13575,7 +13575,7 @@ def dlartv( C: Float64[Flat], S: Float64[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("DLARUV") @external @@ -13584,7 +13584,7 @@ def dlaruv( ISEED: Int32[4], N: Int32, X: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DLARZ") @external @@ -13600,7 +13600,7 @@ def dlarz( C: Float64[LDC, Flat], LDC: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["INCV", Int32], Returns["TAU", Float64], Returns["LDC", Int32]]: ... @bind("DLARZB") @external @@ -13622,7 +13622,7 @@ def dlarzb( LDC: Int32, WORK: Float64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("DLARZT") @external @@ -13637,7 +13637,7 @@ def dlarzt( TAU: Float64[Flat], T: Float64[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("DLAS2") @external @@ -13648,7 +13648,7 @@ def dlas2( H: Float64, SSMIN: Float64, SSMAX: Float64 -) -> None: ... +) -> tuple[Returns["F", Float64], Returns["G", Float64], Returns["H", Float64], Returns["SSMIN", Float64], Returns["SSMAX", Float64]]: ... @bind("DLASCL") @external @@ -13664,7 +13664,7 @@ def dlascl( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["KL", Int32], Returns["KU", Int32], Returns["CFROM", Float64], Returns["CTO", Float64], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DLASCL2") @external @@ -13675,7 +13675,7 @@ def dlascl2( D: Float64[Flat], X: Float64[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("DLASD0") @external @@ -13693,7 +13693,7 @@ def dlasd0( IWORK: Int32[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SQRE", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["SMLSIZ", Int32], Returns["INFO", Int32]]: ... @bind("DLASD1") @external @@ -13713,7 +13713,7 @@ def dlasd1( IWORK: Int32[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["ALPHA", Float64], Returns["BETA", Float64], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["INFO", Int32]]: ... @bind("DLASD2") @external @@ -13742,7 +13742,7 @@ def dlasd2( IDXQ: Int32[Flat], COLTYP: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["BETA", Float64], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LDU2", Int32], Returns["LDVT2", Int32], Returns["INFO", Int32]]: ... @bind("DLASD3") @external @@ -13768,7 +13768,7 @@ def dlasd3( CTOT: Int32[Flat], Z: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["K", Int32], Returns["LDQ", Int32], Returns["LDU", Int32], Returns["LDU2", Int32], Returns["LDVT", Int32], Returns["LDVT2", Int32], Returns["INFO", Int32]]: ... @bind("DLASD4") @external @@ -13783,7 +13783,7 @@ def dlasd4( SIGMA: Float64, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["I", Int32], Returns["RHO", Float64], Returns["SIGMA", Float64], Returns["INFO", Int32]]: ... @bind("DLASD5") @external @@ -13796,7 +13796,7 @@ def dlasd5( RHO: Float64, DSIGMA: Float64, WORK: Float64[2] -) -> None: ... +) -> tuple[Returns["I", Int32], Returns["RHO", Float64], Returns["DSIGMA", Float64]]: ... @bind("DLASD6") @external @@ -13828,7 +13828,7 @@ def dlasd6( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["ALPHA", Float64], Returns["BETA", Float64], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["K", Int32], Returns["C", Float64], Returns["S", Float64], Returns["INFO", Int32]]: ... @bind("DLASD7") @external @@ -13861,7 +13861,7 @@ def dlasd7( C: Float64, S: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["BETA", Float64], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["C", Float64], Returns["S", Float64], Returns["INFO", Int32]]: ... @bind("DLASD8") @external @@ -13879,7 +13879,7 @@ def dlasd8( DSIGMA: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["K", Int32], Returns["LDDIFR", Int32], Returns["INFO", Int32]]: ... @bind("DLASDA") @external @@ -13909,7 +13909,7 @@ def dlasda( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["SQRE", Int32], Returns["LDU", Int32], Returns["LDGCOL", Int32], Returns["INFO", Int32]]: ... @bind("DLASDQ") @external @@ -13931,7 +13931,7 @@ def dlasdq( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SQRE", Int32], Returns["N", Int32], Returns["NCVT", Int32], Returns["NRU", Int32], Returns["NCC", Int32], Returns["LDVT", Int32], Returns["LDU", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DLASDT") @external @@ -13944,7 +13944,7 @@ def dlasdt( NDIML: Int32[Flat], NDIMR: Int32[Flat], MSUB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LVL", Int32], Returns["ND", Int32], Returns["MSUB", Int32]]: ... @bind("DLASET") @external @@ -13957,7 +13957,7 @@ def dlaset( BETA: Float64, A: Float64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["BETA", Float64], Returns["LDA", Int32]]: ... @bind("DLASQ1") @external @@ -13968,7 +13968,7 @@ def dlasq1( E: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DLASQ2") @external @@ -13977,7 +13977,7 @@ def dlasq2( N: Int32, Z: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DLASQ3") @external @@ -14003,7 +14003,7 @@ def dlasq3( DN2: Float64, G: Float64, TAU: Float64 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["DMIN", Float64], Returns["SIGMA", Float64], Returns["DESIG", Float64], Returns["QMAX", Float64], Returns["NFAIL", Int32], Returns["ITER", Int32], Returns["NDIV", Int32], Returns["IEEE", Bool], Returns["TTYPE", Int32], Returns["DMIN1", Float64], Returns["DMIN2", Float64], Returns["DN", Float64], Returns["DN1", Float64], Returns["DN2", Float64], Returns["G", Float64], Returns["TAU", Float64]]: ... @bind("DLASQ4") @external @@ -14023,7 +14023,7 @@ def dlasq4( TAU: Float64, TTYPE: Int32, G: Float64 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["N0IN", Int32], Returns["DMIN", Float64], Returns["DMIN1", Float64], Returns["DMIN2", Float64], Returns["DN", Float64], Returns["DN1", Float64], Returns["DN2", Float64], Returns["TAU", Float64], Returns["TTYPE", Int32], Returns["G", Float64]]: ... @bind("DLASQ5") @external @@ -14043,7 +14043,7 @@ def dlasq5( DNM2: Float64, IEEE: Bool, EPS: Float64 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["TAU", Float64], Returns["SIGMA", Float64], Returns["DMIN", Float64], Returns["DMIN1", Float64], Returns["DMIN2", Float64], Returns["DN", Float64], Returns["DNM1", Float64], Returns["DNM2", Float64], Returns["IEEE", Bool], Returns["EPS", Float64]]: ... @bind("DLASQ6") @external @@ -14059,7 +14059,7 @@ def dlasq6( DN: Float64, DNM1: Float64, DNM2: Float64 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["DMIN", Float64], Returns["DMIN1", Float64], Returns["DMIN2", Float64], Returns["DN", Float64], Returns["DNM1", Float64], Returns["DNM2", Float64]]: ... @bind("DLASR") @external @@ -14074,7 +14074,7 @@ def dlasr( S: Float64[Flat], A: Float64[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("DLASRT") @external @@ -14084,7 +14084,7 @@ def dlasrt( N: Int32, D: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DLASSQ") @external @@ -14095,7 +14095,7 @@ def dlassq( incx: Int32, scale: Float64, sumsq: Float64 -) -> None: ... +) -> tuple[Returns["n", Int32], Returns["incx", Int32], Returns["scale", Float64], Returns["sumsq", Float64]]: ... @bind("DLASV2") @external @@ -14110,7 +14110,7 @@ def dlasv2( CSR: Float64, SNL: Float64, CSL: Float64 -) -> None: ... +) -> tuple[Returns["F", Float64], Returns["G", Float64], Returns["H", Float64], Returns["SSMIN", Float64], Returns["SSMAX", Float64], Returns["SNR", Float64], Returns["CSR", Float64], Returns["SNL", Float64], Returns["CSL", Float64]]: ... @bind("DLASWLQ") @external @@ -14127,7 +14127,7 @@ def dlaswlq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DLASWP") @external @@ -14140,7 +14140,7 @@ def dlaswp( K2: Int32, IPIV: Int32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["K1", Int32], Returns["K2", Int32], Returns["INCX", Int32]]: ... @bind("DLASY2") @external @@ -14162,7 +14162,7 @@ def dlasy2( LDX: Int32, XNORM: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["LTRANL", Bool], Returns["LTRANR", Bool], Returns["ISGN", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LDTL", Int32], Returns["LDTR", Int32], Returns["LDB", Int32], Returns["SCALE", Float64], Returns["LDX", Int32], Returns["XNORM", Float64], Returns["INFO", Int32]]: ... @bind("DLASYF") @external @@ -14178,7 +14178,7 @@ def dlasyf( W: Float64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("DLASYF_AA") @external @@ -14194,7 +14194,7 @@ def dlasyf_aa( H: Float64[LDH, Flat], LDH: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["J1", Int32], Returns["M", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDH", Int32]]: ... @bind("DLASYF_RK") @external @@ -14211,7 +14211,7 @@ def dlasyf_rk( W: Float64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("DLASYF_ROOK") @external @@ -14227,7 +14227,7 @@ def dlasyf_rook( W: Float64[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("DLAT2S") @external @@ -14240,7 +14240,7 @@ def dlat2s( SA: Float32[LDSA, Flat], LDSA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDSA", Int32], Returns["INFO", Int32]]: ... @bind("DLATBS") @external @@ -14258,7 +14258,7 @@ def dlatbs( SCALE: Float64, CNORM: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("DLATDF") @external @@ -14273,7 +14273,7 @@ def dlatdf( RDSCAL: Float64, IPIV: Int32[Flat], JPIV: Int32[Flat] -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["RDSUM", Float64], Returns["RDSCAL", Float64]]: ... @bind("DLATPS") @external @@ -14289,7 +14289,7 @@ def dlatps( SCALE: Float64, CNORM: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("DLATRD") @external @@ -14304,7 +14304,7 @@ def dlatrd( TAU: Float64[Flat], W: Float64[LDW, Flat], LDW: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDW", Int32]]: ... @bind("DLATRS") @external @@ -14321,7 +14321,7 @@ def dlatrs( SCALE: Float64, CNORM: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("DLATRS3") @external @@ -14342,7 +14342,7 @@ def dlatrs3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DLATRZ") @external @@ -14355,7 +14355,7 @@ def dlatrz( LDA: Int32, TAU: Float64[Flat], WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32]]: ... @bind("DLATSQR") @external @@ -14372,7 +14372,7 @@ def dlatsqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DLAUU2") @external @@ -14383,7 +14383,7 @@ def dlauu2( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DLAUUM") @external @@ -14394,7 +14394,7 @@ def dlauum( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DOPGTR") @external @@ -14408,7 +14408,7 @@ def dopgtr( LDQ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("DOPMTR") @external @@ -14425,7 +14425,7 @@ def dopmtr( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DORBDB") @external @@ -14453,7 +14453,7 @@ def dorbdb( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORBDB1") @external @@ -14474,7 +14474,7 @@ def dorbdb1( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORBDB2") @external @@ -14495,7 +14495,7 @@ def dorbdb2( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORBDB3") @external @@ -14516,7 +14516,7 @@ def dorbdb3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORBDB4") @external @@ -14538,7 +14538,7 @@ def dorbdb4( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORBDB5") @external @@ -14558,7 +14558,7 @@ def dorbdb5( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORBDB6") @external @@ -14578,7 +14578,7 @@ def dorbdb6( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORCSD") @external @@ -14614,7 +14614,7 @@ def dorcsd( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORCSD2BY1") @external @@ -14641,7 +14641,7 @@ def dorcsd2by1( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORG2L") @external @@ -14655,7 +14655,7 @@ def dorg2l( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DORG2R") @external @@ -14669,7 +14669,7 @@ def dorg2r( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DORGBR") @external @@ -14685,7 +14685,7 @@ def dorgbr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGHR") @external @@ -14700,7 +14700,7 @@ def dorghr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGL2") @external @@ -14714,7 +14714,7 @@ def dorgl2( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DORGLQ") @external @@ -14729,7 +14729,7 @@ def dorglq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGQL") @external @@ -14744,7 +14744,7 @@ def dorgql( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGQR") @external @@ -14759,7 +14759,7 @@ def dorgqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGR2") @external @@ -14773,7 +14773,7 @@ def dorgr2( TAU: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DORGRQ") @external @@ -14788,7 +14788,7 @@ def dorgrq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGTR") @external @@ -14802,7 +14802,7 @@ def dorgtr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGTSQR") @external @@ -14819,7 +14819,7 @@ def dorgtsqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORGTSQR_ROW") @external @@ -14836,7 +14836,7 @@ def dorgtsqr_row( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORHR_COL") @external @@ -14851,7 +14851,7 @@ def dorhr_col( LDT: Int32, D: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DORM22") @external @@ -14870,7 +14870,7 @@ def dorm22( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LDQ", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORM2L") @external @@ -14888,7 +14888,7 @@ def dorm2l( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DORM2R") @external @@ -14906,7 +14906,7 @@ def dorm2r( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DORMBR") @external @@ -14926,7 +14926,7 @@ def dormbr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORMHR") @external @@ -14946,7 +14946,7 @@ def dormhr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORML2") @external @@ -14964,7 +14964,7 @@ def dorml2( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DORMLQ") @external @@ -14983,7 +14983,7 @@ def dormlq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORMQL") @external @@ -15002,7 +15002,7 @@ def dormql( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORMQR") @external @@ -15021,7 +15021,7 @@ def dormqr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORMR2") @external @@ -15039,7 +15039,7 @@ def dormr2( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DORMR3") @external @@ -15058,7 +15058,7 @@ def dormr3( LDC: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("DORMRQ") @external @@ -15077,7 +15077,7 @@ def dormrq( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORMRZ") @external @@ -15097,7 +15097,7 @@ def dormrz( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DORMTR") @external @@ -15116,7 +15116,7 @@ def dormtr( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DPBCON") @external @@ -15132,7 +15132,7 @@ def dpbcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPBEQU") @external @@ -15147,7 +15147,7 @@ def dpbequ( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DPBRFS") @external @@ -15170,7 +15170,7 @@ def dpbrfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DPBSTF") @external @@ -15182,7 +15182,7 @@ def dpbstf( AB: Float64[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("DPBSV") @external @@ -15197,7 +15197,7 @@ def dpbsv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPBSVX") @external @@ -15224,7 +15224,7 @@ def dpbsvx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPBTF2") @external @@ -15236,7 +15236,7 @@ def dpbtf2( AB: Float64[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("DPBTRF") @external @@ -15248,7 +15248,7 @@ def dpbtrf( AB: Float64[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("DPBTRS") @external @@ -15263,7 +15263,7 @@ def dpbtrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPFTRF") @external @@ -15274,7 +15274,7 @@ def dpftrf( N: Int32, A: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DPFTRI") @external @@ -15285,7 +15285,7 @@ def dpftri( N: Int32, A: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DPFTRS") @external @@ -15299,7 +15299,7 @@ def dpftrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPOCON") @external @@ -15314,7 +15314,7 @@ def dpocon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPOEQU") @external @@ -15327,7 +15327,7 @@ def dpoequ( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DPOEQUB") @external @@ -15340,7 +15340,7 @@ def dpoequb( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DPORFS") @external @@ -15362,7 +15362,7 @@ def dporfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DPORFSX") @external @@ -15391,7 +15391,7 @@ def dporfsx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DPOSV") @external @@ -15405,7 +15405,7 @@ def dposv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPOSVX") @external @@ -15431,7 +15431,7 @@ def dposvx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPOSVXX") @external @@ -15462,7 +15462,7 @@ def dposvxx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DPOTF2") @external @@ -15473,7 +15473,7 @@ def dpotf2( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DPOTRF") @external @@ -15484,7 +15484,7 @@ def dpotrf( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DPOTRF2") @external @@ -15495,7 +15495,7 @@ def dpotrf2( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DPOTRI") @external @@ -15506,7 +15506,7 @@ def dpotri( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DPOTRS") @external @@ -15520,7 +15520,7 @@ def dpotrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPPCON") @external @@ -15534,7 +15534,7 @@ def dppcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPPEQU") @external @@ -15547,7 +15547,7 @@ def dppequ( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DPPRFS") @external @@ -15567,7 +15567,7 @@ def dpprfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DPPSV") @external @@ -15580,7 +15580,7 @@ def dppsv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPPSVX") @external @@ -15604,7 +15604,7 @@ def dppsvx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPPTRF") @external @@ -15614,7 +15614,7 @@ def dpptrf( N: Int32, AP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DPPTRI") @external @@ -15624,7 +15624,7 @@ def dpptri( N: Int32, AP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DPPTRS") @external @@ -15637,7 +15637,7 @@ def dpptrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPSTF2") @external @@ -15652,7 +15652,7 @@ def dpstf2( TOL: Float64, WORK: Float64[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float64], Returns["INFO", Int32]]: ... @bind("DPSTRF") @external @@ -15667,7 +15667,7 @@ def dpstrf( TOL: Float64, WORK: Float64[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float64], Returns["INFO", Int32]]: ... @bind("DPTCON") @external @@ -15680,7 +15680,7 @@ def dptcon( RCOND: Float64, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPTEQR") @external @@ -15694,7 +15694,7 @@ def dpteqr( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DPTRFS") @external @@ -15714,7 +15714,7 @@ def dptrfs( BERR: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DPTSV") @external @@ -15727,7 +15727,7 @@ def dptsv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPTSVX") @external @@ -15749,7 +15749,7 @@ def dptsvx( BERR: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DPTTRF") @external @@ -15759,7 +15759,7 @@ def dpttrf( D: Float64[Flat], E: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DPTTRS") @external @@ -15772,7 +15772,7 @@ def dpttrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DPTTS2") @external @@ -15784,7 +15784,7 @@ def dptts2( E: Float64[Flat], B: Float64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("DRSCL") @external @@ -15794,7 +15794,7 @@ def drscl( SA: Float64, SX: Float64[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SA", Float64], Returns["INCX", Int32]]: ... @bind("DSB2ST_KERNELS") @external @@ -15815,7 +15815,7 @@ def dsb2st_kernels( TAU: Float64[Flat], LDVT: Int32, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["WANTZ", Bool], Returns["TTYPE", Int32], Returns["ST", Int32], Returns["ED", Int32], Returns["SWEEP", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["IB", Int32], Returns["LDA", Int32], Returns["LDVT", Int32]]: ... @bind("DSBEV") @external @@ -15832,7 +15832,7 @@ def dsbev( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSBEV_2STAGE") @external @@ -15850,7 +15850,7 @@ def dsbev_2stage( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSBEVD") @external @@ -15870,7 +15870,7 @@ def dsbevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSBEVD_2STAGE") @external @@ -15890,7 +15890,7 @@ def dsbevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSBEVX") @external @@ -15918,7 +15918,7 @@ def dsbevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSBEVX_2STAGE") @external @@ -15947,7 +15947,7 @@ def dsbevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSBGST") @external @@ -15966,7 +15966,7 @@ def dsbgst( LDX: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DSBGV") @external @@ -15986,7 +15986,7 @@ def dsbgv( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSBGVD") @external @@ -16009,7 +16009,7 @@ def dsbgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSBGVX") @external @@ -16040,7 +16040,7 @@ def dsbgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDQ", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSBTRD") @external @@ -16058,7 +16058,7 @@ def dsbtrd( LDQ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("DSFRK") @external @@ -16074,7 +16074,7 @@ def dsfrk( LDA: Int32, BETA: Float64, C: Float64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["BETA", Float64]]: ... @bind("DSGESV") @external @@ -16093,7 +16093,7 @@ def dsgesv( SWORK: Float32[Flat], ITER: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["ITER", Int32], Returns["INFO", Int32]]: ... @bind("DSPCON") @external @@ -16108,7 +16108,7 @@ def dspcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DSPEV") @external @@ -16123,7 +16123,7 @@ def dspev( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSPEVD") @external @@ -16141,7 +16141,7 @@ def dspevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSPEVX") @external @@ -16165,7 +16165,7 @@ def dspevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSPGST") @external @@ -16177,7 +16177,7 @@ def dspgst( AP: Float64[Flat], BP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DSPGV") @external @@ -16194,7 +16194,7 @@ def dspgv( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSPGVD") @external @@ -16214,7 +16214,7 @@ def dspgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSPGVX") @external @@ -16240,7 +16240,7 @@ def dspgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSPOSV") @external @@ -16259,7 +16259,7 @@ def dsposv( SWORK: Float32[Flat], ITER: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["ITER", Int32], Returns["INFO", Int32]]: ... @bind("DSPRFS") @external @@ -16280,7 +16280,7 @@ def dsprfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DSPSV") @external @@ -16294,7 +16294,7 @@ def dspsv( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSPSVX") @external @@ -16317,7 +16317,7 @@ def dspsvx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DSPTRD") @external @@ -16330,7 +16330,7 @@ def dsptrd( E: Float64[Flat], TAU: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DSPTRF") @external @@ -16341,7 +16341,7 @@ def dsptrf( AP: Float64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DSPTRI") @external @@ -16353,7 +16353,7 @@ def dsptri( IPIV: Int32[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DSPTRS") @external @@ -16367,7 +16367,7 @@ def dsptrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSTEBZ") @external @@ -16391,7 +16391,7 @@ def dstebz( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["NSPLIT", Int32], Returns["INFO", Int32]]: ... @bind("DSTEDC") @external @@ -16408,7 +16408,7 @@ def dstedc( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSTEGR") @external @@ -16434,7 +16434,7 @@ def dstegr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSTEIN") @external @@ -16453,7 +16453,7 @@ def dstein( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSTEMR") @external @@ -16480,7 +16480,7 @@ def dstemr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["NZC", Int32], Returns["TRYRAC", Bool], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSTEQR") @external @@ -16494,7 +16494,7 @@ def dsteqr( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSTERF") @external @@ -16504,7 +16504,7 @@ def dsterf( D: Float64[Flat], E: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DSTEV") @external @@ -16518,7 +16518,7 @@ def dstev( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSTEVD") @external @@ -16535,7 +16535,7 @@ def dstevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSTEVR") @external @@ -16561,7 +16561,7 @@ def dstevr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSTEVX") @external @@ -16585,7 +16585,7 @@ def dstevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("DSYCON") @external @@ -16601,7 +16601,7 @@ def dsycon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DSYCON_3") @external @@ -16618,7 +16618,7 @@ def dsycon_3( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DSYCON_ROOK") @external @@ -16634,7 +16634,7 @@ def dsycon_rook( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DSYCONV") @external @@ -16648,7 +16648,7 @@ def dsyconv( IPIV: Int32[Flat], E: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYCONVF") @external @@ -16662,7 +16662,7 @@ def dsyconvf( E: Float64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYCONVF_ROOK") @external @@ -16676,7 +16676,7 @@ def dsyconvf_rook( E: Float64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYEQUB") @external @@ -16691,7 +16691,7 @@ def dsyequb( AMAX: Float64, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("DSYEV") @external @@ -16706,7 +16706,7 @@ def dsyev( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYEV_2STAGE") @external @@ -16721,7 +16721,7 @@ def dsyev_2stage( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYEVD") @external @@ -16738,7 +16738,7 @@ def dsyevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYEVD_2STAGE") @external @@ -16755,7 +16755,7 @@ def dsyevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYEVR") @external @@ -16782,7 +16782,7 @@ def dsyevr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYEVR_2STAGE") @external @@ -16809,7 +16809,7 @@ def dsyevr_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYEVX") @external @@ -16835,7 +16835,7 @@ def dsyevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYEVX_2STAGE") @external @@ -16861,7 +16861,7 @@ def dsyevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYGS2") @external @@ -16875,7 +16875,7 @@ def dsygs2( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSYGST") @external @@ -16889,7 +16889,7 @@ def dsygst( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSYGV") @external @@ -16907,7 +16907,7 @@ def dsygv( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYGV_2STAGE") @external @@ -16925,7 +16925,7 @@ def dsygv_2stage( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYGVD") @external @@ -16945,7 +16945,7 @@ def dsygvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYGVX") @external @@ -16974,7 +16974,7 @@ def dsygvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYRFS") @external @@ -16997,7 +16997,7 @@ def dsyrfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DSYRFSX") @external @@ -17027,7 +17027,7 @@ def dsyrfsx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DSYSV") @external @@ -17044,7 +17044,7 @@ def dsysv( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYSV_AA") @external @@ -17061,7 +17061,7 @@ def dsysv_aa( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYSV_AA_2STAGE") @external @@ -17081,7 +17081,7 @@ def dsysv_aa_2stage( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYSV_RK") @external @@ -17099,7 +17099,7 @@ def dsysv_rk( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYSV_ROOK") @external @@ -17116,7 +17116,7 @@ def dsysv_rook( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYSVX") @external @@ -17142,7 +17142,7 @@ def dsysvx( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYSVXX") @external @@ -17174,7 +17174,7 @@ def dsysvxx( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("DSYSWAPR") @external @@ -17186,7 +17186,7 @@ def dsyswapr( LDA: Int32, I1: Int32, I2: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["I1", Int32], Returns["I2", Int32]]: ... @bind("DSYTD2") @external @@ -17200,7 +17200,7 @@ def dsytd2( E: Float64[Flat], TAU: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYTF2") @external @@ -17212,7 +17212,7 @@ def dsytf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYTF2_RK") @external @@ -17225,7 +17225,7 @@ def dsytf2_rk( E: Float64[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYTF2_ROOK") @external @@ -17237,7 +17237,7 @@ def dsytf2_rook( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRD") @external @@ -17253,7 +17253,7 @@ def dsytrd( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRD_2STAGE") @external @@ -17272,7 +17272,7 @@ def dsytrd_2stage( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LHOUS2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRD_SB2ST") @external @@ -17292,7 +17292,7 @@ def dsytrd_sb2st( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LHOUS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRD_SY2SB") @external @@ -17309,7 +17309,7 @@ def dsytrd_sy2sb( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDA", Int32], Returns["LDAB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRF") @external @@ -17323,7 +17323,7 @@ def dsytrf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRF_AA") @external @@ -17337,7 +17337,7 @@ def dsytrf_aa( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRF_AA_2STAGE") @external @@ -17354,7 +17354,7 @@ def dsytrf_aa_2stage( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRF_RK") @external @@ -17369,7 +17369,7 @@ def dsytrf_rk( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRF_ROOK") @external @@ -17383,7 +17383,7 @@ def dsytrf_rook( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRI") @external @@ -17396,7 +17396,7 @@ def dsytri( IPIV: Int32[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRI2") @external @@ -17410,7 +17410,7 @@ def dsytri2( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRI2X") @external @@ -17424,7 +17424,7 @@ def dsytri2x( WORK: Float64[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRI_3") @external @@ -17439,7 +17439,7 @@ def dsytri_3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRI_3X") @external @@ -17454,7 +17454,7 @@ def dsytri_3x( WORK: Float64[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRI_ROOK") @external @@ -17467,7 +17467,7 @@ def dsytri_rook( IPIV: Int32[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRS") @external @@ -17482,7 +17482,7 @@ def dsytrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRS2") @external @@ -17498,7 +17498,7 @@ def dsytrs2( LDB: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRS_3") @external @@ -17514,7 +17514,7 @@ def dsytrs_3( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRS_AA") @external @@ -17531,7 +17531,7 @@ def dsytrs_aa( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRS_AA_2STAGE") @external @@ -17549,7 +17549,7 @@ def dsytrs_aa_2stage( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DSYTRS_ROOK") @external @@ -17564,7 +17564,7 @@ def dsytrs_rook( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DTBCON") @external @@ -17581,7 +17581,7 @@ def dtbcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DTBRFS") @external @@ -17604,7 +17604,7 @@ def dtbrfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DTBTRS") @external @@ -17621,7 +17621,7 @@ def dtbtrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DTFSM") @external @@ -17638,7 +17638,7 @@ def dtfsm( A: Float64[Flat], B: Float64[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDB", Int32]]: ... @bind("DTFTRI") @external @@ -17650,7 +17650,7 @@ def dtftri( N: Int32, A: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DTFTTP") @external @@ -17662,7 +17662,7 @@ def dtfttp( ARF: Float64[Flat], AP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DTFTTR") @external @@ -17675,7 +17675,7 @@ def dtfttr( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DTGEVC") @external @@ -17697,7 +17697,7 @@ def dtgevc( M: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDS", Int32], Returns["LDP", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("DTGEX2") @external @@ -17720,7 +17720,7 @@ def dtgex2( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["J1", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTGEXC") @external @@ -17742,7 +17742,7 @@ def dtgexc( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTGSEN") @external @@ -17773,7 +17773,7 @@ def dtgsen( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["M", Int32], Returns["PL", Float64], Returns["PR", Float64], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTGSJA") @external @@ -17804,7 +17804,7 @@ def dtgsja( WORK: Float64[Flat], NCYCLE: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float64], Returns["TOLB", Float64], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["NCYCLE", Int32], Returns["INFO", Int32]]: ... @bind("DTGSNA") @external @@ -17830,7 +17830,7 @@ def dtgsna( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTGSY2") @external @@ -17858,7 +17858,7 @@ def dtgsy2( IWORK: Int32[Flat], PQ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float64], Returns["RDSUM", Float64], Returns["RDSCAL", Float64], Returns["PQ", Int32], Returns["INFO", Int32]]: ... @bind("DTGSYL") @external @@ -17886,7 +17886,7 @@ def dtgsyl( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float64], Returns["DIF", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTPCON") @external @@ -17901,7 +17901,7 @@ def dtpcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DTPLQT") @external @@ -17919,7 +17919,7 @@ def dtplqt( LDT: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DTPLQT2") @external @@ -17935,7 +17935,7 @@ def dtplqt2( T: Float64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DTPMLQT") @external @@ -17958,7 +17958,7 @@ def dtpmlqt( LDB: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DTPMQRT") @external @@ -17981,7 +17981,7 @@ def dtpmqrt( LDB: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DTPQRT") @external @@ -17999,7 +17999,7 @@ def dtpqrt( LDT: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DTPQRT2") @external @@ -18015,7 +18015,7 @@ def dtpqrt2( T: Float64[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("DTPRFB") @external @@ -18039,7 +18039,7 @@ def dtprfb( LDB: Int32, WORK: Float64[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("DTPRFS") @external @@ -18060,7 +18060,7 @@ def dtprfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DTPTRI") @external @@ -18071,7 +18071,7 @@ def dtptri( N: Int32, AP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DTPTRS") @external @@ -18086,7 +18086,7 @@ def dtptrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DTPTTF") @external @@ -18098,7 +18098,7 @@ def dtpttf( AP: Float64[Flat], ARF: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("DTPTTR") @external @@ -18110,7 +18110,7 @@ def dtpttr( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DTRCON") @external @@ -18126,7 +18126,7 @@ def dtrcon( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("DTREVC") @external @@ -18146,7 +18146,7 @@ def dtrevc( M: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("DTREVC3") @external @@ -18167,7 +18167,7 @@ def dtrevc3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTREXC") @external @@ -18183,7 +18183,7 @@ def dtrexc( ILST: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["INFO", Int32]]: ... @bind("DTRRFS") @external @@ -18205,7 +18205,7 @@ def dtrrfs( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("DTRSEN") @external @@ -18229,7 +18229,7 @@ def dtrsen( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["M", Int32], Returns["S", Float64], Returns["SEP", Float64], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTRSNA") @external @@ -18253,7 +18253,7 @@ def dtrsna( LDWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LDWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTRSYL") @external @@ -18272,7 +18272,7 @@ def dtrsyl( LDC: Int32, SCALE: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("DTRSYL3") @external @@ -18295,7 +18295,7 @@ def dtrsyl3( SWORK: Float64[LDSWORK, Flat], LDSWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float64], Returns["LIWORK", Int32], Returns["LDSWORK", Int32], Returns["INFO", Int32]]: ... @bind("DTRTI2") @external @@ -18307,7 +18307,7 @@ def dtrti2( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DTRTRI") @external @@ -18319,7 +18319,7 @@ def dtrtri( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DTRTRS") @external @@ -18335,7 +18335,7 @@ def dtrtrs( B: Float64[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("DTRTTF") @external @@ -18348,7 +18348,7 @@ def dtrttf( LDA: Int32, ARF: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DTRTTP") @external @@ -18360,7 +18360,7 @@ def dtrttp( LDA: Int32, AP: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("DTZRZF") @external @@ -18374,7 +18374,7 @@ def dtzrzf( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("DZSUM1") @external @@ -18383,7 +18383,7 @@ def dzsum1( N: Int32, CX: Complex128[Flat], INCX: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("ICMAX1") @external @@ -18392,7 +18392,7 @@ def icmax1( N: Int32, CX: Complex64[Flat], INCX: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("IEEECK") @external @@ -18401,7 +18401,7 @@ def ieeeck( ISPEC: Int32, ZERO: Float32, ONE: Float32 -) -> Int32: ... +) -> tuple[Int32, Returns["ISPEC", Int32], Returns["ZERO", Float32], Returns["ONE", Float32]]: ... @bind("ILACLC") @external @@ -18411,7 +18411,7 @@ def ilaclc( N: Int32, A: Complex64[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ILACLR") @external @@ -18421,7 +18421,7 @@ def ilaclr( N: Int32, A: Complex64[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ILADIAG") @external @@ -18437,7 +18437,7 @@ def iladlc( N: Int32, A: Float64[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ILADLR") @external @@ -18447,7 +18447,7 @@ def iladlr( N: Int32, A: Float64[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ILAENV") @external @@ -18460,7 +18460,7 @@ def ilaenv( N2: Int32, N3: Int32, N4: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["ISPEC", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["N3", Int32], Returns["N4", Int32]]: ... @bind("ILAENV2STAGE") @external @@ -18473,7 +18473,7 @@ def ilaenv2stage( N2: Int32, N3: Int32, N4: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["ISPEC", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["N3", Int32], Returns["N4", Int32]]: ... @bind("ILAPREC") @external @@ -18489,7 +18489,7 @@ def ilaslc( N: Int32, A: Float32[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ILASLR") @external @@ -18499,7 +18499,7 @@ def ilaslr( N: Int32, A: Float32[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ILATRANS") @external @@ -18521,7 +18521,7 @@ def ilazlc( N: Int32, A: Complex128[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ILAZLR") @external @@ -18531,7 +18531,7 @@ def ilazlr( N: Int32, A: Complex128[LDA, Flat], LDA: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("IPARAM2STAGE") @external @@ -18544,7 +18544,7 @@ def iparam2stage( NBI: Int32, IBI: Int32, NXI: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["ISPEC", Int32], Returns["NI", Int32], Returns["NBI", Int32], Returns["IBI", Int32], Returns["NXI", Int32]]: ... @bind("IPARMQ") @external @@ -18557,7 +18557,7 @@ def iparmq( ILO: Int32, IHI: Int32, LWORK: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["ISPEC", Int32], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LWORK", Int32]]: ... @bind("IZMAX1") @external @@ -18566,7 +18566,7 @@ def izmax1( N: Int32, ZX: Complex128[Flat], INCX: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("LSAMEN") @external @@ -18575,7 +18575,7 @@ def lsamen( N: Int32, CA: String, CB: String -) -> Bool: ... +) -> tuple[Bool, Returns["N", Int32]]: ... @bind("SBBCSD") @external @@ -18610,7 +18610,7 @@ def sbbcsd( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SBDSDC") @external @@ -18630,7 +18630,7 @@ def sbdsdc( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["INFO", Int32]]: ... @bind("SBDSQR") @external @@ -18651,7 +18651,7 @@ def sbdsqr( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NCVT", Int32], Returns["NRU", Int32], Returns["NCC", Int32], Returns["LDVT", Int32], Returns["LDU", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SBDSVDX") @external @@ -18674,7 +18674,7 @@ def sbdsvdx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["NS", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SCSUM1") @external @@ -18683,7 +18683,7 @@ def scsum1( N: Int32, CX: Complex64[Flat], INCX: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("SDISNA") @external @@ -18695,7 +18695,7 @@ def sdisna( D: Float32[Flat], SEP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SGBBRD") @external @@ -18719,7 +18719,7 @@ def sgbbrd( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NCC", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["LDPT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SGBCON") @external @@ -18737,7 +18737,7 @@ def sgbcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SGBEQU") @external @@ -18755,7 +18755,7 @@ def sgbequ( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SGBEQUB") @external @@ -18773,7 +18773,7 @@ def sgbequb( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SGBRFS") @external @@ -18798,7 +18798,7 @@ def sgbrfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SGBRFSX") @external @@ -18831,7 +18831,7 @@ def sgbrfsx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SGBSV") @external @@ -18847,7 +18847,7 @@ def sgbsv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SGBSVX") @external @@ -18877,7 +18877,7 @@ def sgbsvx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SGBSVXX") @external @@ -18912,7 +18912,7 @@ def sgbsvxx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SGBTF2") @external @@ -18926,7 +18926,7 @@ def sgbtf2( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("SGBTRF") @external @@ -18940,7 +18940,7 @@ def sgbtrf( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("SGBTRS") @external @@ -18957,7 +18957,7 @@ def sgbtrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SGEBAK") @external @@ -18973,7 +18973,7 @@ def sgebak( V: Float32[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("SGEBAL") @external @@ -18987,7 +18987,7 @@ def sgebal( IHI: Int32, SCALE: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("SGEBD2") @external @@ -19003,7 +19003,7 @@ def sgebd2( TAUP: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGEBRD") @external @@ -19020,7 +19020,7 @@ def sgebrd( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGECON") @external @@ -19035,11 +19035,11 @@ def sgecon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SGEDMD") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Arg(15), Addr(Arg(16)), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Addr(Arg(21)), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Return('INFO', 10)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Arg(15), Addr(Arg(16)), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Addr(Arg(21)), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Return('INFO', 1)]) def sgedmd( JOBS: String[1], JOBZ: String[1], @@ -19069,11 +19069,11 @@ def sgedmd( LWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Int32, Returns["REIG", Float32[Flat]], Returns["IMEIG", Float32[Flat]], Returns["Z", Float32[LDZ, Flat]], Returns["RES", Float32[Flat]], Returns["B", Float32[LDB, Flat]], Returns["W", Float32[LDW, Flat]], Returns["S", Float32[LDS, Flat]], Returns["WORK", Float32[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("SGEDMDQ") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 2), Arg(17), Arg(18), Arg(19), Addr(Arg(20)), Arg(21), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Arg(28), Addr(Arg(29)), Arg(30), Addr(Arg(31)), Return('INFO', 12)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 0), Arg(17), Arg(18), Arg(19), Addr(Arg(20)), Arg(21), Arg(22), Addr(Arg(23)), Arg(24), Addr(Arg(25)), Arg(26), Addr(Arg(27)), Arg(28), Addr(Arg(29)), Arg(30), Addr(Arg(31)), Return('INFO', 1)]) def sgedmdq( JOBS: String[1], JOBZ: String[1], @@ -19107,7 +19107,7 @@ def sgedmdq( LWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Returns["X", Float32[LDX, Flat]], Returns["Y", Float32[LDY, Flat]], Int32, Returns["REIG", Float32[Flat]], Returns["IMEIG", Float32[Flat]], Returns["Z", Float32[LDZ, Flat]], Returns["RES", Float32[Flat]], Returns["B", Float32[LDB, Flat]], Returns["V", Float32[LDV, Flat]], Returns["S", Float32[LDS, Flat]], Returns["WORK", Float32[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("SGEEQU") @external @@ -19123,7 +19123,7 @@ def sgeequ( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SGEEQUB") @external @@ -19139,7 +19139,7 @@ def sgeequb( COLCND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SGEES") @external @@ -19160,7 +19160,7 @@ def sgees( LWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEESX") @external @@ -19186,7 +19186,7 @@ def sgeesx( LIWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["RCONDE", Float32], Returns["RCONDV", Float32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEEV") @external @@ -19206,7 +19206,7 @@ def sgeev( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEEVX") @external @@ -19235,7 +19235,7 @@ def sgeevx( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEHD2") @external @@ -19249,7 +19249,7 @@ def sgehd2( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGEHRD") @external @@ -19264,7 +19264,7 @@ def sgehrd( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEJSV") @external @@ -19289,7 +19289,7 @@ def sgejsv( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGELQ") @external @@ -19304,7 +19304,7 @@ def sgelq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGELQ2") @external @@ -19317,7 +19317,7 @@ def sgelq2( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGELQF") @external @@ -19331,7 +19331,7 @@ def sgelqf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGELQT") @external @@ -19346,7 +19346,7 @@ def sgelqt( LDT: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("SGELQT3") @external @@ -19359,7 +19359,7 @@ def sgelqt3( T: Float32[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("SGELS") @external @@ -19376,7 +19376,7 @@ def sgels( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGELSD") @external @@ -19396,7 +19396,7 @@ def sgelsd( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGELSS") @external @@ -19415,7 +19415,7 @@ def sgelss( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGELST") @external @@ -19432,7 +19432,7 @@ def sgelst( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGELSY") @external @@ -19451,7 +19451,7 @@ def sgelsy( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEMLQ") @external @@ -19471,7 +19471,7 @@ def sgemlq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEMLQT") @external @@ -19491,7 +19491,7 @@ def sgemlqt( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SGEMQR") @external @@ -19511,7 +19511,7 @@ def sgemqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEMQRT") @external @@ -19531,7 +19531,7 @@ def sgemqrt( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SGEQL2") @external @@ -19544,7 +19544,7 @@ def sgeql2( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGEQLF") @external @@ -19558,7 +19558,7 @@ def sgeqlf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEQP3") @external @@ -19573,7 +19573,7 @@ def sgeqp3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEQP3RK") @external @@ -19596,7 +19596,7 @@ def sgeqp3rk( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float32], Returns["RELTOL", Float32], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float32], Returns["RELMAXC2NRMK", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEQR") @external @@ -19611,7 +19611,7 @@ def sgeqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEQR2") @external @@ -19624,7 +19624,7 @@ def sgeqr2( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGEQR2P") @external @@ -19637,7 +19637,7 @@ def sgeqr2p( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGEQRF") @external @@ -19651,7 +19651,7 @@ def sgeqrf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEQRFP") @external @@ -19665,7 +19665,7 @@ def sgeqrfp( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGEQRT") @external @@ -19680,7 +19680,7 @@ def sgeqrt( LDT: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("SGEQRT2") @external @@ -19693,7 +19693,7 @@ def sgeqrt2( T: Float32[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("SGEQRT3") @external @@ -19706,7 +19706,7 @@ def sgeqrt3( T: Float32[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("SGERFS") @external @@ -19729,7 +19729,7 @@ def sgerfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SGERFSX") @external @@ -19760,7 +19760,7 @@ def sgerfsx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SGERQ2") @external @@ -19773,7 +19773,7 @@ def sgerq2( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGERQF") @external @@ -19787,7 +19787,7 @@ def sgerqf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGESC2") @external @@ -19800,7 +19800,7 @@ def sgesc2( IPIV: Int32[Flat], JPIV: Int32[Flat], SCALE: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float32]]: ... @bind("SGESDD") @external @@ -19820,7 +19820,7 @@ def sgesdd( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGESV") @external @@ -19834,7 +19834,7 @@ def sgesv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SGESVD") @external @@ -19854,7 +19854,7 @@ def sgesvd( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGESVDQ") @external @@ -19882,7 +19882,7 @@ def sgesvdq( RWORK: Float32[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["NUMRANK", Int32], Returns["LIWORK", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGESVDX") @external @@ -19909,7 +19909,7 @@ def sgesvdx( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["NS", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGESVJ") @external @@ -19929,7 +19929,7 @@ def sgesvj( WORK: Float32[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGESVX") @external @@ -19957,7 +19957,7 @@ def sgesvx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SGESVXX") @external @@ -19990,7 +19990,7 @@ def sgesvxx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SGETC2") @external @@ -20002,7 +20002,7 @@ def sgetc2( IPIV: Int32[Flat], JPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGETF2") @external @@ -20014,7 +20014,7 @@ def sgetf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGETRF") @external @@ -20026,7 +20026,7 @@ def sgetrf( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGETRF2") @external @@ -20038,7 +20038,7 @@ def sgetrf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SGETRI") @external @@ -20051,7 +20051,7 @@ def sgetri( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGETRS") @external @@ -20066,7 +20066,7 @@ def sgetrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SGETSLS") @external @@ -20083,7 +20083,7 @@ def sgetsls( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGETSQRHRT") @external @@ -20101,7 +20101,7 @@ def sgetsqrhrt( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB1", Int32], Returns["NB1", Int32], Returns["NB2", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGBAK") @external @@ -20118,7 +20118,7 @@ def sggbak( V: Float32[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("SGGBAL") @external @@ -20136,7 +20136,7 @@ def sggbal( RSCALE: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("SGGES") @external @@ -20163,7 +20163,7 @@ def sgges( LWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGES3") @external @@ -20190,7 +20190,7 @@ def sgges3( LWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGESX") @external @@ -20222,7 +20222,7 @@ def sggesx( LIWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGEV") @external @@ -20245,7 +20245,7 @@ def sggev( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGEV3") @external @@ -20268,7 +20268,7 @@ def sggev3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGEVX") @external @@ -20303,7 +20303,7 @@ def sggevx( IWORK: Int32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float32], Returns["BBNRM", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGGLM") @external @@ -20322,7 +20322,7 @@ def sggglm( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGHD3") @external @@ -20344,7 +20344,7 @@ def sgghd3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGHRD") @external @@ -20364,7 +20364,7 @@ def sgghrd( Z: Float32[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SGGLSE") @external @@ -20383,7 +20383,7 @@ def sgglse( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGQRF") @external @@ -20401,7 +20401,7 @@ def sggqrf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGRQF") @external @@ -20419,7 +20419,7 @@ def sggrqf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGSVD3") @external @@ -20449,7 +20449,7 @@ def sggsvd3( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGGSVP3") @external @@ -20480,7 +20480,7 @@ def sggsvp3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float32], Returns["TOLB", Float32], Returns["K", Int32], Returns["L", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGSVJ0") @external @@ -20503,7 +20503,7 @@ def sgsvj0( WORK: Float32[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float32], Returns["SFMIN", Float32], Returns["TOL", Float32], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGSVJ1") @external @@ -20527,7 +20527,7 @@ def sgsvj1( WORK: Float32[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float32], Returns["SFMIN", Float32], Returns["TOL", Float32], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SGTCON") @external @@ -20545,7 +20545,7 @@ def sgtcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SGTRFS") @external @@ -20571,7 +20571,7 @@ def sgtrfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SGTSV") @external @@ -20585,7 +20585,7 @@ def sgtsv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SGTSVX") @external @@ -20613,7 +20613,7 @@ def sgtsvx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SGTTRF") @external @@ -20626,7 +20626,7 @@ def sgttrf( DU2: Float32[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SGTTRS") @external @@ -20643,7 +20643,7 @@ def sgttrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SGTTS2") @external @@ -20659,7 +20659,7 @@ def sgtts2( IPIV: Int32[Flat], B: Float32[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["ITRANS", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("SHGEQZ") @external @@ -20685,7 +20685,7 @@ def shgeqz( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SHSEIN") @external @@ -20710,7 +20710,7 @@ def shsein( IFAILL: Int32[Flat], IFAILR: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("SHSEQR") @external @@ -20730,7 +20730,7 @@ def shseqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SISNAN") @external @@ -20756,7 +20756,7 @@ def sla_gbamv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Float32], Returns["LDAB", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SLA_GBRCOND") @external @@ -20776,7 +20776,7 @@ def sla_gbrcond( INFO: Int32, WORK: Float32[Flat], IWORK: Int32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("SLA_GBRFSX_EXTENDED") @external @@ -20813,7 +20813,7 @@ def sla_gbrfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("SLA_GBRPVGRW") @external @@ -20827,7 +20827,7 @@ def sla_gbrpvgrw( LDAB: Int32, AFB: Float32[LDAFB, Flat], LDAFB: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NCOLS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32]]: ... @bind("SLA_GEAMV") @external @@ -20844,7 +20844,7 @@ def sla_geamv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SLA_GERCOND") @external @@ -20862,7 +20862,7 @@ def sla_gercond( INFO: Int32, WORK: Float32[Flat], IWORK: Int32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("SLA_GERFSX_EXTENDED") @external @@ -20897,7 +20897,7 @@ def sla_gerfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("SLA_GERPVGRW") @external @@ -20909,7 +20909,7 @@ def sla_gerpvgrw( LDA: Int32, AF: Float32[LDAF, Flat], LDAF: Int32 -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("SLA_LIN_BERR") @external @@ -20921,7 +20921,7 @@ def sla_lin_berr( RES: Float32[N, NRHS], AYB: Float32[N, NRHS], BERR: Float32[NRHS] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NZ", Int32], Returns["NRHS", Int32]]: ... @bind("SLA_PORCOND") @external @@ -20938,7 +20938,7 @@ def sla_porcond( INFO: Int32, WORK: Float32[Flat], IWORK: Int32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("SLA_PORFSX_EXTENDED") @external @@ -20972,7 +20972,7 @@ def sla_porfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("SLA_PORPVGRW") @external @@ -20985,7 +20985,7 @@ def sla_porpvgrw( AF: Float32[LDAF, Flat], LDAF: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("SLA_SYAMV") @external @@ -21001,7 +21001,7 @@ def sla_syamv( BETA: Float32, Y: Float32[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["UPLO", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float32], Returns["INCY", Int32]]: ... @bind("SLA_SYRCOND") @external @@ -21019,7 +21019,7 @@ def sla_syrcond( INFO: Int32, WORK: Float32[Flat], IWORK: Int32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CMODE", Int32], Returns["INFO", Int32]]: ... @bind("SLA_SYRFSX_EXTENDED") @external @@ -21054,7 +21054,7 @@ def sla_syrfsx_extended( DZ_UB: Float32, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float32], Returns["ITHRESH", Int32], Returns["RTHRESH", Float32], Returns["DZ_UB", Float32], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("SLA_SYRPVGRW") @external @@ -21069,7 +21069,7 @@ def sla_syrpvgrw( LDAF: Int32, IPIV: Int32[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["INFO", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("SLA_WWADDW") @external @@ -21079,7 +21079,7 @@ def sla_wwaddw( X: Float32[Flat], Y: Float32[Flat], W: Float32[Flat] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SLABAD") @external @@ -21087,7 +21087,7 @@ def sla_wwaddw( def slabad( SMALL: Float32, LARGE: Float32 -) -> None: ... +) -> tuple[Returns["SMALL", Float32], Returns["LARGE", Float32]]: ... @bind("SLABRD") @external @@ -21106,7 +21106,7 @@ def slabrd( LDX: Int32, Y: Float32[LDY, Flat], LDY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LDY", Int32]]: ... @bind("SLACN2") @external @@ -21119,7 +21119,7 @@ def slacn2( EST: Float32, KASE: Int32, ISAVE: Int32[3] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float32], Returns["KASE", Int32]]: ... @bind("SLACON") @external @@ -21131,7 +21131,7 @@ def slacon( ISGN: Int32[Flat], EST: Float32, KASE: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float32], Returns["KASE", Int32]]: ... @bind("SLACPY") @external @@ -21144,7 +21144,7 @@ def slacpy( LDA: Int32, B: Float32[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("SLADIV") @external @@ -21156,7 +21156,7 @@ def sladiv( D: Float32, P: Float32, Q: Float32 -) -> None: ... +) -> tuple[Returns["A", Float32], Returns["B", Float32], Returns["C", Float32], Returns["D", Float32], Returns["P", Float32], Returns["Q", Float32]]: ... @bind("SLADIV1") @external @@ -21168,7 +21168,7 @@ def sladiv1( D: Float32, P: Float32, Q: Float32 -) -> None: ... +) -> tuple[Returns["A", Float32], Returns["B", Float32], Returns["C", Float32], Returns["D", Float32], Returns["P", Float32], Returns["Q", Float32]]: ... @bind("SLADIV2") @external @@ -21180,7 +21180,7 @@ def sladiv2( D: Float32, R: Float32, T: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["A", Float32], Returns["B", Float32], Returns["C", Float32], Returns["D", Float32], Returns["R", Float32], Returns["T", Float32]]: ... @bind("SLAE2") @external @@ -21191,7 +21191,7 @@ def slae2( C: Float32, RT1: Float32, RT2: Float32 -) -> None: ... +) -> tuple[Returns["A", Float32], Returns["B", Float32], Returns["C", Float32], Returns["RT1", Float32], Returns["RT2", Float32]]: ... @bind("SLAEBZ") @external @@ -21217,7 +21217,7 @@ def slaebz( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["NITMAX", Int32], Returns["N", Int32], Returns["MMAX", Int32], Returns["MINP", Int32], Returns["NBMIN", Int32], Returns["ABSTOL", Float32], Returns["RELTOL", Float32], Returns["PIVMIN", Float32], Returns["MOUT", Int32], Returns["INFO", Int32]]: ... @bind("SLAED0") @external @@ -21235,7 +21235,7 @@ def slaed0( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["QSIZ", Int32], Returns["N", Int32], Returns["LDQ", Int32], Returns["LDQS", Int32], Returns["INFO", Int32]]: ... @bind("SLAED1") @external @@ -21251,7 +21251,7 @@ def slaed1( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["CUTPNT", Int32], Returns["INFO", Int32]]: ... @bind("SLAED2") @external @@ -21274,7 +21274,7 @@ def slaed2( INDXP: Int32[Flat], COLTYP: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["INFO", Int32]]: ... @bind("SLAED3") @external @@ -21294,7 +21294,7 @@ def slaed3( W: Float32[Flat], S: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["INFO", Int32]]: ... @bind("SLAED4") @external @@ -21308,7 +21308,7 @@ def slaed4( RHO: Float32, DLAM: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["I", Int32], Returns["RHO", Float32], Returns["DLAM", Float32], Returns["INFO", Int32]]: ... @bind("SLAED5") @external @@ -21320,7 +21320,7 @@ def slaed5( DELTA: Float32[2], RHO: Float32, DLAM: Float32 -) -> None: ... +) -> tuple[Returns["I", Int32], Returns["RHO", Float32], Returns["DLAM", Float32]]: ... @bind("SLAED6") @external @@ -21334,7 +21334,7 @@ def slaed6( FINIT: Float32, TAU: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["KNITER", Int32], Returns["ORGATI", Bool], Returns["RHO", Float32], Returns["FINIT", Float32], Returns["TAU", Float32], Returns["INFO", Int32]]: ... @bind("SLAED7") @external @@ -21362,7 +21362,7 @@ def slaed7( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["N", Int32], Returns["QSIZ", Int32], Returns["TLVLS", Int32], Returns["CURLVL", Int32], Returns["CURPBM", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["CUTPNT", Int32], Returns["INFO", Int32]]: ... @bind("SLAED8") @external @@ -21390,7 +21390,7 @@ def slaed8( INDXP: Int32[Flat], INDX: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["K", Int32], Returns["N", Int32], Returns["QSIZ", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["CUTPNT", Int32], Returns["LDQ2", Int32], Returns["GIVPTR", Int32], Returns["INFO", Int32]]: ... @bind("SLAED9") @external @@ -21409,7 +21409,7 @@ def slaed9( S: Float32[LDS, Flat], LDS: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["KSTART", Int32], Returns["KSTOP", Int32], Returns["N", Int32], Returns["LDQ", Int32], Returns["RHO", Float32], Returns["LDS", Int32], Returns["INFO", Int32]]: ... @bind("SLAEDA") @external @@ -21429,7 +21429,7 @@ def slaeda( Z: Float32[Flat], ZTEMP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["TLVLS", Int32], Returns["CURLVL", Int32], Returns["CURPBM", Int32], Returns["INFO", Int32]]: ... @bind("SLAEIN") @external @@ -21451,7 +21451,7 @@ def slaein( SMLNUM: Float32, BIGNUM: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["RIGHTV", Bool], Returns["NOINIT", Bool], Returns["N", Int32], Returns["LDH", Int32], Returns["WR", Float32], Returns["WI", Float32], Returns["LDB", Int32], Returns["EPS3", Float32], Returns["SMLNUM", Float32], Returns["BIGNUM", Float32], Returns["INFO", Int32]]: ... @bind("SLAEV2") @external @@ -21464,7 +21464,7 @@ def slaev2( RT2: Float32, CS1: Float32, SN1: Float32 -) -> None: ... +) -> tuple[Returns["A", Float32], Returns["B", Float32], Returns["C", Float32], Returns["RT1", Float32], Returns["RT2", Float32], Returns["CS1", Float32], Returns["SN1", Float32]]: ... @bind("SLAEXC") @external @@ -21481,7 +21481,7 @@ def slaexc( N2: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["J1", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["INFO", Int32]]: ... @bind("SLAG2") @external @@ -21497,7 +21497,7 @@ def slag2( WR1: Float32, WR2: Float32, WI: Float32 -) -> None: ... +) -> tuple[Returns["LDA", Int32], Returns["LDB", Int32], Returns["SAFMIN", Float32], Returns["SCALE1", Float32], Returns["SCALE2", Float32], Returns["WR1", Float32], Returns["WR2", Float32], Returns["WI", Float32]]: ... @bind("SLAG2D") @external @@ -21510,7 +21510,7 @@ def slag2d( A: Float64[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDSA", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SLAGS2") @external @@ -21529,7 +21529,7 @@ def slags2( SNV: Float32, CSQ: Float32, SNQ: Float32 -) -> None: ... +) -> tuple[Returns["UPPER", Bool], Returns["A1", Float32], Returns["A2", Float32], Returns["A3", Float32], Returns["B1", Float32], Returns["B2", Float32], Returns["B3", Float32], Returns["CSU", Float32], Returns["SNU", Float32], Returns["CSV", Float32], Returns["SNV", Float32], Returns["CSQ", Float32], Returns["SNQ", Float32]]: ... @bind("SLAGTF") @external @@ -21544,7 +21544,7 @@ def slagtf( D: Float32[Flat], IN: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LAMBDA", Float32], Returns["TOL", Float32], Returns["INFO", Int32]]: ... @bind("SLAGTM") @external @@ -21562,7 +21562,7 @@ def slagtm( BETA: Float32, B: Float32[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["ALPHA", Float32], Returns["LDX", Int32], Returns["BETA", Float32], Returns["LDB", Int32]]: ... @bind("SLAGTS") @external @@ -21578,7 +21578,7 @@ def slagts( Y: Float32[Flat], TOL: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["JOB", Int32], Returns["N", Int32], Returns["TOL", Float32], Returns["INFO", Int32]]: ... @bind("SLAGV2") @external @@ -21595,7 +21595,7 @@ def slagv2( SNL: Float32, CSR: Float32, SNR: Float32 -) -> None: ... +) -> tuple[Returns["LDA", Int32], Returns["LDB", Int32], Returns["CSL", Float32], Returns["SNL", Float32], Returns["CSR", Float32], Returns["SNR", Float32]]: ... @bind("SLAHQR") @external @@ -21615,7 +21615,7 @@ def slahqr( Z: Float32[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SLAHR2") @external @@ -21631,7 +21631,7 @@ def slahr2( LDT: Int32, Y: Float32[LDY, NB], LDY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDY", Int32]]: ... @bind("SLAIC1") @external @@ -21646,7 +21646,7 @@ def slaic1( SESTPR: Float32, S: Float32, C: Float32 -) -> None: ... +) -> tuple[Returns["JOB", Int32], Returns["J", Int32], Returns["SEST", Float32], Returns["GAMMA", Float32], Returns["SESTPR", Float32], Returns["S", Float32], Returns["C", Float32]]: ... @bind("SLAISNAN") @external @@ -21678,7 +21678,7 @@ def slaln2( SCALE: Float32, XNORM: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["LTRANS", Bool], Returns["NA", Int32], Returns["NW", Int32], Returns["SMIN", Float32], Returns["CA", Float32], Returns["LDA", Int32], Returns["D1", Float32], Returns["D2", Float32], Returns["LDB", Int32], Returns["WR", Float32], Returns["WI", Float32], Returns["LDX", Int32], Returns["SCALE", Float32], Returns["XNORM", Float32], Returns["INFO", Int32]]: ... @bind("SLALS0") @external @@ -21708,7 +21708,7 @@ def slals0( S: Float32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["K", Int32], Returns["C", Float32], Returns["S", Float32], Returns["INFO", Int32]]: ... @bind("SLALSA") @external @@ -21740,7 +21740,7 @@ def slalsa( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["LDU", Int32], Returns["LDGCOL", Int32], Returns["INFO", Int32]]: ... @bind("SLALSD") @external @@ -21759,7 +21759,7 @@ def slalsd( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["RCOND", Float32], Returns["RANK", Int32], Returns["INFO", Int32]]: ... @bind("SLAMRG") @external @@ -21771,7 +21771,7 @@ def slamrg( STRD1: Int32, STRD2: Int32, INDEX: Int32[Flat] -) -> None: ... +) -> tuple[Returns["N1", Int32], Returns["N2", Int32], Returns["STRD1", Int32], Returns["STRD2", Int32]]: ... @bind("SLAMSWLQ") @external @@ -21793,7 +21793,7 @@ def slamswlq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SLAMTSQR") @external @@ -21815,7 +21815,7 @@ def slamtsqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SLANEG") @external @@ -21827,7 +21827,7 @@ def slaneg( SIGMA: Float32, PIVMIN: Float32, R: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["N", Int32], Returns["SIGMA", Float32], Returns["PIVMIN", Float32], Returns["R", Int32]]: ... @bind("SLANGB") @external @@ -21840,7 +21840,7 @@ def slangb( AB: Float32[LDAB, Flat], LDAB: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32]]: ... @bind("SLANGE") @external @@ -21852,7 +21852,7 @@ def slange( A: Float32[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("SLANGT") @external @@ -21863,7 +21863,7 @@ def slangt( DL: Float32[Flat], D: Float32[Flat], DU: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("SLANHS") @external @@ -21874,7 +21874,7 @@ def slanhs( A: Float32[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("SLANSB") @external @@ -21887,7 +21887,7 @@ def slansb( AB: Float32[LDAB, Flat], LDAB: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("SLANSF") @external @@ -21899,7 +21899,7 @@ def slansf( N: Int32, A: Float32[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("SLANSP") @external @@ -21910,7 +21910,7 @@ def slansp( N: Int32, AP: Float32[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("SLANST") @external @@ -21920,7 +21920,7 @@ def slanst( N: Int32, D: Float32[Flat], E: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("SLANSY") @external @@ -21932,7 +21932,7 @@ def slansy( A: Float32[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("SLANTB") @external @@ -21946,7 +21946,7 @@ def slantb( AB: Float32[LDAB, Flat], LDAB: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("SLANTP") @external @@ -21958,7 +21958,7 @@ def slantp( N: Int32, AP: Float32[Flat], WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["N", Int32]]: ... @bind("SLANTR") @external @@ -21972,7 +21972,7 @@ def slantr( A: Float32[LDA, Flat], LDA: Int32, WORK: Float32[Flat] -) -> Float32: ... +) -> tuple[Float32, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("SLANV2") @external @@ -21988,7 +21988,7 @@ def slanv2( RT2I: Float32, CS: Float32, SN: Float32 -) -> None: ... +) -> tuple[Returns["A", Float32], Returns["B", Float32], Returns["C", Float32], Returns["D", Float32], Returns["RT1R", Float32], Returns["RT1I", Float32], Returns["RT2R", Float32], Returns["RT2I", Float32], Returns["CS", Float32], Returns["SN", Float32]]: ... @bind("SLAORHR_COL_GETRFNP") @external @@ -22000,7 +22000,7 @@ def slaorhr_col_getrfnp( LDA: Int32, D: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SLAORHR_COL_GETRFNP2") @external @@ -22012,7 +22012,7 @@ def slaorhr_col_getrfnp2( LDA: Int32, D: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SLAPLL") @external @@ -22024,7 +22024,7 @@ def slapll( Y: Float32[Flat], INCY: Int32, SSMIN: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["SSMIN", Float32]]: ... @bind("SLAPMR") @external @@ -22036,7 +22036,7 @@ def slapmr( X: Float32[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("SLAPMT") @external @@ -22048,7 +22048,7 @@ def slapmt( X: Float32[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("SLAPY2") @external @@ -22056,7 +22056,7 @@ def slapmt( def slapy2( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("SLAPY3") @external @@ -22065,7 +22065,7 @@ def slapy3( X: Float32, Y: Float32, Z: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32], Returns["Z", Float32]]: ... @bind("SLAQGB") @external @@ -22083,7 +22083,7 @@ def slaqgb( COLCND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32]]: ... @bind("SLAQGE") @external @@ -22099,7 +22099,7 @@ def slaqge( COLCND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float32], Returns["COLCND", Float32], Returns["AMAX", Float32]]: ... @bind("SLAQP2") @external @@ -22115,7 +22115,7 @@ def slaqp2( VN1: Float32[Flat], VN2: Float32[Flat], WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["LDA", Int32]]: ... @bind("SLAQP2RK") @external @@ -22141,7 +22141,7 @@ def slaqp2rk( VN2: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float32], Returns["RELTOL", Float32], Returns["KP1", Int32], Returns["MAXC2NRM", Float32], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float32], Returns["RELMAXC2NRMK", Float32], Returns["INFO", Int32]]: ... @bind("SLAQP3RK") @external @@ -22171,7 +22171,7 @@ def slaqp3rk( LDF: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["NB", Int32], Returns["ABSTOL", Float32], Returns["RELTOL", Float32], Returns["KP1", Int32], Returns["MAXC2NRM", Float32], Returns["LDA", Int32], Returns["DONE", Bool], Returns["KB", Int32], Returns["MAXC2NRMK", Float32], Returns["RELMAXC2NRMK", Float32], Returns["LDF", Int32], Returns["INFO", Int32]]: ... @bind("SLAQPS") @external @@ -22191,7 +22191,7 @@ def slaqps( AUXV: Float32[Flat], F: Float32[LDF, Flat], LDF: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDF", Int32]]: ... @bind("SLAQR0") @external @@ -22213,7 +22213,7 @@ def slaqr0( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SLAQR1") @external @@ -22227,7 +22227,7 @@ def slaqr1( SR2: Float32, SI2: Float32, V: Float32[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["SR1", Float32], Returns["SI1", Float32], Returns["SR2", Float32], Returns["SI2", Float32]]: ... @bind("SLAQR2") @external @@ -22259,7 +22259,7 @@ def slaqr2( LDWV: Int32, WORK: Float32[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("SLAQR3") @external @@ -22291,7 +22291,7 @@ def slaqr3( LDWV: Int32, WORK: Float32[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("SLAQR4") @external @@ -22313,7 +22313,7 @@ def slaqr4( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SLAQR5") @external @@ -22344,7 +22344,7 @@ def slaqr5( NH: Int32, WH: Float32[LDWH, Flat], LDWH: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["KACC22", Int32], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NSHFTS", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LDV", Int32], Returns["LDU", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["NH", Int32], Returns["LDWH", Int32]]: ... @bind("SLAQSB") @external @@ -22359,7 +22359,7 @@ def slaqsb( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("SLAQSP") @external @@ -22372,7 +22372,7 @@ def slaqsp( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("SLAQSY") @external @@ -22386,7 +22386,7 @@ def slaqsy( SCOND: Float32, AMAX: Float32, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32]]: ... @bind("SLAQTR") @external @@ -22403,7 +22403,7 @@ def slaqtr( X: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["LTRAN", Bool], Returns["LREAL", Bool], Returns["N", Int32], Returns["LDT", Int32], Returns["W", Float32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("SLAQZ0") @external @@ -22445,7 +22445,7 @@ def slaqz1( BETA1: Float32, BETA2: Float32, V: Float32[Flat] -) -> Returns["V", Float32[Flat]]: ... +) -> None: ... @bind("SLAQZ2") @external @@ -22558,7 +22558,7 @@ def slar1v( RESID: Float32, RQCORR: Float32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["B1", Int32], Returns["BN", Int32], Returns["LAMBDA", Float32], Returns["PIVMIN", Float32], Returns["GAPTOL", Float32], Returns["WANTNC", Bool], Returns["NEGCNT", Int32], Returns["ZTZ", Float32], Returns["MINGMA", Float32], Returns["R", Int32], Returns["NRMINV", Float32], Returns["RESID", Float32], Returns["RQCORR", Float32]]: ... @bind("SLAR2V") @external @@ -22572,7 +22572,7 @@ def slar2v( C: Float32[Flat], S: Float32[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCC", Int32]]: ... @bind("SLARF") @external @@ -22587,7 +22587,7 @@ def slarf( C: Float32[LDC, Flat], LDC: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float32], Returns["LDC", Int32]]: ... @bind("SLARF1F") @external @@ -22602,7 +22602,7 @@ def slarf1f( C: Float32[LDC, Flat], LDC: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float32], Returns["LDC", Int32]]: ... @bind("SLARF1L") @external @@ -22617,7 +22617,7 @@ def slarf1l( C: Float32[LDC, Flat], LDC: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float32], Returns["LDC", Int32]]: ... @bind("SLARFB") @external @@ -22638,7 +22638,7 @@ def slarfb( LDC: Int32, WORK: Float32[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("SLARFB_GETT") @external @@ -22656,7 +22656,7 @@ def slarfb_gett( LDB: Int32, WORK: Float32[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("SLARFG") @external @@ -22667,7 +22667,7 @@ def slarfg( X: Float32[Flat], INCX: Int32, TAU: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["TAU", Float32]]: ... @bind("SLARFGP") @external @@ -22678,7 +22678,7 @@ def slarfgp( X: Float32[Flat], INCX: Int32, TAU: Float32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Float32], Returns["INCX", Int32], Returns["TAU", Float32]]: ... @bind("SLARFT") @external @@ -22693,7 +22693,7 @@ def slarft( TAU: Float32[Flat], T: Float32[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("SLARFX") @external @@ -22707,7 +22707,7 @@ def slarfx( C: Float32[LDC, Flat], LDC: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["TAU", Float32], Returns["LDC", Int32]]: ... @bind("SLARFY") @external @@ -22721,7 +22721,7 @@ def slarfy( C: Float32[LDC, Flat], LDC: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Float32], Returns["LDC", Int32]]: ... @bind("SLARGV") @external @@ -22734,7 +22734,7 @@ def slargv( INCY: Int32, C: Float32[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("SLARMM") @external @@ -22743,7 +22743,7 @@ def slarmm( ANORM: Float32, BNORM: Float32, CNORM: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["ANORM", Float32], Returns["BNORM", Float32], Returns["CNORM", Float32]]: ... @bind("SLARNV") @external @@ -22753,7 +22753,7 @@ def slarnv( ISEED: Int32[4], N: Int32, X: Float32[Flat] -) -> None: ... +) -> tuple[Returns["IDIST", Int32], Returns["N", Int32]]: ... @bind("SLARRA") @external @@ -22768,7 +22768,7 @@ def slarra( NSPLIT: Int32, ISPLIT: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SPLTOL", Float32], Returns["TNRM", Float32], Returns["NSPLIT", Int32], Returns["INFO", Int32]]: ... @bind("SLARRB") @external @@ -22791,7 +22791,7 @@ def slarrb( SPDIAM: Float32, TWIST: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["IFIRST", Int32], Returns["ILAST", Int32], Returns["RTOL1", Float32], Returns["RTOL2", Float32], Returns["OFFSET", Int32], Returns["PIVMIN", Float32], Returns["SPDIAM", Float32], Returns["TWIST", Int32], Returns["INFO", Int32]]: ... @bind("SLARRC") @external @@ -22808,7 +22808,7 @@ def slarrc( LCNT: Int32, RCNT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["PIVMIN", Float32], Returns["EIGCNT", Int32], Returns["LCNT", Int32], Returns["RCNT", Int32], Returns["INFO", Int32]]: ... @bind("SLARRD") @external @@ -22839,7 +22839,7 @@ def slarrd( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["RELTOL", Float32], Returns["PIVMIN", Float32], Returns["NSPLIT", Int32], Returns["M", Int32], Returns["WL", Float32], Returns["WU", Float32], Returns["INFO", Int32]]: ... @bind("SLARRE") @external @@ -22870,7 +22870,7 @@ def slarre( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["RTOL1", Float32], Returns["RTOL2", Float32], Returns["SPLTOL", Float32], Returns["NSPLIT", Int32], Returns["M", Int32], Returns["PIVMIN", Float32], Returns["INFO", Int32]]: ... @bind("SLARRF") @external @@ -22894,7 +22894,7 @@ def slarrf( LPLUS: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["CLSTRT", Int32], Returns["CLEND", Int32], Returns["SPDIAM", Float32], Returns["CLGAPL", Float32], Returns["CLGAPR", Float32], Returns["PIVMIN", Float32], Returns["SIGMA", Float32], Returns["INFO", Int32]]: ... @bind("SLARRJ") @external @@ -22914,7 +22914,7 @@ def slarrj( PIVMIN: Float32, SPDIAM: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["IFIRST", Int32], Returns["ILAST", Int32], Returns["RTOL", Float32], Returns["OFFSET", Int32], Returns["PIVMIN", Float32], Returns["SPDIAM", Float32], Returns["INFO", Int32]]: ... @bind("SLARRK") @external @@ -22931,7 +22931,7 @@ def slarrk( W: Float32, WERR: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["IW", Int32], Returns["GL", Float32], Returns["GU", Float32], Returns["PIVMIN", Float32], Returns["RELTOL", Float32], Returns["W", Float32], Returns["WERR", Float32], Returns["INFO", Int32]]: ... @bind("SLARRR") @external @@ -22941,7 +22941,7 @@ def slarrr( D: Float32[Flat], E: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SLARRV") @external @@ -22972,7 +22972,7 @@ def slarrv( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["PIVMIN", Float32], Returns["M", Int32], Returns["DOL", Int32], Returns["DOU", Int32], Returns["MINRGP", Float32], Returns["RTOL1", Float32], Returns["RTOL2", Float32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SLARSCL2") @external @@ -22983,7 +22983,7 @@ def slarscl2( D: Float32[Flat], X: Float32[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("SLARTG") @external @@ -22994,7 +22994,7 @@ def slartg( c: Float32, s: Float32, r: Float32 -) -> None: ... +) -> tuple[Returns["f", Float32], Returns["g", Float32], Returns["c", Float32], Returns["s", Float32], Returns["r", Float32]]: ... @bind("SLARTGP") @external @@ -23005,7 +23005,7 @@ def slartgp( CS: Float32, SN: Float32, R: Float32 -) -> None: ... +) -> tuple[Returns["F", Float32], Returns["G", Float32], Returns["CS", Float32], Returns["SN", Float32], Returns["R", Float32]]: ... @bind("SLARTGS") @external @@ -23016,7 +23016,7 @@ def slartgs( SIGMA: Float32, CS: Float32, SN: Float32 -) -> None: ... +) -> tuple[Returns["X", Float32], Returns["Y", Float32], Returns["SIGMA", Float32], Returns["CS", Float32], Returns["SN", Float32]]: ... @bind("SLARTV") @external @@ -23030,7 +23030,7 @@ def slartv( C: Float32[Flat], S: Float32[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("SLARUV") @external @@ -23039,7 +23039,7 @@ def slaruv( ISEED: Int32[4], N: Int32, X: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SLARZ") @external @@ -23055,7 +23055,7 @@ def slarz( C: Float32[LDC, Flat], LDC: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["INCV", Int32], Returns["TAU", Float32], Returns["LDC", Int32]]: ... @bind("SLARZB") @external @@ -23077,7 +23077,7 @@ def slarzb( LDC: Int32, WORK: Float32[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("SLARZT") @external @@ -23092,7 +23092,7 @@ def slarzt( TAU: Float32[Flat], T: Float32[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("SLAS2") @external @@ -23103,7 +23103,7 @@ def slas2( H: Float32, SSMIN: Float32, SSMAX: Float32 -) -> None: ... +) -> tuple[Returns["F", Float32], Returns["G", Float32], Returns["H", Float32], Returns["SSMIN", Float32], Returns["SSMAX", Float32]]: ... @bind("SLASCL") @external @@ -23119,7 +23119,7 @@ def slascl( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["KL", Int32], Returns["KU", Int32], Returns["CFROM", Float32], Returns["CTO", Float32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SLASCL2") @external @@ -23130,7 +23130,7 @@ def slascl2( D: Float32[Flat], X: Float32[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("SLASD0") @external @@ -23148,7 +23148,7 @@ def slasd0( IWORK: Int32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SQRE", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["SMLSIZ", Int32], Returns["INFO", Int32]]: ... @bind("SLASD1") @external @@ -23168,7 +23168,7 @@ def slasd1( IWORK: Int32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["ALPHA", Float32], Returns["BETA", Float32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["INFO", Int32]]: ... @bind("SLASD2") @external @@ -23197,7 +23197,7 @@ def slasd2( IDXQ: Int32[Flat], COLTYP: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["BETA", Float32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LDU2", Int32], Returns["LDVT2", Int32], Returns["INFO", Int32]]: ... @bind("SLASD3") @external @@ -23223,7 +23223,7 @@ def slasd3( CTOT: Int32[Flat], Z: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["K", Int32], Returns["LDQ", Int32], Returns["LDU", Int32], Returns["LDU2", Int32], Returns["LDVT", Int32], Returns["LDVT2", Int32], Returns["INFO", Int32]]: ... @bind("SLASD4") @external @@ -23238,7 +23238,7 @@ def slasd4( SIGMA: Float32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["I", Int32], Returns["RHO", Float32], Returns["SIGMA", Float32], Returns["INFO", Int32]]: ... @bind("SLASD5") @external @@ -23251,7 +23251,7 @@ def slasd5( RHO: Float32, DSIGMA: Float32, WORK: Float32[2] -) -> None: ... +) -> tuple[Returns["I", Int32], Returns["RHO", Float32], Returns["DSIGMA", Float32]]: ... @bind("SLASD6") @external @@ -23283,7 +23283,7 @@ def slasd6( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["ALPHA", Float32], Returns["BETA", Float32], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["K", Int32], Returns["C", Float32], Returns["S", Float32], Returns["INFO", Int32]]: ... @bind("SLASD7") @external @@ -23316,7 +23316,7 @@ def slasd7( C: Float32, S: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["BETA", Float32], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["C", Float32], Returns["S", Float32], Returns["INFO", Int32]]: ... @bind("SLASD8") @external @@ -23334,7 +23334,7 @@ def slasd8( DSIGMA: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["K", Int32], Returns["LDDIFR", Int32], Returns["INFO", Int32]]: ... @bind("SLASDA") @external @@ -23364,7 +23364,7 @@ def slasda( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["SQRE", Int32], Returns["LDU", Int32], Returns["LDGCOL", Int32], Returns["INFO", Int32]]: ... @bind("SLASDQ") @external @@ -23386,7 +23386,7 @@ def slasdq( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SQRE", Int32], Returns["N", Int32], Returns["NCVT", Int32], Returns["NRU", Int32], Returns["NCC", Int32], Returns["LDVT", Int32], Returns["LDU", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SLASDT") @external @@ -23399,7 +23399,7 @@ def slasdt( NDIML: Int32[Flat], NDIMR: Int32[Flat], MSUB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LVL", Int32], Returns["ND", Int32], Returns["MSUB", Int32]]: ... @bind("SLASET") @external @@ -23412,7 +23412,7 @@ def slaset( BETA: Float32, A: Float32[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["BETA", Float32], Returns["LDA", Int32]]: ... @bind("SLASQ1") @external @@ -23423,7 +23423,7 @@ def slasq1( E: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SLASQ2") @external @@ -23432,7 +23432,7 @@ def slasq2( N: Int32, Z: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SLASQ3") @external @@ -23458,7 +23458,7 @@ def slasq3( DN2: Float32, G: Float32, TAU: Float32 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["DMIN", Float32], Returns["SIGMA", Float32], Returns["DESIG", Float32], Returns["QMAX", Float32], Returns["NFAIL", Int32], Returns["ITER", Int32], Returns["NDIV", Int32], Returns["IEEE", Bool], Returns["TTYPE", Int32], Returns["DMIN1", Float32], Returns["DMIN2", Float32], Returns["DN", Float32], Returns["DN1", Float32], Returns["DN2", Float32], Returns["G", Float32], Returns["TAU", Float32]]: ... @bind("SLASQ4") @external @@ -23478,7 +23478,7 @@ def slasq4( TAU: Float32, TTYPE: Int32, G: Float32 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["N0IN", Int32], Returns["DMIN", Float32], Returns["DMIN1", Float32], Returns["DMIN2", Float32], Returns["DN", Float32], Returns["DN1", Float32], Returns["DN2", Float32], Returns["TAU", Float32], Returns["TTYPE", Int32], Returns["G", Float32]]: ... @bind("SLASQ5") @external @@ -23498,7 +23498,7 @@ def slasq5( DNM2: Float32, IEEE: Bool, EPS: Float32 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["TAU", Float32], Returns["SIGMA", Float32], Returns["DMIN", Float32], Returns["DMIN1", Float32], Returns["DMIN2", Float32], Returns["DN", Float32], Returns["DNM1", Float32], Returns["DNM2", Float32], Returns["IEEE", Bool], Returns["EPS", Float32]]: ... @bind("SLASQ6") @external @@ -23514,7 +23514,7 @@ def slasq6( DN: Float32, DNM1: Float32, DNM2: Float32 -) -> None: ... +) -> tuple[Returns["I0", Int32], Returns["N0", Int32], Returns["PP", Int32], Returns["DMIN", Float32], Returns["DMIN1", Float32], Returns["DMIN2", Float32], Returns["DN", Float32], Returns["DNM1", Float32], Returns["DNM2", Float32]]: ... @bind("SLASR") @external @@ -23529,7 +23529,7 @@ def slasr( S: Float32[Flat], A: Float32[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("SLASRT") @external @@ -23539,7 +23539,7 @@ def slasrt( N: Int32, D: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SLASSQ") @external @@ -23550,7 +23550,7 @@ def slassq( incx: Int32, scale: Float32, sumsq: Float32 -) -> None: ... +) -> tuple[Returns["n", Int32], Returns["incx", Int32], Returns["scale", Float32], Returns["sumsq", Float32]]: ... @bind("SLASV2") @external @@ -23565,7 +23565,7 @@ def slasv2( CSR: Float32, SNL: Float32, CSL: Float32 -) -> None: ... +) -> tuple[Returns["F", Float32], Returns["G", Float32], Returns["H", Float32], Returns["SSMIN", Float32], Returns["SSMAX", Float32], Returns["SNR", Float32], Returns["CSR", Float32], Returns["SNL", Float32], Returns["CSL", Float32]]: ... @bind("SLASWLQ") @external @@ -23582,7 +23582,7 @@ def slaswlq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SLASWP") @external @@ -23595,7 +23595,7 @@ def slaswp( K2: Int32, IPIV: Int32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["K1", Int32], Returns["K2", Int32], Returns["INCX", Int32]]: ... @bind("SLASY2") @external @@ -23617,7 +23617,7 @@ def slasy2( LDX: Int32, XNORM: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["LTRANL", Bool], Returns["LTRANR", Bool], Returns["ISGN", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LDTL", Int32], Returns["LDTR", Int32], Returns["LDB", Int32], Returns["SCALE", Float32], Returns["LDX", Int32], Returns["XNORM", Float32], Returns["INFO", Int32]]: ... @bind("SLASYF") @external @@ -23633,7 +23633,7 @@ def slasyf( W: Float32[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("SLASYF_AA") @external @@ -23649,7 +23649,7 @@ def slasyf_aa( H: Float32[LDH, Flat], LDH: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["J1", Int32], Returns["M", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDH", Int32]]: ... @bind("SLASYF_RK") @external @@ -23666,7 +23666,7 @@ def slasyf_rk( W: Float32[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("SLASYF_ROOK") @external @@ -23682,7 +23682,7 @@ def slasyf_rook( W: Float32[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("SLATBS") @external @@ -23700,7 +23700,7 @@ def slatbs( SCALE: Float32, CNORM: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("SLATDF") @external @@ -23715,7 +23715,7 @@ def slatdf( RDSCAL: Float32, IPIV: Int32[Flat], JPIV: Int32[Flat] -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["RDSUM", Float32], Returns["RDSCAL", Float32]]: ... @bind("SLATPS") @external @@ -23731,7 +23731,7 @@ def slatps( SCALE: Float32, CNORM: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("SLATRD") @external @@ -23746,7 +23746,7 @@ def slatrd( TAU: Float32[Flat], W: Float32[LDW, Flat], LDW: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDW", Int32]]: ... @bind("SLATRS") @external @@ -23763,7 +23763,7 @@ def slatrs( SCALE: Float32, CNORM: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("SLATRS3") @external @@ -23784,7 +23784,7 @@ def slatrs3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SLATRZ") @external @@ -23797,7 +23797,7 @@ def slatrz( LDA: Int32, TAU: Float32[Flat], WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32]]: ... @bind("SLATSQR") @external @@ -23814,7 +23814,7 @@ def slatsqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SLAUU2") @external @@ -23825,7 +23825,7 @@ def slauu2( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SLAUUM") @external @@ -23836,7 +23836,7 @@ def slauum( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SOPGTR") @external @@ -23850,7 +23850,7 @@ def sopgtr( LDQ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("SOPMTR") @external @@ -23867,7 +23867,7 @@ def sopmtr( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SORBDB") @external @@ -23895,7 +23895,7 @@ def sorbdb( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORBDB1") @external @@ -23916,7 +23916,7 @@ def sorbdb1( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORBDB2") @external @@ -23937,7 +23937,7 @@ def sorbdb2( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORBDB3") @external @@ -23958,7 +23958,7 @@ def sorbdb3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORBDB4") @external @@ -23980,7 +23980,7 @@ def sorbdb4( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORBDB5") @external @@ -24000,7 +24000,7 @@ def sorbdb5( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORBDB6") @external @@ -24020,7 +24020,7 @@ def sorbdb6( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORCSD") @external @@ -24056,7 +24056,7 @@ def sorcsd( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORCSD2BY1") @external @@ -24083,7 +24083,7 @@ def sorcsd2by1( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORG2L") @external @@ -24097,7 +24097,7 @@ def sorg2l( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SORG2R") @external @@ -24111,7 +24111,7 @@ def sorg2r( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SORGBR") @external @@ -24127,7 +24127,7 @@ def sorgbr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGHR") @external @@ -24142,7 +24142,7 @@ def sorghr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGL2") @external @@ -24156,7 +24156,7 @@ def sorgl2( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SORGLQ") @external @@ -24171,7 +24171,7 @@ def sorglq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGQL") @external @@ -24186,7 +24186,7 @@ def sorgql( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGQR") @external @@ -24201,7 +24201,7 @@ def sorgqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGR2") @external @@ -24215,7 +24215,7 @@ def sorgr2( TAU: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SORGRQ") @external @@ -24230,7 +24230,7 @@ def sorgrq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGTR") @external @@ -24244,7 +24244,7 @@ def sorgtr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGTSQR") @external @@ -24261,7 +24261,7 @@ def sorgtsqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORGTSQR_ROW") @external @@ -24278,7 +24278,7 @@ def sorgtsqr_row( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORHR_COL") @external @@ -24293,7 +24293,7 @@ def sorhr_col( LDT: Int32, D: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("SORM22") @external @@ -24312,7 +24312,7 @@ def sorm22( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LDQ", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORM2L") @external @@ -24330,7 +24330,7 @@ def sorm2l( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SORM2R") @external @@ -24348,7 +24348,7 @@ def sorm2r( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SORMBR") @external @@ -24368,7 +24368,7 @@ def sormbr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORMHR") @external @@ -24388,7 +24388,7 @@ def sormhr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORML2") @external @@ -24406,7 +24406,7 @@ def sorml2( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SORMLQ") @external @@ -24425,7 +24425,7 @@ def sormlq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORMQL") @external @@ -24444,7 +24444,7 @@ def sormql( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORMQR") @external @@ -24463,7 +24463,7 @@ def sormqr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORMR2") @external @@ -24481,7 +24481,7 @@ def sormr2( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SORMR3") @external @@ -24500,7 +24500,7 @@ def sormr3( LDC: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("SORMRQ") @external @@ -24519,7 +24519,7 @@ def sormrq( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORMRZ") @external @@ -24539,7 +24539,7 @@ def sormrz( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SORMTR") @external @@ -24558,7 +24558,7 @@ def sormtr( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SPBCON") @external @@ -24574,7 +24574,7 @@ def spbcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPBEQU") @external @@ -24589,7 +24589,7 @@ def spbequ( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SPBRFS") @external @@ -24612,7 +24612,7 @@ def spbrfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SPBSTF") @external @@ -24624,7 +24624,7 @@ def spbstf( AB: Float32[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("SPBSV") @external @@ -24639,7 +24639,7 @@ def spbsv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPBSVX") @external @@ -24666,7 +24666,7 @@ def spbsvx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPBTF2") @external @@ -24678,7 +24678,7 @@ def spbtf2( AB: Float32[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("SPBTRF") @external @@ -24690,7 +24690,7 @@ def spbtrf( AB: Float32[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("SPBTRS") @external @@ -24705,7 +24705,7 @@ def spbtrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPFTRF") @external @@ -24716,7 +24716,7 @@ def spftrf( N: Int32, A: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SPFTRI") @external @@ -24727,7 +24727,7 @@ def spftri( N: Int32, A: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SPFTRS") @external @@ -24741,7 +24741,7 @@ def spftrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPOCON") @external @@ -24756,7 +24756,7 @@ def spocon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPOEQU") @external @@ -24769,7 +24769,7 @@ def spoequ( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SPOEQUB") @external @@ -24782,7 +24782,7 @@ def spoequb( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SPORFS") @external @@ -24804,7 +24804,7 @@ def sporfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SPORFSX") @external @@ -24833,7 +24833,7 @@ def sporfsx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SPOSV") @external @@ -24847,7 +24847,7 @@ def sposv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPOSVX") @external @@ -24873,7 +24873,7 @@ def sposvx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPOSVXX") @external @@ -24904,7 +24904,7 @@ def sposvxx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SPOTF2") @external @@ -24915,7 +24915,7 @@ def spotf2( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SPOTRF") @external @@ -24926,7 +24926,7 @@ def spotrf( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SPOTRF2") @external @@ -24937,7 +24937,7 @@ def spotrf2( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SPOTRI") @external @@ -24948,7 +24948,7 @@ def spotri( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SPOTRS") @external @@ -24962,7 +24962,7 @@ def spotrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPPCON") @external @@ -24976,7 +24976,7 @@ def sppcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPPEQU") @external @@ -24989,7 +24989,7 @@ def sppequ( SCOND: Float32, AMAX: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SPPRFS") @external @@ -25009,7 +25009,7 @@ def spprfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SPPSV") @external @@ -25022,7 +25022,7 @@ def sppsv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPPSVX") @external @@ -25046,7 +25046,7 @@ def sppsvx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPPTRF") @external @@ -25056,7 +25056,7 @@ def spptrf( N: Int32, AP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SPPTRI") @external @@ -25066,7 +25066,7 @@ def spptri( N: Int32, AP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SPPTRS") @external @@ -25079,7 +25079,7 @@ def spptrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPSTF2") @external @@ -25094,7 +25094,7 @@ def spstf2( TOL: Float32, WORK: Float32[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float32], Returns["INFO", Int32]]: ... @bind("SPSTRF") @external @@ -25109,7 +25109,7 @@ def spstrf( TOL: Float32, WORK: Float32[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float32], Returns["INFO", Int32]]: ... @bind("SPTCON") @external @@ -25122,7 +25122,7 @@ def sptcon( RCOND: Float32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPTEQR") @external @@ -25136,7 +25136,7 @@ def spteqr( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SPTRFS") @external @@ -25156,7 +25156,7 @@ def sptrfs( BERR: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SPTSV") @external @@ -25169,7 +25169,7 @@ def sptsv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPTSVX") @external @@ -25191,7 +25191,7 @@ def sptsvx( BERR: Float32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SPTTRF") @external @@ -25201,7 +25201,7 @@ def spttrf( D: Float32[Flat], E: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SPTTRS") @external @@ -25214,7 +25214,7 @@ def spttrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SPTTS2") @external @@ -25226,7 +25226,7 @@ def sptts2( E: Float32[Flat], B: Float32[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("SRSCL") @external @@ -25236,7 +25236,7 @@ def srscl( SA: Float32, SX: Float32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SA", Float32], Returns["INCX", Int32]]: ... @bind("SSB2ST_KERNELS") @external @@ -25257,7 +25257,7 @@ def ssb2st_kernels( TAU: Float32[Flat], LDVT: Int32, WORK: Float32[Flat] -) -> None: ... +) -> tuple[Returns["WANTZ", Bool], Returns["TTYPE", Int32], Returns["ST", Int32], Returns["ED", Int32], Returns["SWEEP", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["IB", Int32], Returns["LDA", Int32], Returns["LDVT", Int32]]: ... @bind("SSBEV") @external @@ -25274,7 +25274,7 @@ def ssbev( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSBEV_2STAGE") @external @@ -25292,7 +25292,7 @@ def ssbev_2stage( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSBEVD") @external @@ -25312,7 +25312,7 @@ def ssbevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSBEVD_2STAGE") @external @@ -25332,7 +25332,7 @@ def ssbevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSBEVX") @external @@ -25360,7 +25360,7 @@ def ssbevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSBEVX_2STAGE") @external @@ -25389,7 +25389,7 @@ def ssbevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSBGST") @external @@ -25408,7 +25408,7 @@ def ssbgst( LDX: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SSBGV") @external @@ -25428,7 +25428,7 @@ def ssbgv( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSBGVD") @external @@ -25451,7 +25451,7 @@ def ssbgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSBGVX") @external @@ -25482,7 +25482,7 @@ def ssbgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDQ", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSBTRD") @external @@ -25500,7 +25500,7 @@ def ssbtrd( LDQ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("SSFRK") @external @@ -25516,7 +25516,7 @@ def ssfrk( LDA: Int32, BETA: Float32, C: Float32[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float32], Returns["LDA", Int32], Returns["BETA", Float32]]: ... @bind("SSPCON") @external @@ -25531,7 +25531,7 @@ def sspcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SSPEV") @external @@ -25546,7 +25546,7 @@ def sspev( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSPEVD") @external @@ -25564,7 +25564,7 @@ def sspevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSPEVX") @external @@ -25588,7 +25588,7 @@ def sspevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSPGST") @external @@ -25600,7 +25600,7 @@ def sspgst( AP: Float32[Flat], BP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SSPGV") @external @@ -25617,7 +25617,7 @@ def sspgv( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSPGVD") @external @@ -25637,7 +25637,7 @@ def sspgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSPGVX") @external @@ -25663,7 +25663,7 @@ def sspgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSPRFS") @external @@ -25684,7 +25684,7 @@ def ssprfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SSPSV") @external @@ -25698,7 +25698,7 @@ def sspsv( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSPSVX") @external @@ -25721,7 +25721,7 @@ def sspsvx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SSPTRD") @external @@ -25734,7 +25734,7 @@ def ssptrd( E: Float32[Flat], TAU: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SSPTRF") @external @@ -25745,7 +25745,7 @@ def ssptrf( AP: Float32[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SSPTRI") @external @@ -25757,7 +25757,7 @@ def ssptri( IPIV: Int32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SSPTRS") @external @@ -25771,7 +25771,7 @@ def ssptrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSTEBZ") @external @@ -25795,7 +25795,7 @@ def sstebz( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["NSPLIT", Int32], Returns["INFO", Int32]]: ... @bind("SSTEDC") @external @@ -25812,7 +25812,7 @@ def sstedc( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSTEGR") @external @@ -25838,7 +25838,7 @@ def sstegr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSTEIN") @external @@ -25857,7 +25857,7 @@ def sstein( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSTEMR") @external @@ -25884,7 +25884,7 @@ def sstemr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["NZC", Int32], Returns["TRYRAC", Bool], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSTEQR") @external @@ -25898,7 +25898,7 @@ def ssteqr( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSTERF") @external @@ -25908,7 +25908,7 @@ def ssterf( D: Float32[Flat], E: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("SSTEV") @external @@ -25922,7 +25922,7 @@ def sstev( LDZ: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSTEVD") @external @@ -25939,7 +25939,7 @@ def sstevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSTEVR") @external @@ -25965,7 +25965,7 @@ def sstevr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSTEVX") @external @@ -25989,7 +25989,7 @@ def sstevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("SSYCON") @external @@ -26005,7 +26005,7 @@ def ssycon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SSYCON_3") @external @@ -26022,7 +26022,7 @@ def ssycon_3( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SSYCON_ROOK") @external @@ -26038,7 +26038,7 @@ def ssycon_rook( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("SSYCONV") @external @@ -26052,7 +26052,7 @@ def ssyconv( IPIV: Int32[Flat], E: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYCONVF") @external @@ -26066,7 +26066,7 @@ def ssyconvf( E: Float32[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYCONVF_ROOK") @external @@ -26080,7 +26080,7 @@ def ssyconvf_rook( E: Float32[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYEQUB") @external @@ -26095,7 +26095,7 @@ def ssyequb( AMAX: Float32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float32], Returns["AMAX", Float32], Returns["INFO", Int32]]: ... @bind("SSYEV") @external @@ -26110,7 +26110,7 @@ def ssyev( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYEV_2STAGE") @external @@ -26125,7 +26125,7 @@ def ssyev_2stage( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYEVD") @external @@ -26142,7 +26142,7 @@ def ssyevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYEVD_2STAGE") @external @@ -26159,7 +26159,7 @@ def ssyevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYEVR") @external @@ -26186,7 +26186,7 @@ def ssyevr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYEVR_2STAGE") @external @@ -26213,7 +26213,7 @@ def ssyevr_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYEVX") @external @@ -26239,7 +26239,7 @@ def ssyevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYEVX_2STAGE") @external @@ -26265,7 +26265,7 @@ def ssyevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYGS2") @external @@ -26279,7 +26279,7 @@ def ssygs2( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSYGST") @external @@ -26293,7 +26293,7 @@ def ssygst( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSYGV") @external @@ -26311,7 +26311,7 @@ def ssygv( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYGV_2STAGE") @external @@ -26329,7 +26329,7 @@ def ssygv_2stage( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYGVD") @external @@ -26349,7 +26349,7 @@ def ssygvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYGVX") @external @@ -26378,7 +26378,7 @@ def ssygvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["VL", Float32], Returns["VU", Float32], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float32], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYRFS") @external @@ -26401,7 +26401,7 @@ def ssyrfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("SSYRFSX") @external @@ -26431,7 +26431,7 @@ def ssyrfsx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SSYSV") @external @@ -26448,7 +26448,7 @@ def ssysv( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYSV_AA") @external @@ -26465,7 +26465,7 @@ def ssysv_aa( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYSV_AA_2STAGE") @external @@ -26485,7 +26485,7 @@ def ssysv_aa_2stage( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYSV_RK") @external @@ -26503,7 +26503,7 @@ def ssysv_rk( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYSV_ROOK") @external @@ -26520,7 +26520,7 @@ def ssysv_rook( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYSVX") @external @@ -26546,7 +26546,7 @@ def ssysvx( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYSVXX") @external @@ -26578,7 +26578,7 @@ def ssysvxx( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float32], Returns["RPVGRW", Float32], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("SSYSWAPR") @external @@ -26590,7 +26590,7 @@ def ssyswapr( LDA: Int32, I1: Int32, I2: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["I1", Int32], Returns["I2", Int32]]: ... @bind("SSYTD2") @external @@ -26604,7 +26604,7 @@ def ssytd2( E: Float32[Flat], TAU: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYTF2") @external @@ -26616,7 +26616,7 @@ def ssytf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYTF2_RK") @external @@ -26629,7 +26629,7 @@ def ssytf2_rk( E: Float32[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYTF2_ROOK") @external @@ -26641,7 +26641,7 @@ def ssytf2_rook( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRD") @external @@ -26657,7 +26657,7 @@ def ssytrd( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRD_2STAGE") @external @@ -26676,7 +26676,7 @@ def ssytrd_2stage( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LHOUS2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRD_SB2ST") @external @@ -26696,7 +26696,7 @@ def ssytrd_sb2st( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LHOUS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRD_SY2SB") @external @@ -26713,7 +26713,7 @@ def ssytrd_sy2sb( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDA", Int32], Returns["LDAB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRF") @external @@ -26727,7 +26727,7 @@ def ssytrf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRF_AA") @external @@ -26741,7 +26741,7 @@ def ssytrf_aa( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRF_AA_2STAGE") @external @@ -26758,7 +26758,7 @@ def ssytrf_aa_2stage( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRF_RK") @external @@ -26773,7 +26773,7 @@ def ssytrf_rk( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRF_ROOK") @external @@ -26787,7 +26787,7 @@ def ssytrf_rook( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRI") @external @@ -26800,7 +26800,7 @@ def ssytri( IPIV: Int32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRI2") @external @@ -26814,7 +26814,7 @@ def ssytri2( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRI2X") @external @@ -26828,7 +26828,7 @@ def ssytri2x( WORK: Float32[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRI_3") @external @@ -26843,7 +26843,7 @@ def ssytri_3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRI_3X") @external @@ -26858,7 +26858,7 @@ def ssytri_3x( WORK: Float32[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRI_ROOK") @external @@ -26871,7 +26871,7 @@ def ssytri_rook( IPIV: Int32[Flat], WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRS") @external @@ -26886,7 +26886,7 @@ def ssytrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRS2") @external @@ -26902,7 +26902,7 @@ def ssytrs2( LDB: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRS_3") @external @@ -26918,7 +26918,7 @@ def ssytrs_3( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRS_AA") @external @@ -26935,7 +26935,7 @@ def ssytrs_aa( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRS_AA_2STAGE") @external @@ -26953,7 +26953,7 @@ def ssytrs_aa_2stage( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("SSYTRS_ROOK") @external @@ -26968,7 +26968,7 @@ def ssytrs_rook( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("STBCON") @external @@ -26985,7 +26985,7 @@ def stbcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("STBRFS") @external @@ -27008,7 +27008,7 @@ def stbrfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("STBTRS") @external @@ -27025,7 +27025,7 @@ def stbtrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("STFSM") @external @@ -27042,7 +27042,7 @@ def stfsm( A: Float32[Flat], B: Float32[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float32], Returns["LDB", Int32]]: ... @bind("STFTRI") @external @@ -27054,7 +27054,7 @@ def stftri( N: Int32, A: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("STFTTP") @external @@ -27066,7 +27066,7 @@ def stfttp( ARF: Float32[Flat], AP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("STFTTR") @external @@ -27079,7 +27079,7 @@ def stfttr( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("STGEVC") @external @@ -27101,7 +27101,7 @@ def stgevc( M: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDS", Int32], Returns["LDP", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("STGEX2") @external @@ -27124,7 +27124,7 @@ def stgex2( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["J1", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("STGEXC") @external @@ -27146,7 +27146,7 @@ def stgexc( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("STGSEN") @external @@ -27177,7 +27177,7 @@ def stgsen( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["M", Int32], Returns["PL", Float32], Returns["PR", Float32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("STGSJA") @external @@ -27208,7 +27208,7 @@ def stgsja( WORK: Float32[Flat], NCYCLE: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float32], Returns["TOLB", Float32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["NCYCLE", Int32], Returns["INFO", Int32]]: ... @bind("STGSNA") @external @@ -27234,7 +27234,7 @@ def stgsna( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("STGSY2") @external @@ -27262,7 +27262,7 @@ def stgsy2( IWORK: Int32[Flat], PQ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float32], Returns["RDSUM", Float32], Returns["RDSCAL", Float32], Returns["PQ", Int32], Returns["INFO", Int32]]: ... @bind("STGSYL") @external @@ -27290,7 +27290,7 @@ def stgsyl( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float32], Returns["DIF", Float32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("STPCON") @external @@ -27305,7 +27305,7 @@ def stpcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("STPLQT") @external @@ -27323,7 +27323,7 @@ def stplqt( LDT: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("STPLQT2") @external @@ -27339,7 +27339,7 @@ def stplqt2( T: Float32[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("STPMLQT") @external @@ -27362,7 +27362,7 @@ def stpmlqt( LDB: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("STPMQRT") @external @@ -27385,7 +27385,7 @@ def stpmqrt( LDB: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("STPQRT") @external @@ -27403,7 +27403,7 @@ def stpqrt( LDT: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("STPQRT2") @external @@ -27419,7 +27419,7 @@ def stpqrt2( T: Float32[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("STPRFB") @external @@ -27443,7 +27443,7 @@ def stprfb( LDB: Int32, WORK: Float32[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("STPRFS") @external @@ -27464,7 +27464,7 @@ def stprfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("STPTRI") @external @@ -27475,7 +27475,7 @@ def stptri( N: Int32, AP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("STPTRS") @external @@ -27490,7 +27490,7 @@ def stptrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("STPTTF") @external @@ -27502,7 +27502,7 @@ def stpttf( AP: Float32[Flat], ARF: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("STPTTR") @external @@ -27514,7 +27514,7 @@ def stpttr( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("STRCON") @external @@ -27530,7 +27530,7 @@ def strcon( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RCOND", Float32], Returns["INFO", Int32]]: ... @bind("STREVC") @external @@ -27550,7 +27550,7 @@ def strevc( M: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("STREVC3") @external @@ -27571,7 +27571,7 @@ def strevc3( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("STREXC") @external @@ -27587,7 +27587,7 @@ def strexc( ILST: Int32, WORK: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["INFO", Int32]]: ... @bind("STRRFS") @external @@ -27609,7 +27609,7 @@ def strrfs( WORK: Float32[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("STRSEN") @external @@ -27633,7 +27633,7 @@ def strsen( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["M", Int32], Returns["S", Float32], Returns["SEP", Float32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("STRSNA") @external @@ -27657,7 +27657,7 @@ def strsna( LDWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LDWORK", Int32], Returns["INFO", Int32]]: ... @bind("STRSYL") @external @@ -27676,7 +27676,7 @@ def strsyl( LDC: Int32, SCALE: Float32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float32], Returns["INFO", Int32]]: ... @bind("STRSYL3") @external @@ -27699,7 +27699,7 @@ def strsyl3( SWORK: Float32[LDSWORK, Flat], LDSWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float32], Returns["LIWORK", Int32], Returns["LDSWORK", Int32], Returns["INFO", Int32]]: ... @bind("STRTI2") @external @@ -27711,7 +27711,7 @@ def strti2( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("STRTRI") @external @@ -27723,7 +27723,7 @@ def strtri( A: Float32[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("STRTRS") @external @@ -27739,7 +27739,7 @@ def strtrs( B: Float32[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("STRTTF") @external @@ -27752,7 +27752,7 @@ def strttf( LDA: Int32, ARF: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("STRTTP") @external @@ -27764,7 +27764,7 @@ def strttp( LDA: Int32, AP: Float32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("STZRZF") @external @@ -27778,7 +27778,7 @@ def stzrzf( WORK: Float32[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("XERBLA") @external @@ -27786,7 +27786,7 @@ def stzrzf( def xerbla( SRNAME: String, INFO: Int32 -) -> None: ... +) -> Returns["INFO", Int32]: ... @bind("XERBLA_ARRAY") @external @@ -27795,7 +27795,7 @@ def xerbla_array( SRNAME_ARRAY: String[1][SRNAME_LEN], SRNAME_LEN: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["SRNAME_LEN", Int32], Returns["INFO", Int32]]: ... @bind("ZBBCSD") @external @@ -27830,7 +27830,7 @@ def zbbcsd( RWORK: Float64[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZBDSQR") @external @@ -27851,7 +27851,7 @@ def zbdsqr( LDC: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NCVT", Int32], Returns["NRU", Int32], Returns["NCC", Int32], Returns["LDVT", Int32], Returns["LDU", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZCGESV") @external @@ -27871,7 +27871,7 @@ def zcgesv( RWORK: Float64[Flat], ITER: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["ITER", Int32], Returns["INFO", Int32]]: ... @bind("ZCPOSV") @external @@ -27891,7 +27891,7 @@ def zcposv( RWORK: Float64[Flat], ITER: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["ITER", Int32], Returns["INFO", Int32]]: ... @bind("ZDRSCL") @external @@ -27901,7 +27901,7 @@ def zdrscl( SA: Float64, SX: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SA", Float64], Returns["INCX", Int32]]: ... @bind("ZGBBRD") @external @@ -27926,7 +27926,7 @@ def zgbbrd( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NCC", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["LDPT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZGBCON") @external @@ -27944,7 +27944,7 @@ def zgbcon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZGBEQU") @external @@ -27962,7 +27962,7 @@ def zgbequ( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZGBEQUB") @external @@ -27980,7 +27980,7 @@ def zgbequb( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZGBRFS") @external @@ -28005,7 +28005,7 @@ def zgbrfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZGBRFSX") @external @@ -28038,7 +28038,7 @@ def zgbrfsx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZGBSV") @external @@ -28054,7 +28054,7 @@ def zgbsv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZGBSVX") @external @@ -28084,7 +28084,7 @@ def zgbsvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZGBSVXX") @external @@ -28119,7 +28119,7 @@ def zgbsvxx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZGBTF2") @external @@ -28133,7 +28133,7 @@ def zgbtf2( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("ZGBTRF") @external @@ -28147,7 +28147,7 @@ def zgbtrf( LDAB: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("ZGBTRS") @external @@ -28164,7 +28164,7 @@ def zgbtrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZGEBAK") @external @@ -28180,7 +28180,7 @@ def zgebak( V: Complex128[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("ZGEBAL") @external @@ -28194,7 +28194,7 @@ def zgebal( IHI: Int32, SCALE: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("ZGEBD2") @external @@ -28210,7 +28210,7 @@ def zgebd2( TAUP: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGEBRD") @external @@ -28227,7 +28227,7 @@ def zgebrd( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGECON") @external @@ -28242,11 +28242,11 @@ def zgecon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZGEDMD") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Addr(Arg(15)), Arg(16), Arg(17), Addr(Arg(18)), Arg(19), Addr(Arg(20)), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Return('INFO', 10)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Addr(Arg(4)), Addr(Arg(5)), Addr(Arg(6)), Arg(7), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Addr(Arg(11)), Addr(Arg(12)), Return('K', 0), Arg(13), Arg(14), Addr(Arg(15)), Arg(16), Arg(17), Addr(Arg(18)), Arg(19), Addr(Arg(20)), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Return('INFO', 1)]) def zgedmd( JOBS: String[1], JOBZ: String[1], @@ -28277,11 +28277,11 @@ def zgedmd( LRWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Int32, Returns["EIGS", Complex128[Flat]], Returns["Z", Complex128[LDZ, Flat]], Returns["RES", Float64[Flat]], Returns["B", Complex128[LDB, Flat]], Returns["W", Complex128[LDW, Flat]], Returns["S", Complex128[LDS, Flat]], Returns["ZWORK", Complex128[Flat]], Returns["RWORK", Float64[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("ZGEDMDQ") @external -@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 2), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Arg(29), Addr(Arg(30)), Arg(31), Addr(Arg(32)), Return('INFO', 12)]) +@native_call([Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Addr(Arg(6)), Addr(Arg(7)), Addr(Arg(8)), Arg(9), Addr(Arg(10)), Arg(11), Addr(Arg(12)), Arg(13), Addr(Arg(14)), Addr(Arg(15)), Addr(Arg(16)), Return('K', 0), Arg(17), Arg(18), Addr(Arg(19)), Arg(20), Arg(21), Addr(Arg(22)), Arg(23), Addr(Arg(24)), Arg(25), Addr(Arg(26)), Arg(27), Addr(Arg(28)), Arg(29), Addr(Arg(30)), Arg(31), Addr(Arg(32)), Return('INFO', 1)]) def zgedmdq( JOBS: String[1], JOBZ: String[1], @@ -28316,7 +28316,7 @@ def zgedmdq( LWORK: Int32, IWORK: Int32[Flat], LIWORK: Int32 -) -> tuple[Returns["X", Complex128[LDX, Flat]], Returns["Y", Complex128[LDY, Flat]], Int32, Returns["EIGS", Complex128[Flat]], Returns["Z", Complex128[LDZ, Flat]], Returns["RES", Float64[Flat]], Returns["B", Complex128[LDB, Flat]], Returns["V", Complex128[LDV, Flat]], Returns["S", Complex128[LDS, Flat]], Returns["ZWORK", Complex128[Flat]], Returns["WORK", Float64[Flat]], Returns["IWORK", Int32[Flat]], Int32]: ... +) -> tuple[Int32, Int32]: ... @bind("ZGEEQU") @external @@ -28332,7 +28332,7 @@ def zgeequ( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZGEEQUB") @external @@ -28348,7 +28348,7 @@ def zgeequb( COLCND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZGEES") @external @@ -28369,7 +28369,7 @@ def zgees( RWORK: Float64[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEESX") @external @@ -28393,7 +28393,7 @@ def zgeesx( RWORK: Float64[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELECT", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["SDIM", Int32], Returns["LDVS", Int32], Returns["RCONDE", Float64], Returns["RCONDV", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEEV") @external @@ -28413,7 +28413,7 @@ def zgeev( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEEVX") @external @@ -28441,7 +28441,7 @@ def zgeevx( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEHD2") @external @@ -28455,7 +28455,7 @@ def zgehd2( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGEHRD") @external @@ -28470,7 +28470,7 @@ def zgehrd( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEJSV") @external @@ -28497,7 +28497,7 @@ def zgejsv( LRWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGELQ") @external @@ -28512,7 +28512,7 @@ def zgelq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGELQ2") @external @@ -28525,7 +28525,7 @@ def zgelq2( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGELQF") @external @@ -28539,7 +28539,7 @@ def zgelqf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGELQT") @external @@ -28554,7 +28554,7 @@ def zgelqt( LDT: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZGELQT3") @external @@ -28567,7 +28567,7 @@ def zgelqt3( T: Complex128[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZGELS") @external @@ -28584,7 +28584,7 @@ def zgels( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGELSD") @external @@ -28605,7 +28605,7 @@ def zgelsd( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGELSS") @external @@ -28625,7 +28625,7 @@ def zgelss( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGELST") @external @@ -28642,7 +28642,7 @@ def zgelst( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGELSY") @external @@ -28662,7 +28662,7 @@ def zgelsy( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEMLQ") @external @@ -28682,7 +28682,7 @@ def zgemlq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEMLQT") @external @@ -28702,7 +28702,7 @@ def zgemlqt( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZGEMQR") @external @@ -28722,7 +28722,7 @@ def zgemqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEMQRT") @external @@ -28742,7 +28742,7 @@ def zgemqrt( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQL2") @external @@ -28755,7 +28755,7 @@ def zgeql2( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQLF") @external @@ -28769,7 +28769,7 @@ def zgeqlf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQP3") @external @@ -28785,7 +28785,7 @@ def zgeqp3( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQP3RK") @external @@ -28809,7 +28809,7 @@ def zgeqp3rk( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float64], Returns["RELTOL", Float64], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float64], Returns["RELMAXC2NRMK", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQR") @external @@ -28824,7 +28824,7 @@ def zgeqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["TSIZE", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQR2") @external @@ -28837,7 +28837,7 @@ def zgeqr2( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQR2P") @external @@ -28850,7 +28850,7 @@ def zgeqr2p( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQRF") @external @@ -28864,7 +28864,7 @@ def zgeqrf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQRFP") @external @@ -28878,7 +28878,7 @@ def zgeqrfp( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQRT") @external @@ -28893,7 +28893,7 @@ def zgeqrt( LDT: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQRT2") @external @@ -28906,7 +28906,7 @@ def zgeqrt2( T: Complex128[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZGEQRT3") @external @@ -28919,7 +28919,7 @@ def zgeqrt3( T: Complex128[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZGERFS") @external @@ -28942,7 +28942,7 @@ def zgerfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZGERFSX") @external @@ -28973,7 +28973,7 @@ def zgerfsx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZGERQ2") @external @@ -28986,7 +28986,7 @@ def zgerq2( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGERQF") @external @@ -29000,7 +29000,7 @@ def zgerqf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGESC2") @external @@ -29013,7 +29013,7 @@ def zgesc2( IPIV: Int32[Flat], JPIV: Int32[Flat], SCALE: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float64]]: ... @bind("ZGESDD") @external @@ -29034,7 +29034,7 @@ def zgesdd( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGESV") @external @@ -29048,7 +29048,7 @@ def zgesv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZGESVD") @external @@ -29069,7 +29069,7 @@ def zgesvd( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGESVDQ") @external @@ -29097,7 +29097,7 @@ def zgesvdq( RWORK: Float64[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["NUMRANK", Int32], Returns["LIWORK", Int32], Returns["LCWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGESVDX") @external @@ -29125,7 +29125,7 @@ def zgesvdx( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["NS", Int32], Returns["LDU", Int32], Returns["LDVT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGESVJ") @external @@ -29147,7 +29147,7 @@ def zgesvj( RWORK: Float64[LRWORK], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGESVX") @external @@ -29175,7 +29175,7 @@ def zgesvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZGESVXX") @external @@ -29208,7 +29208,7 @@ def zgesvxx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZGETC2") @external @@ -29220,7 +29220,7 @@ def zgetc2( IPIV: Int32[Flat], JPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGETF2") @external @@ -29232,7 +29232,7 @@ def zgetf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGETRF") @external @@ -29244,7 +29244,7 @@ def zgetrf( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGETRF2") @external @@ -29256,7 +29256,7 @@ def zgetrf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZGETRI") @external @@ -29269,7 +29269,7 @@ def zgetri( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGETRS") @external @@ -29284,7 +29284,7 @@ def zgetrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZGETSLS") @external @@ -29301,7 +29301,7 @@ def zgetsls( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGETSQRHRT") @external @@ -29319,7 +29319,7 @@ def zgetsqrhrt( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB1", Int32], Returns["NB1", Int32], Returns["NB2", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGBAK") @external @@ -29336,7 +29336,7 @@ def zggbak( V: Complex128[LDV, Flat], LDV: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["M", Int32], Returns["LDV", Int32], Returns["INFO", Int32]]: ... @bind("ZGGBAL") @external @@ -29354,7 +29354,7 @@ def zggbal( RSCALE: Float64[Flat], WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["INFO", Int32]]: ... @bind("ZGGES") @external @@ -29381,7 +29381,7 @@ def zgges( RWORK: Float64[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGES3") @external @@ -29408,7 +29408,7 @@ def zgges3( RWORK: Float64[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGESX") @external @@ -29440,7 +29440,7 @@ def zggesx( LIWORK: Int32, BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SELCTG", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["SDIM", Int32], Returns["LDVSL", Int32], Returns["LDVSR", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGEV") @external @@ -29463,7 +29463,7 @@ def zggev( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGEV3") @external @@ -29486,7 +29486,7 @@ def zggev3( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGEVX") @external @@ -29521,7 +29521,7 @@ def zggevx( IWORK: Int32[Flat], BWORK: Bool[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["ABNRM", Float64], Returns["BBNRM", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGGLM") @external @@ -29540,7 +29540,7 @@ def zggglm( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGHD3") @external @@ -29562,7 +29562,7 @@ def zgghd3( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGHRD") @external @@ -29582,7 +29582,7 @@ def zgghrd( Z: Complex128[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZGGLSE") @external @@ -29601,7 +29601,7 @@ def zgglse( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGQRF") @external @@ -29619,7 +29619,7 @@ def zggqrf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["P", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGRQF") @external @@ -29637,7 +29637,7 @@ def zggrqf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGSVD3") @external @@ -29668,7 +29668,7 @@ def zggsvd3( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["P", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGGSVP3") @external @@ -29700,7 +29700,7 @@ def zggsvp3( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float64], Returns["TOLB", Float64], Returns["K", Int32], Returns["L", Int32], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGSVJ0") @external @@ -29723,7 +29723,7 @@ def zgsvj0( WORK: Complex128[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float64], Returns["SFMIN", Float64], Returns["TOL", Float64], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGSVJ1") @external @@ -29747,7 +29747,7 @@ def zgsvj1( WORK: Complex128[LWORK], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["LDA", Int32], Returns["MV", Int32], Returns["LDV", Int32], Returns["EPS", Float64], Returns["SFMIN", Float64], Returns["TOL", Float64], Returns["NSWEEP", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZGTCON") @external @@ -29764,7 +29764,7 @@ def zgtcon( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZGTRFS") @external @@ -29790,7 +29790,7 @@ def zgtrfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZGTSV") @external @@ -29804,7 +29804,7 @@ def zgtsv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZGTSVX") @external @@ -29832,7 +29832,7 @@ def zgtsvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZGTTRF") @external @@ -29845,7 +29845,7 @@ def zgttrf( DU2: Complex128[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZGTTRS") @external @@ -29862,7 +29862,7 @@ def zgttrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZGTTS2") @external @@ -29878,7 +29878,7 @@ def zgtts2( IPIV: Int32[Flat], B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["ITRANS", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("ZHB2ST_KERNELS") @external @@ -29899,7 +29899,7 @@ def zhb2st_kernels( TAU: Complex128[Flat], LDVT: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["WANTZ", Bool], Returns["TTYPE", Int32], Returns["ST", Int32], Returns["ED", Int32], Returns["SWEEP", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["IB", Int32], Returns["LDA", Int32], Returns["LDVT", Int32]]: ... @bind("ZHBEV") @external @@ -29917,7 +29917,7 @@ def zhbev( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHBEV_2STAGE") @external @@ -29936,7 +29936,7 @@ def zhbev_2stage( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHBEVD") @external @@ -29958,7 +29958,7 @@ def zhbevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHBEVD_2STAGE") @external @@ -29980,7 +29980,7 @@ def zhbevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHBEVX") @external @@ -30009,7 +30009,7 @@ def zhbevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHBEVX_2STAGE") @external @@ -30039,7 +30039,7 @@ def zhbevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHBGST") @external @@ -30059,7 +30059,7 @@ def zhbgst( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZHBGV") @external @@ -30080,7 +30080,7 @@ def zhbgv( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHBGVD") @external @@ -30105,7 +30105,7 @@ def zhbgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHBGVX") @external @@ -30137,7 +30137,7 @@ def zhbgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KA", Int32], Returns["KB", Int32], Returns["LDAB", Int32], Returns["LDBB", Int32], Returns["LDQ", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHBTRD") @external @@ -30155,7 +30155,7 @@ def zhbtrd( LDQ: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("ZHECON") @external @@ -30170,7 +30170,7 @@ def zhecon( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZHECON_3") @external @@ -30186,7 +30186,7 @@ def zhecon_3( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZHECON_ROOK") @external @@ -30201,7 +30201,7 @@ def zhecon_rook( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZHEEQUB") @external @@ -30216,7 +30216,7 @@ def zheequb( AMAX: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZHEEV") @external @@ -30232,7 +30232,7 @@ def zheev( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEEV_2STAGE") @external @@ -30248,7 +30248,7 @@ def zheev_2stage( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEEVD") @external @@ -30267,7 +30267,7 @@ def zheevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEEVD_2STAGE") @external @@ -30286,7 +30286,7 @@ def zheevd_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEEVR") @external @@ -30315,7 +30315,7 @@ def zheevr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEEVR_2STAGE") @external @@ -30344,7 +30344,7 @@ def zheevr_2stage( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEEVX") @external @@ -30371,7 +30371,7 @@ def zheevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEEVX_2STAGE") @external @@ -30398,7 +30398,7 @@ def zheevx_2stage( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEGS2") @external @@ -30412,7 +30412,7 @@ def zhegs2( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHEGST") @external @@ -30426,7 +30426,7 @@ def zhegst( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHEGV") @external @@ -30445,7 +30445,7 @@ def zhegv( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEGV_2STAGE") @external @@ -30464,7 +30464,7 @@ def zhegv_2stage( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEGVD") @external @@ -30486,7 +30486,7 @@ def zhegvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHEGVX") @external @@ -30516,7 +30516,7 @@ def zhegvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHERFS") @external @@ -30539,7 +30539,7 @@ def zherfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZHERFSX") @external @@ -30569,7 +30569,7 @@ def zherfsx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZHESV") @external @@ -30586,7 +30586,7 @@ def zhesv( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHESV_AA") @external @@ -30603,7 +30603,7 @@ def zhesv_aa( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHESV_AA_2STAGE") @external @@ -30623,7 +30623,7 @@ def zhesv_aa_2stage( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHESV_RK") @external @@ -30641,7 +30641,7 @@ def zhesv_rk( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHESV_ROOK") @external @@ -30658,7 +30658,7 @@ def zhesv_rook( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHESVX") @external @@ -30684,7 +30684,7 @@ def zhesvx( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHESVXX") @external @@ -30716,7 +30716,7 @@ def zhesvxx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZHESWAPR") @external @@ -30728,7 +30728,7 @@ def zheswapr( LDA: Int32, I1: Int32, I2: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["I1", Int32], Returns["I2", Int32]]: ... @bind("ZHETD2") @external @@ -30742,7 +30742,7 @@ def zhetd2( E: Float64[Flat], TAU: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZHETF2") @external @@ -30754,7 +30754,7 @@ def zhetf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZHETF2_RK") @external @@ -30767,7 +30767,7 @@ def zhetf2_rk( E: Complex128[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZHETF2_ROOK") @external @@ -30779,7 +30779,7 @@ def zhetf2_rook( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRD") @external @@ -30795,7 +30795,7 @@ def zhetrd( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRD_2STAGE") @external @@ -30814,7 +30814,7 @@ def zhetrd_2stage( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LHOUS2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRD_HB2ST") @external @@ -30834,7 +30834,7 @@ def zhetrd_hb2st( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["LHOUS", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRD_HE2HB") @external @@ -30851,7 +30851,7 @@ def zhetrd_he2hb( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDA", Int32], Returns["LDAB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRF") @external @@ -30865,7 +30865,7 @@ def zhetrf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRF_AA") @external @@ -30879,7 +30879,7 @@ def zhetrf_aa( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRF_AA_2STAGE") @external @@ -30896,7 +30896,7 @@ def zhetrf_aa_2stage( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRF_RK") @external @@ -30911,7 +30911,7 @@ def zhetrf_rk( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRF_ROOK") @external @@ -30925,7 +30925,7 @@ def zhetrf_rook( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRI") @external @@ -30938,7 +30938,7 @@ def zhetri( IPIV: Int32[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRI2") @external @@ -30952,7 +30952,7 @@ def zhetri2( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRI2X") @external @@ -30966,7 +30966,7 @@ def zhetri2x( WORK: Complex128[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRI_3") @external @@ -30981,7 +30981,7 @@ def zhetri_3( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRI_3X") @external @@ -30996,7 +30996,7 @@ def zhetri_3x( WORK: Complex128[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRI_ROOK") @external @@ -31009,7 +31009,7 @@ def zhetri_rook( IPIV: Int32[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRS") @external @@ -31024,7 +31024,7 @@ def zhetrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRS2") @external @@ -31040,7 +31040,7 @@ def zhetrs2( LDB: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRS_3") @external @@ -31056,7 +31056,7 @@ def zhetrs_3( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRS_AA") @external @@ -31073,7 +31073,7 @@ def zhetrs_aa( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRS_AA_2STAGE") @external @@ -31091,7 +31091,7 @@ def zhetrs_aa_2stage( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHETRS_ROOK") @external @@ -31106,7 +31106,7 @@ def zhetrs_rook( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHFRK") @external @@ -31122,7 +31122,7 @@ def zhfrk( LDA: Int32, BETA: Float64, C: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["BETA", Float64]]: ... @bind("ZHGEQZ") @external @@ -31148,7 +31148,7 @@ def zhgeqz( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHPCON") @external @@ -31162,7 +31162,7 @@ def zhpcon( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZHPEV") @external @@ -31178,7 +31178,7 @@ def zhpev( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHPEVD") @external @@ -31198,7 +31198,7 @@ def zhpevd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHPEVX") @external @@ -31223,7 +31223,7 @@ def zhpevx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHPGST") @external @@ -31235,7 +31235,7 @@ def zhpgst( AP: Complex128[Flat], BP: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZHPGV") @external @@ -31253,7 +31253,7 @@ def zhpgv( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHPGVD") @external @@ -31275,7 +31275,7 @@ def zhpgvd( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZHPGVX") @external @@ -31302,7 +31302,7 @@ def zhpgvx( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ITYPE", Int32], Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZHPRFS") @external @@ -31323,7 +31323,7 @@ def zhprfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZHPSV") @external @@ -31337,7 +31337,7 @@ def zhpsv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHPSVX") @external @@ -31360,7 +31360,7 @@ def zhpsvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZHPTRD") @external @@ -31373,7 +31373,7 @@ def zhptrd( E: Float64[Flat], TAU: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZHPTRF") @external @@ -31384,7 +31384,7 @@ def zhptrf( AP: Complex128[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZHPTRI") @external @@ -31396,7 +31396,7 @@ def zhptri( IPIV: Int32[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZHPTRS") @external @@ -31410,7 +31410,7 @@ def zhptrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZHSEIN") @external @@ -31435,7 +31435,7 @@ def zhsein( IFAILL: Int32[Flat], IFAILR: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("ZHSEQR") @external @@ -31454,7 +31454,7 @@ def zhseqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLA_GBAMV") @external @@ -31473,7 +31473,7 @@ def zla_gbamv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["ALPHA", Float64], Returns["LDAB", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("ZLA_GBRCOND_C") @external @@ -31493,7 +31493,7 @@ def zla_gbrcond_c( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_GBRCOND_X") @external @@ -31512,7 +31512,7 @@ def zla_gbrcond_x( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["INFO", Int32]]: ... @bind("ZLA_GBRFSX_EXTENDED") @external @@ -31549,7 +31549,7 @@ def zla_gbrfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_GBRPVGRW") @external @@ -31563,7 +31563,7 @@ def zla_gbrpvgrw( LDAB: Int32, AFB: Complex128[LDAFB, Flat], LDAFB: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["NCOLS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32]]: ... @bind("ZLA_GEAMV") @external @@ -31580,7 +31580,7 @@ def zla_geamv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["TRANS", Int32], Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("ZLA_GERCOND_C") @external @@ -31598,7 +31598,7 @@ def zla_gercond_c( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_GERCOND_X") @external @@ -31615,7 +31615,7 @@ def zla_gercond_x( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("ZLA_GERFSX_EXTENDED") @external @@ -31650,7 +31650,7 @@ def zla_gerfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["TRANS_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_GERPVGRW") @external @@ -31662,7 +31662,7 @@ def zla_gerpvgrw( LDA: Int32, AF: Complex128[LDAF, Flat], LDAF: Int32 -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("ZLA_HEAMV") @external @@ -31678,7 +31678,7 @@ def zla_heamv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["UPLO", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("ZLA_HERCOND_C") @external @@ -31696,7 +31696,7 @@ def zla_hercond_c( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_HERCOND_X") @external @@ -31713,7 +31713,7 @@ def zla_hercond_x( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("ZLA_HERFSX_EXTENDED") @external @@ -31748,7 +31748,7 @@ def zla_herfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_HERPVGRW") @external @@ -31763,7 +31763,7 @@ def zla_herpvgrw( LDAF: Int32, IPIV: Int32[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INFO", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("ZLA_LIN_BERR") @external @@ -31775,7 +31775,7 @@ def zla_lin_berr( RES: Complex128[N, NRHS], AYB: Float64[N, NRHS], BERR: Float64[NRHS] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NZ", Int32], Returns["NRHS", Int32]]: ... @bind("ZLA_PORCOND_C") @external @@ -31792,7 +31792,7 @@ def zla_porcond_c( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_PORCOND_X") @external @@ -31808,7 +31808,7 @@ def zla_porcond_x( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("ZLA_PORFSX_EXTENDED") @external @@ -31842,7 +31842,7 @@ def zla_porfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_PORPVGRW") @external @@ -31855,7 +31855,7 @@ def zla_porpvgrw( AF: Complex128[LDAF, Flat], LDAF: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["NCOLS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("ZLA_SYAMV") @external @@ -31871,7 +31871,7 @@ def zla_syamv( BETA: Float64, Y: Float64[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["UPLO", Int32], Returns["N", Int32], Returns["ALPHA", Float64], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Float64], Returns["INCY", Int32]]: ... @bind("ZLA_SYRCOND_C") @external @@ -31889,7 +31889,7 @@ def zla_syrcond_c( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["CAPPLY", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_SYRCOND_X") @external @@ -31906,7 +31906,7 @@ def zla_syrcond_x( INFO: Int32, WORK: Complex128[Flat], RWORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["INFO", Int32]]: ... @bind("ZLA_SYRFSX_EXTENDED") @external @@ -31941,7 +31941,7 @@ def zla_syrfsx_extended( DZ_UB: Float64, IGNORE_CWISE: Bool, INFO: Int32 -) -> None: ... +) -> tuple[Returns["PREC_TYPE", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["COLEQU", Bool], Returns["LDB", Int32], Returns["LDY", Int32], Returns["N_NORMS", Int32], Returns["RCOND", Float64], Returns["ITHRESH", Int32], Returns["RTHRESH", Float64], Returns["DZ_UB", Float64], Returns["IGNORE_CWISE", Bool], Returns["INFO", Int32]]: ... @bind("ZLA_SYRPVGRW") @external @@ -31956,7 +31956,7 @@ def zla_syrpvgrw( LDAF: Int32, IPIV: Int32[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["INFO", Int32], Returns["LDA", Int32], Returns["LDAF", Int32]]: ... @bind("ZLA_WWADDW") @external @@ -31966,7 +31966,7 @@ def zla_wwaddw( X: Complex128[Flat], Y: Complex128[Flat], W: Complex128[Flat] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ZLABRD") @external @@ -31985,7 +31985,7 @@ def zlabrd( LDX: Int32, Y: Complex128[LDY, Flat], LDY: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LDY", Int32]]: ... @bind("ZLACGV") @external @@ -31994,7 +31994,7 @@ def zlacgv( N: Int32, X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32]]: ... @bind("ZLACN2") @external @@ -32006,7 +32006,7 @@ def zlacn2( EST: Float64, KASE: Int32, ISAVE: Int32[3] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float64], Returns["KASE", Int32]]: ... @bind("ZLACON") @external @@ -32017,7 +32017,7 @@ def zlacon( X: Complex128[N], EST: Float64, KASE: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["EST", Float64], Returns["KASE", Int32]]: ... @bind("ZLACP2") @external @@ -32030,7 +32030,7 @@ def zlacp2( LDA: Int32, B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("ZLACPY") @external @@ -32043,7 +32043,7 @@ def zlacpy( LDA: Int32, B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32]]: ... @bind("ZLACRM") @external @@ -32058,7 +32058,7 @@ def zlacrm( C: Complex128[LDC, Flat], LDC: Int32, RWORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32]]: ... @bind("ZLACRT") @external @@ -32071,7 +32071,7 @@ def zlacrt( INCY: Int32, C: Complex128, S: Complex128 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Complex128], Returns["S", Complex128]]: ... @bind("ZLADIV") @external @@ -32079,7 +32079,7 @@ def zlacrt( def zladiv( X: Complex128, Y: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["X", Complex128], Returns["Y", Complex128]]: ... @bind("ZLAED0") @external @@ -32096,7 +32096,7 @@ def zlaed0( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["QSIZ", Int32], Returns["N", Int32], Returns["LDQ", Int32], Returns["LDQS", Int32], Returns["INFO", Int32]]: ... @bind("ZLAED7") @external @@ -32124,7 +32124,7 @@ def zlaed7( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["CUTPNT", Int32], Returns["QSIZ", Int32], Returns["TLVLS", Int32], Returns["CURLVL", Int32], Returns["CURPBM", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["INFO", Int32]]: ... @bind("ZLAED8") @external @@ -32151,7 +32151,7 @@ def zlaed8( GIVCOL: Int32[2, Flat], GIVNUM: Float64[2, Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["K", Int32], Returns["N", Int32], Returns["QSIZ", Int32], Returns["LDQ", Int32], Returns["RHO", Float64], Returns["CUTPNT", Int32], Returns["LDQ2", Int32], Returns["GIVPTR", Int32], Returns["INFO", Int32]]: ... @bind("ZLAEIN") @external @@ -32170,7 +32170,7 @@ def zlaein( EPS3: Float64, SMLNUM: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["RIGHTV", Bool], Returns["NOINIT", Bool], Returns["N", Int32], Returns["LDH", Int32], Returns["W", Complex128], Returns["LDB", Int32], Returns["EPS3", Float64], Returns["SMLNUM", Float64], Returns["INFO", Int32]]: ... @bind("ZLAESY") @external @@ -32184,7 +32184,7 @@ def zlaesy( EVSCAL: Complex128, CS1: Complex128, SN1: Complex128 -) -> None: ... +) -> tuple[Returns["A", Complex128], Returns["B", Complex128], Returns["C", Complex128], Returns["RT1", Complex128], Returns["RT2", Complex128], Returns["EVSCAL", Complex128], Returns["CS1", Complex128], Returns["SN1", Complex128]]: ... @bind("ZLAEV2") @external @@ -32197,7 +32197,7 @@ def zlaev2( RT2: Float64, CS1: Float64, SN1: Complex128 -) -> None: ... +) -> tuple[Returns["A", Complex128], Returns["B", Complex128], Returns["C", Complex128], Returns["RT1", Float64], Returns["RT2", Float64], Returns["CS1", Float64], Returns["SN1", Complex128]]: ... @bind("ZLAG2C") @external @@ -32210,7 +32210,7 @@ def zlag2c( SA: Complex64[LDSA, Flat], LDSA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDSA", Int32], Returns["INFO", Int32]]: ... @bind("ZLAGS2") @external @@ -32229,7 +32229,7 @@ def zlags2( SNV: Complex128, CSQ: Float64, SNQ: Complex128 -) -> None: ... +) -> tuple[Returns["UPPER", Bool], Returns["A1", Float64], Returns["A2", Complex128], Returns["A3", Float64], Returns["B1", Float64], Returns["B2", Complex128], Returns["B3", Float64], Returns["CSU", Float64], Returns["SNU", Complex128], Returns["CSV", Float64], Returns["SNV", Complex128], Returns["CSQ", Float64], Returns["SNQ", Complex128]]: ... @bind("ZLAGTM") @external @@ -32247,7 +32247,7 @@ def zlagtm( BETA: Float64, B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["ALPHA", Float64], Returns["LDX", Int32], Returns["BETA", Float64], Returns["LDB", Int32]]: ... @bind("ZLAHEF") @external @@ -32263,7 +32263,7 @@ def zlahef( W: Complex128[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("ZLAHEF_AA") @external @@ -32279,7 +32279,7 @@ def zlahef_aa( H: Complex128[LDH, Flat], LDH: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["J1", Int32], Returns["M", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDH", Int32]]: ... @bind("ZLAHEF_RK") @external @@ -32296,7 +32296,7 @@ def zlahef_rk( W: Complex128[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("ZLAHEF_ROOK") @external @@ -32312,7 +32312,7 @@ def zlahef_rook( W: Complex128[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("ZLAHQR") @external @@ -32331,7 +32331,7 @@ def zlahqr( Z: Complex128[LDZ, Flat], LDZ: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZLAHR2") @external @@ -32347,7 +32347,7 @@ def zlahr2( LDT: Int32, Y: Complex128[LDY, NB], LDY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDY", Int32]]: ... @bind("ZLAIC1") @external @@ -32362,7 +32362,7 @@ def zlaic1( SESTPR: Float64, S: Complex128, C: Complex128 -) -> None: ... +) -> tuple[Returns["JOB", Int32], Returns["J", Int32], Returns["SEST", Float64], Returns["GAMMA", Complex128], Returns["SESTPR", Float64], Returns["S", Complex128], Returns["C", Complex128]]: ... @bind("ZLALS0") @external @@ -32392,7 +32392,7 @@ def zlals0( S: Float64, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["NL", Int32], Returns["NR", Int32], Returns["SQRE", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["GIVPTR", Int32], Returns["LDGCOL", Int32], Returns["LDGNUM", Int32], Returns["K", Int32], Returns["C", Float64], Returns["S", Float64], Returns["INFO", Int32]]: ... @bind("ZLALSA") @external @@ -32424,7 +32424,7 @@ def zlalsa( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["ICOMPQ", Int32], Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDBX", Int32], Returns["LDU", Int32], Returns["LDGCOL", Int32], Returns["INFO", Int32]]: ... @bind("ZLALSD") @external @@ -32444,7 +32444,7 @@ def zlalsd( RWORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["SMLSIZ", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["RCOND", Float64], Returns["RANK", Int32], Returns["INFO", Int32]]: ... @bind("ZLAMSWLQ") @external @@ -32466,7 +32466,7 @@ def zlamswlq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLAMTSQR") @external @@ -32488,7 +32488,7 @@ def zlamtsqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLANGB") @external @@ -32501,7 +32501,7 @@ def zlangb( AB: Complex128[LDAB, Flat], LDAB: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32]]: ... @bind("ZLANGE") @external @@ -32513,7 +32513,7 @@ def zlange( A: Complex128[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ZLANGT") @external @@ -32524,7 +32524,7 @@ def zlangt( DL: Complex128[Flat], D: Complex128[Flat], DU: Complex128[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("ZLANHB") @external @@ -32537,7 +32537,7 @@ def zlanhb( AB: Complex128[LDAB, Flat], LDAB: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("ZLANHE") @external @@ -32549,7 +32549,7 @@ def zlanhe( A: Complex128[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ZLANHF") @external @@ -32561,7 +32561,7 @@ def zlanhf( N: Int32, A: Complex128[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("ZLANHP") @external @@ -32572,7 +32572,7 @@ def zlanhp( N: Int32, AP: Complex128[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("ZLANHS") @external @@ -32583,7 +32583,7 @@ def zlanhs( A: Complex128[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ZLANHT") @external @@ -32593,7 +32593,7 @@ def zlanht( N: Int32, D: Float64[Flat], E: Complex128[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("ZLANSB") @external @@ -32606,7 +32606,7 @@ def zlansb( AB: Complex128[LDAB, Flat], LDAB: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("ZLANSP") @external @@ -32617,7 +32617,7 @@ def zlansp( N: Int32, AP: Complex128[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("ZLANSY") @external @@ -32629,7 +32629,7 @@ def zlansy( A: Complex128[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ZLANTB") @external @@ -32643,7 +32643,7 @@ def zlantb( AB: Complex128[LDAB, Flat], LDAB: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32], Returns["K", Int32], Returns["LDAB", Int32]]: ... @bind("ZLANTP") @external @@ -32655,7 +32655,7 @@ def zlantp( N: Int32, AP: Complex128[Flat], WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["N", Int32]]: ... @bind("ZLANTR") @external @@ -32669,7 +32669,7 @@ def zlantr( A: Complex128[LDA, Flat], LDA: Int32, WORK: Float64[Flat] -) -> Float64: ... +) -> tuple[Float64, Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ZLAPLL") @external @@ -32681,7 +32681,7 @@ def zlapll( Y: Complex128[Flat], INCY: Int32, SSMIN: Float64 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["SSMIN", Float64]]: ... @bind("ZLAPMR") @external @@ -32693,7 +32693,7 @@ def zlapmr( X: Complex128[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("ZLAPMT") @external @@ -32705,7 +32705,7 @@ def zlapmt( X: Complex128[LDX, Flat], LDX: Int32, K: Int32[Flat] -) -> None: ... +) -> tuple[Returns["FORWRD", Bool], Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("ZLAQGB") @external @@ -32723,7 +32723,7 @@ def zlaqgb( COLCND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["KL", Int32], Returns["KU", Int32], Returns["LDAB", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQGE") @external @@ -32739,7 +32739,7 @@ def zlaqge( COLCND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["ROWCND", Float64], Returns["COLCND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQHB") @external @@ -32754,7 +32754,7 @@ def zlaqhb( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQHE") @external @@ -32768,7 +32768,7 @@ def zlaqhe( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQHP") @external @@ -32781,7 +32781,7 @@ def zlaqhp( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQP2") @external @@ -32797,7 +32797,7 @@ def zlaqp2( VN1: Float64[Flat], VN2: Float64[Flat], WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["LDA", Int32]]: ... @bind("ZLAQP2RK") @external @@ -32823,7 +32823,7 @@ def zlaqp2rk( VN2: Float64[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["KMAX", Int32], Returns["ABSTOL", Float64], Returns["RELTOL", Float64], Returns["KP1", Int32], Returns["MAXC2NRM", Float64], Returns["LDA", Int32], Returns["K", Int32], Returns["MAXC2NRMK", Float64], Returns["RELMAXC2NRMK", Float64], Returns["INFO", Int32]]: ... @bind("ZLAQP3RK") @external @@ -32853,7 +32853,7 @@ def zlaqp3rk( LDF: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["IOFFSET", Int32], Returns["NB", Int32], Returns["ABSTOL", Float64], Returns["RELTOL", Float64], Returns["KP1", Int32], Returns["MAXC2NRM", Float64], Returns["LDA", Int32], Returns["DONE", Bool], Returns["KB", Int32], Returns["MAXC2NRMK", Float64], Returns["RELMAXC2NRMK", Float64], Returns["LDF", Int32], Returns["INFO", Int32]]: ... @bind("ZLAQPS") @external @@ -32873,7 +32873,7 @@ def zlaqps( AUXV: Complex128[Flat], F: Complex128[LDF, Flat], LDF: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["OFFSET", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDF", Int32]]: ... @bind("ZLAQR0") @external @@ -32894,7 +32894,7 @@ def zlaqr0( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLAQR1") @external @@ -32906,7 +32906,7 @@ def zlaqr1( S1: Complex128, S2: Complex128, V: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDH", Int32], Returns["S1", Complex128], Returns["S2", Complex128]]: ... @bind("ZLAQR2") @external @@ -32937,7 +32937,7 @@ def zlaqr2( LDWV: Int32, WORK: Complex128[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("ZLAQR3") @external @@ -32968,7 +32968,7 @@ def zlaqr3( LDWV: Int32, WORK: Complex128[Flat], LWORK: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NW", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["NS", Int32], Returns["ND", Int32], Returns["LDV", Int32], Returns["NH", Int32], Returns["LDT", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["LWORK", Int32]]: ... @bind("ZLAQR4") @external @@ -32989,7 +32989,7 @@ def zlaqr4( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLAQR5") @external @@ -33019,7 +33019,7 @@ def zlaqr5( NH: Int32, WH: Complex128[LDWH, Flat], LDWH: Int32 -) -> None: ... +) -> tuple[Returns["WANTT", Bool], Returns["WANTZ", Bool], Returns["KACC22", Int32], Returns["N", Int32], Returns["KTOP", Int32], Returns["KBOT", Int32], Returns["NSHFTS", Int32], Returns["LDH", Int32], Returns["ILOZ", Int32], Returns["IHIZ", Int32], Returns["LDZ", Int32], Returns["LDV", Int32], Returns["LDU", Int32], Returns["NV", Int32], Returns["LDWV", Int32], Returns["NH", Int32], Returns["LDWH", Int32]]: ... @bind("ZLAQSB") @external @@ -33034,7 +33034,7 @@ def zlaqsb( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQSP") @external @@ -33047,7 +33047,7 @@ def zlaqsp( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQSY") @external @@ -33061,11 +33061,11 @@ def zlaqsy( SCOND: Float64, AMAX: Float64, EQUED: String[1] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64]]: ... @bind("ZLAQZ0") @external -@native_call([Arg(0), Arg(1), Arg(2), Addr(Arg(3)), Addr(Arg(4)), Addr(Arg(5)), Arg(6), Addr(Arg(7)), Arg(8), Addr(Arg(9)), Arg(10), Arg(11), Arg(12), Addr(Arg(13)), Arg(14), Addr(Arg(15)), Arg(16), Addr(Arg(17)), Arg(18), Addr(Arg(19)), Return('INFO', 1)]) +@native_call([Arg(0), Arg(1), Arg(2), Addr(Arg(3)), Addr(Arg(4)), Addr(Arg(5)), Arg(6), Addr(Arg(7)), Arg(8), Addr(Arg(9)), Arg(10), Arg(11), Arg(12), Addr(Arg(13)), Arg(14), Addr(Arg(15)), Arg(16), Addr(Arg(17)), Arg(18), Addr(Arg(19)), Return('INFO', 0)]) def zlaqz0( WANTS: String[1], WANTQ: String[1], @@ -33087,7 +33087,7 @@ def zlaqz0( LWORK: Int32, RWORK: Float64[Flat], REC: Int32 -) -> tuple[Returns["RWORK", Float64[Flat]], Int32]: ... +) -> Int32: ... @bind("ZLAQZ1") @external @@ -33199,7 +33199,7 @@ def zlar1v( RESID: Float64, RQCORR: Float64, WORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["B1", Int32], Returns["BN", Int32], Returns["LAMBDA", Float64], Returns["PIVMIN", Float64], Returns["GAPTOL", Float64], Returns["WANTNC", Bool], Returns["NEGCNT", Int32], Returns["ZTZ", Float64], Returns["MINGMA", Float64], Returns["R", Int32], Returns["NRMINV", Float64], Returns["RESID", Float64], Returns["RQCORR", Float64]]: ... @bind("ZLAR2V") @external @@ -33213,7 +33213,7 @@ def zlar2v( C: Float64[Flat], S: Complex128[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCC", Int32]]: ... @bind("ZLARCM") @external @@ -33228,7 +33228,7 @@ def zlarcm( C: Complex128[LDC, Flat], LDC: Int32, RWORK: Float64[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32]]: ... @bind("ZLARF") @external @@ -33243,7 +33243,7 @@ def zlarf( C: Complex128[LDC, Flat], LDC: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex128], Returns["LDC", Int32]]: ... @bind("ZLARF1F") @external @@ -33258,7 +33258,7 @@ def zlarf1f( C: Complex128[LDC, Flat], LDC: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex128], Returns["LDC", Int32]]: ... @bind("ZLARF1L") @external @@ -33273,7 +33273,7 @@ def zlarf1l( C: Complex128[LDC, Flat], LDC: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex128], Returns["LDC", Int32]]: ... @bind("ZLARFB") @external @@ -33294,7 +33294,7 @@ def zlarfb( LDC: Int32, WORK: Complex128[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("ZLARFB_GETT") @external @@ -33312,7 +33312,7 @@ def zlarfb_gett( LDB: Int32, WORK: Complex128[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("ZLARFG") @external @@ -33323,7 +33323,7 @@ def zlarfg( X: Complex128[Flat], INCX: Int32, TAU: Complex128 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["TAU", Complex128]]: ... @bind("ZLARFGP") @external @@ -33334,7 +33334,7 @@ def zlarfgp( X: Complex128[Flat], INCX: Int32, TAU: Complex128 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["TAU", Complex128]]: ... @bind("ZLARFT") @external @@ -33349,7 +33349,7 @@ def zlarft( TAU: Complex128[Flat], T: Complex128[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("ZLARFX") @external @@ -33363,7 +33363,7 @@ def zlarfx( C: Complex128[LDC, Flat], LDC: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["TAU", Complex128], Returns["LDC", Int32]]: ... @bind("ZLARFY") @external @@ -33377,7 +33377,7 @@ def zlarfy( C: Complex128[LDC, Flat], LDC: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCV", Int32], Returns["TAU", Complex128], Returns["LDC", Int32]]: ... @bind("ZLARGV") @external @@ -33390,7 +33390,7 @@ def zlargv( INCY: Int32, C: Float64[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("ZLARNV") @external @@ -33400,7 +33400,7 @@ def zlarnv( ISEED: Int32[4], N: Int32, X: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["IDIST", Int32], Returns["N", Int32]]: ... @bind("ZLARRV") @external @@ -33431,7 +33431,7 @@ def zlarrv( WORK: Float64[Flat], IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["PIVMIN", Float64], Returns["M", Int32], Returns["DOL", Int32], Returns["DOU", Int32], Returns["MINRGP", Float64], Returns["RTOL1", Float64], Returns["RTOL2", Float64], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZLARSCL2") @external @@ -33442,7 +33442,7 @@ def zlarscl2( D: Float64[Flat], X: Complex128[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("ZLARTG") @external @@ -33453,7 +33453,7 @@ def zlartg( c: Float64, s: Complex128, r: Complex128 -) -> None: ... +) -> tuple[Returns["f", Complex128], Returns["g", Complex128], Returns["c", Float64], Returns["s", Complex128], Returns["r", Complex128]]: ... @bind("ZLARTV") @external @@ -33467,7 +33467,7 @@ def zlartv( C: Float64[Flat], S: Complex128[Flat], INCC: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["INCC", Int32]]: ... @bind("ZLARZ") @external @@ -33483,7 +33483,7 @@ def zlarz( C: Complex128[LDC, Flat], LDC: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["INCV", Int32], Returns["TAU", Complex128], Returns["LDC", Int32]]: ... @bind("ZLARZB") @external @@ -33505,7 +33505,7 @@ def zlarzb( LDC: Int32, WORK: Complex128[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDC", Int32], Returns["LDWORK", Int32]]: ... @bind("ZLARZT") @external @@ -33520,7 +33520,7 @@ def zlarzt( TAU: Complex128[Flat], T: Complex128[LDT, Flat], LDT: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["K", Int32], Returns["LDV", Int32], Returns["LDT", Int32]]: ... @bind("ZLASCL") @external @@ -33536,7 +33536,7 @@ def zlascl( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["KL", Int32], Returns["KU", Int32], Returns["CFROM", Float64], Returns["CTO", Float64], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZLASCL2") @external @@ -33547,7 +33547,7 @@ def zlascl2( D: Float64[Flat], X: Complex128[LDX, Flat], LDX: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDX", Int32]]: ... @bind("ZLASET") @external @@ -33560,7 +33560,7 @@ def zlaset( BETA: Complex128, A: Complex128[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["BETA", Complex128], Returns["LDA", Int32]]: ... @bind("ZLASR") @external @@ -33575,7 +33575,7 @@ def zlasr( S: Float64[Flat], A: Complex128[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32]]: ... @bind("ZLASSQ") @external @@ -33586,7 +33586,7 @@ def zlassq( incx: Int32, scale: Float64, sumsq: Float64 -) -> None: ... +) -> tuple[Returns["n", Int32], Returns["incx", Int32], Returns["scale", Float64], Returns["sumsq", Float64]]: ... @bind("ZLASWLQ") @external @@ -33603,7 +33603,7 @@ def zlaswlq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLASWP") @external @@ -33616,7 +33616,7 @@ def zlaswp( K2: Int32, IPIV: Int32[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["K1", Int32], Returns["K2", Int32], Returns["INCX", Int32]]: ... @bind("ZLASYF") @external @@ -33632,7 +33632,7 @@ def zlasyf( W: Complex128[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("ZLASYF_AA") @external @@ -33648,7 +33648,7 @@ def zlasyf_aa( H: Complex128[LDH, Flat], LDH: Int32, WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["J1", Int32], Returns["M", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDH", Int32]]: ... @bind("ZLASYF_RK") @external @@ -33665,7 +33665,7 @@ def zlasyf_rk( W: Complex128[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("ZLASYF_ROOK") @external @@ -33681,7 +33681,7 @@ def zlasyf_rook( W: Complex128[LDW, Flat], LDW: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["KB", Int32], Returns["LDA", Int32], Returns["LDW", Int32], Returns["INFO", Int32]]: ... @bind("ZLAT2C") @external @@ -33694,7 +33694,7 @@ def zlat2c( SA: Complex64[LDSA, Flat], LDSA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDSA", Int32], Returns["INFO", Int32]]: ... @bind("ZLATBS") @external @@ -33712,7 +33712,7 @@ def zlatbs( SCALE: Float64, CNORM: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("ZLATDF") @external @@ -33727,7 +33727,7 @@ def zlatdf( RDSCAL: Float64, IPIV: Int32[Flat], JPIV: Int32[Flat] -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["N", Int32], Returns["LDZ", Int32], Returns["RDSUM", Float64], Returns["RDSCAL", Float64]]: ... @bind("ZLATPS") @external @@ -33743,7 +33743,7 @@ def zlatps( SCALE: Float64, CNORM: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("ZLATRD") @external @@ -33758,7 +33758,7 @@ def zlatrd( TAU: Complex128[Flat], W: Complex128[LDW, Flat], LDW: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDW", Int32]]: ... @bind("ZLATRS") @external @@ -33775,7 +33775,7 @@ def zlatrs( SCALE: Float64, CNORM: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("ZLATRS3") @external @@ -33796,7 +33796,7 @@ def zlatrs3( WORK: Float64[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDX", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLATRZ") @external @@ -33809,7 +33809,7 @@ def zlatrz( LDA: Int32, TAU: Complex128[Flat], WORK: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32]]: ... @bind("ZLATSQR") @external @@ -33826,7 +33826,7 @@ def zlatsqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZLAUNHR_COL_GETRFNP") @external @@ -33838,7 +33838,7 @@ def zlaunhr_col_getrfnp( LDA: Int32, D: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZLAUNHR_COL_GETRFNP2") @external @@ -33850,7 +33850,7 @@ def zlaunhr_col_getrfnp2( LDA: Int32, D: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZLAUU2") @external @@ -33861,7 +33861,7 @@ def zlauu2( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZLAUUM") @external @@ -33872,7 +33872,7 @@ def zlauum( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZPBCON") @external @@ -33888,7 +33888,7 @@ def zpbcon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPBEQU") @external @@ -33903,7 +33903,7 @@ def zpbequ( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZPBRFS") @external @@ -33926,7 +33926,7 @@ def zpbrfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZPBSTF") @external @@ -33938,7 +33938,7 @@ def zpbstf( AB: Complex128[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("ZPBSV") @external @@ -33953,7 +33953,7 @@ def zpbsv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPBSVX") @external @@ -33980,7 +33980,7 @@ def zpbsvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDAFB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPBTF2") @external @@ -33992,7 +33992,7 @@ def zpbtf2( AB: Complex128[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("ZPBTRF") @external @@ -34004,7 +34004,7 @@ def zpbtrf( AB: Complex128[LDAB, Flat], LDAB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["INFO", Int32]]: ... @bind("ZPBTRS") @external @@ -34019,7 +34019,7 @@ def zpbtrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPFTRF") @external @@ -34030,7 +34030,7 @@ def zpftrf( N: Int32, A: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZPFTRI") @external @@ -34041,7 +34041,7 @@ def zpftri( N: Int32, A: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZPFTRS") @external @@ -34055,7 +34055,7 @@ def zpftrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPOCON") @external @@ -34070,7 +34070,7 @@ def zpocon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPOEQU") @external @@ -34083,7 +34083,7 @@ def zpoequ( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZPOEQUB") @external @@ -34096,7 +34096,7 @@ def zpoequb( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZPORFS") @external @@ -34118,7 +34118,7 @@ def zporfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZPORFSX") @external @@ -34147,7 +34147,7 @@ def zporfsx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZPOSV") @external @@ -34161,7 +34161,7 @@ def zposv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPOSVX") @external @@ -34187,7 +34187,7 @@ def zposvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPOSVXX") @external @@ -34218,7 +34218,7 @@ def zposvxx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZPOTF2") @external @@ -34229,7 +34229,7 @@ def zpotf2( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZPOTRF") @external @@ -34240,7 +34240,7 @@ def zpotrf( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZPOTRF2") @external @@ -34251,7 +34251,7 @@ def zpotrf2( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZPOTRI") @external @@ -34262,7 +34262,7 @@ def zpotri( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZPOTRS") @external @@ -34276,7 +34276,7 @@ def zpotrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPPCON") @external @@ -34290,7 +34290,7 @@ def zppcon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPPEQU") @external @@ -34303,7 +34303,7 @@ def zppequ( SCOND: Float64, AMAX: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZPPRFS") @external @@ -34323,7 +34323,7 @@ def zpprfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZPPSV") @external @@ -34336,7 +34336,7 @@ def zppsv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPPSVX") @external @@ -34360,7 +34360,7 @@ def zppsvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPPTRF") @external @@ -34370,7 +34370,7 @@ def zpptrf( N: Int32, AP: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZPPTRI") @external @@ -34380,7 +34380,7 @@ def zpptri( N: Int32, AP: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZPPTRS") @external @@ -34393,7 +34393,7 @@ def zpptrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPSTF2") @external @@ -34408,7 +34408,7 @@ def zpstf2( TOL: Float64, WORK: Float64[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float64], Returns["INFO", Int32]]: ... @bind("ZPSTRF") @external @@ -34423,7 +34423,7 @@ def zpstrf( TOL: Float64, WORK: Float64[2 * N], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RANK", Int32], Returns["TOL", Float64], Returns["INFO", Int32]]: ... @bind("ZPTCON") @external @@ -34436,7 +34436,7 @@ def zptcon( RCOND: Float64, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPTEQR") @external @@ -34450,7 +34450,7 @@ def zpteqr( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZPTRFS") @external @@ -34472,7 +34472,7 @@ def zptrfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZPTSV") @external @@ -34485,7 +34485,7 @@ def zptsv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPTSVX") @external @@ -34508,7 +34508,7 @@ def zptsvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZPTTRF") @external @@ -34518,7 +34518,7 @@ def zpttrf( D: Float64[Flat], E: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZPTTRS") @external @@ -34532,7 +34532,7 @@ def zpttrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZPTTS2") @external @@ -34545,7 +34545,7 @@ def zptts2( E: Complex128[Flat], B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["IUPLO", Int32], Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32]]: ... @bind("ZROT") @external @@ -34558,7 +34558,7 @@ def zrot( INCY: Int32, C: Float64, S: Complex128 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INCX", Int32], Returns["INCY", Int32], Returns["C", Float64], Returns["S", Complex128]]: ... @bind("ZRSCL") @external @@ -34568,7 +34568,7 @@ def zrscl( A: Complex128, X: Complex128[Flat], INCX: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["A", Complex128], Returns["INCX", Int32]]: ... @bind("ZSPCON") @external @@ -34582,7 +34582,7 @@ def zspcon( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZSPMV") @external @@ -34597,7 +34597,7 @@ def zspmv( BETA: Complex128, Y: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["BETA", Complex128], Returns["INCY", Int32]]: ... @bind("ZSPR") @external @@ -34609,7 +34609,7 @@ def zspr( X: Complex128[Flat], INCX: Int32, AP: Complex128[Flat] -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32]]: ... @bind("ZSPRFS") @external @@ -34630,7 +34630,7 @@ def zsprfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZSPSV") @external @@ -34644,7 +34644,7 @@ def zspsv( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZSPSVX") @external @@ -34667,7 +34667,7 @@ def zspsvx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZSPTRF") @external @@ -34678,7 +34678,7 @@ def zsptrf( AP: Complex128[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZSPTRI") @external @@ -34690,7 +34690,7 @@ def zsptri( IPIV: Int32[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZSPTRS") @external @@ -34704,7 +34704,7 @@ def zsptrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZSTEDC") @external @@ -34723,7 +34723,7 @@ def zstedc( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSTEGR") @external @@ -34749,7 +34749,7 @@ def zstegr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["ABSTOL", Float64], Returns["M", Int32], Returns["LDZ", Int32], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSTEIN") @external @@ -34768,7 +34768,7 @@ def zstein( IWORK: Int32[Flat], IFAIL: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZSTEMR") @external @@ -34795,7 +34795,7 @@ def zstemr( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["VL", Float64], Returns["VU", Float64], Returns["IL", Int32], Returns["IU", Int32], Returns["M", Int32], Returns["LDZ", Int32], Returns["NZC", Int32], Returns["TRYRAC", Bool], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSTEQR") @external @@ -34809,7 +34809,7 @@ def zsteqr( LDZ: Int32, WORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDZ", Int32], Returns["INFO", Int32]]: ... @bind("ZSYCON") @external @@ -34824,7 +34824,7 @@ def zsycon( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZSYCON_3") @external @@ -34840,7 +34840,7 @@ def zsycon_3( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZSYCON_ROOK") @external @@ -34855,7 +34855,7 @@ def zsycon_rook( RCOND: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["ANORM", Float64], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZSYCONV") @external @@ -34869,7 +34869,7 @@ def zsyconv( IPIV: Int32[Flat], E: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYCONVF") @external @@ -34883,7 +34883,7 @@ def zsyconvf( E: Complex128[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYCONVF_ROOK") @external @@ -34897,7 +34897,7 @@ def zsyconvf_rook( E: Complex128[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYEQUB") @external @@ -34912,7 +34912,7 @@ def zsyequb( AMAX: Float64, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["SCOND", Float64], Returns["AMAX", Float64], Returns["INFO", Int32]]: ... @bind("ZSYMV") @external @@ -34928,7 +34928,7 @@ def zsymv( BETA: Complex128, Y: Complex128[Flat], INCY: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDA", Int32], Returns["INCX", Int32], Returns["BETA", Complex128], Returns["INCY", Int32]]: ... @bind("ZSYR") @external @@ -34941,7 +34941,7 @@ def zsyr( INCX: Int32, A: Complex128[LDA, Flat], LDA: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ALPHA", Complex128], Returns["INCX", Int32], Returns["LDA", Int32]]: ... @bind("ZSYRFS") @external @@ -34964,7 +34964,7 @@ def zsyrfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZSYRFSX") @external @@ -34994,7 +34994,7 @@ def zsyrfsx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSV") @external @@ -35011,7 +35011,7 @@ def zsysv( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSV_AA") @external @@ -35028,7 +35028,7 @@ def zsysv_aa( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSV_AA_2STAGE") @external @@ -35048,7 +35048,7 @@ def zsysv_aa_2stage( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSV_RK") @external @@ -35066,7 +35066,7 @@ def zsysv_rk( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSV_ROOK") @external @@ -35083,7 +35083,7 @@ def zsysv_rook( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSVX") @external @@ -35109,7 +35109,7 @@ def zsysvx( LWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSVXX") @external @@ -35141,7 +35141,7 @@ def zsysvxx( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDAF", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["RCOND", Float64], Returns["RPVGRW", Float64], Returns["N_ERR_BNDS", Int32], Returns["NPARAMS", Int32], Returns["INFO", Int32]]: ... @bind("ZSYSWAPR") @external @@ -35153,7 +35153,7 @@ def zsyswapr( LDA: Int32, I1: Int32, I2: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["I1", Int32], Returns["I2", Int32]]: ... @bind("ZSYTF2") @external @@ -35165,7 +35165,7 @@ def zsytf2( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTF2_RK") @external @@ -35178,7 +35178,7 @@ def zsytf2_rk( E: Complex128[Flat], IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTF2_ROOK") @external @@ -35190,7 +35190,7 @@ def zsytf2_rook( LDA: Int32, IPIV: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRF") @external @@ -35204,7 +35204,7 @@ def zsytrf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRF_AA") @external @@ -35218,7 +35218,7 @@ def zsytrf_aa( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRF_AA_2STAGE") @external @@ -35235,7 +35235,7 @@ def zsytrf_aa_2stage( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRF_RK") @external @@ -35250,7 +35250,7 @@ def zsytrf_rk( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRF_ROOK") @external @@ -35264,7 +35264,7 @@ def zsytrf_rook( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRI") @external @@ -35277,7 +35277,7 @@ def zsytri( IPIV: Int32[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRI2") @external @@ -35291,7 +35291,7 @@ def zsytri2( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRI2X") @external @@ -35305,7 +35305,7 @@ def zsytri2x( WORK: Complex128[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRI_3") @external @@ -35320,7 +35320,7 @@ def zsytri_3( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRI_3X") @external @@ -35335,7 +35335,7 @@ def zsytri_3x( WORK: Complex128[N + NB + 1, Flat], NB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["NB", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRI_ROOK") @external @@ -35348,7 +35348,7 @@ def zsytri_rook( IPIV: Int32[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRS") @external @@ -35363,7 +35363,7 @@ def zsytrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRS2") @external @@ -35379,7 +35379,7 @@ def zsytrs2( LDB: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRS_3") @external @@ -35395,7 +35395,7 @@ def zsytrs_3( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRS_AA") @external @@ -35412,7 +35412,7 @@ def zsytrs_aa( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRS_AA_2STAGE") @external @@ -35430,7 +35430,7 @@ def zsytrs_aa_2stage( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LTB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZSYTRS_ROOK") @external @@ -35445,7 +35445,7 @@ def zsytrs_rook( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZTBCON") @external @@ -35462,7 +35462,7 @@ def ztbcon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["LDAB", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZTBRFS") @external @@ -35485,7 +35485,7 @@ def ztbrfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZTBTRS") @external @@ -35502,7 +35502,7 @@ def ztbtrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["KD", Int32], Returns["NRHS", Int32], Returns["LDAB", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZTFSM") @external @@ -35519,7 +35519,7 @@ def ztfsm( A: Complex128[Flat], B: Complex128[LDB, Flat], LDB: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ALPHA", Complex128], Returns["LDB", Int32]]: ... @bind("ZTFTRI") @external @@ -35531,7 +35531,7 @@ def ztftri( N: Int32, A: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZTFTTP") @external @@ -35543,7 +35543,7 @@ def ztfttp( ARF: Complex128[Flat], AP: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZTFTTR") @external @@ -35556,7 +35556,7 @@ def ztfttr( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZTGEVC") @external @@ -35579,7 +35579,7 @@ def ztgevc( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDS", Int32], Returns["LDP", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("ZTGEX2") @external @@ -35598,7 +35598,7 @@ def ztgex2( LDZ: Int32, J1: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["J1", Int32], Returns["INFO", Int32]]: ... @bind("ZTGEXC") @external @@ -35618,7 +35618,7 @@ def ztgexc( IFST: Int32, ILST: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["INFO", Int32]]: ... @bind("ZTGSEN") @external @@ -35648,7 +35648,7 @@ def ztgsen( IWORK: Int32[Flat], LIWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["WANTQ", Bool], Returns["WANTZ", Bool], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDQ", Int32], Returns["LDZ", Int32], Returns["M", Int32], Returns["PL", Float64], Returns["PR", Float64], Returns["LWORK", Int32], Returns["LIWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZTGSJA") @external @@ -35679,7 +35679,7 @@ def ztgsja( WORK: Complex128[Flat], NCYCLE: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["TOLA", Float64], Returns["TOLB", Float64], Returns["LDU", Int32], Returns["LDV", Int32], Returns["LDQ", Int32], Returns["NCYCLE", Int32], Returns["INFO", Int32]]: ... @bind("ZTGSNA") @external @@ -35705,7 +35705,7 @@ def ztgsna( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZTGSY2") @external @@ -35731,7 +35731,7 @@ def ztgsy2( RDSUM: Float64, RDSCAL: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float64], Returns["RDSUM", Float64], Returns["RDSCAL", Float64], Returns["INFO", Int32]]: ... @bind("ZTGSYL") @external @@ -35759,7 +35759,7 @@ def ztgsyl( LWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["IJOB", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["LDD", Int32], Returns["LDE", Int32], Returns["LDF", Int32], Returns["SCALE", Float64], Returns["DIF", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZTPCON") @external @@ -35774,7 +35774,7 @@ def ztpcon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZTPLQT") @external @@ -35792,7 +35792,7 @@ def ztplqt( LDT: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZTPLQT2") @external @@ -35808,7 +35808,7 @@ def ztplqt2( T: Complex128[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZTPMLQT") @external @@ -35831,7 +35831,7 @@ def ztpmlqt( LDB: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["MB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZTPMQRT") @external @@ -35854,7 +35854,7 @@ def ztpmqrt( LDB: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZTPQRT") @external @@ -35872,7 +35872,7 @@ def ztpqrt( LDT: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZTPQRT2") @external @@ -35888,7 +35888,7 @@ def ztpqrt2( T: Complex128[LDT, Flat], LDT: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZTPRFB") @external @@ -35912,7 +35912,7 @@ def ztprfb( LDB: Int32, WORK: Complex128[LDWORK, Flat], LDWORK: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDV", Int32], Returns["LDT", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDWORK", Int32]]: ... @bind("ZTPRFS") @external @@ -35933,7 +35933,7 @@ def ztprfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZTPTRI") @external @@ -35944,7 +35944,7 @@ def ztptri( N: Int32, AP: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZTPTRS") @external @@ -35959,7 +35959,7 @@ def ztptrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZTPTTF") @external @@ -35971,7 +35971,7 @@ def ztpttf( AP: Complex128[Flat], ARF: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["INFO", Int32]]: ... @bind("ZTPTTR") @external @@ -35983,7 +35983,7 @@ def ztpttr( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZTRCON") @external @@ -35999,7 +35999,7 @@ def ztrcon( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["RCOND", Float64], Returns["INFO", Int32]]: ... @bind("ZTREVC") @external @@ -36020,7 +36020,7 @@ def ztrevc( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["INFO", Int32]]: ... @bind("ZTREVC3") @external @@ -36043,7 +36043,7 @@ def ztrevc3( RWORK: Float64[Flat], LRWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZTREXC") @external @@ -36058,7 +36058,7 @@ def ztrexc( IFST: Int32, ILST: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["IFST", Int32], Returns["ILST", Int32], Returns["INFO", Int32]]: ... @bind("ZTRRFS") @external @@ -36080,7 +36080,7 @@ def ztrrfs( WORK: Complex128[Flat], RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDX", Int32], Returns["INFO", Int32]]: ... @bind("ZTRSEN") @external @@ -36101,7 +36101,7 @@ def ztrsen( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDQ", Int32], Returns["M", Int32], Returns["S", Float64], Returns["SEP", Float64], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZTRSNA") @external @@ -36125,7 +36125,7 @@ def ztrsna( LDWORK: Int32, RWORK: Float64[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDT", Int32], Returns["LDVL", Int32], Returns["LDVR", Int32], Returns["MM", Int32], Returns["M", Int32], Returns["LDWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZTRSYL") @external @@ -36144,7 +36144,7 @@ def ztrsyl( LDC: Int32, SCALE: Float64, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float64], Returns["INFO", Int32]]: ... @bind("ZTRSYL3") @external @@ -36165,7 +36165,7 @@ def ztrsyl3( SWORK: Float64[LDSWORK, Flat], LDSWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["ISGN", Int32], Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["LDC", Int32], Returns["SCALE", Float64], Returns["LDSWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZTRTI2") @external @@ -36177,7 +36177,7 @@ def ztrti2( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZTRTRI") @external @@ -36189,7 +36189,7 @@ def ztrtri( A: Complex128[LDA, Flat], LDA: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZTRTRS") @external @@ -36205,7 +36205,7 @@ def ztrtrs( B: Complex128[LDB, Flat], LDB: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["NRHS", Int32], Returns["LDA", Int32], Returns["LDB", Int32], Returns["INFO", Int32]]: ... @bind("ZTRTTF") @external @@ -36218,7 +36218,7 @@ def ztrttf( LDA: Int32, ARF: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZTRTTP") @external @@ -36230,7 +36230,7 @@ def ztrttp( LDA: Int32, AP: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZTZRZF") @external @@ -36244,7 +36244,7 @@ def ztzrzf( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNBDB") @external @@ -36272,7 +36272,7 @@ def zunbdb( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNBDB1") @external @@ -36293,7 +36293,7 @@ def zunbdb1( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNBDB2") @external @@ -36314,7 +36314,7 @@ def zunbdb2( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNBDB3") @external @@ -36335,7 +36335,7 @@ def zunbdb3( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNBDB4") @external @@ -36357,7 +36357,7 @@ def zunbdb4( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNBDB5") @external @@ -36377,7 +36377,7 @@ def zunbdb5( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNBDB6") @external @@ -36397,7 +36397,7 @@ def zunbdb6( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M1", Int32], Returns["M2", Int32], Returns["N", Int32], Returns["INCX1", Int32], Returns["INCX2", Int32], Returns["LDQ1", Int32], Returns["LDQ2", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNCSD") @external @@ -36435,7 +36435,7 @@ def zuncsd( LRWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX12", Int32], Returns["LDX21", Int32], Returns["LDX22", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LDV2T", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNCSD2BY1") @external @@ -36464,7 +36464,7 @@ def zuncsd2by1( LRWORK: Int32, IWORK: Int32[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["P", Int32], Returns["Q", Int32], Returns["LDX11", Int32], Returns["LDX21", Int32], Returns["LDU1", Int32], Returns["LDU2", Int32], Returns["LDV1T", Int32], Returns["LWORK", Int32], Returns["LRWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNG2L") @external @@ -36478,7 +36478,7 @@ def zung2l( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZUNG2R") @external @@ -36492,7 +36492,7 @@ def zung2r( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGBR") @external @@ -36508,7 +36508,7 @@ def zungbr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGHR") @external @@ -36523,7 +36523,7 @@ def zunghr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGL2") @external @@ -36537,7 +36537,7 @@ def zungl2( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGLQ") @external @@ -36552,7 +36552,7 @@ def zunglq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGQL") @external @@ -36567,7 +36567,7 @@ def zungql( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGQR") @external @@ -36582,7 +36582,7 @@ def zungqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGR2") @external @@ -36596,7 +36596,7 @@ def zungr2( TAU: Complex128[Flat], WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGRQ") @external @@ -36611,7 +36611,7 @@ def zungrq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGTR") @external @@ -36625,7 +36625,7 @@ def zungtr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDA", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGTSQR") @external @@ -36642,7 +36642,7 @@ def zungtsqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNGTSQR_ROW") @external @@ -36659,7 +36659,7 @@ def zungtsqr_row( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["MB", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNHR_COL") @external @@ -36674,7 +36674,7 @@ def zunhr_col( LDT: Int32, D: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["NB", Int32], Returns["LDA", Int32], Returns["LDT", Int32], Returns["INFO", Int32]]: ... @bind("ZUNM22") @external @@ -36693,7 +36693,7 @@ def zunm22( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["N1", Int32], Returns["N2", Int32], Returns["LDQ", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNM2L") @external @@ -36711,7 +36711,7 @@ def zunm2l( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZUNM2R") @external @@ -36729,7 +36729,7 @@ def zunm2r( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMBR") @external @@ -36749,7 +36749,7 @@ def zunmbr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMHR") @external @@ -36769,7 +36769,7 @@ def zunmhr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["ILO", Int32], Returns["IHI", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNML2") @external @@ -36787,7 +36787,7 @@ def zunml2( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMLQ") @external @@ -36806,7 +36806,7 @@ def zunmlq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMQL") @external @@ -36825,7 +36825,7 @@ def zunmql( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMQR") @external @@ -36844,7 +36844,7 @@ def zunmqr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMR2") @external @@ -36862,7 +36862,7 @@ def zunmr2( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMR3") @external @@ -36881,7 +36881,7 @@ def zunmr3( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMRQ") @external @@ -36900,7 +36900,7 @@ def zunmrq( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMRZ") @external @@ -36920,7 +36920,7 @@ def zunmrz( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["K", Int32], Returns["L", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUNMTR") @external @@ -36939,7 +36939,7 @@ def zunmtr( WORK: Complex128[Flat], LWORK: Int32, INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDA", Int32], Returns["LDC", Int32], Returns["LWORK", Int32], Returns["INFO", Int32]]: ... @bind("ZUPGTR") @external @@ -36953,7 +36953,7 @@ def zupgtr( LDQ: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["N", Int32], Returns["LDQ", Int32], Returns["INFO", Int32]]: ... @bind("ZUPMTR") @external @@ -36970,4 +36970,4 @@ def zupmtr( LDC: Int32, WORK: Complex128[Flat], INFO: Int32 -) -> None: ... +) -> tuple[Returns["M", Int32], Returns["N", Int32], Returns["LDC", Int32], Returns["INFO", Int32]]: ... diff --git a/tests/wrapper/fortran/real_libraries/test_real_blas_lapack.py b/tests/wrapper/fortran/real_libraries/test_real_blas_lapack.py index 1de2fb533..9705b2229 100644 --- a/tests/wrapper/fortran/real_libraries/test_real_blas_lapack.py +++ b/tests/wrapper/fortran/real_libraries/test_real_blas_lapack.py @@ -348,18 +348,30 @@ def _import_extension(module_name: str, build_dir: Path, *, lazy: bool = False): def _assert_blas_runtime_smoke(module) -> None: x = np.array([1.0, 2.0, 3.0], dtype=np.float64) y = np.array([10.0, 20.0, 30.0], dtype=np.float64) - module.daxpy(np.int32(3), np.float64(2.0), x, np.int32(1), y, np.int32(1)) + daxpy_scalars = module.daxpy(np.int32(3), np.float64(2.0), x, np.int32(1), y, np.int32(1)) + assert daxpy_scalars == (np.int32(3), np.float64(2.0), np.int32(1), np.int32(1)) np.testing.assert_allclose(y, [12.0, 24.0, 36.0]) - assert module.ddot(np.int32(3), x, np.int32(1), y, np.int32(1)) == np.float64(168.0) - assert module.dasum(np.int32(3), y, np.int32(1)) == np.float64(72.0) - module.dscal(np.int32(3), np.float64(0.5), y, np.int32(1)) + assert module.ddot(np.int32(3), x, np.int32(1), y, np.int32(1)) == ( + np.float64(168.0), + np.int32(3), + np.int32(1), + np.int32(1), + ) + assert module.dasum(np.int32(3), y, np.int32(1)) == ( + np.float64(72.0), + np.int32(3), + np.int32(1), + ) + dscal_scalars = module.dscal(np.int32(3), np.float64(0.5), y, np.int32(1)) + assert dscal_scalars == (np.int32(3), np.float64(0.5), np.int32(1)) np.testing.assert_allclose(y, [6.0, 12.0, 18.0]) def _assert_lapack_runtime_smoke(module) -> None: index = np.zeros(5, dtype=np.int32) values = np.array([1.0, 4.0, 7.0, 2.0, 8.0], dtype=np.float64) - module.dlamrg(np.int32(3), np.int32(2), values, np.int32(1), np.int32(1), index) + scalars = module.dlamrg(np.int32(3), np.int32(2), values, np.int32(1), np.int32(1), index) + assert scalars == (np.int32(3), np.int32(2), np.int32(1), np.int32(1)) np.testing.assert_array_equal(index, [1, 4, 2, 3, 5]) diff --git a/tests/wrapper/fortran/scalars/contracts/fmath/__init__.pyi b/tests/wrapper/fortran/scalars/contracts/fmath/__init__.pyi index f207260ef..200fd18dd 100644 --- a/tests/wrapper/fortran/scalars/contracts/fmath/__init__.pyi +++ b/tests/wrapper/fortran/scalars/contracts/fmath/__init__.pyi @@ -1,60 +1,60 @@ -from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, bind, external, native_call +from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, Returns, bind, external, native_call @bind("SQUARE_R4") @external @native_call([Addr(Arg(0))]) def square_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SQUARE_R8") @external @native_call([Addr(Arg(0))]) def square_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("SQUARE_I4") @external @native_call([Addr(Arg(0))]) def square_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("SQUARE_C4") @external @native_call([Addr(Arg(0))]) def square_c4( Z: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["Z", Complex64]]: ... @bind("SQUARE_C8") @external @native_call([Addr(Arg(0))]) def square_c8( Z: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["Z", Complex128]]: ... @bind("CUBE_R4") @external @native_call([Addr(Arg(0))]) def cube_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("CUBE_R8") @external @native_call([Addr(Arg(0))]) def cube_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("CUBE_I4") @external @native_call([Addr(Arg(0))]) def cube_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("ADD_R4") @external @@ -62,7 +62,7 @@ def cube_i4( def add_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("ADD_R8") @external @@ -70,7 +70,7 @@ def add_r4( def add_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("ADD_I4") @external @@ -78,7 +78,7 @@ def add_r8( def add_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("ADD_C4") @external @@ -86,7 +86,7 @@ def add_i4( def add_c4( X: Complex64, Y: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["X", Complex64], Returns["Y", Complex64]]: ... @bind("ADD_C8") @external @@ -94,7 +94,7 @@ def add_c4( def add_c8( X: Complex128, Y: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["X", Complex128], Returns["Y", Complex128]]: ... @bind("SUB_R4") @external @@ -102,7 +102,7 @@ def add_c8( def sub_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("SUB_R8") @external @@ -110,7 +110,7 @@ def sub_r4( def sub_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("SUB_I4") @external @@ -118,7 +118,7 @@ def sub_r8( def sub_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MUL_R4") @external @@ -126,7 +126,7 @@ def sub_i4( def mul_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MUL_R8") @external @@ -134,7 +134,7 @@ def mul_r4( def mul_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MUL_I4") @external @@ -142,7 +142,7 @@ def mul_r8( def mul_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("DIV_R4") @external @@ -150,7 +150,7 @@ def mul_i4( def div_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("DIV_R8") @external @@ -158,7 +158,7 @@ def div_r4( def div_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("POW_R4") @external @@ -166,7 +166,7 @@ def div_r8( def pow_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("POW_R8") @external @@ -174,133 +174,133 @@ def pow_r4( def pow_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("ABS_R4") @external @native_call([Addr(Arg(0))]) def abs_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ABS_R8") @external @native_call([Addr(Arg(0))]) def abs_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ABS_I4") @external @native_call([Addr(Arg(0))]) def abs_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("NEG_R4") @external @native_call([Addr(Arg(0))]) def neg_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("NEG_R8") @external @native_call([Addr(Arg(0))]) def neg_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("NEG_I4") @external @native_call([Addr(Arg(0))]) def neg_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("SIN_R4") @external @native_call([Addr(Arg(0))]) def sin_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SIN_R8") @external @native_call([Addr(Arg(0))]) def sin_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("COS_R4") @external @native_call([Addr(Arg(0))]) def cos_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("COS_R8") @external @native_call([Addr(Arg(0))]) def cos_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("TAN_R4") @external @native_call([Addr(Arg(0))]) def tan_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("TAN_R8") @external @native_call([Addr(Arg(0))]) def tan_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ASIN_R4") @external @native_call([Addr(Arg(0))]) def asin_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ASIN_R8") @external @native_call([Addr(Arg(0))]) def asin_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ACOS_R4") @external @native_call([Addr(Arg(0))]) def acos_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ACOS_R8") @external @native_call([Addr(Arg(0))]) def acos_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ATAN_R4") @external @native_call([Addr(Arg(0))]) def atan_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ATAN_R8") @external @native_call([Addr(Arg(0))]) def atan_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ATAN2_R4") @external @@ -308,7 +308,7 @@ def atan_r8( def atan2_r4( Y: Float32, X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["Y", Float32], Returns["X", Float32]]: ... @bind("ATAN2_R8") @external @@ -316,63 +316,63 @@ def atan2_r4( def atan2_r8( Y: Float64, X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["Y", Float64], Returns["X", Float64]]: ... @bind("EXP_R4") @external @native_call([Addr(Arg(0))]) def exp_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("EXP_R8") @external @native_call([Addr(Arg(0))]) def exp_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("LOG_R4") @external @native_call([Addr(Arg(0))]) def log_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("LOG_R8") @external @native_call([Addr(Arg(0))]) def log_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("LOG10_R4") @external @native_call([Addr(Arg(0))]) def log10_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("LOG10_R8") @external @native_call([Addr(Arg(0))]) def log10_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("SQRT_R4") @external @native_call([Addr(Arg(0))]) def sqrt_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SQRT_R8") @external @native_call([Addr(Arg(0))]) def sqrt_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("HYPOT_R4") @external @@ -380,7 +380,7 @@ def sqrt_r8( def hypot_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("HYPOT_R8") @external @@ -388,7 +388,7 @@ def hypot_r4( def hypot_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MIN_R4") @external @@ -396,7 +396,7 @@ def hypot_r8( def min_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MIN_R8") @external @@ -404,7 +404,7 @@ def min_r4( def min_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MIN_I4") @external @@ -412,7 +412,7 @@ def min_r8( def min_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MAX_R4") @external @@ -420,7 +420,7 @@ def min_i4( def max_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MAX_R8") @external @@ -428,7 +428,7 @@ def max_r4( def max_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MAX_I4") @external @@ -436,7 +436,7 @@ def max_r8( def max_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("SIGN_R4") @external @@ -444,7 +444,7 @@ def max_i4( def sign_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("SIGN_R8") @external @@ -452,7 +452,7 @@ def sign_r4( def sign_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MOD_I4") @external @@ -460,7 +460,7 @@ def sign_r8( def mod_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MOD_R4") @external @@ -468,7 +468,7 @@ def mod_i4( def mod_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MOD_R8") @external @@ -476,35 +476,35 @@ def mod_r4( def mod_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("DEG2RAD_R4") @external @native_call([Addr(Arg(0))]) def deg2rad_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("DEG2RAD_R8") @external @native_call([Addr(Arg(0))]) def deg2rad_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("RAD2DEG_R4") @external @native_call([Addr(Arg(0))]) def rad2deg_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("RAD2DEG_R8") @external @native_call([Addr(Arg(0))]) def rad2deg_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("DIST2_R4") @external @@ -512,7 +512,7 @@ def rad2deg_r8( def dist2_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("DIST2_R8") @external @@ -520,7 +520,7 @@ def dist2_r4( def dist2_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("DOT2_R4") @external @@ -530,7 +530,7 @@ def dot2_r4( X2: Float32, Y1: Float32, Y2: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X1", Float32], Returns["X2", Float32], Returns["Y1", Float32], Returns["Y2", Float32]]: ... @bind("DOT2_R8") @external @@ -540,7 +540,7 @@ def dot2_r8( X2: Float64, Y1: Float64, Y2: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X1", Float64], Returns["X2", Float64], Returns["Y1", Float64], Returns["Y2", Float64]]: ... @bind("DOT3_R4") @external @@ -552,7 +552,7 @@ def dot3_r4( Y1: Float32, Y2: Float32, Y3: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X1", Float32], Returns["X2", Float32], Returns["X3", Float32], Returns["Y1", Float32], Returns["Y2", Float32], Returns["Y3", Float32]]: ... @bind("DOT3_R8") @external @@ -564,81 +564,81 @@ def dot3_r8( Y1: Float64, Y2: Float64, Y3: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X1", Float64], Returns["X2", Float64], Returns["X3", Float64], Returns["Y1", Float64], Returns["Y2", Float64], Returns["Y3", Float64]]: ... @bind("CONJ_C4") @external @native_call([Addr(Arg(0))]) def conj_c4( Z: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["Z", Complex64]]: ... @bind("CONJ_C8") @external @native_call([Addr(Arg(0))]) def conj_c8( Z: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["Z", Complex128]]: ... @bind("REAL_C4") @external @native_call([Addr(Arg(0))]) def real_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("REAL_C8") @external @native_call([Addr(Arg(0))]) def real_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("AIMAG_C4") @external @native_call([Addr(Arg(0))]) def aimag_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("AIMAG_C8") @external @native_call([Addr(Arg(0))]) def aimag_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("ABS_C4") @external @native_call([Addr(Arg(0))]) def abs_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("ABS_C8") @external @native_call([Addr(Arg(0))]) def abs_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("IS_POSITIVE_R4") @external @native_call([Addr(Arg(0))]) def is_positive_r4( X: Float32 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Float32]]: ... @bind("IS_POSITIVE_R8") @external @native_call([Addr(Arg(0))]) def is_positive_r8( X: Float64 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Float64]]: ... @bind("IS_EVEN_I4") @external @native_call([Addr(Arg(0))]) def is_even_i4( X: Int32 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Int32]]: ... diff --git a/tests/wrapper/fortran/scalars/contracts/fmath_arrays/__init__.pyi b/tests/wrapper/fortran/scalars/contracts/fmath_arrays/__init__.pyi index 106677d39..a44ffa3fa 100644 --- a/tests/wrapper/fortran/scalars/contracts/fmath_arrays/__init__.pyi +++ b/tests/wrapper/fortran/scalars/contracts/fmath_arrays/__init__.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, bind, external, native_call +from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, Returns, bind, external, native_call @bind("SQUARE_R4") @external @@ -7,7 +7,7 @@ def square_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_R8") @external @@ -16,7 +16,7 @@ def square_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_I4") @external @@ -25,7 +25,7 @@ def square_i4( N: Int32, X: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_C4") @external @@ -34,7 +34,7 @@ def square_c4( N: Int32, Z: Complex64[N], R: Complex64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_C8") @external @@ -43,7 +43,7 @@ def square_c8( N: Int32, Z: Complex128[N], R: Complex128[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_R4") @external @@ -52,7 +52,7 @@ def cube_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_R8") @external @@ -61,7 +61,7 @@ def cube_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_I4") @external @@ -70,7 +70,7 @@ def cube_i4( N: Int32, X: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_R4") @external @@ -80,7 +80,7 @@ def add_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_R8") @external @@ -90,7 +90,7 @@ def add_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_I4") @external @@ -100,7 +100,7 @@ def add_i4( X: Int32[N], Y: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_C4") @external @@ -110,7 +110,7 @@ def add_c4( X: Complex64[N], Y: Complex64[N], R: Complex64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_C8") @external @@ -120,7 +120,7 @@ def add_c8( X: Complex128[N], Y: Complex128[N], R: Complex128[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_R4") @external @@ -130,7 +130,7 @@ def sub_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_R8") @external @@ -140,7 +140,7 @@ def sub_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_I4") @external @@ -150,7 +150,7 @@ def sub_i4( X: Int32[N], Y: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_R4") @external @@ -160,7 +160,7 @@ def mul_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_R8") @external @@ -170,7 +170,7 @@ def mul_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_I4") @external @@ -180,7 +180,7 @@ def mul_i4( X: Int32[N], Y: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIV_R4") @external @@ -190,7 +190,7 @@ def div_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIV_R8") @external @@ -200,7 +200,7 @@ def div_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("POW_R4") @external @@ -210,7 +210,7 @@ def pow_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("POW_R8") @external @@ -220,7 +220,7 @@ def pow_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_R4") @external @@ -229,7 +229,7 @@ def abs_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_R8") @external @@ -238,7 +238,7 @@ def abs_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_I4") @external @@ -247,7 +247,7 @@ def abs_i4( N: Int32, X: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_R4") @external @@ -256,7 +256,7 @@ def neg_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_R8") @external @@ -265,7 +265,7 @@ def neg_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_I4") @external @@ -274,7 +274,7 @@ def neg_i4( N: Int32, X: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIN_R4") @external @@ -283,7 +283,7 @@ def sin_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIN_R8") @external @@ -292,7 +292,7 @@ def sin_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("COS_R4") @external @@ -301,7 +301,7 @@ def cos_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("COS_R8") @external @@ -310,7 +310,7 @@ def cos_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("TAN_R4") @external @@ -319,7 +319,7 @@ def tan_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("TAN_R8") @external @@ -328,7 +328,7 @@ def tan_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ASIN_R4") @external @@ -337,7 +337,7 @@ def asin_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ASIN_R8") @external @@ -346,7 +346,7 @@ def asin_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ACOS_R4") @external @@ -355,7 +355,7 @@ def acos_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ACOS_R8") @external @@ -364,7 +364,7 @@ def acos_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN_R4") @external @@ -373,7 +373,7 @@ def atan_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN_R8") @external @@ -382,7 +382,7 @@ def atan_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN2_R4") @external @@ -392,7 +392,7 @@ def atan2_r4( Y: Float32[N], X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN2_R8") @external @@ -402,7 +402,7 @@ def atan2_r8( Y: Float64[N], X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("EXP_R4") @external @@ -411,7 +411,7 @@ def exp_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("EXP_R8") @external @@ -420,7 +420,7 @@ def exp_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG_R4") @external @@ -429,7 +429,7 @@ def log_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG_R8") @external @@ -438,7 +438,7 @@ def log_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG10_R4") @external @@ -447,7 +447,7 @@ def log10_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG10_R8") @external @@ -456,7 +456,7 @@ def log10_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQRT_R4") @external @@ -465,7 +465,7 @@ def sqrt_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQRT_R8") @external @@ -474,7 +474,7 @@ def sqrt_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("HYPOT_R4") @external @@ -484,7 +484,7 @@ def hypot_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("HYPOT_R8") @external @@ -494,7 +494,7 @@ def hypot_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_R4") @external @@ -504,7 +504,7 @@ def min_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_R8") @external @@ -514,7 +514,7 @@ def min_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_I4") @external @@ -524,7 +524,7 @@ def min_i4( X: Int32[N], Y: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_R4") @external @@ -534,7 +534,7 @@ def max_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_R8") @external @@ -544,7 +544,7 @@ def max_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_I4") @external @@ -554,7 +554,7 @@ def max_i4( X: Int32[N], Y: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIGN_R4") @external @@ -564,7 +564,7 @@ def sign_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIGN_R8") @external @@ -574,7 +574,7 @@ def sign_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_I4") @external @@ -584,7 +584,7 @@ def mod_i4( X: Int32[N], Y: Int32[N], R: Int32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_R4") @external @@ -594,7 +594,7 @@ def mod_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_R8") @external @@ -604,7 +604,7 @@ def mod_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DEG2RAD_R4") @external @@ -613,7 +613,7 @@ def deg2rad_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DEG2RAD_R8") @external @@ -622,7 +622,7 @@ def deg2rad_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("RAD2DEG_R4") @external @@ -631,7 +631,7 @@ def rad2deg_r4( N: Int32, X: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("RAD2DEG_R8") @external @@ -640,7 +640,7 @@ def rad2deg_r8( N: Int32, X: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIST2_R4") @external @@ -650,7 +650,7 @@ def dist2_r4( X: Float32[N], Y: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIST2_R8") @external @@ -660,7 +660,7 @@ def dist2_r8( X: Float64[N], Y: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT2_R4") @external @@ -672,7 +672,7 @@ def dot2_r4( Y1: Float32[N], Y2: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT2_R8") @external @@ -684,7 +684,7 @@ def dot2_r8( Y1: Float64[N], Y2: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT3_R4") @external @@ -698,7 +698,7 @@ def dot3_r4( Y2: Float32[N], Y3: Float32[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT3_R8") @external @@ -712,7 +712,7 @@ def dot3_r8( Y2: Float64[N], Y3: Float64[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CONJ_C4") @external @@ -721,7 +721,7 @@ def conj_c4( N: Int32, Z: Complex64[N], R: Complex64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CONJ_C8") @external @@ -730,7 +730,7 @@ def conj_c8( N: Int32, Z: Complex128[N], R: Complex128[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("REAL_C4") @external @@ -739,7 +739,7 @@ def real_c4( N: Int32, Z: Complex64[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("REAL_C8") @external @@ -748,7 +748,7 @@ def real_c8( N: Int32, Z: Complex128[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("AIMAG_C4") @external @@ -757,7 +757,7 @@ def aimag_c4( N: Int32, Z: Complex64[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("AIMAG_C8") @external @@ -766,7 +766,7 @@ def aimag_c8( N: Int32, Z: Complex128[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_C4") @external @@ -775,7 +775,7 @@ def abs_c4( N: Int32, Z: Complex64[N], R: Float32[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_C8") @external @@ -784,7 +784,7 @@ def abs_c8( N: Int32, Z: Complex128[N], R: Float64[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_POSITIVE_R4") @external @@ -793,7 +793,7 @@ def is_positive_r4( N: Int32, X: Float32[N], R: Bool[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_POSITIVE_R8") @external @@ -802,7 +802,7 @@ def is_positive_r8( N: Int32, X: Float64[N], R: Bool[N] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_EVEN_I4") @external @@ -811,4 +811,4 @@ def is_even_i4( N: Int32, X: Int32[N], R: Bool[N] -) -> None: ... +) -> Returns["N", Int32]: ... diff --git a/tests/wrapper/fortran/scalars/contracts/fmath_arrays_f90/fmath_arrays_f90.pyi b/tests/wrapper/fortran/scalars/contracts/fmath_arrays_f90/fmath_arrays_f90.pyi index fc75052ca..3de92f155 100644 --- a/tests/wrapper/fortran/scalars/contracts/fmath_arrays_f90/fmath_arrays_f90.pyi +++ b/tests/wrapper/fortran/scalars/contracts/fmath_arrays_f90/fmath_arrays_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, bind, native_call +from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, Returns, bind, native_call @bind("SQUARE_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -6,7 +6,7 @@ def square_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -14,7 +14,7 @@ def square_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -22,7 +22,7 @@ def square_i4_contiguous( N: Int32, X: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_C4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -30,7 +30,7 @@ def square_c4_contiguous( N: Int32, Z: Complex64[:], R: Complex64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_C8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -38,7 +38,7 @@ def square_c8_contiguous( N: Int32, Z: Complex128[:], R: Complex128[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -46,7 +46,7 @@ def cube_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -54,7 +54,7 @@ def cube_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -62,7 +62,7 @@ def cube_i4_contiguous( N: Int32, X: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -71,7 +71,7 @@ def add_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -80,7 +80,7 @@ def add_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -89,7 +89,7 @@ def add_i4_contiguous( X: Int32[:], Y: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_C4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -98,7 +98,7 @@ def add_c4_contiguous( X: Complex64[:], Y: Complex64[:], R: Complex64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_C8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -107,7 +107,7 @@ def add_c8_contiguous( X: Complex128[:], Y: Complex128[:], R: Complex128[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -116,7 +116,7 @@ def sub_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -125,7 +125,7 @@ def sub_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -134,7 +134,7 @@ def sub_i4_contiguous( X: Int32[:], Y: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -143,7 +143,7 @@ def mul_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -152,7 +152,7 @@ def mul_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -161,7 +161,7 @@ def mul_i4_contiguous( X: Int32[:], Y: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIV_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -170,7 +170,7 @@ def div_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIV_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -179,7 +179,7 @@ def div_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("POW_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -188,7 +188,7 @@ def pow_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("POW_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -197,7 +197,7 @@ def pow_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -205,7 +205,7 @@ def abs_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -213,7 +213,7 @@ def abs_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -221,7 +221,7 @@ def abs_i4_contiguous( N: Int32, X: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -229,7 +229,7 @@ def neg_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -237,7 +237,7 @@ def neg_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -245,7 +245,7 @@ def neg_i4_contiguous( N: Int32, X: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIN_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -253,7 +253,7 @@ def sin_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIN_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -261,7 +261,7 @@ def sin_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("COS_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -269,7 +269,7 @@ def cos_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("COS_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -277,7 +277,7 @@ def cos_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("TAN_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -285,7 +285,7 @@ def tan_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("TAN_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -293,7 +293,7 @@ def tan_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ASIN_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -301,7 +301,7 @@ def asin_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ASIN_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -309,7 +309,7 @@ def asin_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ACOS_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -317,7 +317,7 @@ def acos_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ACOS_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -325,7 +325,7 @@ def acos_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -333,7 +333,7 @@ def atan_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -341,7 +341,7 @@ def atan_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN2_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -350,7 +350,7 @@ def atan2_r4_contiguous( Y: Float32[:], X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN2_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -359,7 +359,7 @@ def atan2_r8_contiguous( Y: Float64[:], X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("EXP_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -367,7 +367,7 @@ def exp_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("EXP_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -375,7 +375,7 @@ def exp_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -383,7 +383,7 @@ def log_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -391,7 +391,7 @@ def log_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG10_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -399,7 +399,7 @@ def log10_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG10_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -407,7 +407,7 @@ def log10_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQRT_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -415,7 +415,7 @@ def sqrt_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQRT_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -423,7 +423,7 @@ def sqrt_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("HYPOT_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -432,7 +432,7 @@ def hypot_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("HYPOT_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -441,7 +441,7 @@ def hypot_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -450,7 +450,7 @@ def min_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -459,7 +459,7 @@ def min_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -468,7 +468,7 @@ def min_i4_contiguous( X: Int32[:], Y: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -477,7 +477,7 @@ def max_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -486,7 +486,7 @@ def max_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -495,7 +495,7 @@ def max_i4_contiguous( X: Int32[:], Y: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIGN_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -504,7 +504,7 @@ def sign_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIGN_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -513,7 +513,7 @@ def sign_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -522,7 +522,7 @@ def mod_i4_contiguous( X: Int32[:], Y: Int32[:], R: Int32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -531,7 +531,7 @@ def mod_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -540,7 +540,7 @@ def mod_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DEG2RAD_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -548,7 +548,7 @@ def deg2rad_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DEG2RAD_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -556,7 +556,7 @@ def deg2rad_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("RAD2DEG_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -564,7 +564,7 @@ def rad2deg_r4_contiguous( N: Int32, X: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("RAD2DEG_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -572,7 +572,7 @@ def rad2deg_r8_contiguous( N: Int32, X: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIST2_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -581,7 +581,7 @@ def dist2_r4_contiguous( X: Float32[:], Y: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIST2_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -590,7 +590,7 @@ def dist2_r8_contiguous( X: Float64[:], Y: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT2_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5)]) @@ -601,7 +601,7 @@ def dot2_r4_contiguous( Y1: Float32[:], Y2: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT2_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5)]) @@ -612,7 +612,7 @@ def dot2_r8_contiguous( Y1: Float64[:], Y2: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT3_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Arg(6), Arg(7)]) @@ -625,7 +625,7 @@ def dot3_r4_contiguous( Y2: Float32[:], Y3: Float32[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT3_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Arg(6), Arg(7)]) @@ -638,7 +638,7 @@ def dot3_r8_contiguous( Y2: Float64[:], Y3: Float64[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CONJ_C4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -646,7 +646,7 @@ def conj_c4_contiguous( N: Int32, Z: Complex64[:], R: Complex64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CONJ_C8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -654,7 +654,7 @@ def conj_c8_contiguous( N: Int32, Z: Complex128[:], R: Complex128[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("REAL_C4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -662,7 +662,7 @@ def real_c4_contiguous( N: Int32, Z: Complex64[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("REAL_C8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -670,7 +670,7 @@ def real_c8_contiguous( N: Int32, Z: Complex128[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("AIMAG_C4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -678,7 +678,7 @@ def aimag_c4_contiguous( N: Int32, Z: Complex64[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("AIMAG_C8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -686,7 +686,7 @@ def aimag_c8_contiguous( N: Int32, Z: Complex128[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_C4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -694,7 +694,7 @@ def abs_c4_contiguous( N: Int32, Z: Complex64[:], R: Float32[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_C8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -702,7 +702,7 @@ def abs_c8_contiguous( N: Int32, Z: Complex128[:], R: Float64[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_POSITIVE_R4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -710,7 +710,7 @@ def is_positive_r4_contiguous( N: Int32, X: Float32[:], R: Bool[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_POSITIVE_R8_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -718,7 +718,7 @@ def is_positive_r8_contiguous( N: Int32, X: Float64[:], R: Bool[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_EVEN_I4_CONTIGUOUS") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -726,7 +726,7 @@ def is_even_i4_contiguous( N: Int32, X: Int32[:], R: Bool[:] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -734,7 +734,7 @@ def square_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -742,7 +742,7 @@ def square_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -750,7 +750,7 @@ def square_i4_strided( N: Int32, X: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_C4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -758,7 +758,7 @@ def square_c4_strided( N: Int32, Z: Complex64[::], R: Complex64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQUARE_C8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -766,7 +766,7 @@ def square_c8_strided( N: Int32, Z: Complex128[::], R: Complex128[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -774,7 +774,7 @@ def cube_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -782,7 +782,7 @@ def cube_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CUBE_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -790,7 +790,7 @@ def cube_i4_strided( N: Int32, X: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -799,7 +799,7 @@ def add_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -808,7 +808,7 @@ def add_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -817,7 +817,7 @@ def add_i4_strided( X: Int32[::], Y: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_C4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -826,7 +826,7 @@ def add_c4_strided( X: Complex64[::], Y: Complex64[::], R: Complex64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ADD_C8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -835,7 +835,7 @@ def add_c8_strided( X: Complex128[::], Y: Complex128[::], R: Complex128[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -844,7 +844,7 @@ def sub_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -853,7 +853,7 @@ def sub_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SUB_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -862,7 +862,7 @@ def sub_i4_strided( X: Int32[::], Y: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -871,7 +871,7 @@ def mul_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -880,7 +880,7 @@ def mul_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MUL_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -889,7 +889,7 @@ def mul_i4_strided( X: Int32[::], Y: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIV_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -898,7 +898,7 @@ def div_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIV_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -907,7 +907,7 @@ def div_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("POW_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -916,7 +916,7 @@ def pow_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("POW_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -925,7 +925,7 @@ def pow_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -933,7 +933,7 @@ def abs_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -941,7 +941,7 @@ def abs_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -949,7 +949,7 @@ def abs_i4_strided( N: Int32, X: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -957,7 +957,7 @@ def neg_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -965,7 +965,7 @@ def neg_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("NEG_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -973,7 +973,7 @@ def neg_i4_strided( N: Int32, X: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIN_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -981,7 +981,7 @@ def sin_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIN_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -989,7 +989,7 @@ def sin_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("COS_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -997,7 +997,7 @@ def cos_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("COS_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1005,7 +1005,7 @@ def cos_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("TAN_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1013,7 +1013,7 @@ def tan_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("TAN_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1021,7 +1021,7 @@ def tan_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ASIN_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1029,7 +1029,7 @@ def asin_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ASIN_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1037,7 +1037,7 @@ def asin_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ACOS_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1045,7 +1045,7 @@ def acos_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ACOS_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1053,7 +1053,7 @@ def acos_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1061,7 +1061,7 @@ def atan_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1069,7 +1069,7 @@ def atan_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN2_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1078,7 +1078,7 @@ def atan2_r4_strided( Y: Float32[::], X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ATAN2_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1087,7 +1087,7 @@ def atan2_r8_strided( Y: Float64[::], X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("EXP_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1095,7 +1095,7 @@ def exp_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("EXP_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1103,7 +1103,7 @@ def exp_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1111,7 +1111,7 @@ def log_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1119,7 +1119,7 @@ def log_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG10_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1127,7 +1127,7 @@ def log10_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("LOG10_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1135,7 +1135,7 @@ def log10_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQRT_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1143,7 +1143,7 @@ def sqrt_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SQRT_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1151,7 +1151,7 @@ def sqrt_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("HYPOT_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1160,7 +1160,7 @@ def hypot_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("HYPOT_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1169,7 +1169,7 @@ def hypot_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1178,7 +1178,7 @@ def min_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1187,7 +1187,7 @@ def min_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MIN_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1196,7 +1196,7 @@ def min_i4_strided( X: Int32[::], Y: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1205,7 +1205,7 @@ def max_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1214,7 +1214,7 @@ def max_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MAX_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1223,7 +1223,7 @@ def max_i4_strided( X: Int32[::], Y: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIGN_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1232,7 +1232,7 @@ def sign_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("SIGN_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1241,7 +1241,7 @@ def sign_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1250,7 +1250,7 @@ def mod_i4_strided( X: Int32[::], Y: Int32[::], R: Int32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1259,7 +1259,7 @@ def mod_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("MOD_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1268,7 +1268,7 @@ def mod_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DEG2RAD_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1276,7 +1276,7 @@ def deg2rad_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DEG2RAD_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1284,7 +1284,7 @@ def deg2rad_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("RAD2DEG_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1292,7 +1292,7 @@ def rad2deg_r4_strided( N: Int32, X: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("RAD2DEG_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1300,7 +1300,7 @@ def rad2deg_r8_strided( N: Int32, X: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIST2_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1309,7 +1309,7 @@ def dist2_r4_strided( X: Float32[::], Y: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DIST2_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3)]) @@ -1318,7 +1318,7 @@ def dist2_r8_strided( X: Float64[::], Y: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT2_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5)]) @@ -1329,7 +1329,7 @@ def dot2_r4_strided( Y1: Float32[::], Y2: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT2_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5)]) @@ -1340,7 +1340,7 @@ def dot2_r8_strided( Y1: Float64[::], Y2: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT3_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Arg(6), Arg(7)]) @@ -1353,7 +1353,7 @@ def dot3_r4_strided( Y2: Float32[::], Y3: Float32[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("DOT3_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5), Arg(6), Arg(7)]) @@ -1366,7 +1366,7 @@ def dot3_r8_strided( Y2: Float64[::], Y3: Float64[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CONJ_C4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1374,7 +1374,7 @@ def conj_c4_strided( N: Int32, Z: Complex64[::], R: Complex64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("CONJ_C8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1382,7 +1382,7 @@ def conj_c8_strided( N: Int32, Z: Complex128[::], R: Complex128[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("REAL_C4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1390,7 +1390,7 @@ def real_c4_strided( N: Int32, Z: Complex64[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("REAL_C8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1398,7 +1398,7 @@ def real_c8_strided( N: Int32, Z: Complex128[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("AIMAG_C4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1406,7 +1406,7 @@ def aimag_c4_strided( N: Int32, Z: Complex64[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("AIMAG_C8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1414,7 +1414,7 @@ def aimag_c8_strided( N: Int32, Z: Complex128[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_C4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1422,7 +1422,7 @@ def abs_c4_strided( N: Int32, Z: Complex64[::], R: Float32[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("ABS_C8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1430,7 +1430,7 @@ def abs_c8_strided( N: Int32, Z: Complex128[::], R: Float64[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_POSITIVE_R4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1438,7 +1438,7 @@ def is_positive_r4_strided( N: Int32, X: Float32[::], R: Bool[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_POSITIVE_R8_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1446,7 +1446,7 @@ def is_positive_r8_strided( N: Int32, X: Float64[::], R: Bool[::] -) -> None: ... +) -> Returns["N", Int32]: ... @bind("IS_EVEN_I4_STRIDED") @native_call([Addr(Arg(0)), Arg(1), Arg(2)]) @@ -1454,4 +1454,4 @@ def is_even_i4_strided( N: Int32, X: Int32[::], R: Bool[::] -) -> None: ... +) -> Returns["N", Int32]: ... diff --git a/tests/wrapper/fortran/scalars/contracts/fmath_f90/fmath_f90.pyi b/tests/wrapper/fortran/scalars/contracts/fmath_f90/fmath_f90.pyi index 6f0265887..86aadfa37 100644 --- a/tests/wrapper/fortran/scalars/contracts/fmath_f90/fmath_f90.pyi +++ b/tests/wrapper/fortran/scalars/contracts/fmath_f90/fmath_f90.pyi @@ -1,456 +1,456 @@ -from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, bind, native_call +from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int32, Returns, bind, native_call @bind("SQUARE_R4") @native_call([Addr(Arg(0))]) def square_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SQUARE_R8") @native_call([Addr(Arg(0))]) def square_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("SQUARE_I4") @native_call([Addr(Arg(0))]) def square_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("SQUARE_C4") @native_call([Addr(Arg(0))]) def square_c4( Z: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["Z", Complex64]]: ... @bind("SQUARE_C8") @native_call([Addr(Arg(0))]) def square_c8( Z: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["Z", Complex128]]: ... @bind("CUBE_R4") @native_call([Addr(Arg(0))]) def cube_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("CUBE_R8") @native_call([Addr(Arg(0))]) def cube_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("CUBE_I4") @native_call([Addr(Arg(0))]) def cube_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("ADD_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def add_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("ADD_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def add_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("ADD_I4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def add_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("ADD_C4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def add_c4( X: Complex64, Y: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["X", Complex64], Returns["Y", Complex64]]: ... @bind("ADD_C8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def add_c8( X: Complex128, Y: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["X", Complex128], Returns["Y", Complex128]]: ... @bind("SUB_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def sub_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("SUB_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def sub_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("SUB_I4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def sub_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MUL_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def mul_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MUL_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def mul_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MUL_I4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def mul_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("DIV_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def div_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("DIV_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def div_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("POW_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def pow_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("POW_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def pow_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("ABS_R4") @native_call([Addr(Arg(0))]) def abs_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ABS_R8") @native_call([Addr(Arg(0))]) def abs_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ABS_I4") @native_call([Addr(Arg(0))]) def abs_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("NEG_R4") @native_call([Addr(Arg(0))]) def neg_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("NEG_R8") @native_call([Addr(Arg(0))]) def neg_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("NEG_I4") @native_call([Addr(Arg(0))]) def neg_i4( X: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32]]: ... @bind("SIN_R4") @native_call([Addr(Arg(0))]) def sin_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SIN_R8") @native_call([Addr(Arg(0))]) def sin_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("COS_R4") @native_call([Addr(Arg(0))]) def cos_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("COS_R8") @native_call([Addr(Arg(0))]) def cos_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("TAN_R4") @native_call([Addr(Arg(0))]) def tan_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("TAN_R8") @native_call([Addr(Arg(0))]) def tan_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ASIN_R4") @native_call([Addr(Arg(0))]) def asin_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ASIN_R8") @native_call([Addr(Arg(0))]) def asin_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ACOS_R4") @native_call([Addr(Arg(0))]) def acos_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ACOS_R8") @native_call([Addr(Arg(0))]) def acos_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ATAN_R4") @native_call([Addr(Arg(0))]) def atan_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("ATAN_R8") @native_call([Addr(Arg(0))]) def atan_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("ATAN2_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def atan2_r4( Y: Float32, X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["Y", Float32], Returns["X", Float32]]: ... @bind("ATAN2_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def atan2_r8( Y: Float64, X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["Y", Float64], Returns["X", Float64]]: ... @bind("EXP_R4") @native_call([Addr(Arg(0))]) def exp_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("EXP_R8") @native_call([Addr(Arg(0))]) def exp_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("LOG_R4") @native_call([Addr(Arg(0))]) def log_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("LOG_R8") @native_call([Addr(Arg(0))]) def log_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("LOG10_R4") @native_call([Addr(Arg(0))]) def log10_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("LOG10_R8") @native_call([Addr(Arg(0))]) def log10_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("SQRT_R4") @native_call([Addr(Arg(0))]) def sqrt_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("SQRT_R8") @native_call([Addr(Arg(0))]) def sqrt_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("HYPOT_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def hypot_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("HYPOT_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def hypot_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MIN_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def min_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MIN_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def min_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MIN_I4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def min_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MAX_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def max_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MAX_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def max_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MAX_I4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def max_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("SIGN_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def sign_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("SIGN_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def sign_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("MOD_I4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def mod_i4( X: Int32, Y: Int32 -) -> Int32: ... +) -> tuple[Int32, Returns["X", Int32], Returns["Y", Int32]]: ... @bind("MOD_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def mod_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("MOD_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def mod_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("DEG2RAD_R4") @native_call([Addr(Arg(0))]) def deg2rad_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("DEG2RAD_R8") @native_call([Addr(Arg(0))]) def deg2rad_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("RAD2DEG_R4") @native_call([Addr(Arg(0))]) def rad2deg_r4( X: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32]]: ... @bind("RAD2DEG_R8") @native_call([Addr(Arg(0))]) def rad2deg_r8( X: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64]]: ... @bind("DIST2_R4") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def dist2_r4( X: Float32, Y: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X", Float32], Returns["Y", Float32]]: ... @bind("DIST2_R8") @native_call([Addr(Arg(0)), Addr(Arg(1))]) def dist2_r8( X: Float64, Y: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X", Float64], Returns["Y", Float64]]: ... @bind("DOT2_R4") @native_call([Addr(Arg(0)), Addr(Arg(1)), Addr(Arg(2)), Addr(Arg(3))]) @@ -459,7 +459,7 @@ def dot2_r4( X2: Float32, Y1: Float32, Y2: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X1", Float32], Returns["X2", Float32], Returns["Y1", Float32], Returns["Y2", Float32]]: ... @bind("DOT2_R8") @native_call([Addr(Arg(0)), Addr(Arg(1)), Addr(Arg(2)), Addr(Arg(3))]) @@ -468,7 +468,7 @@ def dot2_r8( X2: Float64, Y1: Float64, Y2: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X1", Float64], Returns["X2", Float64], Returns["Y1", Float64], Returns["Y2", Float64]]: ... @bind("DOT3_R4") @native_call([Addr(Arg(0)), Addr(Arg(1)), Addr(Arg(2)), Addr(Arg(3)), Addr(Arg(4)), Addr(Arg(5))]) @@ -479,7 +479,7 @@ def dot3_r4( Y1: Float32, Y2: Float32, Y3: Float32 -) -> Float32: ... +) -> tuple[Float32, Returns["X1", Float32], Returns["X2", Float32], Returns["X3", Float32], Returns["Y1", Float32], Returns["Y2", Float32], Returns["Y3", Float32]]: ... @bind("DOT3_R8") @native_call([Addr(Arg(0)), Addr(Arg(1)), Addr(Arg(2)), Addr(Arg(3)), Addr(Arg(4)), Addr(Arg(5))]) @@ -490,70 +490,70 @@ def dot3_r8( Y1: Float64, Y2: Float64, Y3: Float64 -) -> Float64: ... +) -> tuple[Float64, Returns["X1", Float64], Returns["X2", Float64], Returns["X3", Float64], Returns["Y1", Float64], Returns["Y2", Float64], Returns["Y3", Float64]]: ... @bind("CONJ_C4") @native_call([Addr(Arg(0))]) def conj_c4( Z: Complex64 -) -> Complex64: ... +) -> tuple[Complex64, Returns["Z", Complex64]]: ... @bind("CONJ_C8") @native_call([Addr(Arg(0))]) def conj_c8( Z: Complex128 -) -> Complex128: ... +) -> tuple[Complex128, Returns["Z", Complex128]]: ... @bind("REAL_C4") @native_call([Addr(Arg(0))]) def real_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("REAL_C8") @native_call([Addr(Arg(0))]) def real_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("AIMAG_C4") @native_call([Addr(Arg(0))]) def aimag_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("AIMAG_C8") @native_call([Addr(Arg(0))]) def aimag_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("ABS_C4") @native_call([Addr(Arg(0))]) def abs_c4( Z: Complex64 -) -> Float32: ... +) -> tuple[Float32, Returns["Z", Complex64]]: ... @bind("ABS_C8") @native_call([Addr(Arg(0))]) def abs_c8( Z: Complex128 -) -> Float64: ... +) -> tuple[Float64, Returns["Z", Complex128]]: ... @bind("IS_POSITIVE_R4") @native_call([Addr(Arg(0))]) def is_positive_r4( X: Float32 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Float32]]: ... @bind("IS_POSITIVE_R8") @native_call([Addr(Arg(0))]) def is_positive_r8( X: Float64 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Float64]]: ... @bind("IS_EVEN_I4") @native_call([Addr(Arg(0))]) def is_even_i4( X: Int32 -) -> Bool: ... +) -> tuple[Bool, Returns["X", Int32]]: ... diff --git a/tests/wrapper/fortran/scalars/contracts/fscalar_kinds_f90/fscalar_kinds_f90.pyi b/tests/wrapper/fortran/scalars/contracts/fscalar_kinds_f90/fscalar_kinds_f90.pyi index f545dfbf6..429849c25 100644 --- a/tests/wrapper/fortran/scalars/contracts/fscalar_kinds_f90/fscalar_kinds_f90.pyi +++ b/tests/wrapper/fortran/scalars/contracts/fscalar_kinds_f90/fscalar_kinds_f90.pyi @@ -1,4 +1,4 @@ -from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int16, Int32, Int64, Int8, Returns, native_call +from x2py.contracts import Addr, Arg, Bool, Complex128, Complex64, Float32, Float64, Int16, Int32, Int64, Int8, native_call @native_call([Addr(Arg(0))]) def id_i8( @@ -25,7 +25,7 @@ def copy_i16( n: Int32, values: Int16[n], out: Int16[n] -) -> Returns["out", Int16[n]]: ... +) -> None: ... @native_call([Addr(Arg(0))]) def not_flag( @@ -37,7 +37,7 @@ def invert_flags( n: Int32, values: Bool[n], out: Bool[n] -) -> Returns["out", Bool[n]]: ... +) -> None: ... @native_call([Addr(Arg(0))]) def id_r32( @@ -54,7 +54,7 @@ def copy_r64( n: Int32, values: Float64[n], out: Float64[n] -) -> Returns["out", Float64[n]]: ... +) -> None: ... @native_call([Addr(Arg(0))]) def conj_c64( @@ -71,7 +71,7 @@ def copy_c128( n: Int32, values: Complex128[n], out: Complex128[n] -) -> Returns["out", Complex128[n]]: ... +) -> None: ... @native_call([Addr(Arg(0))]) def id_c_i32( diff --git a/tests/wrapper/fortran/scalars/test_scalar_boundary_plan.py b/tests/wrapper/fortran/scalars/test_scalar_boundary_plan.py index a0c663f90..799817413 100644 --- a/tests/wrapper/fortran/scalars/test_scalar_boundary_plan.py +++ b/tests/wrapper/fortran/scalars/test_scalar_boundary_plan.py @@ -94,6 +94,16 @@ def _build_scalar_boundary_modules(tmp_path: Path): value = 43 end subroutine make_raw + function direct_storage_result() result(value) + integer(c_int32_t) :: value + value = 44 + end function direct_storage_result + + subroutine hidden_storage_result(value) + integer(c_int32_t), intent(out) :: value + value = 45 + end subroutine hidden_storage_result + subroutine mapped_status(status, base) integer(c_int32_t), intent(out) :: status integer(c_int32_t), intent(in) :: base @@ -125,6 +135,11 @@ def make_storage(value: Int32[()]) -> None: ... def make_raw(value: Addr(Int32)) -> None: ... +def direct_storage_result() -> Int32[()]: ... + +@native_call([Return("value", 0)]) +def hidden_storage_result() -> Int32[()]: ... + @native_call([Return("status", 0), Addr(Arg(0))]) def mapped_status(base: Int32) -> Int32: ... """, @@ -350,6 +365,18 @@ def test_scalar_value_storage_raw_address_out_and_inout_use_canonical_plan(tmp_p assert module.make_raw(output_raw.ctypes.data) is None assert output_raw[()] == np.int32(43) + direct_storage = module.direct_storage_result() + assert isinstance(direct_storage, np.ndarray) + assert direct_storage.shape == () + assert direct_storage.dtype == np.dtype(np.int32) + assert direct_storage[()] == np.int32(44) + + hidden_storage = module.hidden_storage_result() + assert isinstance(hidden_storage, np.ndarray) + assert hidden_storage.shape == () + assert hidden_storage.dtype == np.dtype(np.int32) + assert hidden_storage[()] == np.int32(45) + assert module.mapped_status(np.int32(4)) == np.int32(15) with pytest.raises(TypeError): diff --git a/tests/wrapper/fortran/scalars/test_verified_baseline.py b/tests/wrapper/fortran/scalars/test_verified_baseline.py index 74c9078a1..2849fddd7 100644 --- a/tests/wrapper/fortran/scalars/test_verified_baseline.py +++ b/tests/wrapper/fortran/scalars/test_verified_baseline.py @@ -111,7 +111,10 @@ def _scalar_conversion_failure(module) -> tuple[type[BaseException], str]: with pytest.raises(TypeError) as error_info: module.add_r8("not-a-real", np.float64(1.0)) - np.testing.assert_allclose(module.add_r8(np.float64(1.5), np.float64(2.25)), np.float64(3.75)) + np.testing.assert_allclose( + module.add_r8(np.float64(1.5), np.float64(2.25)), + (np.float64(3.75), np.float64(1.5), np.float64(2.25)), + ) return type(error_info.value), str(error_info.value) @@ -195,22 +198,19 @@ def test_required_array_buffers_use_canonical_wrapper_plan(tmp_path: Path): values = np.array([2.0, 3.0, -4.0], dtype=np.float64) output = np.zeros_like(values) - assert module.square_r8_contiguous(np.int32(values.size), values, output) is None + assert module.square_r8_contiguous(np.int32(values.size), values, output) == np.int32(values.size) np.testing.assert_array_equal(output, values**2) handle_output = np.zeros_like(values) - assert ( - module.square_r8_contiguous( - np.int32(values.size), - _native_array_actual(values, pointer=False), - _native_array_actual(handle_output, pointer=True), - ) - is None - ) + assert module.square_r8_contiguous( + np.int32(values.size), + _native_array_actual(values, pointer=False), + _native_array_actual(handle_output, pointer=True), + ) == np.int32(values.size) np.testing.assert_array_equal(handle_output, values**2) empty = np.empty(0, dtype=np.float64) - assert module.square_r8_contiguous(np.int32(0), empty, empty.copy()) is None + assert module.square_r8_contiguous(np.int32(0), empty, empty.copy()) == np.int32(0) valid = np.arange(4, dtype=np.float64) output = np.zeros_like(valid) diff --git a/tests/wrapper/fortran/strings/contracts/fstrings_f90/fstrings_f90.pyi b/tests/wrapper/fortran/strings/contracts/fstrings_f90/fstrings_f90.pyi index 34b6d0138..f96e7e6dc 100644 --- a/tests/wrapper/fortran/strings/contracts/fstrings_f90/fstrings_f90.pyi +++ b/tests/wrapper/fortran/strings/contracts/fstrings_f90/fstrings_f90.pyi @@ -38,7 +38,7 @@ def string_result_padded() -> String[8]: ... def string_result_c_char() -> String[8]: ... -@native_call([Arg(0)], result=Allocatable(Return(0))) +@native_call([Arg(0), Allocatable(Return('value', 0))]) def string_result_deferred( text: String ) -> String | None: ... diff --git a/tests/wrapper/fortran/strings/test_character_arguments.py b/tests/wrapper/fortran/strings/test_character_arguments.py index d26ef5616..92d2cd8c4 100644 --- a/tests/wrapper/fortran/strings/test_character_arguments.py +++ b/tests/wrapper/fortran/strings/test_character_arguments.py @@ -223,14 +223,14 @@ def test_deferred_character_array_handles_use_canonical_plan(tmp_path: Path): names = [character(kind=c_char, len=4) :: "gold", "blue"] end function make_names_function - function maybe_name(flag) result(name) + subroutine maybe_name(flag, name) integer(kind=4), intent(in) :: flag - character(kind=c_char, len=:), allocatable :: name + character(kind=c_char, len=:), allocatable, intent(out) :: name if (flag /= 0) then allocate(character(kind=c_char, len=4) :: name) name = "blue" end if - end function maybe_name + end subroutine maybe_name subroutine replace_names(names) character(kind=c_char, len=:), allocatable, intent(inout) :: names(:) @@ -257,7 +257,7 @@ def make_names() -> Allocatable[String[:][:]]: ... def make_names_function() -> Allocatable[String[:][:]]: ... -@native_call([Arg(0)], result=Allocatable(Return(0))) +@native_call([Arg(0), Allocatable(Return("name", 0))]) def maybe_name(flag: Int32) -> String | None: ... def replace_names( diff --git a/tests/wrapper_codegen/printers/test_calls_and_policy_metadata.py b/tests/wrapper_codegen/printers/test_calls_and_policy_metadata.py index dd003508b..14f1aef3d 100644 --- a/tests/wrapper_codegen/printers/test_calls_and_policy_metadata.py +++ b/tests/wrapper_codegen/printers/test_calls_and_policy_metadata.py @@ -27,6 +27,7 @@ pytest, rendered_source, ) +from x2py.semantics.metadata import MAYBE_UNALLOCATED_METADATA def test_emit_optional_scalar_output_as_visible_scalar_storage(): @@ -81,6 +82,25 @@ def test_emit_scalar_character_inout_as_replacement_return(): assert f') -> Returns["name", {annotation}]: ...' in code +def test_emit_primitive_scalar_inout_as_visible_replacement_return(): + source = """ +module outputs +contains +subroutine scale_in_place(value, factor) + real(8), intent(inout) :: value + real(8), intent(in) :: factor +end subroutine scale_in_place +end module outputs +""" + + code = generate_pyi(source) + + assert "@native_call([Addr(Arg(0)), Addr(Arg(1))])" in code + assert "value: Float64" in code + assert "factor: Float64" in code + assert ') -> Returns["value", Float64]: ...' in code + + def test_emit_exact_output(): source = """ module simple_mod @@ -388,6 +408,15 @@ def test_printer_emits_extended_storage_and_callable_forms(): array=SemanticArrayContract(rank=1, shape=["1"], allocatable=True), ), ) + maybe_unallocated_handle = SemanticType( + "Float64", + rank=1, + metadata={MAYBE_UNALLOCATED_METADATA: True}, + storage=SemanticStorageContract( + kind="array", + array=SemanticArrayContract(rank=1, shape=[":"], allocatable=True), + ), + ) pointer_handle = SemanticType( "Float64", rank=1, @@ -448,6 +477,7 @@ def test_printer_emits_extended_storage_and_callable_forms(): assert printer.emit(inferred_array) == "Float64[:, :]" assert printer.emit(allocatable_handle) == "Allocatable[Annotated[Float64[:, :], ORDER_F]]" assert printer.emit(constrained_allocatable_handle) == "Allocatable[Annotated[Bool[1], Finite]]" + assert printer.emit(maybe_unallocated_handle) == "Annotated[Allocatable[Float64[:]], MaybeUnallocated]" assert printer.emit(pointer_handle) == 'Annotated[Pointer[Float64[:]], PointerAssociation("runtime")]' assert printer.emit(string_pointer_handle) == "Pointer[String[8][:]]" assert printer.emit(annotated_array) == "Annotated[Float64[:, :], ORDER_ANY, Finite, Range(1, 3)]" @@ -533,7 +563,7 @@ def update(scale: Float64 | None = ..., target: Float64 | None = ...) -> None: . assert "Default is None." not in c_wrapper -def test_printer_emits_named_prototype_and_reference_with_value_override(): +def test_printer_emits_named_prototype_with_primitive_reference_and_derived_value(): printer = PyiPrinter() missing_reference = SemanticType( "Float64", @@ -553,6 +583,16 @@ def test_printer_emits_named_prototype_and_reference_with_value_override(): "Int32", storage=SemanticStorageContract(kind="reference", read_only=True, pointer_depth=1), ) + descriptor_reference = SemanticType( + "Float64", + metadata={"fortran_allocatable": True}, + storage=SemanticStorageContract(kind="reference", mutable=True, pointer_depth=1), + ) + polymorphic_reference = SemanticType( + "point_t", + metadata={"fortran_polymorphic": True}, + storage=SemanticStorageContract(kind="reference", mutable=True, pointer_depth=1), + ) output_array = SemanticType( "Float64", rank=1, @@ -594,6 +634,16 @@ def test_printer_emits_named_prototype_and_reference_with_value_override(): input_reference, origin=SemanticOrigin(metadata={"value": False}), ), + SemanticArgument( + "descriptor", + descriptor_reference, + origin=SemanticOrigin(metadata={"value": False}), + ), + SemanticArgument( + "polymorphic", + polymorphic_reference, + origin=SemanticOrigin(metadata={"value": False}), + ), SemanticArgument( "write", output_array, @@ -604,6 +654,11 @@ def test_printer_emits_named_prototype_and_reference_with_value_override(): inout_array, origin=SemanticOrigin(metadata={"value": False}), ), + SemanticArgument( + "derived_value", + SemanticType("point_t"), + origin=SemanticOrigin(metadata={"value": True}), + ), ] prototype = SemanticPrototype( name="update_values", @@ -628,8 +683,14 @@ def test_printer_emits_named_prototype_and_reference_with_value_override(): ) assert printer.emit(callback) == "update_values" - assert "@prototype\ndef update_values(" in printer.emit(prototype) - assert "value: Value(Int32)" in printer.emit(prototype) + emitted = printer.emit(prototype) + assert "@prototype\ndef update_values(" in emitted + assert "value: Int32" in emitted + assert "missing: Addr(Float64)" in emitted + assert "read: Addr(Int32)" in emitted + assert "descriptor: Allocatable[Float64]" in emitted + assert "polymorphic: Annotated[point_t, Polymorphic]" in emitted + assert "derived_value: Value(point_t)" in emitted def test_printer_projection_return_helpers_and_keyword_data_members(): diff --git a/tests/wrapper_codegen/printers/test_classes_and_methods.py b/tests/wrapper_codegen/printers/test_classes_and_methods.py index 5d6f6ae24..7b8cc6776 100644 --- a/tests/wrapper_codegen/printers/test_classes_and_methods.py +++ b/tests/wrapper_codegen/printers/test_classes_and_methods.py @@ -187,6 +187,7 @@ def test_emit_explicit_pass_name_and_nopass_methods(): assert " def shift(\n self,\n dx: Float64,\n dy: Float64" in code assert " owner: Addr(vector)" not in code assert "@native_call([Addr(Arg(0)), Pass(), Addr(Arg(1))])" in code + assert "owner: Annotated[vector, Polymorphic]" in code assert ' @staticmethod\n @bind("make_vector")' in code assert "value: Float64" in code assert "-> vector: ..." in code @@ -200,9 +201,9 @@ def test_emit_and_load_module_and_type_bound_overload_sets(): end interface convert type :: box contains - procedure :: set_integer - procedure :: set_real - generic :: set => set_integer, set_real + procedure, private :: set_integer + procedure, private :: set_real + generic, public :: set => set_integer, set_real end type box contains integer function convert_integer(value) @@ -228,8 +229,8 @@ def test_emit_and_load_module_and_type_bound_overload_sets(): assert "from typing import overload" not in code assert code.count('@overload("convert_integer")\ndef convert(') == 1 assert code.count('@overload("convert_real")\ndef convert(') == 1 - assert code.count(' @overload("set_integer")\n def set(') == 1 - assert code.count(' @overload("set_real")\n def set(') == 1 + assert code.count(' @bind("set")\n @overload("set_integer")\n def set(') == 1 + assert code.count(' @bind("set")\n @overload("set_real")\n def set(') == 1 assert '@overload("convert_integer")\n@native_call' not in code assert ' @overload("set_integer")\n @native_call' not in code @@ -245,6 +246,73 @@ def test_emit_and_load_module_and_type_bound_overload_sets(): "set_integer", "set_real", ] + assert {procedure.native_name for procedure in loaded.classes[0].overload_sets[0].procedures} == {"set"} + + +def test_private_module_generic_specifics_bind_overload_candidates_to_public_generic(): + source = """ +module generic_mod + implicit none + private + public :: convert + interface convert + module procedure convert_integer, convert_real + end interface convert +contains + integer function convert_integer(value) + integer :: value + convert_integer = value + end function convert_integer + real function convert_real(value) + real :: value + convert_real = value + end function convert_real +end module generic_mod +""" + + code = generate_pyi(source) + + assert code.count('@bind("convert")\n@overload("convert_integer")') == 1 + assert code.count('@bind("convert")\n@overload("convert_real")') == 1 + loaded = parse_pyi_text(code, module_name="generic_mod") + assert [procedure.native_name for procedure in loaded.overload_sets[0].procedures] == [ + "convert", + "convert", + ] + assert emit_module(loaded) == code + + +def test_public_type_bound_generic_specifics_do_not_emit_bind(): + source = """ +module generic_mod + type :: box + contains + procedure :: set_integer + procedure :: set_real + generic :: set => set_integer, set_real + end type box +contains + subroutine set_integer(self, value) + class(box) :: self + integer :: value + end subroutine set_integer + subroutine set_real(self, value) + class(box) :: self + real :: value + end subroutine set_real +end module generic_mod +""" + + code = generate_pyi(source) + + assert ' @bind("set")' not in code + assert code.count(' @overload("set_integer")\n def set(') == 1 + assert code.count(' @overload("set_real")\n def set(') == 1 + loaded = parse_pyi_text(code, module_name="generic_mod") + assert [procedure.native_name for procedure in loaded.classes[0].overload_sets[0].procedures] == [ + "set_integer", + "set_real", + ] def test_emit_and_load_allocatable_module_variable_declaration(): @@ -358,10 +426,8 @@ def test_bound_constructor_pyi_generates_single_initializer_without_keyword_defa loaded = parse_pyi_text( """ class state: - @private - def init_state(self, seed: Addr(Int32)) -> None: ... - @bind("init_state") + @native_call([Pass(), Arg(0)]) def __init__(self, seed: Addr(Int32)) -> None: ... id: Int32 diff --git a/tests/wrapper_codegen/printers/test_types_and_declarations.py b/tests/wrapper_codegen/printers/test_types_and_declarations.py index 4061f26d6..6a7e60610 100644 --- a/tests/wrapper_codegen/printers/test_types_and_declarations.py +++ b/tests/wrapper_codegen/printers/test_types_and_declarations.py @@ -154,7 +154,7 @@ def test_emit_matrix_shapes(): assert "x: Float64[::]" in code assert "y: Float64[::]" in code assert "Annotated[Float64[::]" not in code - assert 'Returns["y", Float64[::]]' in code + assert "y: Float64[::]\n) -> None: ..." in code def test_emit_explicit_bound_ranges_as_extents_without_source_dimension_metadata(): @@ -372,7 +372,7 @@ def test_emit_complex_fem_module(): # -------------------------------------------------------- assert "K: Float64[::, ::]" in code - assert 'Returns["K", Float64[::, ::]]' in code + assert "connectivity: Int32[::, ::]\n) -> None: ..." in code assert "coords: Float64[::, ::]" in code diff --git a/tests/wrapper_codegen/test_native_binding_support.py b/tests/wrapper_codegen/test_native_binding_support.py index 7a7fcc314..ba661da2b 100644 --- a/tests/wrapper_codegen/test_native_binding_support.py +++ b/tests/wrapper_codegen/test_native_binding_support.py @@ -11,8 +11,16 @@ def test_native_binding_support_is_header_only_and_exposes_the_small_x2py_api(): header = SUPPORT_HEADER.read_text(encoding="utf-8") assert not SUPPORT_SOURCE.exists() + assert '#define X2PY_NATIVE_ARRAY_HANDLE_CAPSULE_NAME "x2py.native_array_handle.v1"' in header + assert "#define X2PY_NATIVE_ARRAY_HANDLE_ABI_VERSION 1u" in header + assert "typedef struct {" in header + assert "x2py_native_array_release_fn release;" in header expected_api = ( + "x2py_native_array_handle_release", + "x2py_native_array_handle_capsule_destructor", + "x2py_native_array_handle_capsule_new", + "x2py_native_array_handle_from_capsule", "x2py_scalar_matches", "x2py_scalar_unpack", "x2py_scalar_to_python", diff --git a/tests/wrapper_codegen/test_phase0d_plan_core.py b/tests/wrapper_codegen/test_phase0d_plan_core.py index af282dd59..78a88aaec 100644 --- a/tests/wrapper_codegen/test_phase0d_plan_core.py +++ b/tests/wrapper_codegen/test_phase0d_plan_core.py @@ -195,6 +195,28 @@ def right_value(x: Int32) -> Int32: ... assert plan.namespaces[2].functions[0].symbol_name == "right_shared_value" +def test_binding_registers_child_namespaces_as_importable_submodules(): + module = parse_pyi_text( + """ +def left_value(x: Int32) -> Int32: ... +def right_value(x: Int32) -> Int32: ... +""", + module_name="namespaced", + ) + module.functions[0].metadata[PYTHON_EXPORTS_METADATA] = [{"namespace": ("left",), "name": "shared_value"}] + module.functions[1].metadata[PYTHON_EXPORTS_METADATA] = [{"namespace": ("right",), "name": "shared_value"}] + complete_semantic_policies(module) + artifacts = WrapperCodeGenerator().generate(WrapperPlanner().build(module)) + c_source = next(source.text for source in artifacts.sources if source.path.name.endswith(".c")) + + assert "PyModule_Create(&namespaced_left_module)" in c_source + assert "PyModule_Create(&namespaced_right_module)" in c_source + left_registration = 'PyDict_SetItemString(PyImport_GetModuleDict(), "namespaced.left", namespace_left) < 0' + assert left_registration in c_source + assert 'PyDict_SetItemString(PyImport_GetModuleDict(), "namespaced.right", namespace_right) < 0' in c_source + assert c_source.index(left_registration) > c_source.index("PyModule_Create(&namespaced_left_module)") + + def test_post_ir_export_policy_fixes_names_within_each_namespace(): module = parse_pyi_text( """ diff --git a/tests/wrapper_codegen/test_phase10_callbacks.py b/tests/wrapper_codegen/test_phase10_callbacks.py index 4e02f24c4..eaa31c991 100644 --- a/tests/wrapper_codegen/test_phase10_callbacks.py +++ b/tests/wrapper_codegen/test_phase10_callbacks.py @@ -59,7 +59,7 @@ def _sources(plan): return c_source, bridge -def test_callback_policy_completes_reference_default_and_value_override_before_planning(): +def test_callback_policy_completes_value_default_and_explicit_reference_before_planning(): module = _module() policies = { function.name: function.metadata[models.RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA] diff --git a/tests/wrapper_codegen/test_phase2e_scalar_boundaries.py b/tests/wrapper_codegen/test_phase2e_scalar_boundaries.py index 38ea53427..5262736dc 100644 --- a/tests/wrapper_codegen/test_phase2e_scalar_boundaries.py +++ b/tests/wrapper_codegen/test_phase2e_scalar_boundaries.py @@ -5,7 +5,7 @@ import pytest from tests._shared.ownership_policy_support import parse_pyi_text -from x2py.semantics.ownership import NativeBarrierAction, PythonBarrierAction +from x2py.semantics.ownership import CodegenAction, NativeBarrierAction, ObjectKind, PythonBarrierAction from x2py.semantics.policy_completion import complete_semantic_policies from x2py.semantics.wrapper_policy import ArgumentHandoffMode, BridgeDataAction from x2py.wrapper_codegen import WrapperCodeGenerator, WrapperPlanner @@ -16,6 +16,9 @@ def _scalar_boundary_plan(): """ def storage(x: Float64[()]) -> None: ... def raw(x: Addr(Float64)) -> None: ... +def direct_storage_result() -> Float64[()]: ... +@native_call([Return("out", 0)]) +def hidden_storage_result() -> Float64[()]: ... """, module_name="scalar_boundaries", ) @@ -28,10 +31,17 @@ def test_scalar_storage_and_raw_address_plans_keep_explicit_boundary_facts(): functions = {function.binding.python_name: function for function in plan.namespaces[0].functions} storage_function = functions["storage"] raw_function = functions["raw"] + direct_function = functions["direct_storage_result"] + hidden_function = functions["hidden_storage_result"] storage = storage_function.arguments[0] raw = raw_function.arguments[0] + direct_result = direct_function.results[0] + hidden_result = hidden_function.results[0] assert storage.native_call_slot is storage_function.native_call_slots[storage.native_position] + assert storage.object_kind is ObjectKind.NUMPY_ARRAY + assert storage.array.rank == 0 + assert storage.array.category == "scalar_storage" assert storage.binding.python_action is PythonBarrierAction.SCALAR_STORAGE assert storage.binding.writable is True assert storage.bridge.native_action is NativeBarrierAction.PASS_STORAGE_ADDRESS @@ -44,6 +54,18 @@ def test_scalar_storage_and_raw_address_plans_keep_explicit_boundary_facts(): assert raw.bridge.handoff_mode is ArgumentHandoffMode.OPAQUE_ADDRESS assert raw.bridge.data_action is BridgeDataAction.ASSOCIATE_VIEW assert raw.bridge.copy_reason is None + assert direct_result.object_kind is ObjectKind.NUMPY_ARRAY + assert direct_result.array.rank == 0 + assert direct_result.array.category == "scalar_storage" + assert direct_result.binding.codegen_action is CodegenAction.COPY_OUT + assert direct_result.bridge.native_action is NativeBarrierAction.NONE + assert direct_result.bridge.data_action is BridgeDataAction.COPY_REPRESENTATION + assert hidden_result.object_kind is ObjectKind.NUMPY_ARRAY + assert hidden_result.array.rank == 0 + assert hidden_result.array.category == "scalar_storage" + assert hidden_result.binding.codegen_action is CodegenAction.COPY_OUT + assert hidden_result.bridge.native_action is NativeBarrierAction.PASS_STORAGE_ADDRESS + assert hidden_result.bridge.data_action is BridgeDataAction.COPY_REPRESENTATION def test_scalar_storage_and_raw_address_lower_to_direct_named_paths(): @@ -63,6 +85,10 @@ def test_scalar_storage_and_raw_address_lower_to_direct_named_paths(): assert "if (!PyLong_Check(bound_x_obj))" in c_source assert "bound_x = PyLong_AsVoidPtr(bound_x_obj);" in c_source assert "bind_c_raw(bound_x);" in c_source + assert "void * bind_c_direct_storage_result(void);" in c_source + assert "void bind_c_hidden_storage_result(void ** out);" in c_source + assert c_source.count("PyArray_New(&PyArray_Type, 0, NULL, NPY_FLOAT64") == 2 + assert "bind_c_hidden_storage_result(&out);" in c_source assert 'subroutine bind_c_storage(bound_x) bind(c, name="bind_c_storage")' in bridge_source assert 'subroutine bind_c_raw(bound_x) bind(c, name="bind_c_raw")' in bridge_source @@ -70,6 +96,20 @@ def test_scalar_storage_and_raw_address_lower_to_direct_named_paths(): assert bridge_source.count("call c_f_pointer(bound_x, x)") == 2 assert "call native_storage(x)" in bridge_source assert "call native_raw(x)" in bridge_source + assert ( + 'function bind_c_direct_storage_result() result(result) bind(c, name="bind_c_direct_storage_result")' + in bridge_source + ) + assert 'subroutine bind_c_hidden_storage_result(out) bind(c, name="bind_c_hidden_storage_result")' in bridge_source + assert "real(c_double) :: result_value" in bridge_source + assert "real(c_double), pointer :: result_copy" in bridge_source + assert "call c_f_pointer(result, result_copy)" in bridge_source + assert "result_copy = result_value" in bridge_source + assert "real(c_double) :: out_value" in bridge_source + assert "real(c_double), pointer :: out_copy" in bridge_source + assert "call c_f_pointer(out, out_copy)" in bridge_source + assert "out_copy = out_value" in bridge_source + assert "dimension()" not in bridge_source def test_scalar_address_handoff_plan_edits_fail_before_lowering(): @@ -77,7 +117,7 @@ def test_scalar_address_handoff_plan_edits_fail_before_lowering(): storage = plan.namespaces[0].functions[0].arguments[0] storage.bridge.handoff_mode = ArgumentHandoffMode.VALUE - with pytest.raises(ValueError, match="invalid-scalar-address-handoff"): + with pytest.raises(ValueError, match="invalid-scalar-storage-handoff-mode"): WrapperCodeGenerator().generate(plan) diff --git a/tests/wrapper_codegen/test_phase6a_array_buffers.py b/tests/wrapper_codegen/test_phase6a_array_buffers.py index 1b8731a4d..f993836a7 100644 --- a/tests/wrapper_codegen/test_phase6a_array_buffers.py +++ b/tests/wrapper_codegen/test_phase6a_array_buffers.py @@ -58,6 +58,8 @@ def test_required_array_buffer_has_one_printable_editable_handoff_plan(): assert argument.array.shape == (":",) assert argument.array.axes == ("dense",) assert argument.array.contiguous is True + assert argument.array.flatten_python_storage is False + assert argument.array.flat_axis is None assert argument.array.data_role == argument.binding.handoff_role assert argument.array.extent_roles == (f"{argument.owner_path}:extent:0",) assert argument.array.upper_bound_roles == () @@ -72,8 +74,8 @@ def test_required_array_buffer_dispatches_through_named_binding_and_bridge_metho assert "double bind_c_sum_values(void * values, int64_t values_extent_0);" in c_source assert '"_native_array_actual_argument_for_binding_positional"' in c_source assert ( - 'PyObject_CallFunction(bound_values_helper, "OsiOOiiiiiii", bound_values_obj, "float64", 1, ' - "bound_values_shape, bound_values_layout, 1, 1, 1, 0, 0, 0, 1)" + 'PyObject_CallFunction(bound_values_helper, "OsiOOiiiiiiiii", bound_values_obj, "float64", 1, ' + "bound_values_shape, bound_values_layout, 1, 1, 1, 0, 0, 0, 1, 0, -1)" ) in c_source assert "bound_values = PyLong_AsVoidPtr(PyTuple_GetItem(bound_values_packed, 0));" in c_source assert "bound_values_extent_0 = (int64_t)PyLong_AsLongLong(PyTuple_GetItem(bound_values_packed, 1));" in c_source diff --git a/tests/wrapper_codegen/test_phase6b_dense_array_shapes.py b/tests/wrapper_codegen/test_phase6b_dense_array_shapes.py index 286404783..32e5b5571 100644 --- a/tests/wrapper_codegen/test_phase6b_dense_array_shapes.py +++ b/tests/wrapper_codegen/test_phase6b_dense_array_shapes.py @@ -116,26 +116,38 @@ def test_dense_array_plan_records_extent_dependencies_flat_storage_and_order(): assert flat.rank == 1 assert flat.shape == (":",) assert flat.category == "assumed_size" + assert flat.flatten_python_storage is True + assert flat.flat_axis == 0 assert flat_rank2_runtime is not None assert flat_rank2_runtime.rank == 2 assert flat_rank2_runtime.shape == (":", ":") assert flat_rank2_runtime.order == "ORDER_F" assert flat_rank2_runtime.category == "assumed_size" + assert flat_rank2_runtime.flatten_python_storage is True + assert flat_rank2_runtime.flat_axis == 1 assert flat_rank2_fixed is not None assert flat_rank2_fixed.rank == 2 assert flat_rank2_fixed.shape == ("3", ":") assert flat_rank2_fixed.order == "ORDER_F" + assert flat_rank2_fixed.flatten_python_storage is True + assert flat_rank2_fixed.flat_axis == 1 assert c_flat_rank2_runtime is not None assert c_flat_rank2_runtime.rank == 2 assert c_flat_rank2_runtime.shape == (":", ":") assert c_flat_rank2_runtime.order == "ORDER_C" assert c_flat_rank2_runtime.category == "assumed_size" + assert c_flat_rank2_runtime.flatten_python_storage is True + assert c_flat_rank2_runtime.flat_axis == 0 assert c_flat_rank2_fixed is not None assert c_flat_rank2_fixed.rank == 2 assert c_flat_rank2_fixed.shape == (":", "3") assert c_flat_rank2_fixed.order == "ORDER_C" + assert c_flat_rank2_fixed.flatten_python_storage is True + assert c_flat_rank2_fixed.flat_axis == 0 assert bounded_flat is not None assert bounded_flat.shape == ("ldb", ":") + assert bounded_flat.flatten_python_storage is True + assert bounded_flat.flat_axis == 1 assert bounded_flat.extent_reference_roles == (("dense_array_shapes.bounded_flat.ldb:value",), ()) @@ -149,6 +161,16 @@ def test_dense_array_lowering_uses_planned_shape_checks_and_bridge_orientation() assert 'bound_values_layout = PyUnicode_FromString("F")' in c_source assert 'bound_values_layout = PyUnicode_FromString("C")' in c_source assert '"_native_array_actual_argument_for_binding_positional"' in c_source + assert "bound_values_shape = PyTuple_New(1)" in c_source + assert "PyTuple_SET_ITEM(bound_values_shape, 0, Py_None)" in c_source + assert ( + 'PyObject_CallFunction(bound_values_helper, "OsiOOiiiiiiiii", bound_values_obj, "float64", 1, ' + "bound_values_shape, bound_values_layout, 1, 1, 1, 0, 0, 0, 1, 1, 0)" + ) in c_source + assert ( + 'PyObject_CallFunction(bound_values_helper, "OsiOOiiiiiiiii", bound_values_obj, "float64", 2, ' + "bound_values_shape, bound_values_layout, 1, 1, 1, 0, 0, 0, 1, 1, 1)" + ) in c_source assert "call c_f_pointer(bound_values, values, [values_extent_0, values_extent_1])" in bridge_source assert "call c_f_pointer(bound_values, values, [values_extent_1, values_extent_0])" in bridge_source assert "subroutine bind_c_flat(n, bound_values, values_extent_0)" in bridge_source diff --git a/tests/wrapper_codegen/test_phase7_native_array_handles.py b/tests/wrapper_codegen/test_phase7_native_array_handles.py index 137ee9296..5244f6c24 100644 --- a/tests/wrapper_codegen/test_phase7_native_array_handles.py +++ b/tests/wrapper_codegen/test_phase7_native_array_handles.py @@ -12,8 +12,10 @@ NativeArrayDescriptorInterop, NativeArrayDescriptorKind, NativeArrayDescriptorOwnership, + NativeArrayDefaultConstruction, NativeArrayOperation, NativeArrayOutputProjection, + NativeArrayResultAllocation, NativeArraySourceKind, NativeDescriptorHandoffABI, ) @@ -23,7 +25,21 @@ def _phase7_plan(): module = parse_pyi_text( """ -from x2py.contracts import Addr, Allocatable, Arg, Float64, Int32, Pointer, Return, Returns, String, native_call +from x2py.contracts import ( + Addr, + Allocatable, + Annotated, + Arg, + Float64, + Int32, + MaybeUnallocated, + Pointer, + PointerPolicy, + Return, + Returns, + String, + native_call, +) def normal(values: Float64[:]) -> Float64: ... def alloc(values: Allocatable[Float64[:]]) -> Float64: ... @@ -39,11 +55,66 @@ def replace( @native_call([Addr(Arg(0))]) def make(n: Int32) -> Allocatable[Float64[:]]: ... -@native_call([Arg(0)], result=Allocatable(Return(0))) +@native_call([Addr(Arg(0))]) +def maybe_make(n: Int32) -> Annotated[Allocatable[Float64[:]], MaybeUnallocated]: ... + +@native_call([Addr(Arg(0)), Addr(Arg(1))]) +def make_matrix(n: Int32, m: Int32) -> Allocatable[Float64[:, :]]: ... + +@native_call([Arg(0), Allocatable(Return("value", 0))]) def deferred(text: String) -> String | None: ... def make_names() -> Allocatable[String[:][:]]: ... +def make_pointer(n: Int32) -> Annotated[ + Pointer[Float64[:]], + PointerPolicy( + nullable=True, + transfer="call_local", + target_owner="module", + lifetime="module", + deallocation="never", + shape_source="pointer_bounds", + contiguity="strided", + reassociation="never", + aliasing="borrowed", + mutability="view", + ), +]: ... + +@native_call([Arg(0), Return("selected", 0)]) +def select_pointer(n: Int32) -> Annotated[ + Pointer[Float64[:]], + PointerPolicy( + nullable=True, + transfer="call_local", + target_owner="module", + lifetime="module", + deallocation="never", + shape_source="pointer_bounds", + contiguity="strided", + reassociation="never", + aliasing="borrowed", + mutability="view", + ), +]: ... + +def make_managed_pointer(n: Int32) -> Annotated[ + Pointer[Float64[:]], + PointerPolicy( + nullable=True, + transfer="call_local", + target_owner="wrapper", + lifetime="wrapper", + deallocation="deallocate_resize", + shape_source="pointer_bounds", + contiguity="contiguous", + reassociation="allocate_resize", + aliasing="descriptor", + mutability="mutable", + ), +]: ... + def replace_names( names: Allocatable[String[:][:]], ) -> Returns["names", Allocatable[String[:][:]]]: ... @@ -117,6 +188,10 @@ def test_phase7_keeps_datatype_specific_state_under_argument_and_result_plans(): assert handle is argument.native_call_slot.native_array_handle assert handle.descriptor_kind is descriptor_kind assert handle.handoff.abi is NativeDescriptorHandoffABI.FACT_PACKED_CALL_LOCAL + assert handle.default_handle.construction is NativeArrayDefaultConstruction.FACT_PACKED_EMPTY + assert handle.default_handle.descriptor_ownership is NativeArrayDescriptorOwnership.OWNED + assert handle.default_handle.owner_storage_role is None + assert NativeArrayOperation.DESTROY in handle.default_handle.operations assert len(handle.handoff.extent_roles) == handle.array.rank == 1 assert argument.binding.python_action is PythonBarrierAction.WRAPPER_INSTANCE assert argument.bridge.handoff_mode is ArgumentHandoffMode.NATIVE_DESCRIPTOR @@ -133,15 +208,32 @@ def test_phase7_keeps_datatype_specific_state_under_argument_and_result_plans(): assert replacement.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.DIRECT_STANDARD_DESCRIPTOR assert replacement.native_array_handle.output_projection is NativeArrayOutputProjection.PROJECTED_HANDLE assert replacement.native_array_handle.handoff.extent_roles == () + assert ( + replacement.native_array_handle.default_handle.construction + is NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR + ) + assert replacement.native_array_handle.default_handle.owner_storage_role is not None + assert NativeArrayOperation.DESTROY in replacement.native_array_handle.default_handle.operations assert replacement.binding.codegen_action is CodegenAction.IN_PLACE_ARGUMENT owned = functions["make"].results[0] assert owned.native_array_handle is not None assert owned.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE assert owned.native_array_handle.descriptor_ownership is NativeArrayDescriptorOwnership.OWNED + assert owned.native_array_handle.result_allocation is NativeArrayResultAllocation.ALWAYS_ALLOCATED assert owned.native_array_handle.handoff.owner_storage_role is not None assert NativeArrayOperation.DESTROY in owned.native_array_handle.operations + maybe_owned = functions["maybe_make"].results[0] + assert maybe_owned.native_array_handle is not None + assert maybe_owned.native_array_handle.result_allocation is NativeArrayResultAllocation.MAYBE_UNALLOCATED + + owned_matrix = functions["make_matrix"].results[0] + assert owned_matrix.native_array_handle is not None + assert owned_matrix.native_array_handle.array.rank == 2 + assert owned_matrix.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE + assert owned_matrix.native_array_handle.descriptor_ownership is NativeArrayDescriptorOwnership.OWNED + deferred = functions["deferred"].results[0] assert deferred.native_array_handle is None assert deferred.scalar_descriptor is not None @@ -158,9 +250,39 @@ def test_phase7_keeps_datatype_specific_state_under_argument_and_result_plans(): replacement_names = functions["replace_names"].arguments[0] assert replacement_names.native_array_handle is not None assert replacement_names.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.DIRECT_STANDARD_DESCRIPTOR + assert replacement_names.native_array_handle.default_handle.construction is NativeArrayDefaultConstruction.NONE assert NativeArrayOperation.ELEMENT_LENGTH in replacement_names.native_array_handle.operations assert plan.required_headers == ("ISO_Fortran_binding.h",) + pointer_result = functions["make_pointer"].results[0] + assert pointer_result.native_array_handle is not None + assert pointer_result.native_array_handle.descriptor_kind is NativeArrayDescriptorKind.POINTER + assert pointer_result.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE + assert pointer_result.native_array_handle.descriptor_ownership is NativeArrayDescriptorOwnership.OWNED + assert pointer_result.native_array_handle.result_allocation is NativeArrayResultAllocation.NOT_APPLICABLE + assert pointer_result.native_array_handle.target_lifetime == "module" + assert NativeArrayOperation.ASSOCIATE in pointer_result.native_array_handle.operations + assert NativeArrayOperation.ASSOCIATED in pointer_result.native_array_handle.operations + assert NativeArrayOperation.NULLIFY in pointer_result.native_array_handle.operations + assert NativeArrayOperation.CONTIGUOUS in pointer_result.native_array_handle.operations + assert NativeArrayOperation.DESTROY in pointer_result.native_array_handle.operations + + pointer_output = functions["select_pointer"].results[0] + assert pointer_output.source_kind == "hidden_output" + assert pointer_output.native_array_handle is not None + assert pointer_output.native_array_handle.descriptor_kind is NativeArrayDescriptorKind.POINTER + assert pointer_output.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE + assert pointer_output.native_call_slot is not None + assert pointer_output.native_call_slot.source_kind == "result" + + managed_pointer = functions["make_managed_pointer"].results[0] + assert managed_pointer.native_array_handle is not None + assert { + NativeArrayOperation.ALLOCATE, + NativeArrayOperation.DEALLOCATE, + NativeArrayOperation.RESIZE, + }.issubset(managed_pointer.native_array_handle.operations) + def test_phase7_module_variables_own_borrowed_handle_plans_and_operation_sets(): plan = _module_handle_plan() @@ -182,6 +304,7 @@ def test_phase7_module_variables_own_borrowed_handle_plans_and_operation_sets(): assert NativeArrayOperation.DEALLOCATE in allocatable.operations assert NativeArrayOperation.RESIZE in allocatable.operations assert NativeArrayOperation.NULLIFY in pointer.operations + assert NativeArrayOperation.ASSOCIATE in pointer.operations assert NativeArrayOperation.CONTIGUOUS in pointer.operations assert NativeArrayOperation.DESTROY not in allocatable.operations assert NativeArrayOperation.ELEMENT_LENGTH in names.operations @@ -195,6 +318,18 @@ def test_phase7_module_variables_own_borrowed_handle_plans_and_operation_sets(): assert plan.required_headers == ("ISO_Fortran_binding.h",) +def test_phase7_module_pointer_association_uses_standard_descriptor_assignment(): + artifacts = WrapperCodeGenerator().generate(_module_handle_plan()) + c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") + bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") + + assert "void bind_c_module_pointer_associate(CFI_cdesc_t * source);" in c_source + assert "pointer association requires 6 descriptor facts" in c_source + assert "bind_c_module_pointer_associate(source_descriptor);" in c_source + assert "subroutine bind_c_module_pointer_associate(source)" in bridge_source + assert "native_module_pointer => source" in bridge_source + + def test_phase7_deferred_character_module_handles_use_runtime_element_length(): artifacts = WrapperCodeGenerator().generate(_module_handle_plan()) c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") @@ -232,9 +367,21 @@ def test_phase7_generated_artifacts_follow_one_typed_action_vocabulary(): assert '"_native_array_descriptor_argument_for_binding_positional"' in c_source assert '"_native_array_descriptor_handoff_for_binding_positional"' in c_source assert '"_native_array_handle_from_generated_ops"' in c_source + assert '"_bind_contract_native_array_handle"' in c_source + assert "x2py_native_array_handle_capsule_new(" in c_source + assert "x2py_native_array_handle_from_capsule(" in c_source + assert "X2PY_NATIVE_ARRAY_KIND_ALLOCATABLE" in c_source + assert "X2PY_NATIVE_ARRAY_KIND_POINTER" in c_source + assert "x2py_native_array_handle_release(owner_handle)" in c_source + assert "bound_values_native_handle = x2py_native_array_handle_from_capsule(bound_values_item" in c_source + assert "x2py_bind_default_phase7_handles_replace_values" in c_source + assert "x2py_owned_phase7_handles_replace_values_destroy" in c_source + assert "bound_values_default_binder" in c_source assert "CFI_CDESC_T(1)" in c_source + assert "CFI_CDESC_T(2)" in c_source assert "real(c_double), allocatable, dimension(:) :: values" in bridge_source assert "real(c_double), pointer, dimension(:) :: values" in bridge_source + assert "real(c_double), allocatable, dimension(:, :) :: result_value" in bridge_source optional_start = bridge_source.index("function bind_c_optional(") optional_end = bridge_source.index("end function bind_c_optional", optional_start) optional_bridge = bridge_source[optional_start:optional_end] @@ -248,17 +395,23 @@ def test_phase7_generated_artifacts_follow_one_typed_action_vocabulary(): assert "bound_values_elem_len = sizeof(double);" in optional_binding assert "bound_values_descriptor_rank = 1;" in optional_binding assert "bound_values = (CFI_cdesc_t *)&bound_values_storage;" in optional_binding - assert "x2py_collect_make_allocatable_array_result" in bridge_source - assert "call x2py_collect_make_allocatable_array_result(native_make(n), result_value)" in bridge_source - assert "call move_alloc(result_value, result)" in bridge_source - assert "x2py_collect_deferred_scalar_descriptor_result" in bridge_source - assert "character(kind=c_char, len=:), allocatable :: result_value" in bridge_source + assert "result_value = native_make(n)" in bridge_source + assert "result_value = native_make_matrix(n, m)" in bridge_source + assert "call x2py_collect_allocatable_array_result(native_maybe_make(n), result)" in bridge_source + assert "if (allocated(value)) then" in bridge_source + assert "call move_alloc(value, result)" in bridge_source + assert "bool bind_c_owned_result_73146804_allocated(CFI_cdesc_t * result);" in c_source + assert "return PyBool_FromLong(bind_c_owned_result_73146804_allocated(owner_descriptor));" in c_source + assert "bind_c_owned_result_73146804_deallocate(owner_descriptor);" in c_source + assert "bind_c_owned_result_73146804_destroy(owner_descriptor);" in c_source + assert "bind_c_owned_result_73146804_shape(owner_descriptor, &extent_0);" in c_source + assert "character(kind=c_char, len=:), allocatable :: value_value" in bridge_source assert "result_itemsize" in c_source assert "CFI_type_char" in c_source assert "character(kind=c_char, len=:), allocatable, dimension(:) :: names" in bridge_source -def test_phase7_numeric_owned_result_is_collected_before_persistent_descriptor_move(): +def test_phase7_numeric_owned_result_defaults_to_assignment_then_move_alloc(): bridge_source = next( source.text for source in WrapperCodeGenerator().generate(_phase7_plan()).sources @@ -270,15 +423,93 @@ def test_phase7_numeric_owned_result_is_collected_before_persistent_descriptor_m assert "real(c_double), allocatable, dimension(:), intent(out) :: result" in procedure assert "real(c_double), allocatable, dimension(:) :: result_value" in procedure - assert "call x2py_collect_make_allocatable_array_result(native_make(n), result_value)" in procedure + assert "result_value = native_make(n)" in procedure + assert "if (allocated(result_value)) then" in procedure assert "call move_alloc(result_value, result)" in procedure + assert "if (allocated(result)) then" in procedure + assert "deallocate(result)" in procedure + assert "call x2py_collect_allocatable_array_result(native_make(n), result)" not in procedure + assert "result = result_value" not in procedure + assert "function bind_c_owned_result_73146804_allocated(" in bridge_source + assert "real(c_double), allocatable, dimension(:), intent(in) :: result" in bridge_source + assert "state = allocated(result)" in bridge_source + assert "subroutine bind_c_owned_result_73146804_deallocate(" in bridge_source + assert "real(c_double), allocatable, dimension(:), intent(inout) :: result" in bridge_source + assert "subroutine bind_c_owned_result_73146804_destroy(" in bridge_source + + +def test_phase7_pointer_result_uses_owned_pointer_descriptor_without_target_deallocation(): + artifacts = WrapperCodeGenerator().generate(_phase7_plan()) + c_source = next(source.text for source in artifacts.sources if source.path.suffix == ".c") + bridge_source = next(source.text for source in artifacts.sources if source.path.suffix == ".f90") + start = bridge_source.index("subroutine bind_c_make_pointer(") + end = bridge_source.index("end subroutine", start) + procedure = bridge_source[start:end] + operations_end = bridge_source.index("bind_c_select_pointer(", end) + pointer_operations = bridge_source[end:operations_end] + + assert "real(c_double), pointer, dimension(:), intent(out) :: result" in procedure + assert "real(c_double), pointer, dimension(:) :: result_value" in procedure + assert "result_value => native_make_pointer(n)" in procedure + assert "result => result_value" in procedure + assert "move_alloc" not in procedure + assert "CFI_attribute_pointer" in c_source + assert "function bind_c_owned_" in bridge_source + assert "associated(CFI_cdesc_t * result)" in c_source + assert "contiguous(CFI_cdesc_t * result)" in c_source + assert "subroutine bind_c_owned_" in bridge_source + assert "pointer association requires 6 descriptor facts" in c_source + assert "result => source" in pointer_operations + assert "nullify(result)" in pointer_operations + assert "deallocate(result)" not in pointer_operations + assert '"failed to allocate owned native array"' in c_source + assert '"failed to resize owned native array"' in c_source + assert "CFI_allocate(owner_descriptor, lower_bounds, upper_bounds" in c_source + + output_start = bridge_source.index("subroutine bind_c_select_pointer(") + output_end = bridge_source.index("end subroutine", output_start) + output_procedure = bridge_source[output_start:output_end] + assert "real(c_double), pointer, dimension(:), intent(out) :: selected" in output_procedure + assert "real(c_double), pointer, dimension(:) :: selected_value" in output_procedure + assert "call native_select_pointer(n, selected_value)" in output_procedure + assert "selected => selected_value" in output_procedure + + +def test_phase7_maybe_unallocated_owned_result_uses_collector_without_local_assignment(): + bridge_source = next( + source.text + for source in WrapperCodeGenerator().generate(_phase7_plan()).sources + if source.path.suffix == ".f90" + ) + start = bridge_source.index("subroutine bind_c_maybe_make(") + end = bridge_source.index("end subroutine", start) + procedure = bridge_source[start:end] + + assert "real(c_double), allocatable, dimension(:), intent(out) :: result" in procedure + assert "call x2py_collect_allocatable_array_result(native_maybe_make(n), result)" in procedure + assert "real(c_double), allocatable, dimension(:) :: value" in procedure + assert "if (allocated(value)) then" in procedure + assert "call move_alloc(value, result)" in procedure + assert "if (allocated(result)) then" in procedure + assert "deallocate(result)" in procedure + assert "result_value = native_make(n)" not in procedure + assert "call move_alloc(result_value, result)" not in procedure assert "result = result_value" not in procedure + assert "subroutine x2py_collect_allocatable_array_result(" in procedure + + +def test_phase7_maybe_unallocated_is_only_valid_on_direct_allocatable_array_results(): + module = parse_pyi_text( + """ +from x2py.contracts import Allocatable, Annotated, Float64, MaybeUnallocated + +def invalid_argument(values: Annotated[Allocatable[Float64[:]], MaybeUnallocated]) -> Float64: ... +""", + module_name="invalid_maybe_unallocated", + ) - helper_start = bridge_source.index("subroutine x2py_collect_make_allocatable_array_result(") - helper_end = bridge_source.index("end subroutine", helper_start) - helper = bridge_source[helper_start:helper_end] - assert "if (allocated(value)) then" in helper - assert "target = value" in helper + with pytest.raises(ValueError, match="MaybeUnallocated metadata"): + complete_semantic_policies(module) @pytest.mark.parametrize( @@ -287,6 +518,7 @@ def test_phase7_numeric_owned_result_is_collected_before_persistent_descriptor_m ("required_presence", "inconsistent-native-descriptor-presence"), ("projected_facts", "invalid-direct-native-descriptor-roles"), ("owned_storage", "invalid-owned-native-descriptor-roles"), + ("default_storage", "inconsistent-default-handle-owner-storage-role"), ("operation", "incomplete-native-array-operations"), ("header", "inconsistent-required-headers"), ], @@ -300,6 +532,8 @@ def test_phase7_plan_edits_fail_central_validation(edit: str, diagnostic: str): functions["replace"].arguments[0].native_array_handle.handoff.extent_roles = ("edited:extent",) elif edit == "owned_storage": functions["make"].results[0].native_array_handle.handoff.owner_storage_role = None + elif edit == "default_storage": + functions["replace"].arguments[0].native_array_handle.default_handle.owner_storage_role = None elif edit == "operation": functions["pointer"].arguments[0].native_array_handle.operations = () else: diff --git a/tests/wrapper_codegen/test_phase8_scalar_derived_actual_dummy_matrix.py b/tests/wrapper_codegen/test_phase8_scalar_derived_actual_dummy_matrix.py index 52855c9bf..be376d595 100644 --- a/tests/wrapper_codegen/test_phase8_scalar_derived_actual_dummy_matrix.py +++ b/tests/wrapper_codegen/test_phase8_scalar_derived_actual_dummy_matrix.py @@ -207,6 +207,29 @@ def test_pointer_result_uses_a_persistent_holder_instead_of_the_removed_blocker( assert result.derived.target_release is DerivedRelease.NATIVE_OWNER +def test_class_only_derived_methods_do_not_emit_unreachable_scoped_trampolines(): + module = parse_pyi_text( + """ +from x2py.contracts import Int32 + +class item: + value: Int32 + + def read(self) -> Int32: ... +""", + module_name="phase8_class_only", + ) + complete_semantic_policies(module) + bridge = next( + source.text + for source in WrapperCodeGenerator().generate(WrapperPlanner().build(module)).sources + if source.path.suffix == ".f90" + ) + + assert "c_funloc(x2py_derived_consumer" not in bridge + assert "bound_self_access == 2_c_int" not in bridge + + def test_validation_rejects_a_pointer_holder_without_completed_target_ownership(): plan = WrapperPlanner().build(_module()) function = next(item for item in plan.namespaces[0].functions if item.symbol_name == "make_pointer") @@ -240,6 +263,7 @@ def test_artifacts_emit_shared_holders_typed_origin_operations_and_one_native_ca assert bridge.count("type :: x2py_item_pointer_holder") == 1 assert "abstract interface" in bridge assert "c_f_procpointer" in bridge + assert "c_funloc(x2py_derived_consumer" in bridge assert "move_alloc" in bridge assert bridge.count("native_object_dummy(") == 1 assert "x2py_derived_origin_ops" in c_source diff --git a/tests/wrapper_codegen/test_phase9_class_surfaces.py b/tests/wrapper_codegen/test_phase9_class_surfaces.py index ff2bb7db1..f27429853 100644 --- a/tests/wrapper_codegen/test_phase9_class_surfaces.py +++ b/tests/wrapper_codegen/test_phase9_class_surfaces.py @@ -4,6 +4,7 @@ import pytest +from x2py import pyi_text_to_semantic_module from x2py.pipeline.pyi import pyi_file_to_semantic_module from x2py.semantics.policy_completion import complete_semantic_policies from x2py.semantics.wrapper_policy import ClassInvocationKind, OverloadMatchKind @@ -21,6 +22,12 @@ def _plan(contract: Path): return WrapperPlanner().build(module) +def _plan_text(source: str): + module = pyi_text_to_semantic_module(source, module_name="constructor_api") + complete_semantic_policies(module) + return WrapperPlanner().build(module) + + def _surface(plan, name: str): return next( surface for namespace in plan.namespaces for surface in namespace.classes if name in surface.python_names @@ -53,7 +60,7 @@ def test_inheritance_and_polymorphism_are_completed_before_planning(): ) -def test_class_overloads_project_exact_typed_matches_and_type_bound_calls(): +def test_class_overloads_project_exact_typed_matches_and_bound_generic_calls(): plan = _plan(OVERLOADS) surface = _surface(plan, "accumulator") method = next(overload for overload in surface.overloads if overload.python_name == "add") @@ -72,34 +79,125 @@ def test_class_overloads_project_exact_typed_matches_and_type_bound_calls(): assert all( candidate.class_call.invocation is ClassInvocationKind.TYPE_BOUND for candidate in overload.candidates ) - assert all(candidate.class_call.type_bound_name == "add" for candidate in overload.candidates) + assert {candidate.class_call.type_bound_name for candidate in overload.candidates} == {"add"} + assert {candidate.bridge.native_name for candidate in overload.candidates} == {"add"} + + +def test_same_named_module_procedure_and_method_are_both_public_without_bind(): + plan = _plan_text( + """ +from x2py.contracts import Addr, Arg, Float64, Pass, native_call + +class point: + @native_call([Pass(), Addr(Arg(0))]) + def move_point(self, dx: Float64) -> None: ... +@native_call([Arg(0), Addr(Arg(1))]) +def move_point(item: point, dx: Float64) -> None: ... +""" + ) + surface = _surface(plan, "point") + method = next(item for item in surface.methods if item.python_name == "move_point") + module_function = next( + function for function in plan.namespaces[0].functions if function.binding.python_name == "move_point" + ) + + assert method.function.class_call is not None + assert method.function.class_call.invocation is ClassInvocationKind.MODULE_PROCEDURE + assert method.function.class_call.passed_object_position == 0 + assert method.function.bridge.native_name == "move_point" + assert module_function.bridge.native_name == "move_point" + assert module_function.binding.public is True + assert ".__method__.move_point." in method.function.arguments[0].owner_path + assert ".__method__." not in module_function.arguments[0].owner_path + WrapperCodeGenerator().generate(plan) + + +def test_private_module_projection_hides_only_the_module_surface(): + plan = _plan_text( + """ +from x2py.contracts import Addr, Arg, Float64, Pass, native_call, private + +class point: + @native_call([Pass(), Addr(Arg(0))]) + def move_point(self, dx: Float64) -> None: ... + +@private +@native_call([Arg(0), Addr(Arg(1))]) +def move_point(item: point, dx: Float64) -> None: ... +""" + ) + surface = _surface(plan, "point") + method = next(item for item in surface.methods if item.python_name == "move_point") -def test_bound_constructor_links_one_existing_method_function_plan(): + assert method.function.class_call is not None + assert method.function.class_call.invocation is ClassInvocationKind.MODULE_PROCEDURE + assert all(function.binding.python_name != "move_point" for function in plan.namespaces[0].functions) + WrapperCodeGenerator().generate(plan) + + +def test_bound_constructor_uses_its_completed_direct_function_plan(): plan = _plan(BOUND_CONSTRUCTOR) surface = _surface(plan, "vector") target = surface.constructor.target namespace = plan.namespaces[0] assert target is not None - assert target is surface.methods[0].function + assert surface.methods == () + assert surface.constructor.target_owner_path == "fclasses_f90.vector.__init__" + assert target.owner_path.endswith("._x2py_class_vector___init__") + assert target.class_call is not None + assert target.class_call.passed_object_position == 1 + assert target.class_call.invocation is ClassInvocationKind.MODULE_PROCEDURE + assert [argument.binding.python_name for argument in target.arguments] == ["dx", "self", "dy"] assert target.binding.python_name.startswith("_x2py_class_") assert target.binding.public is False + assert target in namespace.functions + module_initializer = next( + function for function in namespace.functions if function.binding.python_name == "shift_vector" + ) + assert module_initializer.binding.public is True + assert module_initializer is not target assert "_x2py_class_" not in namespace.docstring +def test_bound_constructor_pass_disambiguates_same_type_arguments_and_keeps_module_export(): + plan = _plan_text( + """ +from x2py.contracts import Arg, Pass, bind, native_call + +class point: + @bind("initialize_point") + @native_call([Arg(0), Pass(), Arg(1)]) + def __init__(self, left: point, right: point) -> None: ... + +def initialize_point(left: point, owner: point, right: point) -> None: ... +""" + ) + surface = _surface(plan, "point") + target = surface.constructor.target + namespace = plan.namespaces[0] + + assert target is not None + assert target.class_call is not None + assert target.class_call.passed_object_position == 1 + assert [argument.binding.python_name for argument in target.arguments] == ["left", "self", "right"] + module_initializer = next( + function for function in namespace.functions if function.binding.python_name == "initialize_point" + ) + assert module_initializer.binding.public is True + assert module_initializer is not target + WrapperCodeGenerator().generate(plan) + + def test_class_docstrings_describe_only_the_public_surface(): plan = _plan(BOUND_CONSTRUCTOR) surface = _surface(plan, "vector") assert "Constructor\n-----------\nvector(dx, dy) -> vector" in surface.docstring assert "Fields\n------\nx : float64\ny : float64" in surface.docstring - assert "Methods\n-------\nshift(dx, dy) -> None" in surface.docstring assert "vector(dx, dy) -> vector" in surface.constructor.docstring assert "dx : float64" in surface.constructor.docstring - assert "shift(dx, dy) -> None" in surface.methods[0].docstring - assert "Updates the wrapped native instance in place." in surface.methods[0].docstring - assert "owner" not in surface.methods[0].docstring assert "_x2py_class_" not in surface.docstring @@ -128,12 +226,3 @@ def test_invalid_class_graph_and_overload_edits_fail_before_emission(): overload.candidate_matches = (overload.candidate_matches[0], overload.candidate_matches[0]) with pytest.raises(ValueError, match="ambiguous-overload"): WrapperCodeGenerator().generate(overloads) - - -def test_incomplete_type_bound_receiver_fails_before_bridge_generation(): - plan = _plan(OVERLOADS) - overload = next(item for item in _surface(plan, "accumulator").overloads if item.python_name == "add") - overload.candidates[0].class_call.type_bound_name = None - - with pytest.raises(ValueError, match="incomplete-type-bound-call"): - WrapperCodeGenerator().generate(plan) diff --git a/tools/mkdocs_publication.py b/tools/mkdocs_publication.py new file mode 100644 index 000000000..427dc5fb9 --- /dev/null +++ b/tools/mkdocs_publication.py @@ -0,0 +1,224 @@ +"""Fail-closed page publication for the x2py MkDocs website.""" + +from __future__ import annotations + +import os +import posixpath +import re +from pathlib import Path, PurePosixPath +from urllib.parse import quote, unquote, urlsplit + + +_PUBLICATION_KEY = "publication" +_REVIEWED = "reviewed" +_TRUE_VALUES = {"1", "true", "yes", "on"} +_MARKDOWN_SUFFIXES = {".md", ".markdown", ".mdown", ".mkdn", ".mkd"} +_LANE_INDEXES = { + "user": "user/index.md", + "developer": "developer/index.md", + "maintainer": "maintainer/README.md", +} +_MARKDOWN_LINK = re.compile(r"(? str | None: + lines = path.read_text(encoding="utf-8").splitlines() + if not lines or lines[0] != "---": + return None + + try: + end = lines.index("---", 1) + except ValueError: + return None + + for line in lines[1:end]: + name, separator, value = line.partition(":") + if separator and name.strip() == key: + return value.strip() + return None + + +def _publication_states(docs_dir: Path) -> tuple[dict[str, str | None], set[str]]: + states: dict[str, str | None] = {} + known_paths: set[str] = set() + for path in docs_dir.rglob("*"): + if not path.is_file() or path.suffix.lower() not in _MARKDOWN_SUFFIXES: + continue + relative_path = path.relative_to(docs_dir).as_posix() + known_paths.add(relative_path) + if relative_path.startswith("old_docs/"): + continue + states[relative_path] = _front_matter_value(path, _PUBLICATION_KEY) + return states, known_paths + + +def _reviewed_paths(states: dict[str, str | None]) -> set[str]: + if states.get("index.md") != _REVIEWED: + return set() + + reviewed = {"index.md"} + for lane, lane_index in _LANE_INDEXES.items(): + if states.get(lane_index) != _REVIEWED: + continue + reviewed.update( + path + for path, state in states.items() + if (path == lane_index or path.startswith(f"{lane}/")) and state == _REVIEWED + ) + return reviewed + + +def _filter_navigation(value, published_paths: set[str]): + if isinstance(value, str): + if PurePosixPath(value).suffix.lower() not in _MARKDOWN_SUFFIXES: + return value + return value if value in published_paths else None + + if isinstance(value, list): + filtered = [] + for item in value: + kept = _filter_navigation(item, published_paths) + if kept is not None: + filtered.append(kept) + return filtered or None + + if isinstance(value, dict): + filtered = {} + for title, item in value.items(): + kept = _filter_navigation(item, published_paths) + if kept is not None: + filtered[title] = kept + return filtered or None + + return value + + +def _relative_document_target(source_uri: str, raw_target: str) -> str | None: + target = raw_target.strip().split(maxsplit=1)[0] + parsed = urlsplit(target) + if parsed.scheme or parsed.netloc or not parsed.path or parsed.path.startswith("/"): + return None + if PurePosixPath(parsed.path).suffix.lower() not in _MARKDOWN_SUFFIXES: + return None + source_parent = PurePosixPath(source_uri).parent.as_posix() + return posixpath.normpath(posixpath.join(source_parent, unquote(parsed.path))) + + +def _document_route(markdown_uri: str) -> str: + path = PurePosixPath(markdown_uri) + route = path.parent.as_posix() if path.name == "index.md" else path.with_suffix("").as_posix() + return "" if route == "." else route + + +def _unpublished_document_site_target(source_uri: str, raw_target: str, resolved_target: str) -> str: + target_parts = raw_target.strip().split(maxsplit=1) + parsed = urlsplit(target_parts[0]) + source_route = _document_route(source_uri) + target_route = _document_route(resolved_target) + rewritten = posixpath.relpath(target_route or ".", start=source_route or ".") + if rewritten == ".": + rewritten = "" + elif not rewritten.endswith("/"): + rewritten += "/" + if parsed.query: + rewritten += f"?{parsed.query}" + if parsed.fragment: + rewritten += f"#{parsed.fragment}" + if len(target_parts) == 2: + rewritten += f" {target_parts[1]}" + return rewritten + + +def _rewrite_unpublished_document_targets(markdown: str, source_uri: str) -> str: + def replace_link(match: re.Match[str]) -> str: + label, target = match.groups() + resolved = _relative_document_target(source_uri, target) + if resolved in _known_document_paths and resolved not in _published_paths: + return f"[{label}]({_unpublished_document_site_target(source_uri, target, resolved)})" + return match.group(0) + + return _MARKDOWN_LINK.sub(replace_link, markdown) + + +def _repository_target(source_uri: str, raw_target: str) -> str | None: + target_parts = raw_target.strip().split(maxsplit=1) + parsed = urlsplit(target_parts[0]) + if parsed.scheme or parsed.netloc or not parsed.path or parsed.path.startswith("/"): + return None + + source_path = _docs_dir / source_uri + resolved = (source_path.parent / unquote(parsed.path)).resolve() + repository_root = _docs_dir.parent.resolve() + if not resolved.is_relative_to(repository_root) or not resolved.exists(): + return None + if resolved.is_relative_to(_docs_dir.resolve()): + return None + + route = "tree" if resolved.is_dir() else "blob" + relative_path = resolved.relative_to(repository_root).as_posix() + rewritten = f"{_repository_url}/{route}/main/{quote(relative_path)}" + if parsed.query: + rewritten += f"?{parsed.query}" + if parsed.fragment: + rewritten += f"#{parsed.fragment}" + if len(target_parts) == 2: + rewritten += f" {target_parts[1]}" + return rewritten + + +def _rewrite_repository_targets(markdown: str, source_uri: str) -> str: + def replace_link(match: re.Match[str]) -> str: + label, target = match.groups() + rewritten = _repository_target(source_uri, target) + if rewritten is None: + return match.group(0) + return f"[{label}]({rewritten})" + + return _MARKDOWN_LINK.sub(replace_link, markdown) + + +def on_config(config, **_kwargs): + """Load publication state and filter production navigation.""" + global _docs_dir, _include_drafts, _known_document_paths, _published_paths, _repository_url + + _include_drafts = os.getenv("X2PY_DOCS_INCLUDE_DRAFTS", "").strip().lower() in _TRUE_VALUES + _docs_dir = Path(config["docs_dir"]) + _repository_url = str(config["repo_url"]).rstrip("/") + states, _known_document_paths = _publication_states(_docs_dir) + _published_paths = _reviewed_paths(states) + + if not _include_drafts: + config["nav"] = _filter_navigation(config["nav"], _published_paths) or [] + return config + + +def on_files(files, **_kwargs): + """Remove unpublished Markdown files from production output and search.""" + if _include_drafts: + return files + + for file in list(files): + if PurePosixPath(file.src_uri).suffix.lower() in _MARKDOWN_SUFFIXES and file.src_uri not in _published_paths: + files.remove(file) + return files + + +def on_page_markdown(markdown: str, page, **_kwargs) -> str: + """Label local drafts and preserve production links to unpublished pages.""" + source_uri = page.file.src_uri + markdown = _rewrite_repository_targets(markdown, source_uri) + if _include_drafts: + if source_uri not in _published_paths: + warning = ( + '!!! warning "Unpublished documentation draft"\n' + " This page is available only in the local draft preview.\n\n" + ) + return warning + markdown + return markdown + return _rewrite_unpublished_document_targets(markdown, source_uri) diff --git a/x2py/binding_support/x2py_binding.h b/x2py/binding_support/x2py_binding.h index 4a812d486..cfa9a2ac8 100644 --- a/x2py/binding_support/x2py_binding.h +++ b/x2py/binding_support/x2py_binding.h @@ -21,6 +21,177 @@ #endif #include +#define X2PY_NATIVE_ARRAY_HANDLE_ABI_VERSION 1u +#define X2PY_NATIVE_ARRAY_HANDLE_CAPSULE_NAME "x2py.native_array_handle.v1" +#define X2PY_NATIVE_ARRAY_HANDLE_MAGIC UINT64_C(0x583250594e414831) +#define X2PY_NATIVE_ARRAY_KIND_ALLOCATABLE 1u +#define X2PY_NATIVE_ARRAY_KIND_POINTER 2u + +typedef void (*x2py_native_array_release_fn)(void *descriptor); + +/* + * Versioned cross-extension record for one persistent Fortran array + * descriptor. The descriptor representation remains compiler-owned; this + * record only makes its metadata, ownership, and validation ABI common to + * independently generated x2py extensions. + */ +typedef struct { + uint64_t magic; + uint32_t abi_version; + uint32_t struct_size; + uint32_t descriptor_kind; + uint32_t rank; + int32_t cfi_type; + uint32_t reserved; + size_t element_size; + size_t descriptor_size; + void *descriptor; + x2py_native_array_release_fn release; +} x2py_native_array_handle; + +/* Release descriptor payload and storage at most once while retaining the record. */ +static inline void x2py_native_array_handle_release(x2py_native_array_handle *handle) +{ + void *descriptor; + + if (handle == NULL || handle->descriptor == NULL) { + return; + } + descriptor = handle->descriptor; + handle->descriptor = NULL; + if (handle->release != NULL) { + handle->release(descriptor); + } + free(descriptor); +} + +/* Finalize one native handle record owned by a Python capsule. */ +static inline void x2py_native_array_handle_capsule_destructor(PyObject *capsule) +{ + PyObject *error_type = NULL; + PyObject *error_value = NULL; + PyObject *error_traceback = NULL; + x2py_native_array_handle *handle; + + PyErr_Fetch(&error_type, &error_value, &error_traceback); + handle = (x2py_native_array_handle *)PyCapsule_GetPointer( + capsule, X2PY_NATIVE_ARRAY_HANDLE_CAPSULE_NAME); + if (handle == NULL) { + PyErr_Clear(); + } else { + x2py_native_array_handle_release(handle); + handle->magic = 0; + free(handle); + } + PyErr_Restore(error_type, error_value, error_traceback); +} + +/* + * Create a capsule that takes descriptor ownership only on success. The + * caller remains responsible for descriptor cleanup when this function + * returns NULL. + */ +static inline PyObject *x2py_native_array_handle_capsule_new( + uint32_t descriptor_kind, + uint32_t rank, + int cfi_type, + size_t element_size, + size_t descriptor_size, + void *descriptor, + x2py_native_array_release_fn release) +{ + x2py_native_array_handle *handle; + PyObject *capsule; + + if (descriptor_kind != X2PY_NATIVE_ARRAY_KIND_ALLOCATABLE + && descriptor_kind != X2PY_NATIVE_ARRAY_KIND_POINTER) { + PyErr_SetString(PyExc_ValueError, "invalid x2py native array descriptor kind"); + return NULL; + } + if (descriptor == NULL || descriptor_size == 0 || element_size == 0 || release == NULL) { + PyErr_SetString(PyExc_ValueError, "incomplete x2py native array handle storage"); + return NULL; + } + handle = (x2py_native_array_handle *)calloc(1, sizeof(*handle)); + if (handle == NULL) { + PyErr_NoMemory(); + return NULL; + } + handle->magic = X2PY_NATIVE_ARRAY_HANDLE_MAGIC; + handle->abi_version = X2PY_NATIVE_ARRAY_HANDLE_ABI_VERSION; + handle->struct_size = (uint32_t)sizeof(*handle); + handle->descriptor_kind = descriptor_kind; + handle->rank = rank; + handle->cfi_type = (int32_t)cfi_type; + handle->element_size = element_size; + handle->descriptor_size = descriptor_size; + handle->descriptor = descriptor; + handle->release = release; + capsule = PyCapsule_New( + handle, + X2PY_NATIVE_ARRAY_HANDLE_CAPSULE_NAME, + x2py_native_array_handle_capsule_destructor); + if (capsule == NULL) { + handle->descriptor = NULL; + handle->magic = 0; + free(handle); + } + return capsule; +} + +/* Validate and unwrap one cross-extension native array handle capsule. */ +static inline x2py_native_array_handle *x2py_native_array_handle_from_capsule( + PyObject *capsule, + uint32_t expected_kind, + uint32_t expected_rank, + int expected_cfi_type, + size_t expected_element_size, + size_t expected_descriptor_size) +{ + x2py_native_array_handle *handle; + + if (!PyCapsule_IsValid(capsule, X2PY_NATIVE_ARRAY_HANDLE_CAPSULE_NAME)) { + PyErr_SetString(PyExc_TypeError, "incompatible x2py native array handle capsule"); + return NULL; + } + handle = (x2py_native_array_handle *)PyCapsule_GetPointer( + capsule, X2PY_NATIVE_ARRAY_HANDLE_CAPSULE_NAME); + if (handle == NULL) { + return NULL; + } + if (handle->magic != X2PY_NATIVE_ARRAY_HANDLE_MAGIC + || handle->abi_version != X2PY_NATIVE_ARRAY_HANDLE_ABI_VERSION + || handle->struct_size != sizeof(*handle)) { + PyErr_SetString(PyExc_TypeError, "incompatible x2py native array handle ABI"); + return NULL; + } + if (handle->descriptor_kind != expected_kind) { + PyErr_SetString(PyExc_TypeError, "x2py native array descriptor kind does not match"); + return NULL; + } + if (handle->rank != expected_rank) { + PyErr_SetString(PyExc_ValueError, "x2py native array descriptor rank does not match"); + return NULL; + } + if (handle->cfi_type != expected_cfi_type) { + PyErr_SetString(PyExc_TypeError, "x2py native array element type does not match"); + return NULL; + } + if (expected_element_size != 0 && handle->element_size != expected_element_size) { + PyErr_SetString(PyExc_TypeError, "x2py native array element size does not match"); + return NULL; + } + if (handle->descriptor_size != expected_descriptor_size) { + PyErr_SetString(PyExc_TypeError, "incompatible Fortran descriptor storage size"); + return NULL; + } + if (handle->descriptor == NULL) { + PyErr_SetString(PyExc_ReferenceError, "x2py native array handle is closed"); + return NULL; + } + return handle; +} + /* Return whether value is exactly the NumPy scalar required by numpy_type. */ static inline bool x2py_scalar_matches(PyObject *value, int numpy_type) { diff --git a/x2py/cli.py b/x2py/cli.py index 1e774de90..e77266235 100644 --- a/x2py/cli.py +++ b/x2py/cli.py @@ -101,9 +101,9 @@ " Replay a build manifest:\n" " python3 -m x2py --build-manifest build/x2py-build.json\n\n" ' See README.md "Quick Start" for the scale.f90 source and expected output.\n' - " See docs/user/guide/fortran-wrapper.md for native flags and libraries.\n\n" + " See docs/user/reference/cli-commands.md for all build options.\n\n" " Manifest overrides: --out, --compiler, -I/--include-dir, --json, --verbose,\n" - " --no-color, and --debug/--debug-traceback." + " --no-color, and --debug." ) _PARSE_HELP_EPILOG = ( f"{_HELP_DIVIDER}\n\n" @@ -1865,8 +1865,6 @@ def _add_diagnostic_controls(group: argparse._ArgumentGroup, *, allow_verbose: b group.add_argument("--no-color", action="store_true", help="Disable ANSI colors in help and diagnostics") group.add_argument( "--debug", - "--debug-traceback", - dest="debug", action="store_true", help="Re-raise command failures and print the full Python traceback", ) diff --git a/x2py/compiling/README.md b/x2py/compiling/README.md index 49f6675a6..09b0d4092 100644 --- a/x2py/compiling/README.md +++ b/x2py/compiling/README.md @@ -51,7 +51,7 @@ policy completion. Those decisions happen before generated sources reach this pa ## Tests And Docs -- Wrapper guide: `docs/user/guide/fortran-wrapper.md` +- Wrapper reference: `docs/user/reference/fortran-wrapper.md` - Build-system docs: `docs/developer/build-system.md` - Quality and static checks: `docs/developer/quality-assurance.md` - Source navigation: `docs/developer/source-map.md`, `docs/developer/feature-to-code-map.md` diff --git a/x2py/compiling/compiler_profiles.py b/x2py/compiling/compiler_profiles.py index e191094b9..af3d367f3 100644 --- a/x2py/compiling/compiler_profiles.py +++ b/x2py/compiling/compiler_profiles.py @@ -77,6 +77,7 @@ def _language( debug_flags: tuple[str, ...], release_flags: tuple[str, ...], general_flags: tuple[str, ...], + optional_general_flags: tuple[str, ...] = (), standard_flags: tuple[str, ...], module_output_flag: str | None = None, openmp: dict[str, tuple[str, ...]] | None = None, @@ -89,6 +90,7 @@ def _language( "debug_flags": debug_flags, "release_flags": release_flags, "general_flags": general_flags, + "optional_general_flags": optional_general_flags, "standard_flags": standard_flags, "mpi": {}, "openmp": openmp or {}, @@ -125,6 +127,7 @@ def _language( debug_flags=("-fcheck=bounds", "-g", "-O0"), release_flags=("-O3", "-funroll-loops", "-DNDEBUG"), general_flags=("-fPIC", "-cpp"), + optional_general_flags=("-ftrampoline-impl=heap",), standard_flags=("-std=f2003",), module_output_flag="-J", openmp={"flags": ("-fopenmp",), "libs": ("gomp",)}, diff --git a/x2py/compiling/compilers.py b/x2py/compiling/compilers.py index 0a93354e8..64f45b050 100644 --- a/x2py/compiling/compilers.py +++ b/x2py/compiling/compilers.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Iterable, Mapping +from functools import cache import json import os from pathlib import Path @@ -66,9 +67,15 @@ def compile_object(self, object_file: ObjectFile, *, verbose: bool | int = False object_file.object_path.parent.mkdir(parents=True, exist_ok=True) language = self._language(object_file.language) + executable = self._executable(language, object_file.tools) command = [ - self._executable(language, object_file.tools), - *self._flags(language, object_file.tools, object_file.flags), + executable, + *self._flags( + language, + object_file.tools, + object_file.flags, + executable=executable if self._execute_commands else None, + ), "-c", *self._path_flags("-I", self._include_dirs(language, object_file.tools, object_file.include_dirs)), str(object_file.source), @@ -115,12 +122,18 @@ def link_extension( ) resolved_library_dirs = self._library_dirs(language_info, selected_tools, selected_library_dirs) extension_path = output_path / f"{module_name}{language_info['python']['shared_suffix']}" + executable = self._executable(language_info, selected_tools) if verbose: print(f">> Create shared library: {extension_path}") command = [ - self._executable(language_info, selected_tools), + executable, "-shared", - *self._flags(language_info, selected_tools - {"python"}, flags), + *self._flags( + language_info, + selected_tools - {"python"}, + flags, + executable=executable if self._execute_commands else None, + ), *self._path_flags("-L", resolved_library_dirs), *self._path_flags("-Wl,-rpath", resolved_library_dirs), *(str(path) for path in object_paths), @@ -198,9 +211,12 @@ def _flags( language: Mapping[str, object], tools: Iterable[str], requested: Iterable[str], + *, + executable: str | None = None, ) -> tuple[str, ...]: profile = "debug_flags" if self._debug else "release_flags" values = [*self._strings(language.get(profile, ())), *self._strings(language.get("general_flags", ()))] + values.extend(self._supported_optional_flags(executable, language.get("optional_general_flags", ()))) for tool in sorted(set(tools)): flags = self._tool_mapping(language, tool).get("flags", ()) if tool == "python": @@ -209,6 +225,28 @@ def _flags( values.extend(str(flag) for flag in requested) return tuple(values) + def _supported_optional_flags(self, executable: str | None, flags: object) -> tuple[str, ...]: + """Return profile flags accepted by the selected compiler executable.""" + if executable is None: + return () + return tuple(flag for flag in self._strings(flags) if self._supports_optional_flag(executable, flag)) + + @staticmethod + @cache + def _supports_optional_flag(executable: str, flag: str) -> bool: + """Probe help text instead of making newer profile flags mandatory.""" + help_key = flag.split("=", maxsplit=1)[0] + "=" if "=" in flag else flag + try: + completed = subprocess.run( + (executable, "-Q", "--help=common"), + capture_output=True, + text=True, + check=False, + ) + except OSError: + return False + return completed.returncode == 0 and help_key in f"{completed.stdout}\n{completed.stderr}" + def _include_dirs( self, language: Mapping[str, object], diff --git a/x2py/contracts/__init__.py b/x2py/contracts/__init__.py index c9bb79aef..6bdacaa1d 100644 --- a/x2py/contracts/__init__.py +++ b/x2py/contracts/__init__.py @@ -1,14 +1,16 @@ """Public names used by x2py semantic ``.pyi`` contracts. -The objects in this module exist so generated stubs have one explicit import -source. X2py parses their syntax; applications should not execute contract -expressions at runtime. +Most objects in this module are syntax markers parsed from generated stubs. +Concrete primitive scalar types and array descriptor annotations additionally +provide the small runtime constructors documented by x2py. """ from __future__ import annotations from typing import Annotated as Annotated, Any as Any, Final as Final +import numpy as np + class _ContractExpression: """Placeholder produced when a contract helper is evaluated.""" @@ -27,17 +29,100 @@ def __call__(self, *args: object, **kwargs: object) -> _ContractExpression: return _ContractExpression(self, *args, **kwargs) -class _ContractType: - """Subscriptable and callable placeholder for semantic contract types.""" +class _ContractTypeMeta(type): + """Preserve contract syntax while constructing supported scalar values.""" + + def __getitem__(cls, item: object) -> _ArrayContract: + return _ArrayContract(cls, item) + + def __call__(cls, *args: object, **kwargs: object) -> object: + constructor_error = getattr(cls, "_constructor_error", None) + if constructor_error is not None: + raise TypeError(constructor_error) + scalar_factory = getattr(cls, "_scalar_factory", None) + if scalar_factory is not None: + if args or kwargs: + raise TypeError(f"{cls.__name__} default constructor takes no arguments") + return scalar_factory(*args, **kwargs) + return _ContractExpression(*args, **kwargs) + + +class _ContractType(metaclass=_ContractTypeMeta): + """Base for semantic contract types.""" + + +class _ArrayContract: + """Runtime description retained by a subscripted contract type.""" + + def __init__(self, element_type: type[_ContractType], shape: object) -> None: + self.element_type = element_type + self.shape = shape + self.rank = len(shape) if isinstance(shape, tuple) else 1 + + def __getitem__(self, item: object) -> _ContractExpression: + return _ContractExpression(self, item) + + def __call__(self, *args: object, **kwargs: object) -> object: + del args, kwargs + raise TypeError("ordinary array contract annotations are not constructors; create the array with NumPy") + + +class _DescriptorContract: + """Subscriptable allocatable or pointer descriptor marker.""" - def __class_getitem__(cls, item: object) -> type[_ContractType]: - del item - return cls + def __init__(self, descriptor_kind: str) -> None: + self.descriptor_kind = descriptor_kind - def __new__(cls, *args: object, **kwargs: object) -> _ContractExpression: + def __getitem__(self, item: object) -> _DescriptorHandleContract: + return _DescriptorHandleContract(self.descriptor_kind, item) + + def __call__(self, *args: object, **kwargs: object) -> _ContractExpression: return _ContractExpression(*args, **kwargs) +class _DescriptorHandleContract: + """Construct one typed, initially empty native array descriptor handle.""" + + def __init__(self, descriptor_kind: str, array: object) -> None: + self.descriptor_kind = descriptor_kind + self.array = array + + def __call__(self, *args: object, **kwargs: object) -> object: + if args or kwargs: + raise TypeError(f"{self.descriptor_kind} handle constructor takes no arguments") + if not isinstance(self.array, _ArrayContract): + raise TypeError(f"scalar {self.descriptor_kind} contracts are values, not runtime handles") + shape_items = self.array.shape if isinstance(self.array.shape, tuple) else (self.array.shape,) + if self.array.rank <= 0 or Ellipsis in shape_items: + raise TypeError(f"{self.descriptor_kind} handle constructor requires one concrete positive array rank") + scalar_dtype = getattr(self.array.element_type, "_scalar_dtype", None) + if scalar_dtype is None: + name = getattr(self.array.element_type, "__name__", type(self.array.element_type).__name__) + raise TypeError(f"{self.descriptor_kind} handle element contract {name!r} has no concrete NumPy dtype") + from x2py.runtime.handles import _native_array_handle_from_contract + + return _native_array_handle_from_contract( + self.descriptor_kind, + scalar_dtype, + self.array.rank, + ) + + +def _contract_type( + name: str, + scalar_factory: object | None = None, + *, + constructor_error: str | None = None, +) -> type[_ContractType]: + namespace = { + "_scalar_factory": scalar_factory, + "_constructor_error": constructor_error, + } + if scalar_factory is not None: + namespace["_scalar_dtype"] = np.dtype(scalar_factory) + return _ContractTypeMeta(name, (_ContractType,), namespace) + + def _expression(*args: object, **kwargs: object) -> _ContractExpression: return _ContractExpression(*args, **kwargs) @@ -51,49 +136,50 @@ def apply(target): return apply -Bool = _ContractType -Byte = _ContractType -CEnum = _ContractType -Char = _ContractType -Complex64 = _ContractType -Complex128 = _ContractType -Complex256 = _ContractType -Float16 = _ContractType -Float32 = _ContractType -Float64 = _ContractType -Float128 = _ContractType -Int = _ContractType -Int8 = _ContractType -Int16 = _ContractType -Int32 = _ContractType -Int64 = _ContractType -Matrix = _ContractType -SizeT = _ContractType -String = _ContractType -UInt = _ContractType -UInt8 = _ContractType -UInt16 = _ContractType -UInt32 = _ContractType -UInt64 = _ContractType -Vector = _ContractType -Void = _ContractType - -Addr = _ContractType -Returns = _ContractType -private = _ContractType +Bool = _contract_type("Bool", np.bool_) +Byte = _contract_type("Byte", constructor_error="Byte has no portable NumPy scalar default") +CEnum = _contract_type("CEnum", constructor_error="CEnum requires a resolved native underlying type") +Char = _contract_type("Char", constructor_error="Char has no portable NumPy scalar default") +Complex64 = _contract_type("Complex64", np.complex64) +Complex128 = _contract_type("Complex128", np.complex128) +Complex256 = _contract_type("Complex256", np.clongdouble) +Float16 = _contract_type("Float16", np.float16) +Float32 = _contract_type("Float32", np.float32) +Float64 = _contract_type("Float64", np.float64) +Float128 = _contract_type("Float128", np.longdouble) +Int = _contract_type("Int", constructor_error="Int requires a resolved native width") +Int8 = _contract_type("Int8", np.int8) +Int16 = _contract_type("Int16", np.int16) +Int32 = _contract_type("Int32", np.int32) +Int64 = _contract_type("Int64", np.int64) +Matrix = _contract_type("Matrix") +SizeT = _contract_type("SizeT", np.uintp) +String = _contract_type("String", constructor_error="String requires an explicit native length and encoding contract") +UInt = _contract_type("UInt", constructor_error="UInt requires a resolved native width") +UInt8 = _contract_type("UInt8", np.uint8) +UInt16 = _contract_type("UInt16", np.uint16) +UInt32 = _contract_type("UInt32", np.uint32) +UInt64 = _contract_type("UInt64", np.uint64) +Vector = _contract_type("Vector") +Void = _contract_type("Void", constructor_error="Void is not a runtime value") + +Addr = _contract_type("Addr") +Returns = _contract_type("Returns") +private = _contract_type("private") Aliased = _ContractExpression() -Allocatable = _ContractExpression() +Allocatable = _DescriptorContract("allocatable") AssumedType = _ContractExpression() Contiguous = _ContractExpression() COPY_F = _ContractExpression() Flat = _ContractExpression() FortranAllocatable = _ContractExpression() Immutable = _ContractExpression() +MaybeUnallocated = _ContractExpression() ORDER_ANY = _ContractExpression() ORDER_C = _ContractExpression() ORDER_F = _ContractExpression() -Pointer = _ContractExpression() +Pointer = _DescriptorContract("pointer") Polymorphic = _ContractExpression() Strided = _ContractExpression() @@ -124,13 +210,13 @@ def apply(target): prototype = _decorator raises = _decorator -CAnonymous = _ContractType -CAnonymousMember = _ContractType -CStruct = _ContractType -CUnion = _ContractType -Opaque = _ContractType -OpaqueHandle = _ContractType -WrappedType = _ContractType +CAnonymous = _contract_type("CAnonymous") +CAnonymousMember = _contract_type("CAnonymousMember") +CStruct = _contract_type("CStruct") +CUnion = _contract_type("CUnion") +Opaque = _contract_type("Opaque") +OpaqueHandle = _contract_type("OpaqueHandle") +WrappedType = _contract_type("WrappedType") CONTRACT_SYMBOLS = frozenset( @@ -175,6 +261,7 @@ def apply(target): "IsPresent", "Len", "Matrix", + "MaybeUnallocated", "Opaque", "OpaqueHandle", "ORDER_ANY", diff --git a/x2py/parsers/c/cli.py b/x2py/parsers/c/cli.py index b9e7da7e2..6162490ef 100644 --- a/x2py/parsers/c/cli.py +++ b/x2py/parsers/c/cli.py @@ -196,8 +196,6 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--no-color", action="store_true", help="Disable ANSI color in parse diagnostics") parser.add_argument( "--debug", - "--debug-traceback", - dest="debug", action="store_true", help="Re-raise parser errors so Python prints a traceback for parser debugging.", ) diff --git a/x2py/parsers/fortran/cli.py b/x2py/parsers/fortran/cli.py index ae77920e8..874b997c0 100644 --- a/x2py/parsers/fortran/cli.py +++ b/x2py/parsers/fortran/cli.py @@ -301,8 +301,6 @@ def main() -> int: ) parser.add_argument( "--debug", - "--debug-traceback", - dest="debug", action="store_true", help="Re-raise parser errors so Python prints a traceback for parser debugging. " "Can also be enabled with FORTRAN_PARSER_DEBUG=1.", diff --git a/x2py/runtime/handles.py b/x2py/runtime/handles.py index 7534a651c..880de6ee8 100644 --- a/x2py/runtime/handles.py +++ b/x2py/runtime/handles.py @@ -44,16 +44,13 @@ def __post_init__(self) -> None: @dataclass(frozen=True) class _NativeArrayDescriptorHandoff: - """Internal opaque handoff for persistent standard C descriptor storage.""" + """Internal opaque handoff for one versioned native-handle capsule.""" - address: int - owner: Any = None + capsule: Any def __post_init__(self) -> None: - if isinstance(self.address, bool) or not isinstance(self.address, int): - raise TypeError("native array descriptor handoff address must be an integer") - if self.address <= 0: - raise ValueError("native array descriptor handoff address must be a non-null positive pointer value") + if self.capsule is None: + raise TypeError("native array descriptor handoff capsule is required") def _numpy_view_from_pointer_c_descriptor( @@ -106,6 +103,11 @@ def _native_array_handle_from_generated_ops( normalized = _generated_owned_descriptor_operation(operation, owner) elif name in {"shape", "to_numpy"} and owned: normalized = _generated_owned_descriptor_record_operation(operation, owner) + elif name == "associate": + normalized = _generated_pointer_associate_operation( + operation, + owner=owner if owned else None, + ) elif name in {"allocate", "resize"}: normalized = _generated_shape_operation(operation, owner=owner if owned else None) elif owned: @@ -137,6 +139,175 @@ def _native_array_handle_from_generated_ops( raise +def _native_array_handle_from_contract( + descriptor_kind: str, + dtype: Any, + rank: int, +) -> NativeArrayHandleBase: + """Create one owned, initially empty descriptor handle from a contract.""" + descriptor_state = { + "record": _empty_descriptor_record(dtype, rank), + "owner": None, + } + + def current_shape(_handle: NativeArrayHandleBase) -> tuple[int, ...] | None: + record = descriptor_state["record"] + if _pointer_descriptor_base_addr(record) == 0: + return None + shape, _strides = _pointer_descriptor_shape_and_strides(record) + return shape + + def current_array_actual(_handle: NativeArrayHandleBase) -> _NativeArrayHandoff | None: + address = _pointer_descriptor_base_addr(descriptor_state["record"]) + if address == 0: + return None + return _NativeArrayHandoff(address, owner=descriptor_state["owner"]) + + def descriptor(_handle: NativeArrayHandleBase) -> Mapping[str, Any]: + return descriptor_state["record"] + + def present(_handle: NativeArrayHandleBase) -> bool: + return _pointer_descriptor_base_addr(descriptor_state["record"]) != 0 + + def current_view(_handle: NativeArrayHandleBase) -> np.ndarray | None: + return _numpy_view_from_pointer_c_descriptor( + descriptor_state["record"], + dtype=dtype, + expected_rank=rank, + ) + + def clear(_handle: NativeArrayHandleBase) -> None: + descriptor_state["record"] = _empty_descriptor_record(dtype, rank) + descriptor_state["owner"] = None + + def associate_record( + _handle: NativeArrayHandleBase, + record: Mapping[str, Any], + owner: NativeArrayHandleBase, + ) -> None: + descriptor_state["record"] = _copy_pointer_descriptor_record(record) + descriptor_state["owner"] = owner + + common_ops = { + "shape": current_shape, + "array_actual": current_array_actual, + "descriptor": descriptor, + "to_numpy": current_view, + "destroy": clear, + } + try: + handle_cls, descriptor_ops = { + "allocatable": (AllocatableArray, {"allocated": present}), + "pointer": ( + PointerArray, + { + "associated": present, + "nullify": clear, + "_associate_record": associate_record, + }, + ), + }[descriptor_kind] + except KeyError: + raise ValueError("contract native array handle kind must be 'allocatable' or 'pointer'") from None + handle = handle_cls( + dtype=dtype, + rank=rank, + ops={**common_ops, **descriptor_ops}, + descriptor_ownership="owned", + to_numpy_policy="borrowed_view", + ) + handle._contract_default = True + return handle + + +def _empty_descriptor_record(dtype: Any, rank: int) -> dict[str, Any]: + """Return canonical unallocated or unassociated descriptor facts.""" + array_dtype = np.dtype(dtype) + return { + "base_addr": 0, + "elem_len": array_dtype.itemsize, + "rank": int(rank), + "dim": [{"lower_bound": 0, "extent": 0, "sm": array_dtype.itemsize} for _axis in range(int(rank))], + } + + +def _copy_pointer_descriptor_record(descriptor: Mapping[str, Any]) -> dict[str, Any]: + """Copy validated standard descriptor facts for independent association state.""" + dimensions = _pointer_descriptor_dimensions(descriptor) + return { + "base_addr": _required_descriptor_int(descriptor, "base_addr"), + "elem_len": _required_descriptor_int(descriptor, "elem_len"), + "rank": _required_descriptor_int(descriptor, "rank"), + "dim": [ + { + "lower_bound": _required_descriptor_int(dimension, "lower_bound", field_owner=f"dim[{index}]"), + "extent": _required_descriptor_int(dimension, "extent", field_owner=f"dim[{index}]"), + "sm": _required_descriptor_int(dimension, "sm", field_owner=f"dim[{index}]"), + } + for index, dimension in enumerate(dimensions) + ], + } + + +def _pointer_descriptor_record_facts(descriptor: Mapping[str, Any]) -> tuple[int, ...]: + """Flatten standard descriptor facts for one generated association operation.""" + record = _copy_pointer_descriptor_record(descriptor) + fields = [ + record["base_addr"], + record["elem_len"], + record["rank"], + ] + for dimension in record["dim"]: + fields.extend( + ( + dimension["lower_bound"], + dimension["extent"], + dimension["sm"], + ) + ) + return tuple(fields) + + +def _bind_contract_native_array_handle( + handle: NativeArrayHandleBase, + descriptor_kind: str, + dtype: Any, + rank: int, + ops: Mapping[str, HandleOperation], + owner: Any, + descriptor_ownership: str, + to_numpy_policy: str, + generation: int | None = None, +) -> None: + """Attach generated persistent descriptor storage to a contract handle.""" + if not isinstance(handle, NativeArrayHandleBase) or not handle._contract_default: + raise TypeError("generated descriptor storage can attach only to a fresh contract handle") + if handle.closed: + raise ReferenceError(f"{handle.descriptor_kind} handle is closed") + if handle.descriptor_kind != descriptor_kind: + raise TypeError(f"cannot attach {descriptor_kind} descriptor storage to {handle.descriptor_kind} handle") + if handle.rank != int(rank): + raise ValueError(f"{descriptor_kind} handle rank {handle.rank} does not match generated rank {int(rank)}") + if not handle._dtype_matches(dtype): + raise TypeError(f"{descriptor_kind} handle dtype {handle.dtype!r} does not match generated dtype {dtype!r}") + pending_pointer_descriptor = ( + handle._association_descriptor_record() if isinstance(handle, PointerArray) and handle.associated else None + ) + generated = _native_array_handle_from_generated_ops( + descriptor_kind, + dtype, + rank, + ops, + owner=owner, + descriptor_ownership=descriptor_ownership, + to_numpy_policy=to_numpy_policy, + generation=generation, + ) + handle._adopt_generated_storage(generated) + if pending_pointer_descriptor is not None: + handle._call_op("associate", pending_pointer_descriptor) + + def _generated_handle_operation(operation: HandleOperation) -> HandleOperation: """Adapt a generated operation callable to the handle operation protocol.""" @@ -186,6 +357,22 @@ def call(_handle: NativeArrayHandleBase, *args: Any) -> Any: return call +def _generated_pointer_associate_operation( + operation: HandleOperation, + *, + owner: Any = None, +) -> HandleOperation: + """Adapt pointer association to one generated standard-descriptor operation.""" + + def call(_handle: NativeArrayHandleBase, descriptor: Mapping[str, Any]) -> Any: + facts = _pointer_descriptor_record_facts(descriptor) + if owner is None: + return operation(facts) + return operation(owner, facts) + + return call + + def _generated_shape_operation(operation: HandleOperation, *, owner: Any = None) -> HandleOperation: """Adapt generated shape operations from one runtime shape tuple to scalar extents.""" @@ -227,16 +414,14 @@ def _native_array_descriptor_handoff_from_generated_result( *, owner: Any = None, ) -> _NativeArrayDescriptorHandoff: - """Normalize a generated standard-descriptor pointer into a typed handoff.""" + """Normalize a generated native-handle capsule into a typed handoff.""" if isinstance(value, _NativeArrayDescriptorHandoff): return value - if isinstance(value, ctypes.c_void_p): - value = value.value - if isinstance(value, bool) or not isinstance(value, int): - raise TypeError( - f"generated native array descriptor handoff address must be an integer; received {type(value).__name__}" - ) - return _NativeArrayDescriptorHandoff(value, owner=owner) + if owner is None: + raise TypeError("generated native array descriptor handoff requires an owner capsule") + if value is not owner: + raise TypeError("generated native array descriptor operation must return its owner capsule") + return _NativeArrayDescriptorHandoff(owner) def _pointer_descriptor_base_addr(descriptor: Any) -> int: @@ -387,7 +572,7 @@ def __init__( raise ValueError( f"native array handle to_numpy_policy must be one of {sorted(self._VALID_TO_NUMPY_POLICIES)!r}" ) - self._dtype = dtype + self._dtype = None if dtype is None else np.dtype(dtype) self._rank = int(rank) self._ops = self._normalize_ops(ops) self._owner = owner @@ -395,11 +580,12 @@ def __init__( self._descriptor_ownership = descriptor_ownership self._to_numpy_policy = to_numpy_policy self._generation = generation + self._contract_default = False self._validate_required_ops() self._closed = False @property - def dtype(self) -> Any: + def dtype(self) -> np.dtype: if self._dtype is not None: return self._dtype return self._deferred_character_dtype() @@ -482,6 +668,24 @@ def close(self) -> Any: return operation(self) finally: self._closed = True + self._owner = None + self._ops = {} + + def _adopt_generated_storage(self, generated: NativeArrayHandleBase) -> None: + """Replace a fresh contract placeholder with validated generated storage.""" + if not self._contract_default: + raise TypeError("native descriptor storage is already attached") + if type(self) is not type(generated): + raise TypeError("generated native descriptor kind does not match contract handle") + if self.rank != generated.rank or not self._dtype_matches(generated.dtype): + raise TypeError("generated native descriptor metadata does not match contract handle") + self._ops = generated._ops + self._owner = generated._owner + self._descriptor_ownership = generated._descriptor_ownership + self._to_numpy_policy = generated._to_numpy_policy + self._generation = generated._generation + self._contract_default = False + generated._closed = True def __del__(self) -> None: with suppress(Exception): @@ -563,6 +767,15 @@ def _descriptor_for_binding( ) return self._contiguous_descriptor_record(address, shape) + def _descriptor_record_for_binding(self) -> Any: + """Return standard descriptor fields for a fact-packed descriptor call.""" + descriptor = self._call_op("to_numpy") + if not _is_pointer_descriptor_record(descriptor): + raise TypeError( + f"{self.descriptor_kind} handle cannot expose standard descriptor fields for binding handoff" + ) + return descriptor + def _contiguous_descriptor_record(self, address: int, shape: tuple[int, ...] | None) -> dict[str, Any]: """Build standard descriptor fields for a contiguous native array actual.""" dtype = np.dtype(self.dtype) @@ -889,6 +1102,41 @@ def _validate_array_actual_state(self) -> None: def _to_numpy_absent_state(self) -> bool: return not self.associated + def _association_descriptor_record(self) -> dict[str, Any]: + """Return independent standard descriptor facts for pointer assignment.""" + descriptor = self._descriptor_for_binding( + expected_dtype=self.dtype, + expected_rank=self.rank, + ) + if isinstance(descriptor, _NativeArrayDescriptorHandoff): + descriptor = self._descriptor_record_for_binding() + if not isinstance(descriptor, Mapping): + raise TypeError("pointer handle cannot expose descriptor facts for association") + record = _copy_pointer_descriptor_record(descriptor) + if record["rank"] != self.rank: + raise ValueError( + f"pointer descriptor rank {record['rank']} does not match declared handle rank {self.rank}" + ) + _validate_pointer_descriptor_itemsize(record, self.dtype) + return record + + def associate(self, other: PointerArray) -> Any: + """Make this pointer's association match another pointer handle.""" + if self.closed: + raise ReferenceError("pointer handle is closed") + if not isinstance(other, PointerArray): + raise TypeError(f"pointer association requires another PointerArray; received {type(other).__name__}") + if other.closed: + raise ReferenceError("source pointer handle is closed") + if self.rank != other.rank: + raise ValueError(f"pointer handle rank {self.rank} does not match source rank {other.rank}") + if not self._dtype_matches(other.dtype): + raise TypeError(f"pointer handle dtype {self.dtype!r} does not match source dtype {other.dtype!r}") + descriptor = other._association_descriptor_record() + if self._contract_default: + return self._call_op("_associate_record", descriptor, other) + return self._call_op("associate", descriptor) + def nullify(self) -> Any: return self._call_op("nullify") @@ -957,6 +1205,8 @@ def _native_array_actual_argument_for_binding_positional( include_itemsize: bool = False, include_strides: bool = False, require_contiguous: bool = False, + flatten_storage: bool = False, + flat_axis: int | None = None, ) -> tuple[int, ...]: """Pack a normal array actual into generated Bind-C array descriptor fields.""" strided_ndarray = include_strides and isinstance(value, np.ndarray) @@ -965,8 +1215,8 @@ def _native_array_actual_argument_for_binding_positional( actual = _native_array_actual_for_binding( value, expected_dtype=expected_dtype, - expected_rank=expected_rank, - expected_shape=expected_shape, + expected_rank=None if flatten_storage else expected_rank, + expected_shape=None if flatten_storage else expected_shape, # Positive-stride validation below is the exact Fortran-order contract # for a strided ndarray; NumPy's contiguous flag is intentionally false. expected_layout=None if strided_ndarray else expected_layout, @@ -976,6 +1226,8 @@ def _native_array_actual_argument_for_binding_positional( require_contiguous=bool(require_contiguous), ) address, shape, itemsize = _normal_array_actual_abi_facts(value, actual, expected_dtype) + if flatten_storage: + shape = _flattened_storage_shape(shape, expected_shape, flat_axis) fields = [address] if include_rank: fields.append(len(shape)) @@ -990,6 +1242,65 @@ def _native_array_actual_argument_for_binding_positional( return tuple(fields) +def _flattened_storage_shape( + shape: tuple[int, ...], + expected_shape: Sequence[int | None] | int | None, + flat_axis: int | None, +) -> tuple[int, ...]: + """Return native extents for a contiguous actual with one flat edge.""" + if not 1 <= len(shape) <= 15: + raise TypeError(f"Flat storage expects NumPy array rank 1 through 15; received rank {len(shape)}") + expected = ( + NativeArrayHandleBase._normalize_expected_shape(expected_shape) if expected_shape is not None else (None,) + ) + if len(shape) < len(expected): + raise TypeError(f"Flat storage expects NumPy array rank at least {len(expected)}; received rank {len(shape)}") + axis = 0 if flat_axis is None or int(flat_axis) < 0 else int(flat_axis) + if axis not in {0, len(expected) - 1}: + raise ValueError("Flat storage axis must be the first or final contract dimension") + if axis == 0: + return _leading_flattened_storage_shape(shape, expected) + return _final_flattened_storage_shape(shape, expected) + + +def _final_flattened_storage_shape(shape: tuple[int, ...], expected: tuple[int | None, ...]) -> tuple[int, ...]: + """Keep prefix extents and flatten all remaining axes into the final extent.""" + prefix_count = len(expected) - 1 + _validate_flat_expected_shape(shape[:prefix_count], expected[:prefix_count], offset=0) + return (*shape[:prefix_count], _extent_product(shape[prefix_count:])) + + +def _leading_flattened_storage_shape(shape: tuple[int, ...], expected: tuple[int | None, ...]) -> tuple[int, ...]: + """Flatten leading axes and keep suffix extents at the Python edge.""" + suffix_count = len(expected) - 1 + suffix_shape = shape[len(shape) - suffix_count :] if suffix_count else () + _validate_flat_expected_shape(suffix_shape, expected[1:], offset=len(shape) - suffix_count) + return (_extent_product(shape[: len(shape) - suffix_count]), *suffix_shape) + + +def _extent_product(shape: tuple[int, ...]) -> int: + """Return the element count covered by a flattened extent segment.""" + size = 1 + for extent in shape: + size *= int(extent) + return size + + +def _validate_flat_expected_shape( + actual: tuple[int, ...], + expected: tuple[int | None, ...], + *, + offset: int, +) -> None: + """Validate fixed non-flat dimensions for a flattened storage contract.""" + for axis, (actual_extent, wanted) in enumerate(zip(actual, expected, strict=True)): + if wanted is not None and actual_extent != wanted: + raise TypeError( + f"NumPy array has incompatible shape at axis {offset + axis}: " + f"received {actual!r}, expected {expected!r}" + ) + + def _normal_array_actual_stride_facts( actual: Any, shape: tuple[int, ...], @@ -1106,6 +1417,8 @@ def _native_array_descriptor_argument_for_binding( raise ValueError("optional absent native array descriptor arguments require an expected rank") fields = (None,) * (3 + 3 * int(expected_rank)) return (*fields, None) + if isinstance(descriptor, _NativeArrayDescriptorHandoff): + descriptor = value._descriptor_record_for_binding() dimensions = _pointer_descriptor_dimensions(descriptor) fields = [ _required_descriptor_int(descriptor, "base_addr"), @@ -1152,8 +1465,23 @@ def _native_array_descriptor_handoff_for_binding( expected_rank: int | None = None, expected_shape: Sequence[int | None] | int | None = None, optional_absent: bool = False, -) -> tuple[int | None, ...]: - """Pack a direct standard-descriptor pointer for projected handle mutation.""" + bind_default: HandleOperation | None = None, +) -> tuple[Any | None, ...]: + """Pack a versioned native-handle capsule for projected descriptor mutation.""" + if isinstance(value, NativeArrayHandleBase) and value._contract_default: + if bind_default is None: + raise TypeError( + f"writable {descriptor_kind} contract handle requires generated persistent descriptor storage" + ) + _native_array_descriptor_for_binding( + value, + descriptor_kind=descriptor_kind, + expected_dtype=expected_dtype, + expected_rank=expected_rank, + expected_shape=expected_shape, + optional=optional_absent, + ) + bind_default(value) descriptor = _native_array_descriptor_for_binding( value, descriptor_kind=descriptor_kind, @@ -1169,8 +1497,8 @@ def _native_array_descriptor_handoff_for_binding( f"writable {descriptor_kind} descriptor argument requires a generated direct descriptor handoff" ) if optional_absent: - return descriptor.address, _PRESENT_NATIVE_ARRAY_DESCRIPTOR_ARGUMENT_ADDRESS - return (descriptor.address,) + return descriptor.capsule, _PRESENT_NATIVE_ARRAY_DESCRIPTOR_ARGUMENT_ADDRESS + return (descriptor.capsule,) def _native_array_descriptor_handoff_for_binding_positional( @@ -1180,7 +1508,8 @@ def _native_array_descriptor_handoff_for_binding_positional( expected_rank: int | None = None, expected_shape: Sequence[int | None] | int | None = None, optional_absent: bool = False, -) -> tuple[int | None, ...]: + bind_default: HandleOperation | None = None, +) -> tuple[Any | None, ...]: """Positional wrapper used by projected-handle CPython binding code.""" return _native_array_descriptor_handoff_for_binding( value, @@ -1189,6 +1518,7 @@ def _native_array_descriptor_handoff_for_binding_positional( expected_rank=None if expected_rank is None else int(expected_rank), expected_shape=expected_shape, optional_absent=bool(optional_absent), + bind_default=bind_default, ) diff --git a/x2py/semantics/fortran2ir.py b/x2py/semantics/fortran2ir.py index 8a5157037..65ea00c1b 100644 --- a/x2py/semantics/fortran2ir.py +++ b/x2py/semantics/fortran2ir.py @@ -23,7 +23,8 @@ FortranVariable, ) from x2py.semantics.ownership import set_ownership_metadata -from x2py.semantics.metadata import PROJECTED_OUTPUT_METADATA, SCALAR_STORAGE_CATEGORY +from x2py.semantics.metadata import BIND_TARGET_METADATA, PROJECTED_OUTPUT_METADATA, SCALAR_STORAGE_CATEGORY +from x2py.types.numpy import SEMANTIC_SCALAR_TYPE_NAMES from x2py.utilities.visitor import ClassVisitor from .models import ( @@ -1455,6 +1456,8 @@ def _apply_pointer_input_policy(semantic_type: SemanticType) -> None: @staticmethod def _apply_pointer_result_policy(semantic_type: SemanticType) -> None: + if semantic_type.rank > 0: + return set_ownership_metadata( semantic_type.metadata, owner="python", @@ -1568,7 +1571,13 @@ def _module_overload_sets( f"Fortran semantic conversion cannot represent generic constructor " f"{module.name}.{interface.name!s}; constructor projection is not implemented" ) - overload_sets.append(self._normal_overload_set(interface.name, procedures)) + 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): + if target_lookup[target_name.casefold()].visibility == "private": + candidate.native_name = interface.name + candidate.metadata[BIND_TARGET_METADATA] = interface.name + overload_sets.append(overload_set) continue defined_sets = self._defined_overload_sets( interface.name, @@ -1601,7 +1610,12 @@ def _bound_overload_sets( overload_sets.append(ProcedureOverloadSet(name)) continue if self._is_procedure_generic_name(name): - overload_sets.append(self._normal_overload_set(name, procedures)) + overload_set = self._normal_overload_set(name, procedures) + for target_name, candidate in zip(binding.get("targets", ()), overload_set.procedures, strict=True): + if lookup[target_name.casefold()].visibility == "private": + candidate.native_name = name + candidate.metadata[BIND_TARGET_METADATA] = name + overload_sets.append(overload_set) continue placeholder = SemanticClass(dtype.name) defined_sets = self._defined_overload_sets( @@ -1973,15 +1987,23 @@ def _is_returned_output_argument( *, is_output: bool, semantic_type: SemanticType | None, + is_primitive_scalar_replacement: bool, is_allocatable_replacement: bool, is_character_replacement: bool, is_descriptor_replacement: bool, ) -> bool: - if is_allocatable_replacement or is_character_replacement or is_descriptor_replacement: + if ( + is_primitive_scalar_replacement + or is_allocatable_replacement + or is_character_replacement + or is_descriptor_replacement + ): return True if not is_output or semantic_type is None: return False - return FortranToIRConverter._is_scalar_copy_return(semantic_type) or semantic_type.rank > 0 + return FortranToIRConverter._is_python_value_scalar_output( + semantic_type + ) or FortranToIRConverter._is_native_descriptor_output(semantic_type) @staticmethod def _is_hidden_output_argument( @@ -1993,8 +2015,8 @@ def _is_hidden_output_argument( if not is_output or getattr(native_arg, "optional", False): return False return ( - FortranToIRConverter._is_scalar_copy_return(semantic_type) - or FortranToIRConverter._is_allocatable_array(semantic_type) + FortranToIRConverter._is_python_value_scalar_output(semantic_type) + or FortranToIRConverter._is_native_descriptor_output(semantic_type) or FortranToIRConverter._is_scalar_descriptor(semantic_type) ) @@ -2012,18 +2034,19 @@ def _procedure_projection( arg = by_name[native_arg.name] reads_argument, writes_argument = FortranToIRConverter._argument_access(native_arg, arg.semantic_type) is_output = writes_argument and not reads_argument - is_allocatable_replacement = ( - reads_argument and writes_argument and FortranToIRConverter._is_allocatable_array(arg.semantic_type) - ) - is_character_replacement = ( - reads_argument and writes_argument and FortranToIRConverter._is_scalar_character(arg.semantic_type) + is_replacement = reads_argument and writes_argument + is_allocatable_replacement = is_replacement and FortranToIRConverter._is_allocatable_array( + arg.semantic_type ) - is_descriptor_replacement = ( - reads_argument and writes_argument and FortranToIRConverter._is_scalar_descriptor(arg.semantic_type) + is_character_replacement = is_replacement and FortranToIRConverter._is_scalar_character(arg.semantic_type) + is_descriptor_replacement = is_replacement and FortranToIRConverter._is_scalar_descriptor(arg.semantic_type) + is_primitive_scalar_replacement = is_replacement and FortranToIRConverter._is_primitive_scalar_replacement( + arg.semantic_type ) is_returned_output = FortranToIRConverter._is_returned_output_argument( is_output=is_output, semantic_type=arg.semantic_type, + is_primitive_scalar_replacement=is_primitive_scalar_replacement, is_allocatable_replacement=is_allocatable_replacement, is_character_replacement=is_character_replacement, is_descriptor_replacement=is_descriptor_replacement, @@ -2074,6 +2097,16 @@ def _is_allocatable_array(semantic_type: SemanticType | None) -> bool: and semantic_type.storage.array.allocatable ) + @staticmethod + def _is_native_descriptor_output(semantic_type: SemanticType | None) -> bool: + if semantic_type is None: + return False + if FortranToIRConverter._is_scalar_descriptor(semantic_type): + return True + storage = semantic_type.storage + array = storage.array if storage is not None else None + return bool(array is not None and (array.allocatable or array.pointer)) + @staticmethod def _is_scalar_descriptor(semantic_type: SemanticType | None) -> bool: return bool( @@ -2082,6 +2115,16 @@ def _is_scalar_descriptor(semantic_type: SemanticType | None) -> bool: and (semantic_type.metadata.get("fortran_allocatable") or semantic_type.metadata.get("fortran_pointer")) ) + @staticmethod + def _is_primitive_scalar_replacement(semantic_type: SemanticType | None) -> bool: + return bool( + semantic_type is not None + and semantic_type.rank == 0 + and semantic_type.name != "String" + and semantic_type.name in SEMANTIC_SCALAR_TYPE_NAMES + and not FortranToIRConverter._is_scalar_descriptor(semantic_type) + ) + @staticmethod def _scalar_descriptor_kind(semantic_type: SemanticType | None) -> str | None: """Return the ABI-relevant descriptor kind for one rank-zero value.""" @@ -2115,8 +2158,13 @@ def _scalar_descriptor_projection_value( raise ValueError(f"Scalar descriptor {name!r} has no Python argument or result projection") @staticmethod - def _is_scalar_copy_return(semantic_type: SemanticType | None) -> bool: - return bool(semantic_type is not None and semantic_type.rank == 0) + def _is_python_value_scalar_output(semantic_type: SemanticType | None) -> bool: + return bool( + semantic_type is not None + and semantic_type.rank == 0 + and not FortranToIRConverter._is_scalar_descriptor(semantic_type) + and (semantic_type.name == "String" or semantic_type.name in SEMANTIC_SCALAR_TYPE_NAMES) + ) @staticmethod def _is_scalar_character(semantic_type: SemanticType | None) -> bool: diff --git a/x2py/semantics/metadata.py b/x2py/semantics/metadata.py index f71e8519b..ba8f633c0 100644 --- a/x2py/semantics/metadata.py +++ b/x2py/semantics/metadata.py @@ -13,4 +13,5 @@ NATIVE_PROJECTION_METADATA = "native_projection" NATIVE_ARRAY_DESCRIPTOR_METADATA = "native_array_descriptor" NATIVE_ARRAY_HANDLE_POLICY_METADATA = "native_array_handle_policy" +MAYBE_UNALLOCATED_METADATA = "maybe_unallocated" OPTIONAL_ABSENT_HANDLE_METADATA = "optional_absent_handle" diff --git a/x2py/semantics/native_array_handles.py b/x2py/semantics/native_array_handles.py index fa485fea7..e5ad646b0 100644 --- a/x2py/semantics/native_array_handles.py +++ b/x2py/semantics/native_array_handles.py @@ -7,6 +7,7 @@ from x2py.semantics.ownership import OWNERSHIP_POLICY_METADATA, POINTER_POLICY_METADATA from x2py.semantics.metadata import ( + MAYBE_UNALLOCATED_METADATA, NATIVE_ARRAY_DESCRIPTOR_METADATA, NATIVE_ARRAY_HANDLE_POLICY_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, @@ -29,6 +30,7 @@ _HANDLE_ONLY_METADATA = ( NATIVE_ARRAY_DESCRIPTOR_METADATA, NATIVE_ARRAY_HANDLE_POLICY_METADATA, + MAYBE_UNALLOCATED_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, OWNERSHIP_POLICY_METADATA, POINTER_POLICY_METADATA, @@ -55,6 +57,7 @@ class NativeArrayHandlePolicy: python_setter: str native_setter: str output_projection: str + result_allocation: str release: str target_lifetime: str destroy_behavior: str @@ -65,6 +68,11 @@ class NativeArrayHandlePolicy: storage_mode: str operations: tuple[str, ...] = () blocker: str | None = None + default_construction: str = "none" + default_descriptor_ownership: str = "unknown" + default_release: str = "none" + default_destroy_behavior: str = "none" + default_operations: tuple[str, ...] = () @property def is_blocked(self) -> bool: diff --git a/x2py/semantics/ownership.py b/x2py/semantics/ownership.py index 857e71db7..af8581e60 100644 --- a/x2py/semantics/ownership.py +++ b/x2py/semantics/ownership.py @@ -538,7 +538,6 @@ def decide_semantic_type(self, semantic_type: Any, context: OwnershipContext) -> facts = self._semantic_facts(semantic_type) decision = self._apply_overrides(self._decide(facts, context), facts, context) decision = self._validate_aliased_decision(decision, facts, context) - decision = self._validate_scalar_descriptor_decision(decision, facts, context) decision = self._validate_pointer_decision(decision, facts, context) decision = self._complete_immutable_policy(decision, facts, context) decision = self._validate_result_projection(decision, context) @@ -681,6 +680,8 @@ def _decide(self, facts: _StorageFacts, context: OwnershipContext) -> OwnershipD return self._handlers[kind](facts, context) def _kind(self, facts: _StorageFacts, context: OwnershipContext) -> ObjectKind: + if facts.scalar_storage and not facts.is_string and not facts.allocatable and not facts.pointer: + return ObjectKind.NUMPY_ARRAY if facts.rank > 0 or facts.is_ndarray: return ObjectKind.NUMPY_ARRAY if facts.is_string: @@ -725,6 +726,15 @@ def _scalar_decision(self, facts: _StorageFacts, context: OwnershipContext) -> O reason="scalar output is returned as a Python value", ) if context.writes_argument and context.reads_argument: + if context.projects_result: + return OwnershipDecision( + ObjectKind.SCALAR, + OwnershipOwner.PYTHON, + TransferMode.COPY_RETURN, + DestructionPolicy.PYTHON_REFCOUNT, + mutates_native=True, + reason="projected scalar update uses call-local native storage and returns a replacement value", + ) return OwnershipDecision( ObjectKind.SCALAR, OwnershipOwner.CALLER, @@ -913,19 +923,9 @@ def _function_scalar_descriptor_decision( ) def _string_decision(self, facts: _StorageFacts, context: OwnershipContext) -> OwnershipDecision: - if context.is_result and (facts.allocatable or facts.pointer): - storage = StorageMode.HEAP if facts.allocatable else StorageMode.ALIAS - return OwnershipDecision( - ObjectKind.STRING, - OwnershipOwner.PYTHON, - TransferMode.COPY_RETURN, - DestructionPolicy.PYTHON_REFCOUNT, - storage_mode=storage, - boundary_storage_mode=storage, - nullable=True, - descriptor_boundary=True, - reason="scalar string descriptor result is copied before native descriptor release", - ) + descriptor_decision = self._string_descriptor_decision(facts, context) + if descriptor_decision is not None: + return descriptor_decision if facts.address_role == ADDRESS_ROLE_RAW: return OwnershipDecision( ObjectKind.STRING, @@ -936,82 +936,132 @@ def _string_decision(self, facts: _StorageFacts, context: OwnershipContext) -> O reason="raw string address aliases caller-owned fixed-width storage", ) if facts.scalar_storage: - if context.is_result: - return OwnershipDecision( - ObjectKind.STRING, - OwnershipOwner.PYTHON, - TransferMode.COPY_RETURN, - DestructionPolicy.PYTHON_REFCOUNT, - reason="scalar string storage result is copied into a Python string", - ) - if context.writes_argument: - return OwnershipDecision( - ObjectKind.STRING, - OwnershipOwner.CALLER, - TransferMode.IN_PLACE, - DestructionPolicy.CALLER, - storage_mode=StorageMode.ALIAS, - mutates_native=True, - reason="rank-0 string storage mutates caller-provided NumPy bytes storage", - ) + return self._scalar_string_storage_decision(context) + if context.is_result: return OwnershipDecision( ObjectKind.STRING, - OwnershipOwner.CALLER, - TransferMode.CALL_LOCAL, - DestructionPolicy.NONE, - storage_mode=StorageMode.ALIAS, - reason="rank-0 string storage is borrowed for the duration of the call", + OwnershipOwner.PYTHON, + TransferMode.COPY_RETURN, + DestructionPolicy.PYTHON_REFCOUNT, + reason="string output is copied into a Python string", ) + if context.writes_argument and not context.reads_argument: + return self._string_output_argument_decision(context) + if context.writes_argument and context.reads_argument: + return self._string_update_argument_decision(context) + return OwnershipDecision( + ObjectKind.STRING, + OwnershipOwner.CALLER, + TransferMode.CALL_LOCAL, + DestructionPolicy.NONE, + reason="string input is converted for the call only", + ) + + @staticmethod + def _string_descriptor_decision( + facts: _StorageFacts, + context: OwnershipContext, + ) -> OwnershipDecision | None: + if not (facts.allocatable or facts.pointer): + return None + + storage = StorageMode.HEAP if facts.allocatable else StorageMode.ALIAS if context.is_result: return OwnershipDecision( ObjectKind.STRING, OwnershipOwner.PYTHON, TransferMode.COPY_RETURN, DestructionPolicy.PYTHON_REFCOUNT, - reason="string output is copied into a Python string", + storage_mode=storage, + boundary_storage_mode=storage, + nullable=True, + descriptor_boundary=True, + reason="scalar string descriptor result is copied before native descriptor release", ) - if context.writes_argument and not context.reads_argument: - if not context.projects_result: - return OwnershipDecision( - ObjectKind.STRING, - OwnershipOwner.TEMPORARY, - TransferMode.CALL_LOCAL, - DestructionPolicy.CALL_LOCAL, - mutates_native=True, - reason="identity string output uses temporary storage and discards native mutation", - ) + if context.writes_argument and context.projects_result and not context.python_visible: return OwnershipDecision( ObjectKind.STRING, OwnershipOwner.PYTHON, TransferMode.COPY_RETURN, DestructionPolicy.PYTHON_REFCOUNT, + storage_mode=storage, + boundary_storage_mode=storage, + nullable=True, + descriptor_boundary=True, mutates_native=True, - reason="string output is copied into a Python string", + projects_result=True, + python_visible=False, + reason="hidden scalar string descriptor output is copied before native descriptor release", ) - if context.writes_argument and context.reads_argument: - if not context.projects_result: - return OwnershipDecision( - ObjectKind.STRING, - OwnershipOwner.TEMPORARY, - TransferMode.CALL_LOCAL, - DestructionPolicy.CALL_LOCAL, - mutates_native=True, - reason="string update uses a mutable call-local copy and discards native mutation", - ) + return None + + @staticmethod + def _scalar_string_storage_decision(context: OwnershipContext) -> OwnershipDecision: + if context.is_result: return OwnershipDecision( ObjectKind.STRING, OwnershipOwner.PYTHON, TransferMode.COPY_RETURN, DestructionPolicy.PYTHON_REFCOUNT, + reason="scalar string storage result is copied into a Python string", + ) + if context.writes_argument: + return OwnershipDecision( + ObjectKind.STRING, + OwnershipOwner.CALLER, + TransferMode.IN_PLACE, + DestructionPolicy.CALLER, + storage_mode=StorageMode.ALIAS, mutates_native=True, - reason="immutable Python strings use copy-in/copy-out replacement for updates", + reason="rank-0 string storage mutates caller-provided NumPy bytes storage", ) return OwnershipDecision( ObjectKind.STRING, OwnershipOwner.CALLER, TransferMode.CALL_LOCAL, DestructionPolicy.NONE, - reason="string input is converted for the call only", + storage_mode=StorageMode.ALIAS, + reason="rank-0 string storage is borrowed for the duration of the call", + ) + + @staticmethod + def _string_output_argument_decision(context: OwnershipContext) -> OwnershipDecision: + if not context.projects_result: + return OwnershipDecision( + ObjectKind.STRING, + OwnershipOwner.TEMPORARY, + TransferMode.CALL_LOCAL, + DestructionPolicy.CALL_LOCAL, + mutates_native=True, + reason="identity string output uses temporary storage and discards native mutation", + ) + return OwnershipDecision( + ObjectKind.STRING, + OwnershipOwner.PYTHON, + TransferMode.COPY_RETURN, + DestructionPolicy.PYTHON_REFCOUNT, + mutates_native=True, + reason="string output is copied into a Python string", + ) + + @staticmethod + def _string_update_argument_decision(context: OwnershipContext) -> OwnershipDecision: + if not context.projects_result: + return OwnershipDecision( + ObjectKind.STRING, + OwnershipOwner.TEMPORARY, + TransferMode.CALL_LOCAL, + DestructionPolicy.CALL_LOCAL, + mutates_native=True, + reason="string update uses a mutable call-local copy and discards native mutation", + ) + return OwnershipDecision( + ObjectKind.STRING, + OwnershipOwner.PYTHON, + TransferMode.COPY_RETURN, + DestructionPolicy.PYTHON_REFCOUNT, + mutates_native=True, + reason="immutable Python strings use copy-in/copy-out replacement for updates", ) def _array_decision(self, facts: _StorageFacts, context: OwnershipContext) -> OwnershipDecision: @@ -1109,19 +1159,6 @@ def _native_array_handle_argument_decision( @staticmethod def _native_array_handle_projected_output_decision(facts: _StorageFacts) -> OwnershipDecision: """Create stable wrapper-owned storage for a Python-hidden descriptor output.""" - if facts.pointer: - return OwnershipDecision( - ObjectKind.NUMPY_ARRAY, - OwnershipOwner.UNKNOWN, - TransferMode.BLOCKED, - DestructionPolicy.BLOCKED, - storage_mode=StorageMode.ALIAS, - boundary_storage_mode=StorageMode.ALIAS, - nullable=True, - descriptor_boundary=True, - blocker="pointer handle results need stable owner storage and target lifetime policy before wrapping", - reason="hidden pointer descriptor output has no completed target lifetime", - ) return OwnershipDecision( ObjectKind.NUMPY_ARRAY, OwnershipOwner.WRAPPER, @@ -1133,25 +1170,12 @@ def _native_array_handle_projected_output_decision(facts: _StorageFacts) -> Owne borrowed=False, mutates_native=True, descriptor_boundary=True, - reason="hidden allocatable descriptor output moves into wrapper-owned stable storage", + reason="hidden descriptor output moves into wrapper-owned stable descriptor storage", ) @staticmethod def _native_array_handle_result_decision(facts: _StorageFacts) -> OwnershipDecision: """Materialize a supported direct descriptor result as one runtime handle.""" - if facts.pointer: - return OwnershipDecision( - ObjectKind.NUMPY_ARRAY, - OwnershipOwner.UNKNOWN, - TransferMode.BLOCKED, - DestructionPolicy.BLOCKED, - storage_mode=StorageMode.ALIAS, - boundary_storage_mode=StorageMode.ALIAS, - nullable=True, - descriptor_boundary=True, - blocker="pointer handle results need stable owner storage and target lifetime policy before wrapping", - reason="pointer descriptor result has no completed stable target owner", - ) return OwnershipDecision( ObjectKind.NUMPY_ARRAY, OwnershipOwner.WRAPPER, @@ -1161,7 +1185,7 @@ def _native_array_handle_result_decision(facts: _StorageFacts) -> OwnershipDecis boundary_storage_mode=StorageMode.ALIAS, nullable=True, descriptor_boundary=True, - reason="allocatable descriptor result moves into wrapper-owned stable storage", + reason="descriptor result moves into wrapper-owned stable descriptor storage", ) def _allocatable_array_decision(self, facts: _StorageFacts, context: OwnershipContext) -> OwnershipDecision: @@ -1230,15 +1254,7 @@ def _pointer_array_decision(self, facts: _StorageFacts, context: OwnershipContex reason=reason, ) if context.is_result: - return OwnershipDecision( - ObjectKind.NUMPY_ARRAY, - OwnershipOwner.PYTHON, - TransferMode.SNAPSHOT_COPY, - DestructionPolicy.PYTHON_REFCOUNT, - storage_mode=StorageMode.ALIAS, - nullable=True, - reason="pointer array result is copied into Python-owned NumPy storage", - ) + return self._native_array_handle_result_decision(facts) if context.writes_argument: return OwnershipDecision( ObjectKind.NUMPY_ARRAY, @@ -1438,7 +1454,9 @@ def _apply_overrides( raw = metadata.get(OWNERSHIP_POLICY_METADATA) pointer_policy = metadata.get(POINTER_POLICY_METADATA) pointer_container = ( - facts.pointer and facts.rank > 0 and (context.is_argument or context.is_field or context.is_module_variable) + facts.pointer + and facts.rank > 0 + and (context.is_argument or context.is_field or context.is_module_variable or context.is_result) ) if facts.pointer and isinstance(pointer_policy, Mapping) and not pointer_container: raw = {**(raw if isinstance(raw, Mapping) else {}), **pointer_policy} @@ -1484,29 +1502,6 @@ def _validate_aliased_decision( """Keep Aliased as addressability metadata, not live-object eligibility.""" return decision - @staticmethod - def _validate_scalar_descriptor_decision( - decision: OwnershipDecision, - facts: _StorageFacts, - context: OwnershipContext, - ) -> OwnershipDecision: - if decision.is_blocked or facts.rank != 0 or not (facts.allocatable or facts.pointer): - return decision - blocker = None - if context.is_argument and context.writes_argument and facts.is_string: - blocker = "scalar descriptor output projection currently supports primitive numeric values only" - if blocker is None: - return decision - return replace( - decision, - owner=OwnershipOwner.UNKNOWN, - transfer=TransferMode.BLOCKED, - destruction=DestructionPolicy.BLOCKED, - borrowed=False, - blocker=blocker, - reason="scalar descriptor construction and readback must have a complete supported policy", - ) - @staticmethod def _validate_pointer_decision( decision: OwnershipDecision, @@ -1515,13 +1510,11 @@ def _validate_pointer_decision( ) -> OwnershipDecision: if not facts.pointer or decision.is_blocked: return decision - if context.is_argument and _is_native_array_handle_facts(facts): + if (context.is_argument or context.is_result) and _is_native_array_handle_facts(facts): return decision - blocker = ( - OwnershipPolicyResolver._pointer_argument_blocker(decision, facts, context) - or OwnershipPolicyResolver._pointer_container_blocker(decision, facts, context) - or OwnershipPolicyResolver._pointer_result_blocker(decision, facts, context) - ) + blocker = OwnershipPolicyResolver._pointer_argument_blocker( + decision, facts, context + ) or OwnershipPolicyResolver._pointer_container_blocker(decision, facts, context) if blocker is None: return decision return replace( @@ -1577,20 +1570,6 @@ def _pointer_container_blocker( return "scalar pointer field and module accessors require snapshot_copy detached values" return None - @staticmethod - def _pointer_result_blocker( - decision: OwnershipDecision, - facts: _StorageFacts, - context: OwnershipContext, - ) -> str | None: - """Return a blocker for an unsupported pointer function result policy.""" - if facts.rank > 0 and context.is_result and decision.transfer is not TransferMode.SNAPSHOT_COPY: - return ( - "pointer array results remain blocked until returned-handle owner storage, " - "target lifetime, descriptor extraction, and destroy behavior are implemented" - ) - return None - @staticmethod def _complete_immutable_policy( decision: OwnershipDecision, diff --git a/x2py/semantics/policy_completion.py b/x2py/semantics/policy_completion.py index ea86ee50a..2b86bad35 100644 --- a/x2py/semantics/policy_completion.py +++ b/x2py/semantics/policy_completion.py @@ -24,6 +24,7 @@ ADDRESS_ROLE_PROJECTION, ADDRESS_ROLE_RAW, BIND_TARGET_METADATA, + MAYBE_UNALLOCATED_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, PROJECTED_OUTPUT_METADATA, SCALAR_STORAGE_CATEGORY, @@ -164,7 +165,6 @@ def _complete_ownership_policies( derived_types, strict_wrapper_names=strict_wrapper_names, ) - class_targets = _class_root_target_names(module.classes) polymorphic_variants = _polymorphic_variant_map(module.classes) _complete_class_method_policies( module.classes, @@ -182,30 +182,20 @@ def _complete_ownership_policies( ) for function in module.functions: function_scope = str(function.origin.native_scope or module.name) - native_name = str(function.native_name or function.name) _complete_function( function, f"{function_scope}.{function.name}", derived_types=derived_types, - module_export=native_name not in class_targets, + module_export=True, polymorphic_variants=polymorphic_variants, ) for overload_set in module.overload_sets: - native_dispatch_name = next( - ( - str(procedure.metadata[models.FORTRAN_GENERIC_NAME_METADATA]) - for procedure in overload_set.procedures - if procedure.metadata.get(models.FORTRAN_GENERIC_NAME_METADATA) - ), - overload_set.name, - ) for procedure in overload_set.procedures: procedure_scope = str(procedure.origin.native_scope or module.name) _complete_function( procedure, f"{procedure_scope}.{overload_set.name}.{procedure.name}", derived_types=derived_types, - native_dispatch_name=native_dispatch_name, ) overload_functions = { f"{(procedure.origin.native_scope or module.name)!s}.{overload_set.name}.{procedure.name}": procedure @@ -509,6 +499,8 @@ def _complete_concrete_class_methods( ) -> None: """Attach completed function policy to constructors and ordinary methods.""" calls = {method.owner_path: method for method in completed_methods} + surface = semantic_class.metadata.get(models.RESOLVED_CLASS_SURFACE_POLICY_METADATA) + constructor_call = surface.constructor.call if isinstance(surface, ClassSurfacePolicy) else None for method in semantic_class.methods: owner_path = f"{derived.owner_path}.{method.name}" if method.name == "__init__": @@ -517,13 +509,15 @@ def _complete_concrete_class_methods( method, owner_path, derived_types=derived_types, + class_call=constructor_call, module_export=False, polymorphic_variants=polymorphic_variants, ) continue + function_owner_path = f"{derived.owner_path}.__method__.{method.name}" _complete_function( method, - owner_path, + function_owner_path, derived_types=derived_types, class_call=calls.get(owner_path), polymorphic_variants=polymorphic_variants, @@ -540,7 +534,7 @@ def _complete_class_overload_methods( ) -> None: """Complete every concrete overload through one typed call leaf.""" generic_bindings = { - str(procedure.native_name or procedure.name): overload.name + _class_overload_native_target(procedure): overload.name for overload in semantic_class.overload_sets if overload.name != "__init__" for procedure in overload.procedures @@ -571,7 +565,8 @@ def _complete_one_class_overload_method( ) -> None: """Complete one overload candidate and its native dispatch spelling.""" owner_path = f"{derived.owner_path}.{overload.name}.{procedure.name}" - native_name = str(procedure.native_name or procedure.name) + native_name = _class_overload_native_target(procedure) + bind_target = procedure.metadata.get(BIND_TARGET_METADATA) passed_position = _class_overload_passed_object_position(procedure) type_bound = native_name in type_bound_targets or ( passed_position is not None and native_name not in module_targets @@ -581,13 +576,19 @@ def _complete_one_class_overload_method( procedure, owner_path, type_bound=type_bound, - type_bound_name=generic_bindings.get(native_name) if type_bound else None, + type_bound_name=(str(bind_target) if bind_target else generic_bindings.get(native_name)) + if type_bound + else None, ) overload_kind = str(procedure.metadata.get(models.OVERLOAD_KIND_METADATA, "generic")) native_dispatch_name = ( - str(procedure.metadata.get(models.FORTRAN_GENERIC_NAME_METADATA, overload.name)) - if overload_kind != "generic" - else None + str(bind_target) + if bind_target + else ( + str(procedure.metadata.get(models.FORTRAN_GENERIC_NAME_METADATA, overload.name)) + if overload_kind != "generic" + else None + ) ) _complete_function( procedure, @@ -599,6 +600,11 @@ def _complete_one_class_overload_method( ) +def _class_overload_native_target(procedure: models.SemanticFunction) -> str: + """Return the completed native target selected by an overload contract.""" + return str(procedure.metadata.get(BIND_TARGET_METADATA) or procedure.native_name or procedure.name) + + def _uses_type_bound_invocation( method: ClassMethodPolicy, explicit_targets: set[str], @@ -781,17 +787,6 @@ def _iter_semantic_classes(classes: list[models.SemanticClass]): yield from _iter_semantic_classes(semantic_class.classes) -def _class_root_target_names(classes: list[models.SemanticClass]) -> frozenset[str]: - """Return native procedures consumed exclusively by completed class descriptors.""" - targets = { - method.native_name or method.name - for semantic_class in _iter_semantic_classes(classes) - for method in semantic_class.methods - if method.name != "__init__" or method.metadata.get("bind_target") - } - return frozenset(str(target) for target in targets) - - def _polymorphic_variant_map( classes: list[models.SemanticClass], ) -> dict[tuple[str, str], tuple[tuple[str, str], ...]]: @@ -859,6 +854,7 @@ def _complete_function( owner_path=f"{owner_path}.{argument.name}", ) if function.return_type is not None: + _validate_maybe_unallocated_return(function, owner_path) decision = default_ownership_policy.decide_semantic_type(function.return_type, OwnershipContext.result()) function.metadata[models.RESOLVED_RETURN_OWNERSHIP_POLICY_METADATA] = decision _complete_native_array_handle_result_policy(function, decision) @@ -1014,6 +1010,15 @@ def _complete_native_array_handle_result_policy( function.metadata[models.RESOLVED_NATIVE_ARRAY_HANDLE_POLICY_METADATA] = policy +def _validate_maybe_unallocated_return(function: models.SemanticFunction, owner_path: str) -> None: + """Require MaybeUnallocated only on direct allocatable array function results.""" + return_type = function.return_type + if return_type is None or not return_type.metadata.get(MAYBE_UNALLOCATED_METADATA): + return + if native_array_descriptor_kind(return_type) != "allocatable" or int(return_type.rank or 0) <= 0: + raise ValueError(f"MaybeUnallocated metadata on {owner_path}.return requires an Allocatable[...] array result") + + def _complete_native_array_handle_variable_policy( variable: models.SemanticVariable, context: OwnershipContext, @@ -1048,6 +1053,8 @@ def _native_array_handle_policy( blocker = _native_array_handle_blocker(descriptor_kind, handle_kind, decision) descriptor_ownership = _native_array_descriptor_ownership(handle_kind) to_numpy = _native_array_to_numpy_policy(descriptor_kind, handle_kind, decision, semantic_type) + operations = _native_array_handle_operations(descriptor_kind, handle_kind, context, semantic_type) + default_construction = _native_array_default_construction(handle_kind, context, semantic_type) return NativeArrayHandlePolicy( descriptor_kind=descriptor_kind, handle_kind=handle_kind, @@ -1060,6 +1067,7 @@ def _native_array_handle_policy( python_setter=_native_array_python_setter(variable), native_setter=_native_array_native_setter(variable), output_projection=_native_array_output_projection(descriptor_kind, handle_kind, context), + result_allocation=_native_array_result_allocation(descriptor_kind, handle_kind, context, semantic_type), release=_native_array_release_responsibility(handle_kind), target_lifetime=_native_array_target_lifetime(descriptor_kind, handle_kind, semantic_type, blocker), destroy_behavior=_native_array_destroy_behavior(handle_kind, blocker), @@ -1072,8 +1080,13 @@ def _native_array_handle_policy( nullable=bool(decision.nullable or optional_absent), optional_absent=optional_absent, storage_mode=decision.storage_mode.value, - operations=_native_array_handle_operations(descriptor_kind, handle_kind, context, semantic_type), + operations=operations, blocker=blocker, + default_construction=default_construction, + default_descriptor_ownership="owned" if default_construction != "none" else "unknown", + default_release="wrapper_dealloc" if default_construction != "none" else "none", + default_destroy_behavior="handle_finalizer" if default_construction != "none" else "none", + default_operations=(tuple(sorted({*operations, "destroy"})) if default_construction != "none" else ()), ) @@ -1090,16 +1103,31 @@ def _native_array_handle_kind( if context.is_field: return "borrowed_field_descriptor" if context.is_argument and context.projects_result and not context.python_visible: - if descriptor_kind == "allocatable": - return "owned_result_descriptor" - return "unsupported" + return "owned_result_descriptor" if context.is_argument: return "argument_descriptor" - if context.is_result and descriptor_kind == "allocatable": + if context.is_result: return "owned_result_descriptor" return "unsupported" +def _native_array_default_construction( + handle_kind: str, + context: OwnershipContext, + semantic_type: models.SemanticType, +) -> str: + """Complete how a runtime-constructed descriptor reaches one argument.""" + if ( + semantic_type.name == "String" + or handle_kind not in {"argument_descriptor", "optional_absent_handle"} + or not context.is_argument + ): + return "none" + if context.projects_result: + return "lazy_owned_descriptor" + return "fact_packed_empty" + + def _native_array_handle_origin(context: OwnershipContext) -> str: if context.is_module_variable: return "module_variable" @@ -1174,12 +1202,25 @@ def _native_array_output_projection( if handle_kind == "unsupported": return "unsupported" if context.is_result: - return "handle_result" if descriptor_kind == "allocatable" else "unsupported" + return "handle_result" if context.is_argument and context.projects_result: return "projected_handle" return "none" +def _native_array_result_allocation( + descriptor_kind: str, + handle_kind: str, + context: OwnershipContext, + semantic_type: models.SemanticType, +) -> str: + if context.is_result and descriptor_kind == "allocatable" and handle_kind == "owned_result_descriptor": + if semantic_type.metadata.get(MAYBE_UNALLOCATED_METADATA): + return "maybe_unallocated" + return "always_allocated" + return "not_applicable" + + def _native_array_release_responsibility(handle_kind: str) -> str: return { "argument_descriptor": "none", @@ -1203,6 +1244,8 @@ def _native_array_target_lifetime( pointer_lifetime = _pointer_policy_value(_pointer_policy_metadata(semantic_type), "lifetime") if pointer_lifetime: return pointer_lifetime + if handle_kind == "owned_result_descriptor": + return "unknown" if blocker is not None and handle_kind in {"borrowed_field_descriptor", "borrowed_module_descriptor"}: return "unknown" return { @@ -1273,7 +1316,7 @@ def _native_array_handle_operations( if not _is_deferred_character_array(semantic_type): operations.add("resize") return tuple(sorted(operations)) - operations = {"associated", "nullify", "to_numpy"} + operations = {"associate", "associated", "nullify", "to_numpy"} pointer_policy = _pointer_policy_metadata(semantic_type) if _pointer_policy_allows_allocate(pointer_policy): operations.add("allocate") @@ -1332,14 +1375,12 @@ def _pointer_policy_allows_resize(policy: dict[str, object]) -> bool: def _native_array_handle_blocker( - descriptor_kind: str, + _descriptor_kind: str, handle_kind: str, decision: OwnershipDecision, ) -> str | None: if decision.is_blocked: return decision.blocker or decision.reason - if handle_kind == "unsupported" and descriptor_kind == "pointer": - return "pointer handle results need stable owner storage and target lifetime policy before wrapping" if handle_kind == "unsupported": return "native array handle origin is unsupported before wrapper lowering" return None @@ -1530,6 +1571,10 @@ def _complete_variable( *, owner_path: str | None = None, ) -> None: + if variable.semantic_type.metadata.get(MAYBE_UNALLOCATED_METADATA): + raise ValueError( + f"MaybeUnallocated metadata on {owner_path or variable.name!r} is only valid on function return types" + ) decision = default_ownership_policy.decide_semantic_variable(variable, context) variable.metadata[models.RESOLVED_OWNERSHIP_POLICY_METADATA] = decision _complete_prototype_reference_policy(variable.semantic_type, owner_path=owner_path or variable.name) diff --git a/x2py/semantics/pyi2ir.py b/x2py/semantics/pyi2ir.py index 303738a30..ba6b15339 100644 --- a/x2py/semantics/pyi2ir.py +++ b/x2py/semantics/pyi2ir.py @@ -13,6 +13,7 @@ ADDRESS_ROLE_PROJECTION, ADDRESS_ROLE_RAW, BIND_TARGET_METADATA, + MAYBE_UNALLOCATED_METADATA, NATIVE_PROJECTION_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, PROJECTED_OUTPUT_METADATA, @@ -132,7 +133,6 @@ def __init__(self, *, module_name: str, source: str = "", native_language: str = def parse(self, tree: ast.Module) -> SemanticModule: _ModuleVisitor(self)._visit(tree) self._resolve_overloads() - self._restore_type_bound_targets() self._resolve_local_prototype_references() return self.module @@ -241,34 +241,12 @@ def class_def( visibility=visibility, origin=origin, ) - self._validate_bound_constructor_targets(semantic_class) self._pending_overloads.extend( _PendingOverload(semantic_class, declaration, target, generic_name) for declaration, target, generic_name in body.pending_overloads ) return semantic_class - @staticmethod - def _validate_bound_constructor_targets(semantic_class: SemanticClass) -> None: - for constructor in semantic_class.methods: - target_name = constructor.metadata.get(BIND_TARGET_METADATA) - if constructor.name != "__init__" or not isinstance(target_name, str): - continue - candidates = [ - method for method in semantic_class.methods if method is not constructor and method.name == target_name - ] - if not candidates: - raise ValueError(f"Bound constructor references missing class method {target_name!r}") - if len(candidates) > 1: - raise ValueError(f"Bound constructor target {target_name!r} is ambiguous") - target = candidates[0] - target_arguments = list(target.arguments) - if isinstance(target, SemanticMethod) and target.passed_object_position is not None: - target_arguments.pop(target.passed_object_position) - if constructor.arguments != target_arguments or constructor.return_type != target.return_type: - raise ValueError(f"Bound constructor declaration is incompatible with class method {target_name!r}") - constructor.native_name = target.native_name or target.name - @staticmethod def _class_metadata(base_classes: list[str]) -> dict[str, object]: metadata: dict[str, object] = {} @@ -402,8 +380,10 @@ def method_def( metadata[NATIVE_PROJECTION_METADATA] = True passed_object_name = None passed_object_position = None - if infer_passed_object and not is_static and node.name != "__init__": + if infer_passed_object and not is_static: pass_mappings = [mapping for mapping in actual_projection if mapping.value_kind == "pass"] + if node.name == "__init__" and len(pass_mappings) != 1: + raise ValueError("Bound constructor native_call requires exactly one Pass() entry") if len(pass_mappings) > 1: raise ValueError("native_call may contain at most one Pass() entry") passed_object_position = pass_mappings[0].native_position if pass_mappings else 0 @@ -487,8 +467,6 @@ def decorators(self, nodes: list[ast.expr], *, context: str) -> _Decorators: parsed = _Decorators() for node in nodes: self._apply_decorator(parsed, node, context=context) - if parsed.overload_target is not None and parsed.bind_target is not None: - raise ValueError("bind cannot be combined with overload") if parsed.overload_target is not None and parsed.has_native_call: raise ValueError("overload cannot be combined with native_call; put native_call on the specific procedure") if parsed.prototype and len(nodes) != 1: @@ -690,30 +668,6 @@ def _resolve_overloads(self) -> None: ) overload_set.procedures.append(candidate) - def _restore_type_bound_targets(self) -> None: - """Mark module procedures referenced by type-bound method declarations.""" - - by_name = { - target: function - for function in self.module.functions - for target in {function.name, function.native_name} - if target - } - for semantic_class in self._iter_classes(self.module.classes): - for method in semantic_class.methods: - if method.is_static or method.passed_object_position is None: - continue - target = by_name.get(method.native_name or method.name) - if target is None: - continue - passed_position = method.passed_object_position - if not 0 <= passed_position < len(target.arguments): - continue - target.metadata["fortran_type_bound_target"] = True - target.metadata["fortran_passed_object_name"] = target.arguments[passed_position].name - target.metadata["fortran_passed_object_position"] = passed_position - target.arguments[passed_position].semantic_type.metadata["fortran_polymorphic"] = True - @classmethod def _iter_classes(cls, classes: list[SemanticClass]): for semantic_class in classes: @@ -766,8 +720,12 @@ def _validated_overload_candidate( candidate.metadata[key] = deepcopy(declaration.metadata[key]) if isinstance(owner, SemanticModule): + if generic_name is not None: + raise ValueError("generic is only valid for class overloads; use bind on a module overload") self._validate_overload_signature(declaration, candidate, list(candidate.arguments)) - candidate.metadata[FORTRAN_GENERIC_NAME_METADATA] = generic_name or declaration.name + if bind_target := declaration.metadata.get(BIND_TARGET_METADATA): + candidate.native_name = str(bind_target) + candidate.metadata[BIND_TARGET_METADATA] = str(bind_target) candidate.metadata[OVERLOAD_KIND_METADATA] = "generic" return candidate @@ -786,6 +744,9 @@ def _validated_overload_candidate( candidate.metadata[FORTRAN_GENERIC_NAME_METADATA] = native_name candidate.metadata[OVERLOAD_KIND_METADATA] = kind candidate.metadata[PYTHON_METHOD_NAME_METADATA] = declaration.name + if bind_target := declaration.metadata.get(BIND_TARGET_METADATA): + candidate.native_name = str(bind_target) + candidate.metadata[BIND_TARGET_METADATA] = str(bind_target) if bound_position is not None: candidate.metadata[PYTHON_BOUND_POSITION_METADATA] = bound_position if isinstance(declaration, SemanticMethod) and declaration.is_static: @@ -1740,6 +1701,9 @@ def _apply_metadata_name(self, semantic_type: SemanticType, name: str) -> bool: if name == "Immutable": semantic_type.metadata[PYTHON_VALUE_MUTABILITY_METADATA] = PYTHON_VALUE_IMMUTABLE return True + if name == "MaybeUnallocated": + semantic_type.metadata[MAYBE_UNALLOCATED_METADATA] = True + return True if name == "FortranAllocatable": semantic_type.metadata["fortran_allocatable"] = True return True @@ -1929,16 +1893,64 @@ def _prototype_argument_spec(self, node: ast.expr) -> _CallbackArgumentSpec: semantic_type = self.semantic_type(node.args[0]) if semantic_type.rank > 0: raise ValueError("Value(...) callback arguments must be scalar") + if self._is_primitive_scalar_value_type(semantic_type): + raise ValueError( + "Value(...) is unnecessary for primitive callback arguments; " + "bare primitive types are passed by value" + ) + if ( + semantic_type.name == "String" + or semantic_type.storage is not None + or self._has_callback_descriptor_metadata(semantic_type) + ): + raise ValueError("Value(...) callback arguments are only valid for rank-zero wrapped types") return _CallbackArgumentSpec(semantic_type, True) if self._is_addr_call(node): - raise ValueError( - "Addr(...) is unnecessary inside prototype declarations; reference passing is the default" - ) + return self._prototype_address_argument_spec(node) semantic_type = self.semantic_type(node) + if self._is_primitive_scalar_value_type(semantic_type): + return _CallbackArgumentSpec(semantic_type, True) self._mark_callback_reference_type(semantic_type) return _CallbackArgumentSpec(semantic_type, False) + def _prototype_address_argument_spec(self, node: ast.Call) -> _CallbackArgumentSpec: + """Parse the callback-only primitive reference marker.""" + if len(node.args) != 1 or node.keywords: + raise ValueError(f"Addr type expects one callback argument type: {ast.unparse(node)!r}") + if self._addr_depth(node.func) != 1: + raise ValueError("Addr[...](...) is not supported inside prototype declarations; use Addr(T)") + semantic_type = self.semantic_type(node.args[0]) + if not self._is_primitive_scalar_value_type(semantic_type): + raise ValueError( + "Addr(...) inside prototype declarations is only valid for primitive scalar reference dummies; " + "arrays, strings, and wrapped objects already use reference storage" + ) + self._mark_callback_reference_type(semantic_type) + return _CallbackArgumentSpec(semantic_type, False) + + @staticmethod + def _is_primitive_scalar_value_type(semantic_type: SemanticType) -> bool: + return bool( + semantic_type.rank == 0 + and semantic_type.name not in {"String", "Void"} + and (semantic_type.dtype or semantic_type.name) in SEMANTIC_SCALAR_TYPE_NAMES + and semantic_type.storage is None + and not _PyiAstParser._has_callback_descriptor_metadata(semantic_type) + ) + + @staticmethod + def _has_callback_descriptor_metadata(semantic_type: SemanticType) -> bool: + return any( + semantic_type.metadata.get(name) + for name in ( + "fortran_allocatable", + "fortran_pointer", + "fortran_polymorphic", + "fortran_assumed_type", + ) + ) + @staticmethod def _mark_callback_reference_type(semantic_type: SemanticType) -> None: storage = semantic_type.storage @@ -2645,7 +2657,7 @@ def _visit_FunctionDef(self, node: ast.FunctionDef) -> None: hold_gil=decorators.hold_gil, error_status_policy=decorators.error_status_policy, ) - if node.name == "__init__" and decorators.bind_target is not None: + 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: self.pending_overloads.append((method, decorators.overload_target, decorators.overload_generic)) diff --git a/x2py/semantics/wrapper_policy.py b/x2py/semantics/wrapper_policy.py index 8fad3314a..a4c269f94 100644 --- a/x2py/semantics/wrapper_policy.py +++ b/x2py/semantics/wrapper_policy.py @@ -13,6 +13,7 @@ ADDRESS_ROLE_METADATA, ADDRESS_ROLE_RAW, BIND_TARGET_METADATA, + SCALAR_STORAGE_CATEGORY, SUPPRESS_DEFAULT_CONSTRUCTOR_METADATA, ) from x2py.semantics.native_array_handles import ( @@ -64,9 +65,7 @@ FIXED_STRING_RESULT_COPY_REASON = "copy fixed-length Fortran character output into C-owned null-terminated storage" ORDINARY_ARRAY_RESULT_COPY_REASON = "copy non-descriptor Fortran array output into C-owned contiguous storage" -OWNED_NATIVE_ARRAY_HANDLE_COPY_REASON = ( - "materialize native allocatable descriptor into persistent wrapper-owned CFI storage" -) +OWNED_NATIVE_ARRAY_HANDLE_COPY_REASON = "materialize native descriptor into persistent wrapper-owned CFI storage" SCALAR_DESCRIPTOR_RESULT_COPY_REASON = ( "copy a present rank-zero native descriptor value before releasing call-local descriptor storage" ) @@ -103,6 +102,13 @@ class ArgumentHandoffMode(str, Enum): NATIVE_DESCRIPTOR = "native_descriptor" +class ArgumentConversionPhase(str, Enum): + """Completed binding conversion schedule for one Python argument.""" + + IMMEDIATE = "immediate" + DEFERRED_REPLACEMENT = "deferred_replacement" + + class BridgeDataAction(str, Enum): """Completed bridge-side data movement for one boundary value.""" @@ -112,6 +118,18 @@ class BridgeDataAction(str, Enum): BLOCKED = "blocked" +_ARRAY_VALUE_OPTIONAL_MODES = frozenset({OptionalMode.REQUIRED, OptionalMode.NULLABLE_VALUE}) +_ARRAY_DESCRIPTOR_OPTIONAL_MODES = frozenset({OptionalMode.REQUIRED, OptionalMode.DESCRIPTOR}) +_ARRAY_VIEW_CODEGEN_ACTIONS = frozenset( + { + CodegenAction.CALL_LOCAL_INPUT, + CodegenAction.IN_PLACE_ARGUMENT, + CodegenAction.IDENTITY_OUTPUT, + } +) +_RAW_ARRAY_VIEW_CODEGEN_ACTIONS = frozenset({CodegenAction.CALL_LOCAL_INPUT, CodegenAction.IN_PLACE_ARGUMENT}) + + class WritebackPhase(str, Enum): """Ordered phases of one completed replacement writeback.""" @@ -549,6 +567,7 @@ class ConstructorPolicy: fields: tuple[ConstructorFieldPolicy, ...] target_owner_path: str | None overload_name: str | None + call: ClassMethodPolicy | None lifecycle: tuple[ConstructionLifecycleAction, ...] rejection_message: str | None = None @@ -645,6 +664,14 @@ class NativeDescriptorHandoffABI(str, Enum): OWNED_RESULT_STORAGE = "owned_result_storage" +class NativeArrayDefaultConstruction(str, Enum): + """Completed storage path for a runtime-constructed empty descriptor.""" + + NONE = "none" + FACT_PACKED_EMPTY = "fact_packed_empty" + LAZY_OWNED_DESCRIPTOR = "lazy_owned_descriptor" + + class NativeArraySourceKind(str, Enum): """Python runtime sources accepted by an ordinary array argument.""" @@ -696,6 +723,14 @@ class NativeArrayOutputProjection(str, Enum): HANDLE_RESULT = "handle_result" +class NativeArrayResultAllocation(str, Enum): + """Direct native allocatable function result allocation contract.""" + + NOT_APPLICABLE = "not_applicable" + ALWAYS_ALLOCATED = "always_allocated" + MAYBE_UNALLOCATED = "maybe_unallocated" + + class NativeArrayRelease(str, Enum): """Completed release owner for descriptor storage.""" @@ -748,6 +783,7 @@ class NativeArrayOperation(str, Enum): ALLOCATE = "allocate" DEALLOCATE = "deallocate" RESIZE = "resize" + ASSOCIATE = "associate" NULLIFY = "nullify" DESTROY = "destroy" @@ -832,6 +868,8 @@ class ArrayHandoffPolicy: order: str | None native_order: str | None contiguous: bool | None + flatten_python_storage: bool = False + flat_axis: int | None = None itemsize: int | None = None category: str | None = None extent_references: tuple[tuple[str, ...], ...] = () @@ -862,6 +900,8 @@ class NativeArrayActualPolicy: require_native_byte_order: bool require_aligned: bool require_contiguous: bool + flatten_storage: bool = False + flat_axis: int | None = None @dataclass(frozen=True) @@ -873,6 +913,17 @@ class NativeDescriptorHandoffPolicy: optional_presence: bool +@dataclass(frozen=True) +class NativeArrayDefaultHandlePolicy: + """Completed lifecycle for a caller-created empty descriptor handle.""" + + construction: NativeArrayDefaultConstruction + descriptor_ownership: NativeArrayDescriptorOwnership | None + release: NativeArrayRelease + destroy_behavior: NativeArrayDestroyBehavior + operations: tuple[NativeArrayOperation, ...] + + @dataclass(frozen=True) class NativeArrayHandleWrapperPolicy: """Typed wrapper-facing projection of completed native handle policy.""" @@ -888,6 +939,7 @@ class NativeArrayHandleWrapperPolicy: setter_action: SetterAction native_assignment: AssignmentMode output_projection: NativeArrayOutputProjection + result_allocation: NativeArrayResultAllocation release: NativeArrayRelease target_lifetime: str destroy_behavior: NativeArrayDestroyBehavior @@ -900,6 +952,7 @@ class NativeArrayHandleWrapperPolicy: required_headers: tuple[str, ...] array: ArrayHandoffPolicy handoff: NativeDescriptorHandoffPolicy + default_handle: NativeArrayDefaultHandlePolicy @dataclass(frozen=True) @@ -979,6 +1032,7 @@ class ArgumentPolicy: rank: int optional: bool optional_mode: OptionalMode + conversion_phase: ArgumentConversionPhase handoff_mode: ArgumentHandoffMode bridge_data_action: BridgeDataAction bridge_copy_reason: str | None @@ -1010,6 +1064,7 @@ class _ArgumentBoundaryPolicy: """Normalized wrapper-boundary fields for one ordinary or callback input.""" optional_mode: OptionalMode + conversion_phase: ArgumentConversionPhase handoff_mode: ArgumentHandoffMode nullable: bool writable: bool @@ -1429,19 +1484,24 @@ def _class_constructor_policy( fields=(), target_owner_path=None, overload_name=overload.name, + call=None, lifecycle=lifecycle, ), tuple(blockers), ) if bound: method = bound[0] - target_name = str(method.metadata[BIND_TARGET_METADATA]) + call = replace(_class_method_policy(owner_path, method), public=False) + blocker = _class_method_blockers(call) + if blocker: + blockers.append(blocker) return ( ConstructorPolicy( kind=ClassConstructorKind.BOUND_PROCEDURE, fields=(), - target_owner_path=f"{owner_path}.{target_name}", + target_owner_path=call.owner_path, overload_name=None, + call=call, lifecycle=lifecycle, ), tuple(blockers), @@ -1453,6 +1513,7 @@ def _class_constructor_policy( fields=(), target_owner_path=None, overload_name=None, + call=None, lifecycle=(), rejection_message=(f"{semantic_class.name} has no public constructor in the edited .pyi contract"), ), @@ -1477,6 +1538,7 @@ def _class_constructor_policy( fields=fields, target_owner_path=None, overload_name=None, + call=None, lifecycle=lifecycle, ), tuple(blockers), @@ -2165,6 +2227,13 @@ def _callback_transfer_blockers( """Reject callback forms whose typed adapter ABI is incomplete.""" semantic_type = argument.semantic_type blockers = list(_runtime_semantic_validation_blockers(semantic_type, f"callback argument {argument.name!r}")) + if argument.optional: + blockers.append(f"callback argument {argument.name!r} cannot be optional") + if _uses_unsupported_callback_descriptor(semantic_type): + blockers.append( + f"callback argument {argument.name!r} uses unsupported allocatable, pointer, " + "polymorphic, or assumed-type storage" + ) if transfer.passed_by_value and transfer.rank > 0: blockers.append(f"callback argument {argument.name!r} cannot pass an array by value") if semantic_type.name == "String": @@ -2175,13 +2244,7 @@ def _callback_transfer_blockers( blockers.append(f"callback argument {argument.name!r} has incomplete array shape") if semantic_type.name not in _PLAN_PRIMITIVE_SCALAR_TYPES: blockers.append(f"callback argument {argument.name!r} is an unsupported array of derived values") - elif transfer.derived_type_identity is not None: - if any( - semantic_type.metadata.get(name) - for name in ("fortran_allocatable", "fortran_pointer", "fortran_polymorphic") - ): - blockers.append(f"callback argument {argument.name!r} uses unsupported derived descriptor storage") - elif semantic_type.name not in _PLAN_PRIMITIVE_SCALAR_TYPES: + elif transfer.derived_type_identity is None and semantic_type.name not in _PLAN_PRIMITIVE_SCALAR_TYPES: blockers.append(f"callback argument {argument.name!r} has unsupported type {semantic_type.name!r}") return tuple(blockers) @@ -2240,6 +2303,8 @@ def _callback_result_blockers( return ("callback result is missing a completed semantic type",) transfer = result.transfer blockers = list(_runtime_semantic_validation_blockers(return_type, "callback result")) + if _uses_unsupported_callback_descriptor(return_type): + blockers.append("callback result uses unsupported allocatable, pointer, polymorphic, or assumed-type storage") if result.action is CallbackResultAction.REJECT_RESULT: blockers.append(f"callback result type {return_type.name!r} is unsupported") if result.action is CallbackResultAction.RETURN_ARRAY_ADDRESS: @@ -2247,13 +2312,22 @@ def _callback_result_blockers( blockers.append("callback array result requires a complete fixed shape") if return_type.name not in _PLAN_PRIMITIVE_SCALAR_TYPES: blockers.append("callback array result must contain a primitive scalar type") - if result.action is CallbackResultAction.RETURN_DERIVED_ADDRESS and any( - return_type.metadata.get(name) for name in ("fortran_allocatable", "fortran_pointer", "fortran_polymorphic") - ): - blockers.append("callback derived result uses unsupported descriptor storage") return tuple(blockers) +def _uses_unsupported_callback_descriptor(semantic_type: models.SemanticType) -> bool: + """Return whether callback lowering lacks the native descriptor ABI.""" + return any( + semantic_type.metadata.get(name) + for name in ( + "fortran_allocatable", + "fortran_pointer", + "fortran_polymorphic", + "fortran_assumed_type", + ) + ) + + def build_function_wrapper_policy( function: models.SemanticFunction, *, @@ -2484,7 +2558,8 @@ def _argument_policy( derived, polymorphic_variants, owner_path=argument_path, - force=_is_passed_object_argument(class_call, native_position), + force=_is_passed_object_argument(class_call, native_position) + or _is_exported_passed_object_argument(function, native_position), ) bridge_data_action, bridge_copy_reason = _completed_argument_bridge_action( decision, @@ -2518,6 +2593,7 @@ def _argument_policy( rank=int(argument.semantic_type.rank or 0), optional=argument.optional, optional_mode=boundary.optional_mode, + conversion_phase=boundary.conversion_phase, handoff_mode=boundary.handoff_mode, bridge_data_action=bridge_data_action, bridge_copy_reason=bridge_copy_reason, @@ -2598,6 +2674,14 @@ def _is_passed_object_argument(class_call: ClassMethodPolicy | None, native_posi ) +def _is_exported_passed_object_argument(function: models.SemanticFunction, native_position: int) -> bool: + """Reuse passed-object dispatch when its native procedure is also exported.""" + return bool( + function.metadata.get("fortran_type_bound_target") + and function.metadata.get("fortran_passed_object_position") == native_position + ) + + def _completed_argument_bridge_action( decision: OwnershipDecision, optional_mode: OptionalMode, @@ -2627,6 +2711,7 @@ def _argument_boundary_policy( if callback is not None: return _ArgumentBoundaryPolicy( optional_mode=OptionalMode.REQUIRED, + conversion_phase=ArgumentConversionPhase.IMMEDIATE, handoff_mode=ArgumentHandoffMode.VALUE, nullable=False, writable=False, @@ -2641,6 +2726,7 @@ def _argument_boundary_policy( ) return _ArgumentBoundaryPolicy( optional_mode=_optional_mode(argument, decision), + conversion_phase=_argument_conversion_phase(decision), handoff_mode=_argument_handoff_mode(decision), nullable=decision.nullable, # COPY_RETURN mutates a binding-owned replacement rather than the @@ -2657,6 +2743,16 @@ def _argument_boundary_policy( ) +def _argument_conversion_phase(decision: OwnershipDecision) -> ArgumentConversionPhase: + """Schedule stack scalar replacements before allocated replacements.""" + if ( + decision.codegen_action is CodegenAction.COPY_IN_OUT + and decision.python_barrier_action is not PythonBarrierAction.SCALAR_VALUE + ): + return ArgumentConversionPhase.DEFERRED_REPLACEMENT + return ArgumentConversionPhase.IMMEDIATE + + def _completed_argument_blockers( argument: models.SemanticArgument, decision: OwnershipDecision, @@ -2751,6 +2847,11 @@ def _result_policies( ) scalar_descriptor = _scalar_descriptor_result_policy(function.return_type, decision) blockers = list(_result_blockers(function.return_type, decision)) + if scalar_descriptor is not None and scalar_descriptor.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE: + blockers.append( + "direct allocatable scalar function results cannot preserve unallocated state; " + "use an allocatable hidden output projection" + ) bridge_data_action, bridge_copy_reason = _result_bridge_data_action(function.return_type) if bridge_data_action is BridgeDataAction.BLOCKED and decision.kind is not ObjectKind.SCALAR: blockers.append("result has no completed bridge data action") @@ -3775,7 +3876,7 @@ def _argument_shape_blockers( """Dispatch one argument to its scalar/string or array policy family.""" if decision.kind is ObjectKind.DERIVED_TYPE: return _derived_argument_shape_blockers(argument, decision, polymorphic) - if int(argument.semantic_type.rank or 0) > 0: + if decision.kind is ObjectKind.NUMPY_ARRAY: return _array_argument_shape_blockers(argument, decision) return _scalar_or_string_argument_shape_blockers(argument, decision) @@ -3970,6 +4071,8 @@ def _array_boundary_blockers( ) -> tuple[str, ...]: """Dispatch one completed array boundary without backend inference.""" action = decision.python_barrier_action + if action is PythonBarrierAction.SCALAR_STORAGE: + return _scalar_storage_array_boundary_blockers(argument, decision) if action is PythonBarrierAction.ARRAY_STORAGE: return _array_storage_boundary_blockers(argument, decision) if action is PythonBarrierAction.RAW_ADDRESS: @@ -3979,6 +4082,51 @@ def _array_boundary_blockers( return (f"argument {argument.name!r} has unsupported array Python action {action.value}",) +def _scalar_storage_array_boundary_blockers( + argument: models.SemanticArgument, + decision: OwnershipDecision, +) -> tuple[str, ...]: + """Require one rank-zero NumPy storage handoff to a scalar native dummy.""" + blockers = [] + if decision.owner is not OwnershipOwner.CALLER: + blockers.append(f"argument {argument.name!r} scalar-storage owner is {decision.owner.value}, not caller") + expected_transfer = TransferMode.IN_PLACE if decision.mutates_native else TransferMode.CALL_LOCAL + if decision.transfer is not expected_transfer: + blockers.append( + f"argument {argument.name!r} scalar-storage transfer is " + f"{decision.transfer.value}, not {expected_transfer.value}" + ) + expected_destruction = DestructionPolicy.CALLER if decision.mutates_native else DestructionPolicy.NONE + if decision.destruction is not expected_destruction: + blockers.append( + f"argument {argument.name!r} scalar-storage destruction is " + f"{decision.destruction.value}, not {expected_destruction.value}" + ) + if decision.storage_mode is not StorageMode.STACK: + blockers.append( + f"argument {argument.name!r} scalar-storage storage is {decision.storage_mode.value}, not stack" + ) + if (decision.boundary_storage_mode or decision.storage_mode) is not StorageMode.STACK: + blockers.append(f"argument {argument.name!r} scalar-storage boundary storage is not stack") + if decision.native_barrier_action is not NativeBarrierAction.PASS_STORAGE_ADDRESS: + blockers.append(f"argument {argument.name!r} scalar storage does not use its storage address") + if decision.codegen_action not in { + CodegenAction.CALL_LOCAL_INPUT, + CodegenAction.IN_PLACE_ARGUMENT, + CodegenAction.IDENTITY_OUTPUT, + }: + blockers.append( + f"argument {argument.name!r} scalar-storage action is " + f"{decision.codegen_action.value}, not a storage-address action" + ) + if decision.descriptor_boundary: + blockers.append(f"argument {argument.name!r} scalar storage must be non-descriptor storage") + array_policy = _array_handoff_policy(argument.semantic_type) + if not _is_scalar_storage_array_policy(array_policy): + blockers.append(f"argument {argument.name!r} is not rank-zero scalar storage") + return tuple(blockers) + + def _array_storage_boundary_blockers( argument: models.SemanticArgument, decision: OwnershipDecision, @@ -4322,11 +4470,7 @@ def _result_blockers(semantic_type: models.SemanticType, decision: OwnershipDeci family_blockers = _scalar_descriptor_result_blockers(semantic_type, decision, "result") else: descriptor_kind = native_array_descriptor_kind(semantic_type) - if descriptor_kind == "pointer": - family_blockers = ( - "pointer handle results need stable owner storage and target lifetime policy before wrapping", - ) - elif descriptor_kind is not None: + if descriptor_kind is not None: family_blockers = _native_array_handle_result_blockers(decision, "result") elif _is_phase6_ordinary_array_type(semantic_type): family_blockers = _ordinary_array_result_blockers(semantic_type, decision, "result") @@ -4425,11 +4569,7 @@ def _hidden_result_blockers( ) else: descriptor_kind = native_array_descriptor_kind(argument.semantic_type) - if descriptor_kind == "pointer": - family_blockers = ( - "pointer handle results need stable owner storage and target lifetime policy before wrapping", - ) - elif descriptor_kind is not None: + if descriptor_kind is not None: family_blockers = _native_array_handle_result_blockers(decision, f"hidden result {argument.name!r}") elif _is_phase6_ordinary_array_type(argument.semantic_type): family_blockers = _ordinary_array_hidden_result_blockers(argument, decision, mapping) @@ -4538,7 +4678,7 @@ def _native_array_handle_result_blockers( decision: OwnershipDecision, label: str, ) -> tuple[str, ...]: - """Require one wrapper-owned allocatable handle result.""" + """Require one wrapper-owned native descriptor handle result.""" blockers = [] if decision.is_blocked: blockers.append(f"{label} has blocked ownership policy: {decision.blocker or decision.reason}") @@ -4556,7 +4696,7 @@ def _native_array_handle_result_blockers( if actual is not required ) if not decision.nullable: - blockers.append(f"{label} native handle must preserve unallocated state") + blockers.append(f"{label} native handle must preserve an absent descriptor state") return tuple(blockers) @@ -4607,8 +4747,13 @@ def _ordinary_array_hidden_result_blockers( label = f"hidden result {argument.name!r}" blockers = list(_ordinary_array_result_blockers(argument.semantic_type, decision, label)) blockers = [item for item in blockers if " native action is " not in item] - if decision.native_barrier_action is not NativeBarrierAction.PASS_ARRAY_BUFFER: - blockers.append(f"{label} native action is {decision.native_barrier_action.value}, not array buffer") + expected_native = ( + NativeBarrierAction.PASS_STORAGE_ADDRESS + if _is_scalar_storage_array_policy(_array_handoff_policy(argument.semantic_type)) + else NativeBarrierAction.PASS_ARRAY_BUFFER + ) + if decision.native_barrier_action is not expected_native: + blockers.append(f"{label} native action is {decision.native_barrier_action.value}, not {expected_native.value}") if decision.python_visible or not decision.projects_result: blockers.append(f"{label} projection visibility is inconsistent") if not isinstance(mapping.native_position, int): @@ -4865,11 +5010,18 @@ def _is_first_lane_scalar_type(semantic_type: models.SemanticType) -> bool: scalar_name = semantic_type.dtype or semantic_type.name return bool( int(semantic_type.rank or 0) == 0 + and not _is_scalar_storage_type(semantic_type) and semantic_type.name != "String" and scalar_name in _PLAN_PRIMITIVE_SCALAR_TYPES ) +def _is_scalar_storage_type(semantic_type: models.SemanticType) -> bool: + storage = semantic_type.storage + array = storage.array if storage is not None else None + return bool(array is not None and array.category == SCALAR_STORAGE_CATEGORY) + + def _is_plan_string_value_type(semantic_type: models.SemanticType) -> bool: """Return whether one semantic type is a scalar Python string value.""" return bool(int(semantic_type.rank or 0) == 0 and semantic_type.name == "String") @@ -5025,6 +5177,12 @@ def _native_array_handle_wrapper_policy( setter_action=_native_array_setter_action(completed.python_setter, owner_path), native_assignment=_native_array_assignment(completed.native_setter, owner_path), output_projection=output_projection, + result_allocation=_native_array_enum( + NativeArrayResultAllocation, + completed.result_allocation, + owner_path, + "result allocation", + ), release=_native_array_enum(NativeArrayRelease, completed.release, owner_path, "release"), target_lifetime=completed.target_lifetime, destroy_behavior=_native_array_enum( @@ -5059,6 +5217,67 @@ def _native_array_handle_wrapper_policy( ), array=array, handoff=handoff, + default_handle=_native_array_default_handle_policy(completed, operations, owner_path), + ) + + +def _native_array_default_handle_policy( + completed: CompletedNativeArrayHandlePolicy, + operations: set[NativeArrayOperation], + owner_path: str, +) -> NativeArrayDefaultHandlePolicy: + """Translate completed caller-construction lifecycle selectors.""" + construction = _native_array_enum( + NativeArrayDefaultConstruction, + completed.default_construction, + owner_path, + "default construction", + ) + if construction is NativeArrayDefaultConstruction.NONE: + descriptor_ownership = None + else: + descriptor_ownership = _native_array_enum( + NativeArrayDescriptorOwnership, + completed.default_descriptor_ownership, + owner_path, + "default descriptor ownership", + ) + default_operations = { + _native_array_enum(NativeArrayOperation, item, owner_path, "default operation") + for item in completed.default_operations + } + if construction is not NativeArrayDefaultConstruction.NONE: + default_operations.update( + operation + for operation in operations + if operation + in { + NativeArrayOperation.SHAPE, + NativeArrayOperation.ARRAY_ACTUAL, + NativeArrayOperation.DESCRIPTOR, + NativeArrayOperation.NATIVE_BYTE_ORDER, + NativeArrayOperation.ALIGNED, + NativeArrayOperation.WRITEABLE, + NativeArrayOperation.LAYOUT, + NativeArrayOperation.CONTIGUOUS, + } + ) + return NativeArrayDefaultHandlePolicy( + construction=construction, + descriptor_ownership=descriptor_ownership, + release=_native_array_enum( + NativeArrayRelease, + completed.default_release, + owner_path, + "default release", + ), + destroy_behavior=_native_array_enum( + NativeArrayDestroyBehavior, + completed.default_destroy_behavior, + owner_path, + "default destroy behavior", + ), + operations=tuple(sorted(default_operations, key=lambda item: item.value)), ) @@ -5254,6 +5473,8 @@ def _native_array_actual_policy( require_native_byte_order=True, require_aligned=True, require_contiguous=array.contiguous is True, + flatten_storage=array.flatten_python_storage, + flat_axis=array.flat_axis, ) @@ -5586,43 +5807,72 @@ def _array_argument_bridge_data_action( optional_mode: OptionalMode, ) -> tuple[BridgeDataAction, str | None]: """Complete one buffer, raw-address, or native-descriptor bridge view.""" - if ( + if _scalar_storage_array_bridge_uses_view(decision, optional_mode): + return BridgeDataAction.ASSOCIATE_VIEW, None + if _copy_in_out_array_bridge_uses_view(decision, optional_mode): + return BridgeDataAction.ASSOCIATE_VIEW, None + native_descriptor_action = _native_descriptor_array_bridge_data_action(decision, optional_mode) + if native_descriptor_action is not None: + return native_descriptor_action, None + if _raw_array_address_bridge_uses_view(decision, optional_mode): + return BridgeDataAction.ASSOCIATE_VIEW, None + if _array_storage_bridge_uses_view(decision, optional_mode): + return BridgeDataAction.ASSOCIATE_VIEW, None + return BridgeDataAction.BLOCKED, None + + +def _scalar_storage_array_bridge_uses_view(decision: OwnershipDecision, optional_mode: OptionalMode) -> bool: + return ( + optional_mode in _ARRAY_VALUE_OPTIONAL_MODES + and decision.python_barrier_action is PythonBarrierAction.SCALAR_STORAGE + and decision.native_barrier_action is NativeBarrierAction.PASS_STORAGE_ADDRESS + and decision.codegen_action in _ARRAY_VIEW_CODEGEN_ACTIONS + ) + + +def _copy_in_out_array_bridge_uses_view(decision: OwnershipDecision, optional_mode: OptionalMode) -> bool: + return ( optional_mode is OptionalMode.REQUIRED and decision.python_barrier_action is PythonBarrierAction.ARRAY_STORAGE and decision.native_barrier_action is NativeBarrierAction.PASS_ARRAY_BUFFER and decision.codegen_action is CodegenAction.COPY_IN_OUT and decision.transfer is TransferMode.COPY_RETURN - ): - return BridgeDataAction.ASSOCIATE_VIEW, None - if ( - optional_mode in {OptionalMode.REQUIRED, OptionalMode.DESCRIPTOR} - and decision.python_barrier_action is PythonBarrierAction.WRAPPER_INSTANCE - and decision.native_barrier_action is NativeBarrierAction.PASS_NATIVE_DESCRIPTOR - ): - if decision.codegen_action is CodegenAction.CALL_LOCAL_INPUT: - return BridgeDataAction.ASSOCIATE_VIEW, None - if decision.codegen_action is CodegenAction.IN_PLACE_ARGUMENT: - return BridgeDataAction.DIRECT_TRANSFER, None - if ( + ) + + +def _native_descriptor_array_bridge_data_action( + decision: OwnershipDecision, + optional_mode: OptionalMode, +) -> BridgeDataAction | None: + if optional_mode not in _ARRAY_DESCRIPTOR_OPTIONAL_MODES: + return None + if decision.python_barrier_action is not PythonBarrierAction.WRAPPER_INSTANCE: + return None + if decision.native_barrier_action is not NativeBarrierAction.PASS_NATIVE_DESCRIPTOR: + return None + if decision.codegen_action is CodegenAction.CALL_LOCAL_INPUT: + return BridgeDataAction.ASSOCIATE_VIEW + if decision.codegen_action is CodegenAction.IN_PLACE_ARGUMENT: + return BridgeDataAction.DIRECT_TRANSFER + return None + + +def _raw_array_address_bridge_uses_view(decision: OwnershipDecision, optional_mode: OptionalMode) -> bool: + return ( optional_mode is OptionalMode.REQUIRED and decision.python_barrier_action is PythonBarrierAction.RAW_ADDRESS and decision.native_barrier_action is NativeBarrierAction.PASS_RAW_ADDRESS - and decision.codegen_action in {CodegenAction.CALL_LOCAL_INPUT, CodegenAction.IN_PLACE_ARGUMENT} - ): - return BridgeDataAction.ASSOCIATE_VIEW, None - if ( - optional_mode in {OptionalMode.REQUIRED, OptionalMode.NULLABLE_VALUE} + and decision.codegen_action in _RAW_ARRAY_VIEW_CODEGEN_ACTIONS + ) + + +def _array_storage_bridge_uses_view(decision: OwnershipDecision, optional_mode: OptionalMode) -> bool: + return ( + optional_mode in _ARRAY_VALUE_OPTIONAL_MODES and decision.python_barrier_action is PythonBarrierAction.ARRAY_STORAGE and decision.native_barrier_action is NativeBarrierAction.PASS_ARRAY_BUFFER - and decision.codegen_action - in { - CodegenAction.CALL_LOCAL_INPUT, - CodegenAction.IN_PLACE_ARGUMENT, - CodegenAction.IDENTITY_OUTPUT, - } - ): - return BridgeDataAction.ASSOCIATE_VIEW, None - return BridgeDataAction.BLOCKED, None + and decision.codegen_action in _ARRAY_VIEW_CODEGEN_ACTIONS + ) # String bridge data policy. @@ -5702,7 +5952,7 @@ def _result_bridge_data_action( ) if _is_scalar_descriptor_result_type(semantic_type, descriptor_kind=descriptor_kind): return BridgeDataAction.COPY_REPRESENTATION, SCALAR_DESCRIPTOR_RESULT_COPY_REASON - if native_array_descriptor_kind(semantic_type) == "allocatable": + if native_array_descriptor_kind(semantic_type) is not None: return BridgeDataAction.COPY_REPRESENTATION, OWNED_NATIVE_ARRAY_HANDLE_COPY_REASON if _is_first_lane_scalar_type(semantic_type): return BridgeDataAction.DIRECT_TRANSFER, None @@ -5753,7 +6003,7 @@ def _native_result_bridge_data_action( ) if _is_scalar_descriptor_result_type(semantic_type, descriptor_kind=descriptor_kind): return BridgeDataAction.COPY_REPRESENTATION, SCALAR_DESCRIPTOR_RESULT_COPY_REASON - if native_array_descriptor_kind(semantic_type) == "allocatable": + if native_array_descriptor_kind(semantic_type) is not None: return BridgeDataAction.COPY_REPRESENTATION, OWNED_NATIVE_ARRAY_HANDLE_COPY_REASON if _is_first_lane_scalar_type(semantic_type): return BridgeDataAction.DIRECT_TRANSFER, None @@ -5773,6 +6023,8 @@ def _argument_handoff_mode(decision: OwnershipDecision) -> ArgumentHandoffMode: return ArgumentHandoffMode.OPAQUE_ADDRESS if decision.python_barrier_action is PythonBarrierAction.RAW_ADDRESS: return ArgumentHandoffMode.OPAQUE_ADDRESS + if decision.python_barrier_action is PythonBarrierAction.SCALAR_STORAGE: + return ArgumentHandoffMode.OPAQUE_ADDRESS if decision.native_barrier_action is NativeBarrierAction.PASS_NATIVE_DESCRIPTOR: return ArgumentHandoffMode.NATIVE_DESCRIPTOR if decision.kind is ObjectKind.NUMPY_ARRAY: @@ -5803,9 +6055,11 @@ def _array_handoff_policy(semantic_type: models.SemanticType) -> ArrayHandoffPol array = storage.array if storage is not None else None if array is None: return None + if semantic_type.name == "String" and array.category == SCALAR_STORAGE_CATEGORY: + return None assumed_rank = array.category == "assumed_rank" rank = _array_handoff_rank(semantic_type, array.rank, assumed_rank) - if rank is not None and rank <= 0: + if rank is not None and rank <= 0 and not (rank == 0 and array.category == SCALAR_STORAGE_CATEGORY): return None shape = tuple(str(item) for item in (array.shape or semantic_type.shape)) axes = tuple(str(item) for item in array.axes) @@ -5815,7 +6069,9 @@ def _array_handoff_policy(semantic_type: models.SemanticType) -> ArrayHandoffPol axes=axes, order=_array_handoff_order(array.order, assumed_rank), native_order=_array_handoff_native_order(array.order, array.copy_order, assumed_rank), - contiguous=_array_handoff_contiguous(array.contiguous, assumed_rank), + contiguous=_array_handoff_contiguous(array.contiguous, assumed_rank, array.category), + flatten_python_storage=_array_handoff_flattens_python_storage(array), + flat_axis=_array_handoff_flat_axis(array), itemsize=_array_handoff_itemsize(semantic_type), category=array.category, extent_references=tuple(_array_extent_references(item) for item in shape), @@ -5851,13 +6107,30 @@ def _array_handoff_native_order( return copy_order if copy_order is not None else order -def _array_handoff_contiguous(contiguous: bool | None, assumed_rank: bool) -> bool | None: +def _array_handoff_contiguous(contiguous: bool | None, assumed_rank: bool, category: str | None) -> bool | None: """Default assumed-rank handoff to one contiguous native buffer.""" + if category == SCALAR_STORAGE_CATEGORY and contiguous is None: + return True if assumed_rank and contiguous is None: return True return contiguous +def _array_handoff_flattens_python_storage(array: models.SemanticArrayContract) -> bool: + """Return whether Python may flatten a contiguous actual through one flat edge.""" + return bool(array.category == "assumed_size" and _array_handoff_flat_axis(array) is not None) + + +def _array_handoff_flat_axis(array: models.SemanticArrayContract) -> int | None: + """Return the concrete flat-edge axis completed by semantic conversion.""" + if array.category != "assumed_size": + return None + for axis, dimension in enumerate(array.source_shape): + if "*" in str(dimension): + return axis + return None + + def _array_handoff_itemsize(semantic_type: models.SemanticType) -> int | None: """Carry fixed character width only for string array elements.""" if semantic_type.name == "String": @@ -5874,13 +6147,15 @@ def _is_phase6_ordinary_array_type(semantic_type: models.SemanticType) -> bool: return False storage = semantic_type.storage array = storage.array if storage is not None else None + scalar_storage = _is_scalar_storage_array_policy(array_policy) + supported_element = semantic_type.name in _PLAN_PRIMITIVE_SCALAR_TYPES or ( + semantic_type.name == "String" and array_policy.itemsize is not None and not scalar_storage + ) + supported_rank = array_policy.rank is None or 1 <= array_policy.rank <= 15 or scalar_storage return bool( array is not None - and ( - semantic_type.name in _PLAN_PRIMITIVE_SCALAR_TYPES - or (semantic_type.name == "String" and array_policy.itemsize is not None) - ) - and (array_policy.rank is None or 1 <= array_policy.rank <= 15) + and supported_element + and supported_rank and (array_policy.rank is None or len(array_policy.shape) == array_policy.rank) and (array_policy.rank is None or len(array_policy.axes) == array_policy.rank) and not array.allocatable @@ -5888,6 +6163,12 @@ def _is_phase6_ordinary_array_type(semantic_type: models.SemanticType) -> bool: ) +def _is_scalar_storage_array_policy(array_policy: ArrayHandoffPolicy | None) -> bool: + return bool( + array_policy is not None and array_policy.rank == 0 and array_policy.category == SCALAR_STORAGE_CATEGORY + ) + + def _is_phase6_raw_array_address_type(semantic_type: models.SemanticType) -> bool: """Return whether one type is a supported concrete raw array pointee.""" if not _is_raw_array_address_type(semantic_type): diff --git a/x2py/types/numpy.py b/x2py/types/numpy.py index 717e056f3..79cc41dfd 100644 --- a/x2py/types/numpy.py +++ b/x2py/types/numpy.py @@ -18,6 +18,7 @@ "UInt16": "numpy.uint16", "UInt32": "numpy.uint32", "UInt64": "numpy.uint64", + "Float16": "numpy.float16", "Float32": "numpy.float32", "Float64": "numpy.float64", "Float128": "numpy.longdouble", @@ -34,7 +35,6 @@ "Byte", "CEnum", "Char", - "Float16", "Int", "UInt", "Void", diff --git a/x2py/wrapper_codegen/__init__.py b/x2py/wrapper_codegen/__init__.py index a9d5e5c39..78a1fb74b 100644 --- a/x2py/wrapper_codegen/__init__.py +++ b/x2py/wrapper_codegen/__init__.py @@ -69,6 +69,7 @@ ModulePlan, ModuleVariablePlan, NativeArrayActualPlan, + NativeArrayDefaultHandlePlan, NativeArrayHandlePlan, NativeCallSlotPlan, NativeDescriptorHandoffPlan, @@ -151,6 +152,7 @@ "ModuleVariablePlan", "NamespacePlan", "NativeArrayActualPlan", + "NativeArrayDefaultHandlePlan", "NativeArrayHandlePlan", "NativeCallSlotPlan", "NativeDescriptorHandoffPlan", diff --git a/x2py/wrapper_codegen/c/binding.py b/x2py/wrapper_codegen/c/binding.py index 3c2bf7974..5cbc022db 100644 --- a/x2py/wrapper_codegen/c/binding.py +++ b/x2py/wrapper_codegen/c/binding.py @@ -11,7 +11,9 @@ PythonBarrierAction, SetterAction, ) +from x2py.semantics.metadata import SCALAR_STORAGE_CATEGORY from x2py.semantics.wrapper_policy import ( + ArgumentConversionPhase, ArgumentHandoffMode, BridgeDataAction, CallbackABIKind, @@ -33,6 +35,7 @@ ModuleGetterAction, NativeArrayDescriptorKind, NativeArrayDescriptorInterop, + NativeArrayDefaultConstruction, NativeArrayOperation, NativeDescriptorHandoffABI, OptionalMode, @@ -87,6 +90,8 @@ ModulePlan, ModuleVariablePlan, NamespacePlan, + NativeArrayActualPlan, + NativeArrayHandlePlan, NativeCallSlotPlan, ResultPlan, ) @@ -392,8 +397,26 @@ def _require_array_argument_supported(self, argument: ArgumentTransferPlan) -> N if argument.binding.python_action is PythonBarrierAction.RAW_ADDRESS: self._require_raw_array_argument_supported(argument) return + if argument.binding.python_action is PythonBarrierAction.SCALAR_STORAGE: + self._require_scalar_storage_array_argument_supported(argument) + return self._require_array_buffer_argument_supported(argument) + def _require_scalar_storage_array_argument_supported(self, argument: ArgumentTransferPlan) -> None: + """Require one rank-zero NumPy storage handoff to a scalar native dummy.""" + array = argument.array + if not self._is_scalar_storage_array(array): + raise ValueError(f"Unsupported C scalar-storage array rank for {argument.owner_path!r}") + if argument.bridge.handoff_mode is not ArgumentHandoffMode.OPAQUE_ADDRESS: + raise ValueError(f"Unsupported C scalar-storage handoff for {argument.owner_path!r}") + if argument.bridge.data_action is not BridgeDataAction.ASSOCIATE_VIEW: + raise ValueError(f"Unsupported C scalar-storage data action for {argument.owner_path!r}") + PrimitiveScalarTypeRegistry.type_for(argument.semantic_type_name) + + @staticmethod + def _is_scalar_storage_array(array) -> bool: + return bool(array is not None and array.rank == 0 and array.category == SCALAR_STORAGE_CATEGORY) + def _require_native_array_handle_argument_supported(self, argument: ArgumentTransferPlan) -> None: """Require one typed standard-descriptor argument handoff.""" handle = argument.native_array_handle @@ -469,11 +492,10 @@ def _require_array_binding_result_supported(self, result: ResultPlan) -> None: PrimitiveScalarTypeRegistry.type_for(result.semantic_type_name) def _require_owned_native_array_result_supported(self, result: ResultPlan) -> None: - """Require one wrapper-owned allocatable result descriptor.""" + """Require one wrapper-owned standard result descriptor.""" handle = result.native_array_handle if ( handle is None - or handle.descriptor_kind is not NativeArrayDescriptorKind.ALLOCATABLE or handle.handoff.abi is not NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE or handle.array.rank is None ): @@ -1775,12 +1797,12 @@ def _derived_origin_declarations(self, plan: ModulePlan) -> tuple: CDeclaration( self._derived_origin_active_name(variable), "static atomic_bool", - CodeExpression("ATOMIC_VAR_INIT(false)"), + CodeExpression("false"), ), CDeclaration( self._derived_origin_poisoned_name(variable), "static atomic_bool", - CodeExpression("ATOMIC_VAR_INIT(false)"), + CodeExpression("false"), ), ) ) @@ -2116,6 +2138,8 @@ def _module_declarations( ) for derived in self._pointer_holder_types(plan) ), + *self._owned_native_array_bridge_prototypes(plan), + *self._default_native_array_bridge_prototypes(plan), *self._derived_field_bridge_prototypes(plan), *self._derived_private_method_prototypes(plan), *self._derived_handle_operation_declarations(plan), @@ -2741,6 +2765,12 @@ def _derived_handle_bridge_prototype( return self._derived_handle_shape_prototype(name, owner, rank) if operation is NativeArrayOperation.DESCRIPTOR: return self._derived_handle_descriptor_prototype(name, owner) + if operation is NativeArrayOperation.ASSOCIATE: + return CFunctionPrototype( + name, + "void", + (*owner, CParameter("source", "CFI_cdesc_t *")), + ) if operation in {NativeArrayOperation.ALLOCATE, NativeArrayOperation.RESIZE}: return self._derived_handle_extent_prototype(name, owner, rank) if operation in {NativeArrayOperation.DEALLOCATE, NativeArrayOperation.NULLIFY}: @@ -4141,6 +4171,8 @@ def _field_handle_operation_body(self, owner, field: DerivedFieldPlan, operation NativeArrayOperation.DESCRIPTOR, ) return (*prefix, *self._field_handle_actual_nodes(descriptor_bridge, owner_args, callback)) + if operation is NativeArrayOperation.ASSOCIATE: + return self._field_handle_associate_body(field, prefix, bridge, owner_args) if operation in {NativeArrayOperation.ALLOCATE, NativeArrayOperation.RESIZE}: return (*prefix, *self._field_handle_shape_mutation_nodes(field, bridge, owner_args)) if operation in {NativeArrayOperation.DEALLOCATE, NativeArrayOperation.NULLIFY}: @@ -4152,6 +4184,24 @@ def _field_handle_operation_body(self, owner, field: DerivedFieldPlan, operation ) raise ValueError(f"Unsupported field handle operation for {field.owner_path!r}: {operation!r}") + def _field_handle_associate_body( + self, + field: DerivedFieldPlan, + prefix: tuple, + bridge: str, + owner_args: str, + ) -> tuple: + """Associate one field pointer through its selected bridge operation.""" + arguments = f"{owner_args}, source_descriptor" if owner_args else "source_descriptor" + return ( + *prefix, + CDeclaration("source_packed", "PyObject *"), + CExpressionStatement(CodeExpression('if (!PyArg_ParseTuple(args, "O", &source_packed)) return NULL')), + *self._pointer_association_source_nodes(field), + CExpressionStatement(CodeExpression(f"{bridge}({arguments})")), + CExpressionStatement(CodeExpression("Py_RETURN_NONE")), + ) + def _field_handle_owner_nodes(self, owner) -> tuple: """Extract an address only for a completed direct-parent target.""" if isinstance(owner, DerivedTypePlan): @@ -4337,11 +4387,53 @@ def _native_array_operation_declarations( ), ) ) + for function, argument in self._default_native_array_arguments(plan): + binder_name = self._default_native_array_binder_name(argument) + declarations.extend( + ( + CFunctionPrototype( + binder_name, + "PyObject *", + (CParameter("self", "PyObject *"), CParameter("args", "PyObject *")), + storage="static", + ), + CDeclaration( + self._default_native_array_binder_def_name(argument), + "static PyMethodDef", + CodeExpression(f'{{"{binder_name}", (PyCFunction){binder_name}, METH_VARARGS, ""}}'), + ), + ) + ) + for operation in argument.native_array_handle.default_handle.operations: + name = self._owned_native_array_operation_name(function, argument, operation) + declarations.extend( + ( + CFunctionPrototype( + name, + "PyObject *", + (CParameter("self", "PyObject *"), CParameter("args", "PyObject *")), + storage="static", + ), + CDeclaration( + self._owned_native_array_operation_def_name(function, argument, operation), + "static PyMethodDef", + CodeExpression(f'{{"{name}", (PyCFunction){name}, METH_VARARGS, ""}}'), + ), + ) + ) return tuple(declarations) def _native_array_operation_functions(self, plan: ModulePlan) -> tuple[CFunction, ...]: """Lower every planned owned-descriptor operation into a named C method.""" return ( + *( + self._native_array_capsule_release_function(result) + for _function, result in self._owned_native_array_results(plan) + ), + *( + self._native_array_capsule_release_function(argument) + for _function, argument in self._default_native_array_arguments(plan) + ), *( callback for variable in self._module_native_array_variables(plan) @@ -4358,8 +4450,62 @@ def _native_array_operation_functions(self, plan: ModulePlan) -> tuple[CFunction for function, result in self._owned_native_array_results(plan) for operation in result.native_array_handle.operations ), + *self._default_native_array_operation_functions(plan), ) + def _native_array_capsule_release_function( + self, + plan: ArgumentTransferPlan | ResultPlan, + ) -> CFunction: + """Release descriptor payload through the module that created its record.""" + descriptor = "owner_descriptor" + body: tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...] = ( + CDeclaration(descriptor, "CFI_cdesc_t *", CodeExpression("(CFI_cdesc_t *)storage")), + CIf(CodeExpression(f"{descriptor} == NULL"), body=(CReturn(),)), + ) + handle = plan.native_array_handle + if handle is None: + raise ValueError(f"Native array handle {plan.owner_path!r} has no release policy") + if plan.datatype_family is DatatypeFamily.STRING: + if handle.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE: + body = ( + *body, + CIf( + CodeExpression(f"{descriptor}->base_addr != NULL"), + body=(CExpressionStatement(CodeExpression(f"(void)CFI_deallocate({descriptor})")),), + ), + ) + else: + body = ( + *body, + CExpressionStatement( + CodeExpression( + f"{self._owned_native_array_bridge_operation_name(plan, NativeArrayOperation.DESTROY)}" + f"({descriptor})" + ) + ), + ) + return CFunction( + self._native_array_capsule_release_name(plan), + "void", + parameters=(CParameter("storage", "void *"),), + storage="static", + body=body, + ) + + def _default_native_array_operation_functions(self, plan: ModulePlan) -> tuple[CFunction, ...]: + """Lower lazy caller-handle binders and their owned operation methods.""" + arguments = self._default_native_array_arguments(plan) + operations = tuple( + self._owned_native_array_operation_function(function, argument, operation) + for function, argument in arguments + for operation in argument.native_array_handle.default_handle.operations + ) + binders = tuple( + self._default_native_array_binder_function(function, argument) for function, argument in arguments + ) + return (*operations, *binders) + def _module_native_array_variables(self, plan: ModulePlan) -> tuple[ModuleVariablePlan, ...]: """Return borrowed module-handle plans in stable namespace order.""" return tuple( @@ -4457,6 +4603,18 @@ def _module_native_array_data_operation_body( return self._module_native_array_descriptor_body(variable) if operation is NativeArrayOperation.DESCRIPTOR: return self._module_native_array_descriptor_body(variable) + if operation is NativeArrayOperation.ASSOCIATE: + return ( + CDeclaration("source_packed", "PyObject *"), + CExpressionStatement(CodeExpression('if (!PyArg_ParseTuple(args, "O", &source_packed)) return NULL')), + *self._pointer_association_source_nodes(variable), + CExpressionStatement( + CodeExpression( + f"{self._module_native_array_bridge_operation_name(variable, operation)}(source_descriptor)" + ) + ), + CExpressionStatement(CodeExpression("Py_RETURN_NONE")), + ) if operation in {NativeArrayOperation.ALLOCATE, NativeArrayOperation.RESIZE}: return self._module_native_array_shape_mutation_body(variable, operation) if operation in { @@ -4803,6 +4961,20 @@ def _owned_native_array_results(self, plan: ModulePlan) -> tuple[tuple[FunctionP and result.native_array_handle.handoff.abi is NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE ) + def _default_native_array_arguments( + self, + plan: ModulePlan, + ) -> tuple[tuple[FunctionPlan, ArgumentTransferPlan], ...]: + """Return arguments that can attach storage to caller-created handles.""" + return tuple( + (function, argument) + for function in self._functions(plan) + for argument in function.arguments + if argument.native_array_handle is not None + and argument.native_array_handle.default_handle.construction + is NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR + ) + def _owned_native_array_operation_function( self, function: FunctionPlan, @@ -4820,31 +4992,197 @@ def _owned_native_array_operation_function( body=body, ) + def _default_native_array_binder_function( + self, + function: FunctionPlan, + argument: ArgumentTransferPlan, + ) -> CFunction: + """Attach one compiler-compatible owned descriptor to a fresh handle.""" + handle = argument.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Default handle argument {argument.owner_path!r} has no descriptor rank") + default = handle.default_handle + if ( + default.construction is not NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR + or default.descriptor_ownership is None + ): + raise ValueError(f"Default handle argument {argument.owner_path!r} has no lazy owner policy") + dtype = self._native_array_dtype_for_semantic_type( + argument.semantic_type_name, + argument.datatype_family, + ) + cfi_type = self._native_array_cfi_type(argument) + if dtype is None or cfi_type is None: + raise ValueError(f"Default handle argument {argument.owner_path!r} has no concrete numeric dtype") + elem_len = f"sizeof({PrimitiveScalarTypeRegistry.type_for(argument.semantic_type_name).c_spelling})" + nodes: list[CDeclaration | CExpressionStatement | CIf | CReturn] = [ + CDeclaration("handle_obj", "PyObject *"), + CDeclaration("owner_descriptor", "CFI_cdesc_t *", CodeExpression("NULL")), + CDeclaration("owner_status", "int", CodeExpression("CFI_SUCCESS")), + CDeclaration("ops", "PyObject *", CodeExpression("NULL")), + CDeclaration("operation", "PyObject *", CodeExpression("NULL")), + CDeclaration("owner_obj", "PyObject *", CodeExpression("NULL")), + CDeclaration("runtime", "PyObject *", CodeExpression("NULL")), + CDeclaration("helper", "PyObject *", CodeExpression("NULL")), + CDeclaration("result", "PyObject *", CodeExpression("NULL")), + CExpressionStatement(CodeExpression('if (!PyArg_ParseTuple(args, "O", &handle_obj)) return NULL')), + CExpressionStatement( + CodeExpression(f"owner_descriptor = {self._zeroed_descriptor_allocation(handle.array.rank)}") + ), + CIf( + CodeExpression("owner_descriptor == NULL"), + body=( + CExpressionStatement(CodeExpression("PyErr_NoMemory()")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement( + CodeExpression( + f"owner_status = CFI_establish(owner_descriptor, NULL, " + f"{self._owned_native_array_cfi_attribute(handle)}, {cfi_type}, {elem_len}, " + f"{handle.array.rank}, NULL)" + ) + ), + CIf( + CodeExpression("owner_status != CFI_SUCCESS"), + body=( + CExpressionStatement(CodeExpression("free(owner_descriptor)")), + CExpressionStatement( + CodeExpression( + 'PyErr_SetString(PyExc_RuntimeError, "failed to establish caller-created native ' + 'array descriptor storage")' + ) + ), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement(CodeExpression("ops = PyDict_New()")), + CIf( + CodeExpression("ops == NULL"), + body=( + CExpressionStatement(CodeExpression("free(owner_descriptor)")), + CReturn(CodeExpression("NULL")), + ), + ), + ] + for operation in default.operations: + definition = self._owned_native_array_operation_def_name(function, argument, operation) + nodes.extend( + ( + CExpressionStatement(CodeExpression(f"operation = PyCFunction_NewEx(&{definition}, NULL, NULL)")), + CIf( + CodeExpression("operation == NULL"), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(ops)")), + CExpressionStatement(CodeExpression("free(owner_descriptor)")), + CReturn(CodeExpression("NULL")), + ), + ), + CIf( + CodeExpression(f'PyDict_SetItemString(ops, "{operation.value}", operation) < 0'), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(operation)")), + CExpressionStatement(CodeExpression("Py_DECREF(ops)")), + CExpressionStatement(CodeExpression("free(owner_descriptor)")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement(CodeExpression("Py_DECREF(operation)")), + ) + ) + nodes.extend( + ( + CExpressionStatement( + CodeExpression( + f"owner_obj = {self._native_array_capsule_new_expression(argument, 'owner_descriptor')}" + ) + ), + CIf( + CodeExpression("owner_obj == NULL"), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(ops)")), + CExpressionStatement(CodeExpression("free(owner_descriptor)")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement(CodeExpression("owner_descriptor = NULL")), + CExpressionStatement(CodeExpression('runtime = PyImport_ImportModule("x2py.runtime.handles")')), + CIf( + CodeExpression("runtime == NULL"), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(owner_obj)")), + CExpressionStatement(CodeExpression("Py_DECREF(ops)")), + CExpressionStatement(CodeExpression("free(owner_descriptor)")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement( + CodeExpression('helper = PyObject_GetAttrString(runtime, "_bind_contract_native_array_handle")') + ), + CExpressionStatement(CodeExpression("Py_DECREF(runtime)")), + CIf( + CodeExpression("helper == NULL"), + body=( + CExpressionStatement(CodeExpression("Py_DECREF(owner_obj)")), + CExpressionStatement(CodeExpression("Py_DECREF(ops)")), + CExpressionStatement(CodeExpression("free(owner_descriptor)")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement( + CodeExpression( + f'result = PyObject_CallFunction(helper, "OssiOOssO", handle_obj, ' + f'"{handle.descriptor_kind.value}", "{dtype}", {handle.array.rank}, ops, owner_obj, ' + f'"{default.descriptor_ownership.value}", "{handle.extraction_action.value}", Py_None)' + ) + ), + CExpressionStatement(CodeExpression("Py_DECREF(helper)")), + CExpressionStatement(CodeExpression("Py_DECREF(owner_obj)")), + CExpressionStatement(CodeExpression("Py_DECREF(ops)")), + CReturn(CodeExpression("result")), + ) + ) + return CFunction( + self._default_native_array_binder_name(argument), + "PyObject *", + parameters=(CParameter("self", "PyObject *"), CParameter("args", "PyObject *")), + storage="static", + body=tuple(nodes), + ) + def _owned_native_array_operation_body( self, result: ResultPlan, operation: NativeArrayOperation, ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: """Return one operation body over persistent CFI owner storage.""" - if operation is NativeArrayOperation.RESIZE: - return self._owned_native_array_resize_body(result) + if operation is NativeArrayOperation.ASSOCIATE: + return self._owned_native_array_associate_body(result) + if operation in {NativeArrayOperation.ALLOCATE, NativeArrayOperation.RESIZE}: + return self._owned_native_array_shape_mutation_body( + result, + release_existing=operation is NativeArrayOperation.RESIZE, + ) handler = self._owned_native_array_operation_handler(operation) - return (*self._owned_native_array_owner_nodes("owner"), *handler(result)) + return (*self._owned_native_array_owner_nodes(result, "owner"), *handler(result)) def _owned_native_array_operation_handler(self, operation: NativeArrayOperation): """Return one directly named operation lowerer.""" handlers = { - NativeArrayOperation.SHAPE: self._owned_native_array_descriptor_record_body, + NativeArrayOperation.SHAPE: self._owned_native_array_shape_body, NativeArrayOperation.TO_NUMPY: self._owned_native_array_descriptor_record_body, NativeArrayOperation.ELEMENT_LENGTH: self._owned_native_array_element_length_body, NativeArrayOperation.ARRAY_ACTUAL: self._owned_native_array_actual_body, NativeArrayOperation.DESCRIPTOR: self._owned_native_array_descriptor_body, NativeArrayOperation.ALLOCATED: self._owned_native_array_allocated_body, + NativeArrayOperation.ASSOCIATED: self._owned_native_array_associated_body, NativeArrayOperation.NATIVE_BYTE_ORDER: self._owned_native_array_true_body, NativeArrayOperation.ALIGNED: self._owned_native_array_true_body, NativeArrayOperation.WRITEABLE: self._owned_native_array_true_body, NativeArrayOperation.LAYOUT: self._owned_native_array_layout_body, + NativeArrayOperation.CONTIGUOUS: self._owned_native_array_contiguous_body, NativeArrayOperation.DEALLOCATE: self._owned_native_array_deallocate_body, + NativeArrayOperation.NULLIFY: self._owned_native_array_nullify_body, NativeArrayOperation.DESTROY: self._owned_native_array_destroy_body, } try: @@ -4852,6 +5190,23 @@ def _owned_native_array_operation_handler(self, operation: NativeArrayOperation) except KeyError: raise ValueError(f"Unsupported owned native array operation {operation.value!r}") from None + def _owned_native_array_associate_body( + self, + result: ArgumentTransferPlan | ResultPlan, + ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: + """Apply pointer assignment to one persistent owned descriptor.""" + bridge = self._owned_native_array_bridge_operation_name(result, NativeArrayOperation.ASSOCIATE) + return ( + *self._owned_native_array_owner_nodes( + result, + "owner", + trailing_objects=("source_packed",), + ), + *self._pointer_association_source_nodes(result), + CExpressionStatement(CodeExpression(f"{bridge}(owner_descriptor, source_descriptor)")), + CExpressionStatement(CodeExpression("Py_RETURN_NONE")), + ) + def _owned_native_array_descriptor_record_body( self, result: ResultPlan, @@ -4862,6 +5217,28 @@ def _owned_native_array_descriptor_record_body( raise ValueError(f"Owned result {result.owner_path!r} has no descriptor rank") return self._native_array_descriptor_record_nodes(handle.array.rank, "owner_descriptor") + def _owned_native_array_shape_body( + self, + result: ResultPlan, + ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: + """Expose extents using the typed compiler descriptor inquiry.""" + if self._is_owned_deferred_character_result(result): + return self._owned_native_array_descriptor_record_body(result) + handle = result.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Owned result {result.owner_path!r} has no shape rank") + dimensions = tuple(f"extent_{axis}" for axis in range(handle.array.rank)) + return ( + *(CDeclaration(name, "int64_t", CodeExpression("0")) for name in dimensions), + CExpressionStatement( + CodeExpression( + f"{self._owned_native_array_bridge_operation_name(result, NativeArrayOperation.SHAPE)}" + f"(owner_descriptor, {', '.join(f'&{name}' for name in dimensions)})" + ) + ), + CReturn(CodeExpression(f'Py_BuildValue("({",".join("L" for _ in dimensions)})", {", ".join(dimensions)})')), + ) + def _owned_native_array_actual_body(self, _result: ResultPlan) -> tuple[CReturn, ...]: """Expose the current owned allocation data address.""" return (CReturn(CodeExpression("PyLong_FromVoidPtr(owner_descriptor->base_addr)")),) @@ -4870,13 +5247,51 @@ def _owned_native_array_element_length_body(self, _result: ResultPlan) -> tuple[ """Expose the current deferred character element width.""" return (CReturn(CodeExpression("PyLong_FromSize_t(owner_descriptor->elem_len)")),) - def _owned_native_array_descriptor_body(self, _result: ResultPlan) -> tuple[CReturn, ...]: - """Expose persistent standard-descriptor storage.""" - return (CReturn(CodeExpression("PyLong_FromVoidPtr(owner_descriptor)")),) + def _owned_native_array_descriptor_body( + self, + _result: ResultPlan, + ) -> tuple[CExpressionStatement | CReturn, ...]: + """Expose the versioned owner capsule for cross-extension handoff.""" + return ( + CExpressionStatement(CodeExpression("Py_INCREF(owner_obj)")), + CReturn(CodeExpression("owner_obj")), + ) def _owned_native_array_allocated_body(self, _result: ResultPlan) -> tuple[CReturn, ...]: """Report the current allocation state.""" - return (CReturn(CodeExpression("PyBool_FromLong(owner_descriptor->base_addr != NULL)")),) + if self._is_owned_deferred_character_result(_result): + return (CReturn(CodeExpression("PyBool_FromLong(owner_descriptor->base_addr != NULL)")),) + return ( + CReturn( + CodeExpression( + f"PyBool_FromLong({self._owned_native_array_bridge_operation_name(_result, NativeArrayOperation.ALLOCATED)}" + "(owner_descriptor))" + ) + ), + ) + + def _owned_native_array_associated_body(self, result: ResultPlan) -> tuple[CReturn, ...]: + """Report the current pointer association state.""" + return self._owned_native_array_bridge_state_body(result, NativeArrayOperation.ASSOCIATED) + + def _owned_native_array_contiguous_body(self, result: ResultPlan) -> tuple[CReturn, ...]: + """Report whether the current pointer target is contiguous.""" + return self._owned_native_array_bridge_state_body(result, NativeArrayOperation.CONTIGUOUS) + + def _owned_native_array_bridge_state_body( + self, + result: ResultPlan, + operation: NativeArrayOperation, + ) -> tuple[CReturn, ...]: + """Call one typed compiler descriptor inquiry.""" + return ( + CReturn( + CodeExpression( + f"PyBool_FromLong({self._owned_native_array_bridge_operation_name(result, operation)}" + "(owner_descriptor))" + ) + ), + ) def _owned_native_array_true_body(self, _result: ResultPlan) -> tuple[CReturn, ...]: """Return one invariant true array capability.""" @@ -4888,38 +5303,196 @@ def _owned_native_array_layout_body(self, _result: ResultPlan) -> tuple[CReturn, def _owned_native_array_deallocate_body( self, - _result: ResultPlan, + result: ResultPlan, ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: """Deallocate payload while retaining owner storage.""" - return self._owned_native_array_deallocate_nodes(free_owner=False) + return self._owned_native_array_deallocate_nodes(result, NativeArrayOperation.DEALLOCATE, free_owner=False) + + def _owned_native_array_nullify_body( + self, + result: ResultPlan, + ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: + """Clear pointer association while retaining owner storage.""" + return self._owned_native_array_deallocate_nodes(result, NativeArrayOperation.NULLIFY, free_owner=False) def _owned_native_array_destroy_body( self, _result: ResultPlan, - ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: + ) -> tuple[CExpressionStatement, ...]: """Destroy payload and persistent owner storage.""" - return self._owned_native_array_deallocate_nodes(free_owner=True) + return ( + CExpressionStatement(CodeExpression("x2py_native_array_handle_release(owner_handle)")), + CExpressionStatement(CodeExpression("Py_RETURN_NONE")), + ) def _owned_native_array_owner_nodes( self, + plan: ArgumentTransferPlan | ResultPlan, prefix: str, + *, + trailing_objects: tuple[str, ...] = (), ) -> tuple[CDeclaration | CExpressionStatement, ...]: - """Decode the persistent descriptor owner passed by the runtime adapter.""" + """Validate and decode a versioned descriptor owner capsule.""" + handle = plan.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Native array handle {plan.owner_path!r} has no descriptor metadata") + cfi_type = self._native_array_cfi_type(plan) + if cfi_type is None: + raise ValueError(f"Native array handle {plan.owner_path!r} has no CFI element type") return ( CDeclaration(f"{prefix}_obj", "PyObject *"), + *(CDeclaration(name, "PyObject *") for name in trailing_objects), + CDeclaration(f"{prefix}_handle", "x2py_native_array_handle *", CodeExpression("NULL")), CDeclaration(f"{prefix}_descriptor", "CFI_cdesc_t *", CodeExpression("NULL")), - CExpressionStatement(CodeExpression(f'if (!PyArg_ParseTuple(args, "O", &{prefix}_obj)) return NULL')), CExpressionStatement( - CodeExpression(f"{prefix}_descriptor = (CFI_cdesc_t *)PyLong_AsVoidPtr({prefix}_obj)") + CodeExpression( + f'if (!PyArg_ParseTuple(args, "{"O" * (1 + len(trailing_objects))}", ' + f"&{prefix}_obj{', ' if trailing_objects else ''}" + f"{', '.join(f'&{name}' for name in trailing_objects)})) return NULL" + ) + ), + CExpressionStatement( + CodeExpression( + f"{prefix}_handle = x2py_native_array_handle_from_capsule({prefix}_obj, " + f"{self._native_array_handle_kind_constant(handle)}, {handle.array.rank}, {cfi_type}, " + f"{self._native_array_expected_element_size(plan)}, " + f"sizeof(CFI_CDESC_T({handle.array.rank})))" + ) + ), + CExpressionStatement(CodeExpression(f"if ({prefix}_handle == NULL) return NULL")), + CExpressionStatement(CodeExpression(f"{prefix}_descriptor = (CFI_cdesc_t *){prefix}_handle->descriptor")), + ) + + def _pointer_association_source_nodes( + self, + plan: ArgumentTransferPlan | ResultPlan | ModuleVariablePlan | DerivedFieldPlan, + ) -> tuple[CDeclaration | CExpressionStatement, ...]: + """Establish one call-local pointer descriptor from validated source facts.""" + handle = plan.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Pointer association {plan.owner_path!r} has no descriptor rank") + if handle.descriptor_kind is not NativeArrayDescriptorKind.POINTER: + raise ValueError(f"Pointer association {plan.owner_path!r} requires a pointer descriptor") + rank = handle.array.rank + cfi_type = self._pointer_association_cfi_type(plan) + expected_fields = 3 + 3 * rank + nodes: list[CDeclaration | CExpressionStatement] = [ + CDeclaration("source_item", "PyObject *", CodeExpression("NULL")), + CDeclaration("source_storage", f"CFI_CDESC_T({rank})"), + CDeclaration("source_descriptor", "CFI_cdesc_t *", CodeExpression("NULL")), + CDeclaration("source_base_addr", "void *", CodeExpression("NULL")), + CDeclaration("source_elem_len", "size_t", CodeExpression("0")), + CDeclaration("source_descriptor_rank", "CFI_rank_t", CodeExpression("0")), + CDeclaration(f"source_extents[{rank}]", "CFI_index_t"), + *( + CDeclaration(f"source_{label}_{axis}", "CFI_index_t", CodeExpression("0")) + for axis in range(rank) + for label in ("lower_bound", "extent", "stride_multiplier") ), + CDeclaration("source_establish_status", "int", CodeExpression("CFI_SUCCESS")), CExpressionStatement( CodeExpression( - f"if ({prefix}_descriptor == NULL) {{ if (!PyErr_Occurred()) " - 'PyErr_SetString(PyExc_ReferenceError, "native array owner descriptor is NULL"); return NULL; }' + f"if (!PyTuple_Check(source_packed) || PyTuple_GET_SIZE(source_packed) != {expected_fields}) {{ " + f'PyErr_SetString(PyExc_TypeError, "pointer association requires {expected_fields} ' + 'descriptor facts"); return NULL; }' ) ), + *self._pointer_association_fact_nodes("source_base_addr", 0, pointer=True), + *self._pointer_association_fact_nodes("source_elem_len", 1, unsigned=True), + *self._pointer_association_fact_nodes("source_descriptor_rank", 2), + ] + for axis in range(rank): + offset = 3 + 3 * axis + nodes.extend( + ( + *self._pointer_association_fact_nodes(f"source_lower_bound_{axis}", offset), + *self._pointer_association_fact_nodes(f"source_extent_{axis}", offset + 1), + *self._pointer_association_fact_nodes(f"source_stride_multiplier_{axis}", offset + 2), + CExpressionStatement(CodeExpression(f"source_extents[{axis}] = source_extent_{axis}")), + ) + ) + nodes.extend( + ( + CExpressionStatement( + CodeExpression( + f"if (source_descriptor_rank != {rank}) {{ PyErr_Format(PyExc_ValueError, " + f'"pointer association source rank %d does not match destination rank {rank}", ' + "(int)source_descriptor_rank); return NULL; }" + ) + ), + CExpressionStatement( + CodeExpression( + "source_establish_status = CFI_establish((CFI_cdesc_t *)&source_storage, " + f"source_base_addr, CFI_attribute_pointer, {cfi_type}, source_elem_len, " + f"{rank}, source_extents)" + ) + ), + CExpressionStatement( + CodeExpression( + "if (source_establish_status != CFI_SUCCESS) { " + 'PyErr_SetString(PyExc_RuntimeError, "failed to establish pointer association source"); ' + "return NULL; }" + ) + ), + ) + ) + for axis in range(rank): + nodes.extend( + ( + CExpressionStatement( + CodeExpression( + f"((CFI_cdesc_t *)&source_storage)->dim[{axis}].lower_bound = source_lower_bound_{axis}" + ) + ), + CExpressionStatement( + CodeExpression(f"((CFI_cdesc_t *)&source_storage)->dim[{axis}].extent = source_extent_{axis}") + ), + CExpressionStatement( + CodeExpression( + f"((CFI_cdesc_t *)&source_storage)->dim[{axis}].sm = source_stride_multiplier_{axis}" + ) + ), + ) + ) + nodes.append(CExpressionStatement(CodeExpression("source_descriptor = (CFI_cdesc_t *)&source_storage"))) + return tuple(nodes) + + @staticmethod + def _pointer_association_fact_nodes( + target: str, + index: int, + *, + pointer: bool = False, + unsigned: bool = False, + ) -> tuple[CExpressionStatement, ...]: + """Decode one pointer-association descriptor fact.""" + if pointer: + conversion = "(void *)PyLong_AsVoidPtr(source_item)" + error = f"{target} == NULL && PyErr_Occurred()" + elif unsigned: + conversion = "(size_t)PyLong_AsUnsignedLongLong(source_item)" + error = "PyErr_Occurred()" + else: + conversion = "PyLong_AsLongLong(source_item)" + error = "PyErr_Occurred()" + return ( + CExpressionStatement(CodeExpression(f"source_item = PyTuple_GET_ITEM(source_packed, {index})")), + CExpressionStatement(CodeExpression(f"{target} = {conversion}")), + CExpressionStatement(CodeExpression(f"if ({error}) return NULL")), ) + @staticmethod + def _pointer_association_cfi_type( + plan: ArgumentTransferPlan | ResultPlan | ModuleVariablePlan | DerivedFieldPlan, + ) -> str: + """Return the completed standard-descriptor type for pointer assignment.""" + if isinstance(plan, DerivedFieldPlan): + if plan.string_element: + return "CFI_type_char" + elif plan.datatype_family is DatatypeFamily.STRING: + return "CFI_type_char" + return PrimitiveScalarTypeRegistry.type_for(plan.semantic_type_name).cfi_type_spelling + def _native_array_descriptor_record_nodes( self, rank: int, @@ -4982,10 +5555,24 @@ def _native_array_descriptor_record_nodes( def _owned_native_array_deallocate_nodes( self, + result: ResultPlan, + operation: NativeArrayOperation, *, free_owner: bool, ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: """Release payload and optionally persistent descriptor storage.""" + if not self._is_owned_deferred_character_result(result): + nodes: list[CExpressionStatement | CReturn] = [ + CExpressionStatement( + CodeExpression( + f"{self._owned_native_array_bridge_operation_name(result, operation)}(owner_descriptor)" + ) + ), + ] + if free_owner: + nodes.append(CExpressionStatement(CodeExpression("free(owner_descriptor)"))) + nodes.append(CExpressionStatement(CodeExpression("Py_RETURN_NONE"))) + return tuple(nodes) nodes: list[CDeclaration | CExpressionStatement | CIf | CReturn] = [ CDeclaration("status", "int", CodeExpression("CFI_SUCCESS")), CIf( @@ -5012,20 +5599,26 @@ def _owned_native_array_deallocate_nodes( nodes.append(CExpressionStatement(CodeExpression("Py_RETURN_NONE"))) return tuple(nodes) - def _owned_native_array_resize_body( + def _owned_native_array_shape_mutation_body( self, - result: ResultPlan, + result: ArgumentTransferPlan | ResultPlan, + *, + release_existing: bool, ) -> tuple[CDeclaration | CExpressionStatement | CIf | CReturn, ...]: - """Replace owned allocatable payload with one validated requested shape.""" + """Allocate or replace descriptor payload with one validated shape.""" handle = result.native_array_handle if handle is None or handle.array.rank is None: raise ValueError(f"Owned result {result.owner_path!r} has no resize rank") rank = handle.array.rank + cfi_type = self._native_array_cfi_type(result) + if cfi_type is None: + raise ValueError(f"Owned result {result.owner_path!r} has no CFI element type") extent_objects = tuple(f"extent_{axis}_obj" for axis in range(rank)) targets = ", ".join(f"&{name}" for name in ("owner_obj", *extent_objects)) nodes: list[CDeclaration | CExpressionStatement | CIf | CReturn] = [ CDeclaration("owner_obj", "PyObject *"), *(CDeclaration(name, "PyObject *") for name in extent_objects), + CDeclaration("owner_handle", "x2py_native_array_handle *", CodeExpression("NULL")), CDeclaration("owner_descriptor", "CFI_cdesc_t *", CodeExpression("NULL")), CDeclaration(f"lower_bounds[{rank}]", "CFI_index_t"), CDeclaration(f"upper_bounds[{rank}]", "CFI_index_t"), @@ -5033,13 +5626,15 @@ def _owned_native_array_resize_body( CExpressionStatement( CodeExpression(f'if (!PyArg_ParseTuple(args, "{"O" * (rank + 1)}", {targets})) return NULL') ), - CExpressionStatement(CodeExpression("owner_descriptor = (CFI_cdesc_t *)PyLong_AsVoidPtr(owner_obj)")), CExpressionStatement( CodeExpression( - "if (owner_descriptor == NULL) { if (!PyErr_Occurred()) PyErr_SetString(" - 'PyExc_ReferenceError, "native array owner descriptor is NULL"); return NULL; }' + "owner_handle = x2py_native_array_handle_from_capsule(owner_obj, " + f"{self._native_array_handle_kind_constant(handle)}, {rank}, {cfi_type}, " + f"{self._native_array_expected_element_size(result)}, sizeof(CFI_CDESC_T({rank})))" ) ), + CExpressionStatement(CodeExpression("if (owner_handle == NULL) return NULL")), + CExpressionStatement(CodeExpression("owner_descriptor = (CFI_cdesc_t *)owner_handle->descriptor")), ] for axis, item in enumerate(extent_objects): nodes.extend( @@ -5047,24 +5642,15 @@ def _owned_native_array_resize_body( CExpressionStatement( CodeExpression(f"upper_bounds[{axis}] = (CFI_index_t)PyLong_AsLongLong({item}) - 1") ), - CExpressionStatement(CodeExpression("if (PyErr_Occurred()) return NULL")), - CExpressionStatement(CodeExpression(f"lower_bounds[{axis}] = 0")), - ) - ) - nodes.extend( - ( - CIf( - CodeExpression("owner_descriptor->base_addr != NULL"), - body=( - CExpressionStatement(CodeExpression("status = CFI_deallocate(owner_descriptor)")), - CExpressionStatement( - CodeExpression( - "if (status != CFI_SUCCESS) { PyErr_SetString(PyExc_RuntimeError, " - '"failed to release owned native array before resize"); return NULL; }' - ) - ), - ), - ), + CExpressionStatement(CodeExpression("if (PyErr_Occurred()) return NULL")), + CExpressionStatement(CodeExpression(f"lower_bounds[{axis}] = 0")), + ) + ) + release_nodes = self._owned_native_array_resize_release_nodes(result) if release_existing else () + action = "resize" if release_existing else "allocate" + nodes.extend( + ( + *release_nodes, CExpressionStatement( CodeExpression( "status = CFI_allocate(owner_descriptor, lower_bounds, upper_bounds, " @@ -5074,7 +5660,7 @@ def _owned_native_array_resize_body( CExpressionStatement( CodeExpression( "if (status != CFI_SUCCESS) { PyErr_SetString(PyExc_RuntimeError, " - '"failed to resize owned native array"); return NULL; }' + f'"failed to {action} owned native array"); return NULL; }}' ) ), CExpressionStatement(CodeExpression("Py_RETURN_NONE")), @@ -5082,25 +5668,78 @@ def _owned_native_array_resize_body( ) return tuple(nodes) + def _owned_native_array_resize_release_nodes( + self, + result: ResultPlan, + ) -> tuple[CExpressionStatement | CIf, ...]: + """Release existing owned payload before resize through the selected descriptor path.""" + if not self._is_owned_deferred_character_result(result): + return ( + CExpressionStatement( + CodeExpression( + f"{self._owned_native_array_bridge_operation_name(result, NativeArrayOperation.DEALLOCATE)}" + "(owner_descriptor)" + ) + ), + ) + return ( + CIf( + CodeExpression("owner_descriptor->base_addr != NULL"), + body=( + CExpressionStatement(CodeExpression("status = CFI_deallocate(owner_descriptor)")), + CExpressionStatement( + CodeExpression( + "if (status != CFI_SUCCESS) { PyErr_SetString(PyExc_RuntimeError, " + '"failed to release owned native array before resize"); return NULL; }' + ) + ), + ), + ), + ) + def _owned_native_array_operation_name( self, _function: FunctionPlan | None, - result: ResultPlan, + result: ArgumentTransferPlan | ResultPlan, operation: NativeArrayOperation, ) -> str: """Return one stable private operation symbol.""" owner = re.sub(r"\W", "_", result.owner_path).casefold() return f"x2py_owned_{owner}_{operation.value}" + def _owned_native_array_bridge_operation_name( + self, + result: ArgumentTransferPlan | ResultPlan, + operation: NativeArrayOperation, + ) -> str: + """Return the C-visible typed bridge operation symbol.""" + preferred = result.bridge.native_name or "result" + owner = NativeSymbolNames.compact(result.owner_path, preferred, limit=38) + return f"bind_c_owned_{owner}_{operation.value}" + def _owned_native_array_operation_def_name( self, function: FunctionPlan | None, - result: ResultPlan, + result: ArgumentTransferPlan | ResultPlan, operation: NativeArrayOperation, ) -> str: """Return the private PyMethodDef symbol for one operation.""" return f"{self._owned_native_array_operation_name(function, result, operation)}_def" + def _default_native_array_binder_name(self, argument: ArgumentTransferPlan) -> str: + """Return one private lazy descriptor-attachment callable name.""" + owner = re.sub(r"\W", "_", argument.owner_path).casefold() + return f"x2py_bind_default_{owner}" + + def _default_native_array_binder_def_name(self, argument: ArgumentTransferPlan) -> str: + return f"{self._default_native_array_binder_name(argument)}_def" + + @staticmethod + def _native_array_capsule_release_name(plan: ArgumentTransferPlan | ResultPlan) -> str: + """Return one stable descriptor-payload release callback symbol.""" + owner = re.sub(r"\W", "_", plan.owner_path).casefold() + return f"x2py_release_native_handle_{owner}" + def _visit_ModuleVariablePlan(self, plan: ModuleVariablePlan) -> tuple[CFunction, ...]: """Lower binding-owned getter and setter actions into C functions.""" return ( @@ -5647,12 +6286,12 @@ def _derived_alias_preflight_nodes( ) def _binding_conversion_order(self, plan: FunctionPlan) -> tuple[ArgumentTransferPlan, ...]: - """Convert non-owning inputs before allocating the sole replacement buffer.""" + """Apply the completed argument conversion schedule.""" return tuple( sorted( plan.arguments, key=lambda argument: ( - argument.binding.codegen_action is CodegenAction.COPY_IN_OUT, + argument.binding.conversion_phase is ArgumentConversionPhase.DEFERRED_REPLACEMENT, argument.python_position, ), ) @@ -6335,20 +6974,19 @@ def _native_array_actual_call_nodes( ), CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_runtime)")), CExpressionStatement(CodeExpression(f"if ({prefix}_helper == NULL) return NULL")), - CExpressionStatement(CodeExpression(f"{prefix}_shape = PyTuple_New({actual.rank})")), - CExpressionStatement( - CodeExpression(f"if ({prefix}_shape == NULL) {{ Py_DECREF({prefix}_helper); return NULL; }}") - ), + *self._native_array_actual_shape_object_nodes(plan, names), *self._native_array_actual_shape_nodes(plan, context, names), *self._native_array_actual_layout_nodes(plan, names), CExpressionStatement( CodeExpression( - f'{prefix}_packed = PyObject_CallFunction({prefix}_helper, "OsiOOiiiiiii", ' - f'{names.object_name}, "{actual.dtype}", {actual.rank}, {prefix}_shape, {prefix}_layout, ' + f'{prefix}_packed = PyObject_CallFunction({prefix}_helper, "OsiOOiiiiiiiii", ' + f'{names.object_name}, "{actual.dtype}", {self._native_array_actual_expected_rank(actual)}, ' + f"{prefix}_shape, {prefix}_layout, " f"{int(actual.writable)}, {int(actual.require_native_byte_order)}, {int(actual.require_aligned)}, " f"{int(plan.array.runtime_rank_role is not None)}, " f"{int(plan.array.itemsize_role is not None)}, {int(bool(plan.array.stride_roles))}, " - f"{int(actual.require_contiguous)})" + f"{int(actual.require_contiguous)}, {int(actual.flatten_storage)}, " + f"{self._native_array_actual_flat_axis(actual)})" ) ), CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_helper)")), @@ -6358,6 +6996,33 @@ def _native_array_actual_call_nodes( ] return tuple(nodes) + def _native_array_actual_shape_object_nodes( + self, + plan: ArgumentTransferPlan, + names: _CArgumentNames, + ) -> tuple[CExpressionStatement, ...]: + """Create the expected-shape object consumed by the runtime helper.""" + actual = plan.native_array_actual + if actual is None: + return () + prefix = names.value_name + return ( + CExpressionStatement(CodeExpression(f"{prefix}_shape = PyTuple_New({actual.rank})")), + CExpressionStatement( + CodeExpression(f"if ({prefix}_shape == NULL) {{ Py_DECREF({prefix}_helper); return NULL; }}") + ), + ) + + @staticmethod + def _native_array_actual_expected_rank(actual: NativeArrayActualPlan) -> int: + """Return the runtime-helper rank selector selected by completed policy.""" + return actual.rank + + @staticmethod + def _native_array_actual_flat_axis(actual: NativeArrayActualPlan) -> int: + """Return the flattened contract axis marker consumed by the runtime helper.""" + return -1 if actual.flat_axis is None else actual.flat_axis + def _native_array_actual_shape_nodes( self, plan: ArgumentTransferPlan, @@ -6372,7 +7037,7 @@ def _native_array_actual_shape_nodes( prefix = names.value_name nodes = [] for axis, expression in enumerate(actual.shape): - if expression in {":", "::Strided", "Flat"}: + if expression in {":", "::Strided", "Flat"} or (actual.flatten_storage and axis == actual.flat_axis): nodes.append(CExpressionStatement(CodeExpression("Py_INCREF(Py_None)"))) item = "Py_None" else: @@ -6495,11 +7160,7 @@ def _array_type_and_rank_check( raise ValueError(f"Unsupported array element type {plan.semantic_type_name!r}") numpy_type = scalar_type.numpy_type_macro python_type = scalar_type.python_type_name - rank_check = ( - f"PyArray_NDIM({array}) < 1 || PyArray_NDIM({array}) > 15" - if handoff.rank is None - else f"PyArray_NDIM({array}) != {handoff.rank}" - ) + rank_check = self._array_rank_check_expression(handoff, array) return CExpressionStatement( CodeExpression( f"if (!PyArray_Check({names.object_name}) || PyArray_TYPE({array}) != {numpy_type} || " @@ -6509,6 +7170,15 @@ def _array_type_and_rank_check( ) ) + @staticmethod + def _array_rank_check_expression(handoff, array: str) -> str: + """Render the Python-rank predicate selected by completed array policy.""" + if handoff.rank is None: + return f"PyArray_NDIM({array}) < 1 || PyArray_NDIM({array}) > 15" + if handoff.flatten_python_storage: + return f"PyArray_NDIM({array}) < {handoff.rank} || PyArray_NDIM({array}) > 15" + return f"PyArray_NDIM({array}) != {handoff.rank}" + def _array_access_checks( self, plan: ArgumentTransferPlan, @@ -6623,10 +7293,11 @@ def _array_shape_checks( if expression in runtime_markers: continue expected = self._array_extent_expression(handoff, axis, expression, context) + actual_axis = self._array_actual_axis_expression(handoff, array, axis) checks.append( CExpressionStatement( CodeExpression( - f"if (PyArray_DIM({array}, {axis}) != (npy_intp)({expected})) {{ " + f"if (PyArray_DIM({array}, {actual_axis}) != (npy_intp)({expected})) {{ " f'PyErr_SetString(PyExc_TypeError, "Argument {plan.binding.python_name} has incompatible ' f'shape at axis {axis}"); return NULL; }}' ) @@ -6634,6 +7305,17 @@ def _array_shape_checks( ) return tuple(checks) + @staticmethod + def _array_actual_axis_expression(handoff, array: str, axis: int) -> str: + """Map one contract axis to the runtime ndarray axis selected by the plan.""" + if not handoff.flatten_python_storage or handoff.flat_axis is None: + return str(axis) + if handoff.flat_axis == 0 and axis > 0: + suffix_count = handoff.rank - 1 + suffix_offset = axis - 1 + return f"(PyArray_NDIM({array}) - {suffix_count} + {suffix_offset})" + return str(axis) + def _array_extent_expression( self, handoff, @@ -6680,6 +7362,9 @@ def _array_extraction_nodes( ), ) ) + if handoff.flatten_python_storage: + nodes.extend(self._flat_array_extraction_nodes(handoff, names, array)) + return tuple(nodes) active_rank = 15 if handoff.rank is None else handoff.rank for axis in range(active_rank): guard = f"if (PyArray_NDIM({array}) > {axis}) " if handoff.rank is None else "" @@ -6692,6 +7377,80 @@ def _array_extraction_nodes( nodes.extend(self._strided_array_extraction_nodes(handoff.rank, names, array)) return tuple(nodes) + def _flat_array_extraction_nodes( + self, + handoff, + names: _CArgumentNames, + array: str, + ) -> tuple[CExpressionStatement | CFor, ...]: + """Compute native extents for a contiguous Python array with one flat edge.""" + if handoff.rank is None or handoff.flat_axis is None: + raise ValueError("Flat array extraction requires a completed concrete flat axis") + if handoff.flat_axis == 0: + return self._leading_flat_array_extraction_nodes(handoff, names, array) + return self._final_flat_array_extraction_nodes(handoff, names, array) + + def _final_flat_array_extraction_nodes( + self, + handoff, + names: _CArgumentNames, + array: str, + ) -> tuple[CExpressionStatement | CFor, ...]: + """Keep prefix extents and flatten all runtime axes at the final flat edge.""" + flat_axis = handoff.flat_axis + nodes: list[CExpressionStatement | CFor] = [ + *( + CExpressionStatement( + CodeExpression(f"{names.extent_names[axis]} = (int64_t)PyArray_DIM({array}, {axis})") + ) + for axis in range(flat_axis) + ), + CExpressionStatement(CodeExpression(f"{names.extent_names[flat_axis]} = 1")), + CFor( + f"int axis = {flat_axis}", + CodeExpression(f"axis < PyArray_NDIM({array})"), + CodeExpression("++axis"), + body=( + CExpressionStatement( + CodeExpression(f"{names.extent_names[flat_axis]} *= (int64_t)PyArray_DIM({array}, axis)") + ), + ), + ), + ] + return tuple(nodes) + + def _leading_flat_array_extraction_nodes( + self, + handoff, + names: _CArgumentNames, + array: str, + ) -> tuple[CExpressionStatement | CFor, ...]: + """Flatten leading runtime axes and keep suffix extents at the Python edge.""" + suffix_count = handoff.rank - 1 + nodes: list[CExpressionStatement | CFor] = [ + CExpressionStatement(CodeExpression(f"{names.extent_names[0]} = 1")), + CFor( + "int axis = 0", + CodeExpression(f"axis < PyArray_NDIM({array}) - {suffix_count}"), + CodeExpression("++axis"), + body=( + CExpressionStatement( + CodeExpression(f"{names.extent_names[0]} *= (int64_t)PyArray_DIM({array}, axis)") + ), + ), + ), + ] + nodes.extend( + CExpressionStatement( + CodeExpression( + f"{names.extent_names[axis]} = (int64_t)PyArray_DIM({array}, " + f"PyArray_NDIM({array}) - {suffix_count} + {axis - 1})" + ) + ) + for axis in range(1, handoff.rank) + ) + return tuple(nodes) + def _strided_array_extraction_nodes( self, rank: int | None, @@ -6934,12 +7693,28 @@ def _lower_argument_native_array_direct( context: _CFunctionContext, ) -> tuple[CDeclaration | CExpressionStatement | CIf, ...]: """Borrow one persistent runtime-owned standard descriptor pointer.""" + handle = plan.native_array_handle + if handle is None: + return () names = context.arguments[plan.owner_path] prefix = names.value_name + binder_definition = ( + self._default_native_array_binder_def_name(plan) + if handle.default_handle.construction is NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR + else None + ) nodes: list[CDeclaration | CExpressionStatement | CIf] = [ self._native_descriptor_object_declaration(plan, names), CDeclaration(names.value_name, "CFI_cdesc_t *", CodeExpression("NULL")), - *self._native_descriptor_helper_declarations(prefix), + CDeclaration( + f"{names.value_name}_native_handle", + "x2py_native_array_handle *", + CodeExpression("NULL"), + ), + *self._native_descriptor_helper_declarations( + prefix, + include_default_binder=binder_definition is not None, + ), *(self._native_descriptor_presence_declarations(plan, names)), ] nodes.extend( @@ -6948,6 +7723,7 @@ def _lower_argument_native_array_direct( context, names, "_native_array_descriptor_handoff_for_binding_positional", + default_binder_definition=binder_definition, ) ) nodes.extend(self._native_descriptor_presence_unpack_nodes(plan, names, 1)) @@ -6964,12 +7740,20 @@ def _native_descriptor_object_declaration( initializer = CodeExpression("Py_None") if plan.binding.optional_mode is OptionalMode.DESCRIPTOR else None return CDeclaration(names.object_name, "PyObject *", initializer) - def _native_descriptor_helper_declarations(self, prefix: str) -> tuple[CDeclaration, ...]: + def _native_descriptor_helper_declarations( + self, + prefix: str, + *, + include_default_binder: bool = False, + ) -> tuple[CDeclaration, ...]: """Return binding-local Python objects used by one runtime helper call.""" - return tuple( + declarations = tuple( CDeclaration(f"{prefix}_{suffix}", "PyObject *", CodeExpression("NULL")) for suffix in ("runtime", "helper", "shape", "packed", "item") ) + if include_default_binder: + return (*declarations, CDeclaration(f"{prefix}_default_binder", "PyObject *", CodeExpression("NULL"))) + return declarations def _native_descriptor_presence_declarations( self, @@ -6987,6 +7771,7 @@ def _native_descriptor_helper_call_nodes( context: _CFunctionContext, names: _CArgumentNames, helper_name: str, + default_binder_definition: str | None = None, ) -> tuple[CExpressionStatement, ...]: """Call one planned native-descriptor runtime packer.""" handle = plan.native_array_handle @@ -6996,7 +7781,7 @@ def _native_descriptor_helper_call_nodes( dtype = self._native_array_dtype(plan) dtype_format = "O" if dtype is None else "s" dtype_argument = "Py_None" if dtype is None else f'"{dtype}"' - nodes = [ + nodes: list[CExpressionStatement] = [ CExpressionStatement(CodeExpression(f'{prefix}_runtime = PyImport_ImportModule("x2py.runtime.handles")')), CExpressionStatement(CodeExpression(f"if ({prefix}_runtime == NULL) return NULL")), CExpressionStatement( @@ -7009,17 +7794,44 @@ def _native_descriptor_helper_call_nodes( CodeExpression(f"if ({prefix}_shape == NULL) {{ Py_DECREF({prefix}_helper); return NULL; }}") ), *self._native_descriptor_expected_shape_nodes(plan, context, names), + ] + binder_argument = "" + binder_format = "" + if default_binder_definition is not None: + binder = f"{prefix}_default_binder" + nodes.extend( + ( + CExpressionStatement( + CodeExpression(f"{binder} = PyCFunction_NewEx(&{default_binder_definition}, NULL, NULL)") + ), + CExpressionStatement( + CodeExpression( + f"if ({binder} == NULL) {{ Py_DECREF({prefix}_helper); " + f"Py_DECREF({prefix}_shape); return NULL; }}" + ) + ), + ) + ) + binder_format = "O" + binder_argument = f", {binder}" + nodes.append( CExpressionStatement( CodeExpression( - f'{prefix}_packed = PyObject_CallFunction({prefix}_helper, "Os{dtype_format}iOi", ' + f'{prefix}_packed = PyObject_CallFunction({prefix}_helper, "Os{dtype_format}iOi{binder_format}", ' f'{names.object_name}, "{handle.descriptor_kind.value}", {dtype_argument}, ' - f"{handle.array.rank}, {prefix}_shape, {int(handle.optional_absent)})" + f"{handle.array.rank}, {prefix}_shape, {int(handle.optional_absent)}{binder_argument})" ) - ), - CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_helper)")), - CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_shape)")), - CExpressionStatement(CodeExpression(f"if ({prefix}_packed == NULL) return NULL")), - ] + ) + ) + if default_binder_definition is not None: + nodes.append(CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_default_binder)"))) + nodes.extend( + ( + CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_helper)")), + CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_shape)")), + CExpressionStatement(CodeExpression(f"if ({prefix}_packed == NULL) return NULL")), + ) + ) return tuple(nodes) def _native_descriptor_expected_shape_nodes( @@ -7084,8 +7896,14 @@ def _native_descriptor_pointer_unpack_nodes( self, plan: ArgumentTransferPlan, names: _CArgumentNames, - ) -> tuple[CExpressionStatement, ...]: - """Decode one persistent standard-descriptor pointer.""" + ) -> tuple[CExpressionStatement | CIf, ...]: + """Validate one native-handle capsule and decode its descriptor.""" + handle = plan.native_array_handle + if handle is None or handle.array.rank is None: + return () + cfi_type = self._native_array_cfi_type(plan) + if cfi_type is None: + raise ValueError(f"Native array argument {plan.owner_path!r} has no CFI element type") prefix = names.value_name condition = "1" if plan.binding.optional_mode is OptionalMode.REQUIRED else f"{names.present_name} != NULL" return ( @@ -7093,14 +7911,28 @@ def _native_descriptor_pointer_unpack_nodes( CExpressionStatement( CodeExpression(f"if ({prefix}_item == NULL) {{ Py_DECREF({prefix}_packed); return NULL; }}") ), - CExpressionStatement( - CodeExpression(f"if ({condition}) {names.value_name} = (CFI_cdesc_t *)PyLong_AsVoidPtr({prefix}_item)") - ), - CExpressionStatement( - CodeExpression( - f"if ({names.value_name} == NULL && PyErr_Occurred()) {{ " - f"Py_DECREF({prefix}_packed); return NULL; }}" - ) + CIf( + CodeExpression(condition), + body=( + CExpressionStatement( + CodeExpression( + f"{prefix}_native_handle = x2py_native_array_handle_from_capsule({prefix}_item, " + f"{self._native_array_handle_kind_constant(handle)}, {handle.array.rank}, {cfi_type}, " + f"{self._native_array_expected_element_size(plan)}, " + f"sizeof(CFI_CDESC_T({handle.array.rank})))" + ) + ), + CIf( + CodeExpression(f"{prefix}_native_handle == NULL"), + body=( + CExpressionStatement(CodeExpression(f"Py_DECREF({prefix}_packed)")), + CReturn(CodeExpression("NULL")), + ), + ), + CExpressionStatement( + CodeExpression(f"{names.value_name} = (CFI_cdesc_t *){prefix}_native_handle->descriptor") + ), + ), ), ) @@ -7358,6 +8190,8 @@ def _lower_argument_nullable_value( context: _CFunctionContext, ) -> tuple[CDeclaration | CExpressionStatement | CIf, ...]: """Return omitted-or-value conversion nodes for an optional value.""" + if plan.binding.python_action is PythonBarrierAction.SCALAR_STORAGE: + return self._lower_argument_nullable_scalar_storage(plan, context) if plan.object_kind is ObjectKind.NUMPY_ARRAY: return self._lower_argument_nullable_array_storage(plan, context) if plan.object_kind is ObjectKind.STRING: @@ -7368,8 +8202,6 @@ def _lower_argument_nullable_value( raise ValueError( f"Unsupported optional C argument object kind for {plan.owner_path!r}: {plan.object_kind!r}" ) - if plan.binding.python_action is PythonBarrierAction.SCALAR_STORAGE: - return self._lower_argument_nullable_scalar_storage(plan, context) scalar_type = PrimitiveScalarTypeRegistry.type_for(plan.semantic_type_name) names = context.arguments[plan.owner_path] return ( @@ -7612,7 +8444,7 @@ def _lower_result_owned_native_array_handle( if python_name is None or handle is None or handle.array.rank is None: raise ValueError(f"Owned native array result {plan.owner_path!r} has no binding consumer") prefix = f"{descriptor_name}_handle" - cleanup = self._owned_descriptor_failure_cleanup(descriptor_name) + cleanup = self._owned_descriptor_failure_cleanup(plan, descriptor_name) nodes: list[CDeclaration | CExpressionStatement | CIf] = [ CDeclaration(f"{prefix}_runtime", "PyObject *", CodeExpression("NULL")), CDeclaration(f"{prefix}_helper", "PyObject *", CodeExpression("NULL")), @@ -7629,7 +8461,11 @@ def _lower_result_owned_native_array_handle( nodes.extend(self._owned_native_array_ops_dictionary_nodes(plan, prefix, cleanup, failure_cleanup)) nodes.extend( ( - CExpressionStatement(CodeExpression(f"{prefix}_owner = PyLong_FromVoidPtr({descriptor_name})")), + CExpressionStatement( + CodeExpression( + f"{prefix}_owner = {self._native_array_capsule_new_expression(plan, descriptor_name)}" + ) + ), CIf( CodeExpression(f"{prefix}_owner == NULL"), body=( @@ -7639,6 +8475,7 @@ def _lower_result_owned_native_array_handle( CReturn(CodeExpression("NULL")), ), ), + CExpressionStatement(CodeExpression(f"{descriptor_name} = NULL")), CExpressionStatement( CodeExpression(f'{prefix}_runtime = PyImport_ImportModule("x2py.runtime.handles")') ), @@ -7758,8 +8595,16 @@ def _lower_result_array_copy( self._array_extent_expression(handoff, axis, expression, context) for axis, expression in enumerate(handoff.shape) ) - dims_name = f"{python_name}_dims" - fortran_order = 0 if handoff.order == "ORDER_C" or handoff.rank == 1 else 1 + dimension_declarations: tuple[CDeclaration, ...] + if handoff.rank == 0: + dims_name = "NULL" + dimension_declarations = () + else: + dims_name = f"{python_name}_dims" + dimension_declarations = ( + CDeclaration(f"{dims_name}[]", "npy_intp", CodeExpression(f"{{{', '.join(dimensions)}}}")), + ) + fortran_order = 0 if handoff.order == "ORDER_C" or handoff.rank <= 1 else 1 base_name = f"{python_name}_base" decrefs = tuple(CExpressionStatement(CodeExpression(f"Py_DECREF({name})")) for name in failure_cleanup) return ( @@ -7775,7 +8620,7 @@ def _lower_result_array_copy( CReturn(CodeExpression("NULL")), ), ), - CDeclaration(f"{dims_name}[]", "npy_intp", CodeExpression(f"{{{', '.join(dimensions)}}}")), + *dimension_declarations, CDeclaration( python_name, "PyObject *", @@ -8539,7 +9384,10 @@ def _owned_result_descriptor_failure_nodes( node for result in plan.results if self._is_owned_native_array_result(result) - for node in self._owned_descriptor_failure_cleanup(self._owned_result_descriptor_name(result, context)) + for node in self._owned_descriptor_failure_cleanup( + result, + self._owned_result_descriptor_name(result, context), + ) ) def _derived_native_storage_cleanup_nodes( @@ -9127,7 +9975,9 @@ def _native_call_setup_nodes( raise ValueError(f"Owned result {result.owner_path!r} is missing a CFI element type") elem_len = f"sizeof({PrimitiveScalarTypeRegistry.type_for(result.semantic_type_name).c_spelling})" cleanup = tuple( - node for previous in reversed(initialized) for node in self._owned_descriptor_failure_cleanup(previous) + node + for previous_result, previous_descriptor in reversed(initialized) + for node in self._owned_descriptor_failure_cleanup(previous_result, previous_descriptor) ) nodes.extend( ( @@ -9146,7 +9996,8 @@ def _native_call_setup_nodes( CExpressionStatement( CodeExpression( f"{descriptor}_owner_status = CFI_establish({descriptor}, NULL, " - f"CFI_attribute_allocatable, {cfi_type}, {elem_len}, {handle.array.rank}, NULL)" + f"{self._owned_native_array_cfi_attribute(handle)}, {cfi_type}, {elem_len}, " + f"{handle.array.rank}, NULL)" ) ), CIf( @@ -9167,14 +10018,54 @@ def _native_call_setup_nodes( ), ) ) - initialized.append(descriptor) + initialized.append((result, descriptor)) return tuple(nodes) + @staticmethod + def _owned_native_array_cfi_attribute(handle: NativeArrayHandlePlan) -> str: + """Return the CFI descriptor attribute selected by completed policy.""" + if handle.descriptor_kind is NativeArrayDescriptorKind.POINTER: + return "CFI_attribute_pointer" + return "CFI_attribute_allocatable" + @staticmethod def _zeroed_descriptor_allocation(rank: int) -> str: """Allocate initialized CFI storage so native runtimes never inspect padding.""" return f"(CFI_cdesc_t *)calloc(1, sizeof(CFI_CDESC_T({rank})))" + @staticmethod + def _native_array_handle_kind_constant(handle: NativeArrayHandlePlan) -> str: + """Return the common-header descriptor-kind constant.""" + if handle.descriptor_kind is NativeArrayDescriptorKind.POINTER: + return "X2PY_NATIVE_ARRAY_KIND_POINTER" + return "X2PY_NATIVE_ARRAY_KIND_ALLOCATABLE" + + @staticmethod + def _native_array_expected_element_size(plan: ArgumentTransferPlan | ResultPlan) -> str: + """Return a fixed element-size check or zero for runtime-width strings.""" + if plan.datatype_family is DatatypeFamily.STRING: + return "0" + return f"sizeof({PrimitiveScalarTypeRegistry.type_for(plan.semantic_type_name).c_spelling})" + + def _native_array_capsule_new_expression( + self, + plan: ArgumentTransferPlan | ResultPlan, + descriptor: str, + ) -> str: + """Create one versioned capsule around established descriptor storage.""" + handle = plan.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Native array handle {plan.owner_path!r} has no descriptor metadata") + cfi_type = self._native_array_cfi_type(plan) + if cfi_type is None: + raise ValueError(f"Native array handle {plan.owner_path!r} has no CFI element type") + return ( + "x2py_native_array_handle_capsule_new(" + f"{self._native_array_handle_kind_constant(handle)}, {handle.array.rank}, {cfi_type}, " + f"{descriptor}->elem_len, sizeof(CFI_CDESC_T({handle.array.rank})), {descriptor}, " + f"{self._native_array_capsule_release_name(plan)})" + ) + # Binding-owned representation transformations. def _binding_transformation_setup_nodes( self, @@ -9296,16 +10187,23 @@ def _array_transformation_temp_name(names: _CArgumentNames) -> str: """Name the binding-owned NumPy representation temporary.""" return f"{names.value_name}_representation" - @staticmethod def _owned_descriptor_failure_cleanup( + self, + result: ResultPlan, descriptor_name: str, ) -> tuple[CExpressionStatement, ...]: - """Release unpublished owner storage without changing Python ownership.""" + """Release unpublished descriptor storage without releasing pointer targets.""" + handle = result.native_array_handle + if handle is None: + raise ValueError(f"Owned result {result.owner_path!r} has no descriptor policy") + payload_release = "" + if handle.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE: + payload_release = f"if ({descriptor_name}->base_addr != NULL) (void)CFI_deallocate({descriptor_name}); " return ( CExpressionStatement( CodeExpression( - f"if ({descriptor_name} != NULL) {{ if ({descriptor_name}->base_addr != NULL) " - f"(void)CFI_deallocate({descriptor_name}); free({descriptor_name}); {descriptor_name} = NULL; }}" + f"if ({descriptor_name} != NULL) {{ {payload_release}free({descriptor_name}); " + f"{descriptor_name} = NULL; }}" ) ), ) @@ -9512,6 +10410,74 @@ def _bridge_prototype(self, plan: FunctionPlan) -> CFunctionPrototype: (*argument_parameters, *result_parameters, *direct_parameters), ) + def _owned_native_array_bridge_prototypes(self, plan: ModulePlan) -> tuple[CFunctionPrototype, ...]: + """Declare typed Fortran operations over binding-owned result descriptors.""" + return tuple( + prototype + for _function, result in self._owned_native_array_results(plan) + if not self._is_owned_deferred_character_result(result) + for operation in result.native_array_handle.operations + if (prototype := self._owned_native_array_bridge_prototype(result, operation)) is not None + ) + + def _default_native_array_bridge_prototypes(self, plan: ModulePlan) -> tuple[CFunctionPrototype, ...]: + """Declare typed operations over lazily attached caller descriptors.""" + return tuple( + prototype + for _function, argument in self._default_native_array_arguments(plan) + for operation in argument.native_array_handle.default_handle.operations + if (prototype := self._owned_native_array_bridge_prototype(argument, operation)) is not None + ) + + def _owned_native_array_bridge_prototype( + self, + result: ArgumentTransferPlan | ResultPlan, + operation: NativeArrayOperation, + ) -> CFunctionPrototype | None: + """Return one compiler-backed owned-result operation prototype.""" + if operation in { + NativeArrayOperation.ALLOCATED, + NativeArrayOperation.ASSOCIATED, + NativeArrayOperation.CONTIGUOUS, + }: + return CFunctionPrototype( + self._owned_native_array_bridge_operation_name(result, operation), + "bool", + (CParameter("result", "CFI_cdesc_t *"),), + ) + if operation is NativeArrayOperation.SHAPE: + handle = result.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Owned result {result.owner_path!r} has no shape rank") + return CFunctionPrototype( + self._owned_native_array_bridge_operation_name(result, operation), + "void", + ( + CParameter("result", "CFI_cdesc_t *"), + *(CParameter(f"extent_{axis}", "int64_t *") for axis in range(handle.array.rank)), + ), + ) + if operation is NativeArrayOperation.ASSOCIATE: + return CFunctionPrototype( + self._owned_native_array_bridge_operation_name(result, operation), + "void", + ( + CParameter("result", "CFI_cdesc_t *"), + CParameter("source", "CFI_cdesc_t *"), + ), + ) + if operation in { + NativeArrayOperation.DEALLOCATE, + NativeArrayOperation.NULLIFY, + NativeArrayOperation.DESTROY, + }: + return CFunctionPrototype( + self._owned_native_array_bridge_operation_name(result, operation), + "void", + (CParameter("result", "CFI_cdesc_t *"),), + ) + return None + def _bridge_return_type(self, plan: FunctionPlan) -> str: """Return the direct bridge result type, or void for subroutines.""" result = self._direct_result(plan) @@ -9802,6 +10768,8 @@ def _module_native_array_required_bridge_prototype( return self._module_native_array_shape_prototype(plan, name, pointer=True) if operation is NativeArrayOperation.DESCRIPTOR: return self._module_native_array_descriptor_prototype(plan, name) + if operation is NativeArrayOperation.ASSOCIATE: + return CFunctionPrototype(name, "void", (CParameter("source", "CFI_cdesc_t *"),)) if operation in {NativeArrayOperation.ALLOCATE, NativeArrayOperation.RESIZE}: return self._module_native_array_shape_prototype(plan, name, pointer=False) if operation in {NativeArrayOperation.DEALLOCATE, NativeArrayOperation.NULLIFY}: @@ -10320,6 +11288,7 @@ def _module_def(self, module: ModulePlan, namespace: NamespacePlan) -> CModuleDe def _module_init(self, plan: ModulePlan, needs_native_support: bool) -> CFunction: module_name = plan.binding.owner_path root_namespace = self._namespace(plan, ()) + child_namespaces = self._ordered_child_namespaces(plan) return CFunction( f"PyInit_{module_name}", "PyMODINIT_FUNC", @@ -10332,10 +11301,11 @@ def _module_init(self, plan: ModulePlan, needs_native_support: bool) -> CFunctio ), CExpressionStatement(CodeExpression("if (mod == NULL) return NULL")), *self._namespace_configuration_nodes(plan, root_namespace, "mod"), + *(node for namespace in child_namespaces for node in self._child_namespace_nodes(plan, namespace)), *( node - for namespace in self._ordered_child_namespaces(plan) - for node in self._child_namespace_nodes(plan, namespace) + for namespace in child_namespaces + for node in self._child_namespace_import_registration_nodes(plan, namespace) ), CReturn(CodeExpression("mod")), ), @@ -10372,6 +11342,23 @@ def _child_namespace_nodes( *self._namespace_configuration_nodes(module, namespace, object_name), ) + def _child_namespace_import_registration_nodes( + self, + module: ModulePlan, + namespace: NamespacePlan, + ) -> tuple[CExpressionStatement, ...]: + """Register one generated child module under its qualified import name.""" + module_name = self._c_string_literal(self._namespace_module_name(module, namespace)) + object_name = self._namespace_object_name(namespace) + return ( + CExpressionStatement( + CodeExpression( + f"if (PyDict_SetItemString(PyImport_GetModuleDict(), {module_name}, {object_name}) < 0) " + f"{{ Py_DECREF(mod); return NULL; }}" + ) + ), + ) + def _namespace_configuration_nodes( self, module: ModulePlan, diff --git a/x2py/wrapper_codegen/docstrings.py b/x2py/wrapper_codegen/docstrings.py index 19091491d..db4b49340 100644 --- a/x2py/wrapper_codegen/docstrings.py +++ b/x2py/wrapper_codegen/docstrings.py @@ -56,18 +56,18 @@ def namespace( overloads: tuple[OverloadPlan, ...], ) -> str: """Index every public owner in one generated Python namespace.""" - qualified_name = ".".join((module_name, *path)) - lines = [qualified_name, "", f"Generated Python interface for native namespace {qualified_name}."] + display_name = path[-1] if path else module_name + lines = [display_name] callable_lines = ( *(self._first_line(function.binding.docstring) for function in functions if function.binding.public), *(self._first_line(overload.docstring) for overload in overloads), ) - self._append_section(lines, "Functions", callable_lines) self._append_section( lines, "Module Attributes", tuple(line for variable in variables for line in self._module_variable_summary_lines(variable)), ) + self._append_section(lines, "Functions", callable_lines) self._append_section(lines, "Classes", tuple(name for surface in classes for name in surface.python_names)) return "\n".join(lines) @@ -264,16 +264,14 @@ def module_variable(self, variable: ModuleVariablePlan) -> str: lines = [f"{name} : {self._type(variable, nullable=nullable, signature=False)}"] lines.extend(self._array_lines(variable.array)) if variable.binding.getter_action is ModuleGetterAction.CONSTANT_VALUE: - lines.append(" Read-only native constant.") + lines.append(" Read-only constant.") elif variable.binding.getter_action is ModuleGetterAction.BORROWED_ARRAY_VIEW: lines.append(" Native-owned borrowed view; mutations affect module storage.") elif variable.native_array_handle is not None: lines.append(f" Persistent {variable.native_array_handle.descriptor_kind.value} descriptor handle.") elif variable.derived is not None: lines.append(" Live native module object.") - if variable.binding.setter_action is SetterAction.WRITE_THROUGH: - lines.append(" Assignment writes through to native storage.") - elif variable.binding.setter_action is SetterAction.REJECT_REPLACEMENT: + if variable.binding.setter_action is SetterAction.REJECT_REPLACEMENT: lines.append(" Replacement assignment is not supported.") return "\n".join(lines) @@ -435,6 +433,8 @@ def _argument_lines(self, argument: ArgumentTransferPlan) -> tuple[str, ...]: lines.extend(self._array_lines(argument.array)) lines.extend(self._optional_lines(argument)) lines.extend(self._mutation_lines(argument)) + if argument.datatype_family is DatatypeFamily.DERIVED or argument.array is not None: + lines.extend(self._ownership_lines(argument.ownership_owner)) if argument.native_array_handle is not None: lines.append(f" Descriptor ownership: {argument.native_array_handle.descriptor_ownership.value}.") return tuple(lines) @@ -572,7 +572,7 @@ def _callback_transfer_type(transfer: CallbackTransferPlan) -> str: def _array_lines(array: ArrayHandoffPlan | None) -> tuple[str, ...]: if array is None: return () - lines = [" Rank: 1..15" if array.rank is None else f" Rank: {array.rank}"] + lines = [WrapperDocstringBuilder._array_rank_line(array)] if array.shape and all(str(extent) not in _UNKNOWN_EXTENTS for extent in array.shape): lines.append(f" Shape: ({', '.join(map(str, array.shape))})") if (array.rank is None or array.rank > 1) and array.order in {"ORDER_C", "ORDER_F"}: @@ -580,6 +580,18 @@ def _array_lines(array: ArrayHandoffPlan | None) -> tuple[str, ...]: lines.append(f" Layout: {layout}") return tuple(lines) + @staticmethod + def _array_rank_line(array: ArrayHandoffPlan) -> str: + if array.flatten_python_storage: + native_rank = 1 if array.rank is None else array.rank + if native_rank == 1: + return " Rank: 1..15, flattened to native rank 1" + edge = "leading" if array.flat_axis == 0 else "final" + return f" Rank: {native_rank}..15, flattened at {edge} Flat axis to native rank {native_rank}" + if array.rank is None: + return " Rank: 1..15" + return f" Rank: {array.rank}" + @staticmethod def _ownership_lines(owner: OwnershipOwner) -> tuple[str, ...]: label = { diff --git a/x2py/wrapper_codegen/fortran/bridge.py b/x2py/wrapper_codegen/fortran/bridge.py index fd7116b04..8374ab716 100644 --- a/x2py/wrapper_codegen/fortran/bridge.py +++ b/x2py/wrapper_codegen/fortran/bridge.py @@ -12,6 +12,7 @@ PythonBarrierAction, SetterAction, ) +from x2py.semantics.metadata import SCALAR_STORAGE_CATEGORY from x2py.semantics.wrapper_policy import ( ArgumentHandoffMode, BridgeDataAction, @@ -32,7 +33,9 @@ ModuleObjectAccessMechanism, NativeArrayDescriptorKind, NativeArrayDescriptorInterop, + NativeArrayDefaultConstruction, NativeArrayOperation, + NativeArrayResultAllocation, NativeDescriptorHandoffABI, NativeInvocationKind, OptionalMode, @@ -73,6 +76,7 @@ ModulePlan, ModuleVariablePlan, NamespacePlan, + NativeArrayHandlePlan, NativeCallSlotPlan, ResultPlan, ) @@ -83,6 +87,10 @@ class FortranBridgeGenerator(ClassVisitor): """Recursively lower bridge plan views directly into Fortran nodes.""" + def __init__(self, *, method_prefix: str | None = None): + super().__init__(method_prefix=method_prefix) + self._active_scoped_type_identities: frozenset[tuple[str, str]] = frozenset() + def require_supported(self, plan: ModulePlan) -> None: """Reject unsupported Fortran ABI actions and scalar types.""" for derived in self._derived_types(plan): @@ -284,8 +292,24 @@ def _require_array_argument_supported(self, argument: ArgumentTransferPlan) -> N if argument.binding.python_action is PythonBarrierAction.RAW_ADDRESS: self._require_raw_array_argument_supported(argument) return + if argument.binding.python_action is PythonBarrierAction.SCALAR_STORAGE: + self._require_scalar_storage_array_argument_supported(argument) + return self._require_array_buffer_argument_supported(argument) + def _require_scalar_storage_array_argument_supported(self, argument: ArgumentTransferPlan) -> None: + """Require one rank-zero NumPy storage handoff to a scalar native dummy.""" + array = argument.array + if not self._is_scalar_storage_array(array): + raise ValueError(f"Unsupported Fortran scalar-storage array rank for {argument.owner_path!r}") + if argument.bridge.handoff_mode is not ArgumentHandoffMode.OPAQUE_ADDRESS: + raise ValueError(f"Unsupported Fortran scalar-storage handoff for {argument.owner_path!r}") + if argument.bridge.native_action is not NativeBarrierAction.PASS_STORAGE_ADDRESS: + raise ValueError(f"Unsupported Fortran scalar-storage native action for {argument.owner_path!r}") + if argument.bridge.data_action is not BridgeDataAction.ASSOCIATE_VIEW: + raise ValueError(f"Unsupported Fortran scalar-storage data action for {argument.owner_path!r}") + PrimitiveScalarTypeRegistry.type_for(argument.semantic_type_name) + def _require_native_array_handle_argument_supported(self, argument: ArgumentTransferPlan) -> None: """Require one typed standard-descriptor bridge argument.""" handle = argument.native_array_handle @@ -357,7 +381,11 @@ def _require_array_plan_result_supported(self, result: ResultPlan) -> None: def _require_array_plan_result_shape_supported(self, result: ResultPlan) -> None: """Require one fixed-rank non-C-oriented direct result shape.""" array = result.array - if array is None or array.rank is None or not 1 <= array.rank <= 15: + if ( + array is None + or array.rank is None + or (not 1 <= array.rank <= 15 and not self._is_scalar_storage_array(array)) + ): raise ValueError(f"Unsupported Fortran array result rank for {result.owner_path!r}") if array.native_order == "ORDER_C" and array.rank > 1: raise ValueError(f"Unsupported Fortran array result order for {result.owner_path!r}") @@ -389,11 +417,10 @@ def _require_array_result_supported(self, function: FunctionPlan, slot: NativeCa self._require_array_result_type_supported(function, slot) def _require_owned_native_array_result_supported(self, result: ResultPlan) -> None: - """Require one wrapper-owned allocatable standard descriptor result.""" + """Require one wrapper-owned standard descriptor result.""" handle = result.native_array_handle if ( handle is None - or handle.descriptor_kind is not NativeArrayDescriptorKind.ALLOCATABLE or handle.handoff.abi is not NativeDescriptorHandoffABI.OWNED_RESULT_STORAGE or handle.array.rank is None ): @@ -404,7 +431,11 @@ def _require_owned_native_array_result_supported(self, result: ResultPlan) -> No def _require_array_result_shape_supported(self, slot: NativeCallSlotPlan) -> None: """Require one fixed-rank non-C-oriented array result shape.""" array = slot.array - if array is None or array.rank is None or not 1 <= array.rank <= 15: + if ( + array is None + or array.rank is None + or (not 1 <= array.rank <= 15 and not self._is_scalar_storage_array(array)) + ): raise ValueError(f"Unsupported Fortran array output rank for {slot.owner_path!r}") if array.native_order == "ORDER_C" and array.rank > 1: raise ValueError(f"Unsupported Fortran array output order for {slot.owner_path!r}") @@ -580,45 +611,50 @@ def _require_nested_derived_field(field: DerivedFieldPlan) -> None: def _visit_ModulePlan(self, plan: ModulePlan) -> FortranModule: """Return one complete Fortran bridge module.""" - return FortranModule( - name=f"bind_c_{plan.bridge.owner_path}_wrapper", - uses=( - FortranUse("iso_c_binding", self._iso_c_symbols(plan)), - *self._native_module_uses(plan), - ), - type_definitions=self._derived_holder_definitions(plan), - interfaces=( - *self._derived_call_interfaces(plan), - *self._external_interfaces(plan), - *self._module_descriptor_callback_interfaces(plan), - *self._derived_array_callback_interfaces(plan), - *self._allocator_interfaces(plan), - ), - procedures=( - *(procedure for namespace in plan.namespaces for procedure in self.visit(namespace)), - # Typed derived-field access remains separate from class orchestration. - *self._derived_field_procedures(plan), - # Native-aware opaque-owner destruction is Phase 8 substrate, not class orchestration. - *self._class_constructor_procedures(plan), - *(self._derived_destroy_procedure(derived) for derived in self._owned_derived_types(plan)), - *( - self._allocatable_holder_destroy_procedure(derived) - for derived in self._allocatable_holder_types(plan) + previous_scoped = self._active_scoped_type_identities + self._active_scoped_type_identities = self._scoped_origin_type_identities(plan) + try: + return FortranModule( + name=f"bind_c_{plan.bridge.owner_path}_wrapper", + uses=( + FortranUse("iso_c_binding", self._iso_c_symbols(plan)), + *self._native_module_uses(plan), ), - *( - self._allocatable_holder_presence_procedure(derived) - for derived in self._allocatable_holder_types(plan) + type_definitions=self._derived_holder_definitions(plan), + interfaces=( + *self._derived_call_interfaces(plan), + *self._external_interfaces(plan), + *self._module_descriptor_callback_interfaces(plan), + *self._derived_array_callback_interfaces(plan), + *self._allocator_interfaces(plan), ), - *(self._pointer_holder_destroy_procedure(derived) for derived in self._pointer_holder_types(plan)), - *(self._pointer_holder_presence_procedure(derived) for derived in self._pointer_holder_types(plan)), - *( - procedure - for variable in self._derived_origin_variables(plan) - for procedure in self._derived_origin_procedures(variable) + procedures=( + *(procedure for namespace in plan.namespaces for procedure in self.visit(namespace)), + # Typed derived-field access remains separate from class orchestration. + *self._derived_field_procedures(plan), + # Native-aware opaque-owner destruction is Phase 8 substrate, not class orchestration. + *self._class_constructor_procedures(plan), + *(self._derived_destroy_procedure(derived) for derived in self._owned_derived_types(plan)), + *( + self._allocatable_holder_destroy_procedure(derived) + for derived in self._allocatable_holder_types(plan) + ), + *( + self._allocatable_holder_presence_procedure(derived) + for derived in self._allocatable_holder_types(plan) + ), + *(self._pointer_holder_destroy_procedure(derived) for derived in self._pointer_holder_types(plan)), + *(self._pointer_holder_presence_procedure(derived) for derived in self._pointer_holder_types(plan)), + *( + procedure + for variable in self._derived_origin_variables(plan) + for procedure in self._derived_origin_procedures(variable) + ), ), - ), - external_procedures=self._callback_external_adapter_procedures(plan), - ) + external_procedures=self._callback_external_adapter_procedures(plan), + ) + finally: + self._active_scoped_type_identities = previous_scoped def _callback_external_adapter_procedures(self, plan: ModulePlan) -> tuple[FortranFunction, ...]: """Return separately linked callback adapters in stable site order.""" @@ -664,9 +700,8 @@ def _visit_NamespacePlan(self, plan: NamespacePlan) -> tuple[FortranFunction, .. for function in plan.functions for procedure in ( self.visit(function), - *self._scalar_descriptor_result_collectors(function), - *self._allocatable_array_result_collectors(function), - *self._allocatable_derived_result_collectors(function), + *self._owned_native_array_result_operations(function), + *self._default_native_array_argument_operations(function), ) ), *(procedure for variable in plan.variables for procedure in self.visit(variable)), @@ -735,6 +770,7 @@ def _visit_FunctionPlan(self, plan: FunctionPlan) -> FortranFunction: is_subroutine=is_subroutine, internal_procedures=( *optional_procedures, + *self._direct_result_internal_procedures(plan), *internal_procedures, ), ) @@ -1177,6 +1213,7 @@ def _scoped_derived_arguments( argument for argument in arguments if self._derived_argument_uses_access(argument, DerivedActualAccess.SCOPED_ADDRESS) + and self._has_scoped_origin_for_argument(argument) ) @staticmethod @@ -1237,7 +1274,11 @@ def _derived_argument_preparation(self, argument: ArgumentTransferPlan) -> Fortr 5: self._derived_allocatable_transaction_preparation, 6: self._derived_pointer_transaction_preparation, } - cases.extend(FortranCase(code, builders[code](argument)) for code in sorted(compatible) if code in builders) + cases.extend( + FortranCase(code, builders[code](argument)) + for code in sorted(compatible) + if code in builders and (code != 2 or self._has_scoped_origin_for_argument(argument)) + ) cases.append( FortranCase( None, @@ -1797,7 +1838,7 @@ def _owned_direct_result_parameters( FortranParameter( "result", self._array_result_element_type(result), - ("allocatable", dimension, "intent(out)"), + (self._owned_native_array_descriptor_attribute(handle), dimension, "intent(out)"), ), ) @@ -1814,6 +1855,224 @@ def _scalar_descriptor_direct_result_parameters( parameters.append(FortranParameter("result_length", "integer(c_int64_t)")) return tuple(parameters) + # Owned native-array result operations. + def _owned_native_array_result_operations(self, function: FunctionPlan) -> tuple[FortranFunction, ...]: + """Lower typed operations over binding-owned result descriptors.""" + procedures = [] + for result in function.results: + if not self._supports_owned_native_array_result_operations(result): + continue + handle = result.native_array_handle + if handle is None: + continue + for operation in handle.operations: + procedure = self._owned_native_array_result_operation(result, operation) + if procedure is not None: + procedures.append(procedure) + return tuple(procedures) + + def _default_native_array_argument_operations( + self, + function: FunctionPlan, + ) -> tuple[FortranFunction, ...]: + """Lower typed operations used after lazy caller-handle attachment.""" + procedures = [] + for argument in function.arguments: + handle = argument.native_array_handle + if ( + handle is None + or handle.default_handle.construction is not NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR + ): + continue + for operation in handle.default_handle.operations: + procedure = self._owned_native_array_result_operation(argument, operation) + if procedure is not None: + procedures.append(procedure) + return tuple(procedures) + + def _supports_owned_native_array_result_operations(self, result: ResultPlan) -> bool: + """Return whether typed helper operations use a Fortran descriptor dummy.""" + return self._is_owned_native_array_result(result) and not self._is_owned_deferred_character_result(result) + + def _owned_native_array_result_operation( + self, + result: ArgumentTransferPlan | ResultPlan, + operation: NativeArrayOperation, + ) -> FortranFunction | None: + """Dispatch one generated operation selected by completed handle policy.""" + if operation in {NativeArrayOperation.ALLOCATED, NativeArrayOperation.ASSOCIATED}: + return self._owned_native_array_result_state_operation(result, operation) + if operation is NativeArrayOperation.CONTIGUOUS: + return self._owned_native_array_result_contiguous_operation(result) + if operation is NativeArrayOperation.SHAPE: + return self._owned_native_array_result_shape_operation(result) + if operation is NativeArrayOperation.ASSOCIATE: + return self._owned_native_array_result_associate_operation(result) + if operation in {NativeArrayOperation.DEALLOCATE, NativeArrayOperation.NULLIFY, NativeArrayOperation.DESTROY}: + return self._owned_native_array_result_release_operation(result, operation) + return None + + def _owned_native_array_result_state_operation( + self, + result: ArgumentTransferPlan | ResultPlan, + operation: NativeArrayOperation, + ) -> FortranFunction: + """Return descriptor presence using its completed compiler inquiry.""" + inquiry = self._owned_native_array_result_presence_inquiry(result) + name = self._owned_native_array_result_operation_name(result, operation) + return FortranFunction( + name=name, + parameters=(self._owned_native_array_result_parameter(result, intent="in"),), + result_name="state", + result_type="logical(c_bool)", + bind_name=name, + body=(FortranAssignment("state", CodeExpression(f"{inquiry}(result)")),), + ) + + def _owned_native_array_result_contiguous_operation( + self, + result: ArgumentTransferPlan | ResultPlan, + ) -> FortranFunction: + """Return target contiguity without querying an absent pointer target.""" + name = self._owned_native_array_result_operation_name(result, NativeArrayOperation.CONTIGUOUS) + return FortranFunction( + name=name, + parameters=(self._owned_native_array_result_parameter(result, intent="in"),), + result_name="state", + result_type="logical(c_bool)", + bind_name=name, + body=( + FortranAssignment("state", CodeExpression(".false._c_bool")), + FortranIf( + CodeExpression("associated(result)"), + body=(FortranAssignment("state", CodeExpression("is_contiguous(result)")),), + ), + ), + ) + + def _owned_native_array_result_shape_operation( + self, + result: ArgumentTransferPlan | ResultPlan, + ) -> FortranFunction: + """Return shape through Fortran when the owned descriptor is allocated.""" + handle = result.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Owned result {result.owner_path!r} has no shape rank") + name = self._owned_native_array_result_operation_name(result, NativeArrayOperation.SHAPE) + extents = tuple(FortranParameter(f"extent_{axis}", "integer(c_int64_t)") for axis in range(handle.array.rank)) + present = tuple( + FortranAssignment( + f"extent_{axis}", + CodeExpression(f"size(result, {axis + 1}, kind=c_int64_t)"), + ) + for axis in range(handle.array.rank) + ) + absent = tuple( + FortranAssignment(f"extent_{axis}", CodeExpression("0_c_int64_t")) for axis in range(handle.array.rank) + ) + inquiry = self._owned_native_array_result_presence_inquiry(result) + return FortranFunction( + name=name, + parameters=(self._owned_native_array_result_parameter(result, intent="in"), *extents), + bind_name=name, + body=( + FortranIf( + CodeExpression(f"{inquiry}(result)"), + body=present, + else_body=absent, + ), + ), + is_subroutine=True, + ) + + def _owned_native_array_result_release_operation( + self, + result: ArgumentTransferPlan | ResultPlan, + operation: NativeArrayOperation, + ) -> FortranFunction: + """Apply the planned payload or association release operation.""" + name = self._owned_native_array_result_operation_name(result, operation) + handle = result.native_array_handle + if handle is None: + raise ValueError(f"Owned result {result.owner_path!r} has no descriptor policy") + pointer = handle.descriptor_kind is NativeArrayDescriptorKind.POINTER + nullify_only = pointer and operation in {NativeArrayOperation.NULLIFY, NativeArrayOperation.DESTROY} + inquiry = self._owned_native_array_result_presence_inquiry(result) + release = FortranNullify("result") if nullify_only else FortranDeallocate("result") + return FortranFunction( + name=name, + parameters=(self._owned_native_array_result_parameter(result, intent="inout"),), + bind_name=name, + body=( + FortranIf( + CodeExpression(f"{inquiry}(result)"), + body=(release,), + ), + ), + is_subroutine=True, + ) + + def _owned_native_array_result_associate_operation( + self, + result: ArgumentTransferPlan | ResultPlan, + ) -> FortranFunction: + """Make one owned pointer descriptor match another pointer descriptor.""" + name = self._owned_native_array_result_operation_name(result, NativeArrayOperation.ASSOCIATE) + return FortranFunction( + name=name, + parameters=( + self._owned_native_array_result_parameter(result, intent="inout"), + self._owned_native_array_result_parameter(result, intent="in", name="source"), + ), + bind_name=name, + body=(FortranPointerAssignment("result", CodeExpression("source")),), + is_subroutine=True, + ) + + def _owned_native_array_result_parameter( + self, + result: ArgumentTransferPlan | ResultPlan, + *, + intent: str, + name: str = "result", + ) -> FortranParameter: + """Return the typed descriptor dummy used by owned-result operations.""" + handle = result.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Owned result {result.owner_path!r} has no descriptor rank") + return FortranParameter( + name, + self._array_result_element_type(result), + ( + self._owned_native_array_descriptor_attribute(handle), + self._array_dimension_attribute(handle.array.rank), + f"intent({intent})", + ), + ) + + @staticmethod + def _owned_native_array_descriptor_attribute(handle: NativeArrayHandlePlan) -> str: + """Return the descriptor attribute selected by completed handle policy.""" + return handle.descriptor_kind.value + + @staticmethod + def _owned_native_array_result_presence_inquiry(result: ArgumentTransferPlan | ResultPlan) -> str: + """Return the compiler inquiry selected by completed descriptor kind.""" + handle = result.native_array_handle + if handle is None: + raise ValueError(f"Owned result {result.owner_path!r} has no descriptor policy") + return "associated" if handle.descriptor_kind is NativeArrayDescriptorKind.POINTER else "allocated" + + def _owned_native_array_result_operation_name( + self, + result: ArgumentTransferPlan | ResultPlan, + operation: NativeArrayOperation, + ) -> str: + """Return the C-visible typed operation name for one owned result.""" + preferred = result.bridge.native_name or "result" + owner = NativeSymbolNames.compact(result.owner_path, preferred, limit=38) + return f"bind_c_owned_{owner}_{operation.value}" + def _visit_ModuleVariablePlan(self, plan: ModuleVariablePlan) -> tuple[FortranFunction, ...]: """Lower bridge-owned getter and setter actions into procedures.""" if plan.binding.getter_action is ModuleGetterAction.NATIVE_ARRAY_HANDLE: @@ -1895,6 +2154,18 @@ def _lower_module_derived_presence(self, plan: ModuleVariablePlan) -> tuple[Fort def _derived_origin_variables(self, plan: ModulePlan) -> tuple[ModuleVariablePlan, ...]: return tuple(variable for variable in self._variables(plan) if variable.derived is not None) + def _scoped_origin_type_identities(self, plan: ModulePlan) -> frozenset[tuple[str, str]]: + """Return derived identities with at least one scoped module-origin producer.""" + return frozenset( + variable.derived.handoff.type_identity + for variable in self._derived_origin_variables(plan) + if self._derived_origin_supports(variable, "scoped") + ) + + def _has_scoped_origin_for_argument(self, argument: ArgumentTransferPlan) -> bool: + """Return whether this bridge module can produce a scoped origin for the argument type.""" + return argument.derived is not None and argument.derived.type_identity in self._active_scoped_type_identities + def _derived_origin_procedures(self, variable: ModuleVariablePlan) -> tuple[FortranFunction, ...]: """Emit only the typed leaves supported by one completed module storage.""" builders = { @@ -2258,6 +2529,8 @@ def _lower_module_native_array_bridge_operation( return self._module_native_array_shape_operation(plan) if operation is NativeArrayOperation.DESCRIPTOR: return self._module_native_array_descriptor_operation(plan) + if operation is NativeArrayOperation.ASSOCIATE: + return self._module_native_array_associate_operation(plan) if operation in {NativeArrayOperation.ALLOCATE, NativeArrayOperation.RESIZE}: return self._module_native_array_shape_mutation_operation(plan, operation) if operation is NativeArrayOperation.DEALLOCATE: @@ -2455,6 +2728,31 @@ def _module_native_array_shape_mutation_operation(self, plan: ModuleVariablePlan is_subroutine=True, ) + def _module_native_array_associate_operation(self, plan: ModuleVariablePlan) -> FortranFunction: + """Make one module pointer association match the source descriptor.""" + handle = plan.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Module pointer handle {plan.owner_path!r} has no association rank") + name = self._module_native_array_operation_name(plan, NativeArrayOperation.ASSOCIATE) + return FortranFunction( + name=name, + parameters=( + FortranParameter( + "source", + self._module_native_array_element_type(plan), + ("pointer", self._array_dimension_attribute(handle.array.rank), "intent(in)"), + ), + ), + bind_name=name, + body=( + FortranPointerAssignment( + self._native_variable_name(plan), + CodeExpression("source"), + ), + ), + is_subroutine=True, + ) + def _module_native_array_deallocate_operation(self, plan: ModuleVariablePlan) -> FortranFunction: """Deallocate one policy-authorized module descriptor payload.""" name = self._module_native_array_operation_name(plan, NativeArrayOperation.DEALLOCATE) @@ -2738,10 +3036,28 @@ def _lower_array_argument( raise ValueError(f"Unsupported Fortran array presence mode for {plan.owner_path!r}: {mode!r}") if plan.bridge.handoff_mode is ArgumentHandoffMode.ARRAY_BUFFER: return self._lower_argument_array_buffer(plan) - if mode is OptionalMode.REQUIRED and plan.bridge.handoff_mode is ArgumentHandoffMode.OPAQUE_ADDRESS: + if plan.bridge.handoff_mode is ArgumentHandoffMode.OPAQUE_ADDRESS: + return self._lower_opaque_array_argument(plan, mode) + raise ValueError(f"Unsupported Fortran array handoff for {plan.owner_path!r}: {plan.bridge.handoff_mode!r}") + + def _lower_opaque_array_argument( + self, + plan: ArgumentTransferPlan, + mode: OptionalMode, + ) -> tuple[FortranParameter, ...]: + """Lower raw array addresses and rank-zero scalar-storage arrays.""" + if mode is OptionalMode.REQUIRED and self._is_opaque_array_required_argument(plan): return self._lower_argument_required_opaque_address(plan) + if mode is OptionalMode.NULLABLE_VALUE and self._is_scalar_storage_array(plan.array): + return self._lower_argument_nullable_value(plan) raise ValueError(f"Unsupported Fortran array handoff for {plan.owner_path!r}: {plan.bridge.handoff_mode!r}") + def _is_opaque_array_required_argument(self, plan: ArgumentTransferPlan) -> bool: + """Return whether a required array-shaped argument uses an opaque address.""" + return bool( + plan.binding.python_action is PythonBarrierAction.RAW_ADDRESS or self._is_scalar_storage_array(plan.array) + ) + # Native-array-handle bridge parameters. def _lower_argument_native_array_descriptor( self, @@ -3183,38 +3499,31 @@ def _native_result_expression_invocation( ) -> FortranAssignment | FortranCall | FortranPointerAssignment: """Store one completed native result expression through its handoff leaf.""" direct_result = self._direct_result(plan) - collector = self._native_result_collector_name(plan, direct_result) - if collector is not None: + if self._uses_owned_direct_array_result_collector(plan): return FortranCall( - collector, - (CodeExpression(expression), CodeExpression(result_name)), + self._owned_direct_array_result_collector_name(), + (CodeExpression(expression), CodeExpression("result")), ) if self._uses_pointer_result_assignment(direct_result): return FortranPointerAssignment(result_name, CodeExpression(expression)) return FortranAssignment(result_name, CodeExpression(expression)) - def _native_result_collector_name( - self, - plan: FunctionPlan, - result: ResultPlan | None, - ) -> str | None: - """Return the preselected nullable-result collector, when required.""" - if self._is_allocatable_scalar_descriptor_result(result): - return self._scalar_descriptor_result_collector_name(plan) - if result is not None and self._is_owned_native_array_result(result): - return self._allocatable_array_result_collector_name(plan) - if self._is_allocatable_derived_holder_result(result): - return self._allocatable_derived_result_collector_name(plan) - return None - def _uses_pointer_result_assignment(self, result: ResultPlan | None) -> bool: """Return whether the completed result keeps native pointer association.""" if self._is_pointer_derived_holder_result(result): return True return bool( result is not None - and result.scalar_descriptor is not None - and result.scalar_descriptor.descriptor_kind is NativeArrayDescriptorKind.POINTER + and ( + ( + result.scalar_descriptor is not None + and result.scalar_descriptor.descriptor_kind is NativeArrayDescriptorKind.POINTER + ) + or ( + result.native_array_handle is not None + and result.native_array_handle.descriptor_kind is NativeArrayDescriptorKind.POINTER + ) + ) ) def _native_invocation_target( @@ -3237,76 +3546,6 @@ def _native_invocation_target( expression = replacements.get(receiver.owner_path, self._native_argument_expression(receiver)) return f"{expression}%{class_call.type_bound_name}", receiver.native_call_slot.native_position - def _allocatable_derived_result_collectors( - self, - plan: FunctionPlan, - ) -> tuple[FortranFunction, ...]: - """Capture an allocatable function result before its temporary expires.""" - result = self._direct_result(plan) - if not self._is_allocatable_derived_holder_result(result): - return () - native_type = f"type({self._derived_native_alias(result.derived.backend_symbol)})" - return ( - FortranFunction( - name=self._allocatable_derived_result_collector_name(plan), - parameters=( - FortranParameter("value", native_type, ("allocatable", "intent(in)")), - FortranParameter("target", native_type, ("allocatable", "intent(out)")), - ), - body=( - FortranIf( - CodeExpression("allocated(value)"), - body=(FortranAssignment("target", CodeExpression("value")),), - ), - ), - is_subroutine=True, - ), - ) - - def _allocatable_array_result_collectors( - self, - plan: FunctionPlan, - ) -> tuple[FortranFunction, ...]: - """Capture an allocatable array result without referencing an absent payload.""" - result = self._direct_result(plan) - if result is None or not self._is_owned_native_array_result(result): - return () - handle = result.native_array_handle - if handle is None or handle.array.rank is None: - raise ValueError(f"Owned result {result.owner_path!r} has no descriptor rank") - attributes = ("allocatable", self._array_dimension_attribute(handle.array.rank)) - element_type = self._array_result_element_type(result) - return ( - FortranFunction( - name=self._allocatable_array_result_collector_name(plan), - parameters=( - FortranParameter("value", element_type, (*attributes, "intent(in)")), - FortranParameter("target", element_type, (*attributes, "intent(out)")), - ), - body=( - FortranIf( - CodeExpression("allocated(value)"), - body=(FortranAssignment("target", CodeExpression("value")),), - ), - ), - is_subroutine=True, - ), - ) - - @staticmethod - def _allocatable_array_result_collector_name(plan: FunctionPlan) -> str: - """Return the stable collector name for one owned array result.""" - return f"x2py_collect_{plan.symbol_name}_allocatable_array_result" - - @staticmethod - def _is_allocatable_derived_holder_result(result: ResultPlan | None) -> bool: - return bool( - result is not None - and result.object_kind is ObjectKind.DERIVED_TYPE - and result.derived is not None - and result.derived.storage is DerivedObjectStorage.ALLOCATABLE_HOLDER - ) - @staticmethod def _is_pointer_derived_holder_result(result: ResultPlan | None) -> bool: return bool( @@ -3316,45 +3555,6 @@ def _is_pointer_derived_holder_result(result: ResultPlan | None) -> bool: and result.derived.storage is DerivedObjectStorage.POINTER_HOLDER ) - @staticmethod - def _allocatable_derived_result_collector_name(plan: FunctionPlan) -> str: - return f"x2py_collect_{plan.symbol_name}_allocatable_derived_result" - - # Nullable rank-zero allocatable result collection. - def _scalar_descriptor_result_collectors( - self, - plan: FunctionPlan, - ) -> tuple[FortranFunction, ...]: - """Preserve an unallocated direct scalar result before copy-out.""" - result = self._direct_result(plan) - if not self._is_allocatable_scalar_descriptor_result(result): - return () - element_type = ( - "character(kind=c_char, len=:)" - if result.object_kind is ObjectKind.STRING - else PrimitiveScalarTypeRegistry.type_for(result.semantic_type_name).fortran_spelling - ) - return ( - FortranFunction( - name=self._scalar_descriptor_result_collector_name(plan), - parameters=( - FortranParameter("value", element_type, ("allocatable", "intent(in)")), - FortranParameter("target", element_type, ("allocatable", "intent(out)")), - ), - body=( - FortranIf( - CodeExpression("allocated(value)"), - body=(FortranAssignment("target", CodeExpression("value")),), - ), - ), - is_subroutine=True, - ), - ) - - def _scalar_descriptor_result_collector_name(self, plan: FunctionPlan) -> str: - """Return the stable helper name for one rank-zero allocatable result.""" - return f"x2py_collect_{plan.symbol_name}_scalar_descriptor_result" - def _native_arguments( self, plan: FunctionPlan, @@ -3652,7 +3852,10 @@ def _opaque_address_declarations(self, plan: FunctionPlan) -> tuple[FortranDecla and argument.bridge.handoff_mode is ArgumentHandoffMode.OPAQUE_ADDRESS and argument.bridge.data_action in {BridgeDataAction.ASSOCIATE_VIEW, BridgeDataAction.COPY_REPRESENTATION} - and argument.object_kind in {ObjectKind.SCALAR, ObjectKind.DERIVED_TYPE} + and ( + argument.object_kind in {ObjectKind.SCALAR, ObjectKind.DERIVED_TYPE} + or self._is_scalar_storage_array(argument.array) + ) ) for declaration in ( self._derived_argument_declarations(argument) @@ -3699,7 +3902,10 @@ def _opaque_address_initializers( and argument.bridge.handoff_mode is ArgumentHandoffMode.OPAQUE_ADDRESS and argument.bridge.data_action in {BridgeDataAction.ASSOCIATE_VIEW, BridgeDataAction.COPY_REPRESENTATION} - and argument.object_kind in {ObjectKind.SCALAR, ObjectKind.DERIVED_TYPE} + and ( + argument.object_kind in {ObjectKind.SCALAR, ObjectKind.DERIVED_TYPE} + or self._is_scalar_storage_array(argument.array) + ) ) for node in self._opaque_address_initializer_nodes(argument) ) @@ -4190,7 +4396,11 @@ def _owned_native_array_output_parameters(self, slot: NativeCallSlotPlan) -> tup FortranParameter( name, self._array_result_element_type(slot), - ("allocatable", self._array_dimension_attribute(handle.array.rank), "intent(out)"), + ( + self._owned_native_array_descriptor_attribute(handle), + self._array_dimension_attribute(handle.array.rank), + "intent(out)", + ), ), ) @@ -4211,7 +4421,10 @@ def _native_output_declarations(self, plan: FunctionPlan) -> tuple[FortranDeclar FortranDeclaration( f"{slot.native_name.lower()}_value", self._array_result_element_type(slot), - ("allocatable", self._array_dimension_attribute(handle.array.rank)), + ( + self._owned_native_array_descriptor_attribute(handle), + self._array_dimension_attribute(handle.array.rank), + ), ) ) if self._is_owned_deferred_character_slot(slot): @@ -4241,6 +4454,8 @@ def _direct_result_declarations(self, plan: FunctionPlan) -> tuple[FortranDeclar if result.scalar_descriptor is not None: return self._scalar_descriptor_copy_declarations(result, "result") if self._is_owned_native_array_result(result): + if self._uses_owned_direct_array_result_collector(plan): + return () return self._owned_array_result_declarations(result) if result.object_kind is ObjectKind.NUMPY_ARRAY: return self._direct_array_result_declarations(plan, result) @@ -4267,7 +4482,10 @@ def _owned_array_result_declarations(self, result: ResultPlan) -> tuple[FortranD FortranDeclaration( "result_value", self._array_result_element_type(result), - ("allocatable", self._array_dimension_attribute(handle.array.rank)), + ( + self._owned_native_array_descriptor_attribute(handle), + self._array_dimension_attribute(handle.array.rank), + ), ), ] if self._is_owned_deferred_character_result(result): @@ -4416,6 +4634,11 @@ def _direct_array_result_declarations( """Declare typed native and contiguous-copy storage for one array result.""" shape = self._array_result_shape(plan, result) element_type = self._array_result_element_type(result) + if self._is_scalar_storage_array(result.array): + return ( + FortranDeclaration("result_value", element_type), + FortranDeclaration("result_copy", element_type, ("pointer",)), + ) copy_type = "character(kind=c_char)" if result.datatype_family is DatatypeFamily.STRING else element_type return ( FortranDeclaration("result_value", element_type, (f"dimension({', '.join(shape)})",)), @@ -4433,14 +4656,7 @@ def _direct_result_finalizers( if result.scalar_descriptor is not None: return self._scalar_descriptor_copy_nodes(result, "result") if self._is_owned_native_array_result(result): - if self._is_owned_deferred_character_result(result): - return self._owned_deferred_character_copy_nodes(result, "result", "result_value", "result_copy") - return ( - FortranCall( - "move_alloc", - (CodeExpression("result_value"), CodeExpression("result")), - ), - ) + return self._owned_direct_native_array_result_finalizers(plan, result) if result.object_kind is ObjectKind.NUMPY_ARRAY: if result.array is None: raise ValueError(f"Array result {result.owner_path!r} has no shape plan") @@ -4463,6 +4679,79 @@ def _direct_result_finalizers( copy_name="result_copy", ) + def _owned_direct_native_array_result_finalizers( + self, + plan: FunctionPlan, + result: ResultPlan, + ) -> tuple[FortranAssignment | FortranCall | FortranIf | FortranPointerAssignment, ...]: + """Finalize one owned result through its completed descriptor kind.""" + if self._uses_owned_direct_array_result_collector(plan): + return () + if self._is_owned_deferred_character_result(result): + return self._owned_deferred_character_copy_nodes(result, "result", "result_value", "result_copy") + handle = result.native_array_handle + if handle is None: + raise ValueError(f"Owned result {result.owner_path!r} has no descriptor policy") + if handle.descriptor_kind is NativeArrayDescriptorKind.POINTER: + return (FortranPointerAssignment("result", CodeExpression("result_value")),) + return ( + FortranIf( + CodeExpression("allocated(result_value)"), + body=( + FortranCall( + "move_alloc", + (CodeExpression("result_value"), CodeExpression("result")), + ), + ), + else_body=( + FortranIf( + CodeExpression("allocated(result)"), + body=(FortranDeallocate("result"),), + ), + ), + ), + ) + + def _direct_result_internal_procedures(self, plan: FunctionPlan) -> tuple[FortranFunction, ...]: + """Return helper procedures needed by direct-result lowering.""" + result = self._direct_result(plan) + if result is None or not self._uses_owned_direct_array_result_collector(plan): + return () + return (self._owned_direct_array_result_collector(result),) + + def _owned_direct_array_result_collector(self, result: ResultPlan) -> FortranFunction: + """Move a GNU allocatable function result without the crashing assignment path.""" + handle = result.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Owned result {result.owner_path!r} has no descriptor rank") + element_type = self._array_result_element_type(result) + dimension = self._array_dimension_attribute(handle.array.rank) + return FortranFunction( + name=self._owned_direct_array_result_collector_name(), + parameters=( + FortranParameter("value", element_type, ("allocatable", dimension)), + FortranParameter("result", element_type, ("allocatable", dimension, "intent(out)")), + ), + body=( + FortranIf( + CodeExpression("allocated(value)"), + body=( + FortranCall( + "move_alloc", + (CodeExpression("value"), CodeExpression("result")), + ), + ), + else_body=( + FortranIf( + CodeExpression("allocated(result)"), + body=(FortranDeallocate("result"),), + ), + ), + ), + ), + is_subroutine=True, + ) + @staticmethod def _derived_direct_result_finalizers( result: ResultPlan, @@ -4543,6 +4832,12 @@ def _array_copy_output_declarations( if slot.semantic_type_name is None: raise ValueError(f"Missing array output datatype for {slot.owner_path!r}") element_type = self._array_result_element_type(slot) + if self._is_scalar_storage_array(slot.array): + name = slot.native_name.lower() + return ( + FortranDeclaration(f"{name}_value", element_type), + FortranDeclaration(f"{name}_copy", element_type, ("pointer",)), + ) copy_type = "character(kind=c_char)" if slot.datatype_family is DatatypeFamily.STRING else element_type name = slot.native_name.lower() return ( @@ -4574,6 +4869,9 @@ def _native_output_finalizers( ) ) continue + if slot.native_array_handle.descriptor_kind is NativeArrayDescriptorKind.POINTER: + nodes.append(FortranPointerAssignment(name, CodeExpression(f"{name}_value"))) + continue nodes.append( FortranIf( CodeExpression(f"allocated({name}_value)"), @@ -4763,8 +5061,15 @@ def _fixed_array_copy_nodes( copy_name: str, ) -> tuple[FortranAssignment | FortranIf, ...]: """Allocate and fill one detached contiguous ordinary-array copy.""" - if rank is None or rank <= 0: + if rank is None or rank < 0: raise ValueError(f"Array copy {value_name!r} requires a fixed positive rank") + if rank == 0: + return self._fixed_scalar_storage_copy_nodes( + itemsize, + target_name=target_name, + value_name=value_name, + copy_name=copy_name, + ) if order == "ORDER_C" and rank > 1: raise ValueError(f"Array copy {value_name!r} requires Fortran element order") if itemsize is not None: @@ -4798,6 +5103,37 @@ def _fixed_array_copy_nodes( ), ) + def _fixed_scalar_storage_copy_nodes( + self, + itemsize: int | None, + *, + target_name: str, + value_name: str, + copy_name: str, + ) -> tuple[FortranAssignment | FortranIf, ...]: + """Allocate and fill one detached copy for a rank-zero NumPy result.""" + if itemsize is not None: + raise ValueError(f"Scalar-storage copy {value_name!r} does not support character itemsize") + return ( + FortranAssignment( + target_name, + CodeExpression(f"c_malloc(max(1_c_size_t, c_sizeof({value_name})))"), + ), + FortranIf( + CodeExpression(f"c_associated({target_name})"), + body=( + FortranCall( + "c_f_pointer", + ( + CodeExpression(target_name), + CodeExpression(copy_name), + ), + ), + FortranAssignment(copy_name, CodeExpression(value_name)), + ), + ), + ) + def _fixed_character_array_copy_nodes( self, itemsize: int, @@ -4955,15 +5291,21 @@ def _owned_direct_result(self, plan: FunctionPlan) -> ResultPlan | None: result = self._direct_result(plan) return result if result is not None and self._is_owned_native_array_result(result) else None - @staticmethod - def _is_allocatable_scalar_descriptor_result(result: ResultPlan | None) -> bool: - """Return whether a direct rank-zero result must preserve unallocated state.""" - return ( + def _uses_owned_direct_array_result_collector(self, plan: FunctionPlan) -> bool: + """Return whether a direct function result may be returned unallocated.""" + result = self._direct_result(plan) + return bool( result is not None - and result.scalar_descriptor is not None - and result.scalar_descriptor.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE + and self._is_owned_native_array_result(result) + and not self._is_owned_deferred_character_result(result) + and result.native_array_handle is not None + and result.native_array_handle.result_allocation is NativeArrayResultAllocation.MAYBE_UNALLOCATED ) + @staticmethod + def _owned_direct_array_result_collector_name() -> str: + return "x2py_collect_allocatable_array_result" + @staticmethod def _is_owned_native_array_result(result: ResultPlan) -> bool: """Return whether one result owns persistent standard-descriptor storage.""" @@ -5531,6 +5873,8 @@ def _native_handle_field_procedure( return self._native_handle_field_shape_procedure(owner, field) if operation is NativeArrayOperation.DESCRIPTOR: return self._native_handle_field_descriptor_procedure(owner, field) + if operation is NativeArrayOperation.ASSOCIATE: + return self._native_handle_field_associate_procedure(owner, field) if operation in {NativeArrayOperation.ALLOCATE, NativeArrayOperation.RESIZE}: return self._native_handle_field_resize_procedure(owner, field, operation) if operation is NativeArrayOperation.DEALLOCATE: @@ -5673,6 +6017,37 @@ def _native_handle_field_resize_procedure(self, owner, field, operation) -> Fort is_subroutine=True, ) + def _native_handle_field_associate_procedure(self, owner, field) -> FortranFunction: + """Make one pointer field association match the source descriptor.""" + handle = field.native_array_handle + if handle is None or handle.array.rank is None: + raise ValueError(f"Pointer field {field.owner_path!r} has no association rank") + element_type = ( + "character(kind=c_char, len=:)" + if field.string_element + else PrimitiveScalarTypeRegistry.type_for(field.semantic_type_name).fortran_spelling + ) + expression = self._native_handle_field_expression(owner, field) + name = self._native_handle_field_bridge_name(owner, field, NativeArrayOperation.ASSOCIATE) + return FortranFunction( + name=name, + parameters=( + *self._native_handle_field_owner_parameters(owner), + FortranParameter( + "source", + element_type, + ("pointer", self._array_dimension_attribute(handle.array.rank), "intent(in)"), + ), + ), + bind_name=name, + declarations=self._native_handle_field_owner_declarations(owner), + body=( + *self._native_handle_field_owner_body(owner), + FortranPointerAssignment(expression, CodeExpression("source")), + ), + is_subroutine=True, + ) + def _native_handle_field_deallocate_procedure(self, owner, field) -> FortranFunction: expression = self._native_handle_field_expression(owner, field) name = self._native_handle_field_bridge_name(owner, field, NativeArrayOperation.DEALLOCATE) @@ -6803,6 +7178,8 @@ def _external_interface_array_result_parameter( """Declare one completed ordinary or descriptor array output.""" if slot.array is None: raise ValueError(f"Array output {slot.owner_path!r} has no shape plan") + if self._is_scalar_storage_array(slot.array): + return FortranParameter(slot.native_name.lower(), self._array_result_element_type(slot)) attributes = [] if slot.native_array_handle is not None: attributes.append( @@ -6855,6 +7232,8 @@ def _native_result_type(self, plan: FunctionPlan, result: ResultPlan | None) -> scalar_type = PrimitiveScalarTypeRegistry.type_for(result.semantic_type_name) return f"{scalar_type.fortran_spelling}, {attribute}" if result.object_kind is ObjectKind.NUMPY_ARRAY: + if self._is_scalar_storage_array(result.array): + return self._array_result_element_type(result) shape = self._array_result_shape(plan, result) return f"{self._array_result_element_type(result)}, dimension({', '.join(shape)})" if result.object_kind is ObjectKind.STRING: @@ -6886,22 +7265,11 @@ def _external_interface_parameter( else () ) if argument.object_kind is ObjectKind.NUMPY_ARRAY: - array = argument.array - if array is None: - raise ValueError(f"Array argument {argument.owner_path!r} has no shape plan") - element_type = self._array_element_fortran_type(argument) - dimension = self._external_array_dimension(plan, argument) - if argument.native_array_handle is not None: - descriptor_attribute = ( - "allocatable" - if argument.native_array_handle.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE - else "pointer" - ) - attributes = (*attributes, descriptor_attribute) - return FortranParameter( + return self._external_interface_array_argument_parameter( + plan, + argument, parameter_name, - element_type, - (*attributes, f"dimension({dimension})"), + attributes, ) if argument.object_kind is ObjectKind.STRING: length = argument.native_call_slot.character_length @@ -6917,6 +7285,34 @@ def _external_interface_parameter( attributes, ) + def _external_interface_array_argument_parameter( + self, + plan: FunctionPlan, + argument: ArgumentTransferPlan, + parameter_name: str, + attributes: tuple[str, ...], + ) -> FortranParameter: + """Declare a native array or scalar-storage dummy from completed array facts.""" + array = argument.array + if array is None: + raise ValueError(f"Array argument {argument.owner_path!r} has no shape plan") + element_type = self._array_element_fortran_type(argument) + if self._is_scalar_storage_array(array): + return FortranParameter(parameter_name, element_type, attributes) + dimension = self._external_array_dimension(plan, argument) + if argument.native_array_handle is not None: + descriptor_attribute = ( + "allocatable" + if argument.native_array_handle.descriptor_kind is NativeArrayDescriptorKind.ALLOCATABLE + else "pointer" + ) + attributes = (*attributes, descriptor_attribute) + return FortranParameter( + parameter_name, + element_type, + (*attributes, f"dimension({dimension})"), + ) + def _external_array_dimension(self, plan: FunctionPlan, argument: ArgumentTransferPlan) -> str: """Lower the completed native dummy shape without changing its ABI category.""" array = argument.array @@ -6945,6 +7341,10 @@ def _external_assumed_size_dimension(array: ArrayHandoffPlan, shape: list[str]) shape[-1] = "*" return ", ".join(shape) + @staticmethod + def _is_scalar_storage_array(array: ArrayHandoffPlan | None) -> bool: + return bool(array is not None and array.rank == 0 and array.category == SCALAR_STORAGE_CATEGORY) + # Ordinary-array result-shape lowering. def _array_result_shape(self, plan: FunctionPlan, result: ResultPlan) -> tuple[str, ...]: """Lower one result shape through the plan's native scalar roles.""" diff --git a/x2py/wrapper_codegen/generator.py b/x2py/wrapper_codegen/generator.py index 50c55bafb..4b8413d7e 100644 --- a/x2py/wrapper_codegen/generator.py +++ b/x2py/wrapper_codegen/generator.py @@ -24,6 +24,7 @@ StorageMode, TransferMode, ) +from x2py.semantics.metadata import SCALAR_STORAGE_CATEGORY from x2py.semantics.wrapper_policy import ( ArgumentHandoffMode, BridgeDataAction, @@ -58,6 +59,7 @@ NativeArrayDescriptorInterop, NativeArrayDescriptorKind, NativeArrayDescriptorOwnership, + NativeArrayDefaultConstruction, NativeArrayDestroyBehavior, NativeArrayExtractionAction, NativeArrayHandleKind, @@ -1870,6 +1872,7 @@ def _native_array_handle_argument_diagnostics( *self._native_array_handle_argument_ownership_diagnostics(plan, handle), *self._native_array_handle_shape_diagnostics(plan.owner_path, handle), *self._native_descriptor_handoff_diagnostics(plan.owner_path, handle, plan), + *self._native_array_default_handle_diagnostics(plan.owner_path, handle), ] if plan.array is not handle.array: diagnostics.append(self._diagnostic(plan.owner_path, "inconsistent-handle-array-facet", plan.array)) @@ -1931,6 +1934,125 @@ def _native_array_handle_argument_ownership_diagnostics( ) return tuple(diagnostics) + def _native_array_default_handle_diagnostics( + self, + owner_path: str, + handle: NativeArrayHandlePlan, + ) -> tuple[WrapperPlanDiagnostic, ...]: + """Validate caller-construction ownership, lifecycle, and named roles.""" + default = handle.default_handle + if default.construction is NativeArrayDefaultConstruction.NONE: + return self._disabled_native_array_default_handle_diagnostics(owner_path, handle) + return ( + *self._native_array_default_handle_lifecycle_diagnostics(owner_path, handle), + *self._native_array_default_handle_operation_diagnostics(owner_path, handle), + *self._native_array_default_handle_storage_diagnostics(owner_path, handle), + ) + + def _disabled_native_array_default_handle_diagnostics( + self, + owner_path: str, + handle: NativeArrayHandlePlan, + ) -> tuple[WrapperPlanDiagnostic, ...]: + """Require an omitted lifecycle when caller construction is disabled.""" + default = handle.default_handle + actual = ( + default.descriptor_ownership, + default.release, + default.destroy_behavior, + default.operations, + default.operation_roles, + default.owner_storage_role, + ) + expected = ( + None, + NativeArrayRelease.NONE, + NativeArrayDestroyBehavior.NONE, + (), + (), + None, + ) + if actual == expected: + return () + return (self._diagnostic(owner_path, "invalid-disabled-default-handle-policy", default),) + + def _native_array_default_handle_lifecycle_diagnostics( + self, + owner_path: str, + handle: NativeArrayHandlePlan, + ) -> tuple[WrapperPlanDiagnostic, ...]: + """Require owned descriptor storage and finalizer release.""" + default = handle.default_handle + diagnostics = [] + if default.descriptor_ownership is not NativeArrayDescriptorOwnership.OWNED: + diagnostics.append( + self._diagnostic( + owner_path, + "invalid-default-handle-descriptor-ownership", + default.descriptor_ownership, + ) + ) + lifecycle = default.release, default.destroy_behavior + expected = NativeArrayRelease.WRAPPER_DEALLOC, NativeArrayDestroyBehavior.HANDLE_FINALIZER + if lifecycle != expected: + diagnostics.append(self._diagnostic(owner_path, "invalid-default-handle-lifecycle", default.release)) + return tuple(diagnostics) + + def _native_array_default_handle_operation_diagnostics( + self, + owner_path: str, + handle: NativeArrayHandlePlan, + ) -> tuple[WrapperPlanDiagnostic, ...]: + """Require complete unique operation names and matching roles.""" + operations = handle.default_handle.operations + roles = handle.default_handle.operation_roles + required = { + NativeArrayOperation.SHAPE, + NativeArrayOperation.ARRAY_ACTUAL, + NativeArrayOperation.DESCRIPTOR, + NativeArrayOperation.DESTROY, + } + if handle.descriptor_kind is NativeArrayDescriptorKind.POINTER: + required.add(NativeArrayOperation.ASSOCIATE) + diagnostics = [] + complete = len(set(operations)) == len(operations) and required.issubset(operations) + if not complete: + diagnostics.append(self._diagnostic(owner_path, "incomplete-default-handle-operations", operations)) + named_operations = tuple(operation for operation, _role in roles) + roles_complete = named_operations == operations and all(role for _operation, role in roles) + if not roles_complete: + diagnostics.append(self._diagnostic(owner_path, "inconsistent-default-handle-operation-roles", roles)) + return tuple(diagnostics) + + def _native_array_default_handle_storage_diagnostics( + self, + owner_path: str, + handle: NativeArrayHandlePlan, + ) -> tuple[WrapperPlanDiagnostic, ...]: + """Match persistent owner storage and descriptor ABI to construction.""" + default = handle.default_handle + expected_owner_role = { + NativeArrayDefaultConstruction.FACT_PACKED_EMPTY: None, + NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR: True, + }[default.construction] + owner_role = True if default.owner_storage_role is not None else None + diagnostics = [] + if owner_role is not expected_owner_role: + diagnostics.append( + self._diagnostic( + owner_path, "inconsistent-default-handle-owner-storage-role", default.owner_storage_role + ) + ) + expected_abi = { + NativeArrayDefaultConstruction.FACT_PACKED_EMPTY: NativeDescriptorHandoffABI.FACT_PACKED_CALL_LOCAL, + NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR: NativeDescriptorHandoffABI.DIRECT_STANDARD_DESCRIPTOR, + }[default.construction] + if handle.handoff.abi is not expected_abi: + diagnostics.append( + self._diagnostic(owner_path, "inconsistent-default-handle-descriptor-abi", handle.handoff.abi) + ) + return tuple(diagnostics) + def _array_ownership_diagnostics( self, plan: ArgumentTransferPlan, @@ -2011,7 +2133,13 @@ def _native_array_actual_shape_diagnostics( """Validate handle-actual rank and shape against the shared array facet.""" actual = plan.native_array_actual array = plan.array - if actual is None or (array is not None and actual.rank == array.rank and actual.shape == array.shape): + if actual is None or ( + array is not None + and actual.rank == array.rank + and actual.shape == array.shape + and actual.flatten_storage == array.flatten_python_storage + and actual.flat_axis == array.flat_axis + ): return () return (self._diagnostic(plan.owner_path, "inconsistent-array-actual-shape", actual.shape),) @@ -2045,18 +2173,12 @@ def _native_array_handle_shape_diagnostics( handle: NativeArrayHandlePlan, ) -> tuple[WrapperPlanDiagnostic, ...]: """Validate one concrete descriptor data facet.""" - diagnostics = [ + return ( *self._native_array_handle_rank_diagnostics(owner_path, handle), *self._native_array_handle_buffer_role_diagnostics(owner_path, handle), *self._native_array_handle_header_diagnostics(owner_path, handle), *self._native_array_handle_extraction_diagnostics(owner_path, handle), - ] - if ( - handle.descriptor_kind is NativeArrayDescriptorKind.POINTER - and handle.handle_kind is NativeArrayHandleKind.OWNED_RESULT_DESCRIPTOR - ): - diagnostics.append(self._diagnostic(owner_path, "pointer-result-without-stable-owner", None)) - return tuple(diagnostics) + ) def _native_array_handle_extraction_diagnostics( self, @@ -2257,7 +2379,7 @@ def _native_array_operation_diagnostics( ) -> tuple[WrapperPlanDiagnostic, ...]: """Require every handle to expose its common runtime operations exactly once.""" operations = handle.operations - required = {NativeArrayOperation.SHAPE, NativeArrayOperation.ARRAY_ACTUAL, NativeArrayOperation.DESCRIPTOR} + required = self._required_native_array_operations(handle) diagnostics = [] if len(set(operations)) != len(operations) or not required.issubset(operations): diagnostics.append(self._diagnostic(owner_path, "incomplete-native-array-operations", operations)) @@ -2271,18 +2393,70 @@ def _native_array_operation_diagnostics( diagnostics.append(self._diagnostic(owner_path, "borrowed-native-array-has-destroy-operation", None)) return tuple(diagnostics) + @staticmethod + def _required_native_array_operations( + handle: NativeArrayHandlePlan, + ) -> set[NativeArrayOperation]: + """Return common operations required by the completed descriptor kind.""" + required = { + NativeArrayOperation.SHAPE, + NativeArrayOperation.ARRAY_ACTUAL, + NativeArrayOperation.DESCRIPTOR, + } + if handle.descriptor_kind is NativeArrayDescriptorKind.POINTER: + required.add(NativeArrayOperation.ASSOCIATE) + return required + def _array_action_diagnostics( self, plan: ArgumentTransferPlan, ) -> tuple[WrapperPlanDiagnostic, ...]: """Dispatch completed buffer or raw-address array actions.""" action = plan.binding.python_action + if action is PythonBarrierAction.SCALAR_STORAGE: + return self._scalar_storage_array_action_diagnostics(plan) if action is PythonBarrierAction.ARRAY_STORAGE: return self._array_buffer_action_diagnostics(plan) if action is PythonBarrierAction.RAW_ADDRESS: return self._raw_array_action_diagnostics(plan) return (self._diagnostic(plan.owner_path, "invalid-array-python-action", action.value),) + def _scalar_storage_array_action_diagnostics( + self, + plan: ArgumentTransferPlan, + ) -> tuple[WrapperPlanDiagnostic, ...]: + """Validate rank-zero NumPy storage passed as a scalar native address.""" + diagnostics = [] + if plan.bridge.native_action is not NativeBarrierAction.PASS_STORAGE_ADDRESS: + diagnostics.append( + self._diagnostic( + plan.owner_path, "invalid-scalar-storage-native-action", plan.bridge.native_action.value + ) + ) + if plan.bridge.handoff_mode is not ArgumentHandoffMode.OPAQUE_ADDRESS: + diagnostics.append( + self._diagnostic(plan.owner_path, "invalid-scalar-storage-handoff-mode", plan.bridge.handoff_mode.value) + ) + if plan.bridge.data_action is not BridgeDataAction.ASSOCIATE_VIEW: + diagnostics.append( + self._diagnostic(plan.owner_path, "invalid-scalar-storage-data-action", plan.bridge.data_action.value) + ) + if plan.binding.codegen_action not in { + CodegenAction.CALL_LOCAL_INPUT, + CodegenAction.IN_PLACE_ARGUMENT, + CodegenAction.IDENTITY_OUTPUT, + }: + diagnostics.append( + self._diagnostic( + plan.owner_path, + "invalid-scalar-storage-codegen-action", + plan.binding.codegen_action.value, + ) + ) + if not self._is_scalar_storage_array(plan.array): + diagnostics.append(self._diagnostic(plan.owner_path, "invalid-scalar-storage-array", plan.array)) + return tuple(diagnostics) + def _array_buffer_action_diagnostics( self, plan: ArgumentTransferPlan, @@ -2515,7 +2689,7 @@ def _concrete_rank_array_diagnostics( if array is None or array.rank is None: return () diagnostics = [] - if not 1 <= array.rank <= 15: + if not 1 <= array.rank <= 15 and not self._is_scalar_storage_array(array): diagnostics.append(self._diagnostic(plan.owner_path, "invalid-array-rank", array.rank)) if len(array.shape) != array.rank or len(array.axes) != array.rank: diagnostics.append(self._diagnostic(plan.owner_path, "inconsistent-array-rank", array.rank)) @@ -3362,7 +3536,7 @@ def _array_result_diagnostics(self, plan: ResultPlan) -> tuple[WrapperPlanDiagno # Native-array-handle result validation. def _native_array_handle_result_diagnostics(self, plan: ResultPlan) -> tuple[WrapperPlanDiagnostic, ...]: - """Validate one wrapper-owned allocatable descriptor result.""" + """Validate one wrapper-owned native descriptor result.""" handle = plan.native_array_handle if handle is None: return () @@ -3398,7 +3572,7 @@ def _native_array_result_handle_diagnostics( self, plan: ResultPlan, ) -> tuple[WrapperPlanDiagnostic, ...]: - """Validate owned allocatable handle identity and release policy.""" + """Validate owned native descriptor handle identity and release policy.""" handle = plan.native_array_handle if handle is None: return () @@ -3407,8 +3581,6 @@ def _native_array_result_handle_diagnostics( diagnostics.append( self._diagnostic(plan.owner_path, "invalid-native-array-result-kind", handle.handle_kind) ) - if handle.descriptor_kind is not NativeArrayDescriptorKind.ALLOCATABLE: - diagnostics.append(self._diagnostic(plan.owner_path, "unsupported-pointer-array-result", None)) if handle.descriptor_ownership is not NativeArrayDescriptorOwnership.OWNED or handle.borrowed: diagnostics.append(self._diagnostic(plan.owner_path, "invalid-native-array-result-ownership", None)) if handle.release is not NativeArrayRelease.WRAPPER_DEALLOC: @@ -3479,7 +3651,9 @@ def _array_result_shape_diagnostics(self, plan: ResultPlan) -> tuple[WrapperPlan def _array_result_rank_diagnostics(self, plan: ResultPlan) -> tuple[WrapperPlanDiagnostic, ...]: """Require a supported concrete ordinary array result rank.""" array = plan.array - if array is not None and (array.rank is None or not 1 <= array.rank <= 15): + if array is not None and ( + array.rank is None or (not 1 <= array.rank <= 15 and not self._is_scalar_storage_array(array)) + ): return (self._diagnostic(plan.owner_path, "invalid-array-result-rank", array.rank),) return () @@ -3528,7 +3702,11 @@ def _array_result_source_diagnostics(self, plan: ResultPlan) -> tuple[WrapperPla expected_native = NativeBarrierAction.NONE else: expected_action = CodegenAction.COPY_OUT - expected_native = NativeBarrierAction.PASS_ARRAY_BUFFER + expected_native = ( + NativeBarrierAction.PASS_STORAGE_ADDRESS + if self._is_scalar_storage_array(plan.array) + else NativeBarrierAction.PASS_ARRAY_BUFFER + ) diagnostics = [] if plan.binding.codegen_action is not expected_action: diagnostics.append( @@ -3540,6 +3718,10 @@ def _array_result_source_diagnostics(self, plan: ResultPlan) -> tuple[WrapperPla ) return tuple(diagnostics) + @staticmethod + def _is_scalar_storage_array(array) -> bool: + return bool(array is not None and array.rank == 0 and array.category == SCALAR_STORAGE_CATEGORY) + def _native_slot_diagnostics(self, plan: NativeCallSlotPlan) -> tuple[WrapperPlanDiagnostic, ...]: """Return hidden literal and hidden result slot diagnostics.""" diagnostics = list( diff --git a/x2py/wrapper_codegen/plan.py b/x2py/wrapper_codegen/plan.py index 4e1b517cc..128cbe601 100644 --- a/x2py/wrapper_codegen/plan.py +++ b/x2py/wrapper_codegen/plan.py @@ -19,6 +19,7 @@ TransferMode, ) from x2py.semantics.wrapper_policy import ( + ArgumentConversionPhase, ArgumentHandoffMode, BridgeDataAction, CallbackABIKind, @@ -52,6 +53,7 @@ NativeArrayDescriptorInterop, NativeArrayDescriptorKind, NativeArrayDescriptorOwnership, + NativeArrayDefaultConstruction, NativeArrayDestroyBehavior, NativeArrayExtractionAction, NativeArrayGetterBehavior, @@ -59,6 +61,7 @@ NativeArrayHandleOrigin, NativeArrayOperation, NativeArrayOutputProjection, + NativeArrayResultAllocation, NativeArrayOwnerRetention, NativeArrayRelease, NativeArraySourceKind, @@ -285,6 +288,8 @@ class ArrayHandoffPlan(StageRecord): order: str | None native_order: str | None contiguous: bool | None + flatten_python_storage: bool + flat_axis: int | None itemsize: int | None category: str | None data_role: str @@ -309,6 +314,8 @@ class NativeArrayActualPlan(StageRecord): require_native_byte_order: bool require_aligned: bool require_contiguous: bool + flatten_storage: bool = False + flat_axis: int | None = None @dataclass @@ -328,6 +335,19 @@ class NativeDescriptorHandoffPlan(StageRecord): operation_roles: tuple[tuple[NativeArrayOperation, str], ...] +@dataclass +class NativeArrayDefaultHandlePlan(StageRecord): + """Caller-created descriptor storage and lifecycle selected before lowering.""" + + construction: NativeArrayDefaultConstruction + descriptor_ownership: NativeArrayDescriptorOwnership | None + release: NativeArrayRelease + destroy_behavior: NativeArrayDestroyBehavior + operations: tuple[NativeArrayOperation, ...] + owner_storage_role: str | None + operation_roles: tuple[tuple[NativeArrayOperation, str], ...] + + @dataclass class NativeArrayHandlePlan(StageRecord): """One typed editable native-array handle policy and descriptor handoff.""" @@ -343,6 +363,7 @@ class NativeArrayHandlePlan(StageRecord): setter_action: SetterAction native_assignment: AssignmentMode output_projection: NativeArrayOutputProjection + result_allocation: NativeArrayResultAllocation release: NativeArrayRelease target_lifetime: str destroy_behavior: NativeArrayDestroyBehavior @@ -355,6 +376,7 @@ class NativeArrayHandlePlan(StageRecord): required_headers: tuple[str, ...] array: ArrayHandoffPlan handoff: NativeDescriptorHandoffPlan + default_handle: NativeArrayDefaultHandlePlan @dataclass @@ -486,6 +508,7 @@ class BindingArgumentPlan(StageRecord): python_name: str python_action: PythonBarrierAction codegen_action: CodegenAction + conversion_phase: ArgumentConversionPhase handoff_role: str optional_mode: OptionalMode nullable: bool diff --git a/x2py/wrapper_codegen/planner.py b/x2py/wrapper_codegen/planner.py index 63dd5406a..2ed78c1bb 100644 --- a/x2py/wrapper_codegen/planner.py +++ b/x2py/wrapper_codegen/planner.py @@ -30,6 +30,8 @@ LifecyclePolicy, NativeCallSlotPolicy, NativeArrayActualPolicy, + NativeArrayDefaultConstruction, + NativeArrayDefaultHandlePolicy, NativeArrayHandleWrapperPolicy, NativeDescriptorHandoffABI, NativeDescriptorHandoffPolicy, @@ -88,6 +90,7 @@ NamespacePlan, NativeCallSlotPlan, NativeArrayActualPlan, + NativeArrayDefaultHandlePlan, NativeArrayHandlePlan, NativeDescriptorHandoffPlan, PolymorphicDispatchPlan, @@ -362,7 +365,6 @@ def _class_surface_plan( namespace, semantic_class, policy, - methods, overloads_by_name, python_name=python_names[0], fields=fields, @@ -393,7 +395,7 @@ def _class_method_plans( semantic_class: models.SemanticClass, policy: ClassSurfacePolicy, ) -> tuple[ClassMethodPlan, ...]: - """Link public methods and a private constructor target in source order.""" + """Link public methods in source order.""" methods_by_owner = self._class_methods_by_owner(policy) methods = [] for method in semantic_class.methods: @@ -401,7 +403,7 @@ def _class_method_plans( continue owner_path = f"{policy.owner_path}.{method.name}" method_policy = methods_by_owner[owner_path] - if not self._class_method_is_planned(method, method_policy, owner_path, policy): + if not method_policy.public: continue methods.append( self._class_method_plan( @@ -419,11 +421,6 @@ def _class_methods_by_owner(policy: ClassSurfacePolicy) -> dict[str, object]: """Index completed method records by their stable semantic owner path.""" return {method.owner_path: method for method in policy.methods} - @staticmethod - def _class_method_is_planned(method, method_policy, owner_path: str, policy: ClassSurfacePolicy) -> bool: - """Keep public methods plus the private target selected for construction.""" - return method_policy.public or owner_path == policy.constructor.target_owner_path - def _class_overload_plans( self, module_name: str, @@ -452,7 +449,6 @@ def _constructor_plan( namespace: tuple[str, ...], semantic_class: models.SemanticClass, policy: ClassSurfacePolicy, - methods: tuple[ClassMethodPlan, ...], overloads_by_name: dict, *, python_name: str, @@ -460,7 +456,12 @@ def _constructor_plan( ) -> ConstructorPlan: """Link one completed constructor to its target and lifecycle records.""" constructor = policy.constructor - target = next((item.function for item in methods if item.owner_path == constructor.target_owner_path), None) + target = self._bound_constructor_target_plan( + module_name, + namespace, + semantic_class, + policy, + ) overload = self._constructor_overload_plan( module_name, namespace, @@ -481,6 +482,33 @@ def _constructor_plan( plan.docstring = self.docstrings.constructor(python_name, plan, fields) return plan + def _bound_constructor_target_plan( + self, + module_name: str, + namespace: tuple[str, ...], + semantic_class: models.SemanticClass, + policy: ClassSurfacePolicy, + ) -> FunctionPlan | None: + """Project the direct constructor call selected by completed policy.""" + target_path = policy.constructor.target_owner_path + if target_path is None: + return None + method = next( + (item for item in semantic_class.methods if f"{policy.owner_path}.{item.name}" == target_path), + None, + ) + if method is None: + return None + return self._function_plan( + completed_function_wrapper_policy(method), + PythonExportPolicy( + namespace, + self._class_callable_name(policy.type_identity, method.name), + ), + module_name, + public=False, + ) + def _constructor_overload_plan( self, module_name: str, @@ -630,9 +658,17 @@ def _class_function_plans(surface: ClassSurfacePlan) -> tuple[FunctionPlan, ...] return ( *(method.function for method in surface.methods), *(candidate for overload in surface.overloads for candidate in overload.candidates), + *WrapperPlanner._constructor_target_functions(surface.constructor), *(surface.constructor.overload.candidates if surface.constructor.overload is not None else ()), ) + @staticmethod + def _constructor_target_functions(constructor: ConstructorPlan) -> tuple[FunctionPlan, ...]: + """Return the direct constructor target when one was selected.""" + if constructor.target is None: + return () + return (constructor.target,) + def _derived_field_plan(self, policy: DerivedFieldPolicy) -> DerivedFieldPlan: """Project one completed field once for every backend and module path.""" cached = self._derived_field_plans.get(policy.owner_path) @@ -749,7 +785,7 @@ def _module_overload_callable_name(name: str, index: int) -> str: @staticmethod def _module_function_policy(function: models.SemanticFunction) -> FunctionWrapperPolicy | None: - """Return a public function plan policy, excluding class-only root targets.""" + """Return one completed module-function policy when it is exportable.""" policy = function.metadata.get(models.RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA) if isinstance(policy, FunctionWrapperPolicy) and not policy.module_export: return None @@ -1118,6 +1154,7 @@ def _binding_argument_plan( python_name=policy.python_name, python_action=policy.python_barrier_action, codegen_action=policy.codegen_action, + conversion_phase=policy.conversion_phase, handoff_role=role, optional_mode=policy.optional_mode, nullable=policy.nullable, @@ -1427,6 +1464,8 @@ def _native_array_actual_plan(self, policy: NativeArrayActualPolicy | None) -> N require_native_byte_order=policy.require_native_byte_order, require_aligned=policy.require_aligned, require_contiguous=policy.require_contiguous, + flatten_storage=policy.flatten_storage, + flat_axis=policy.flat_axis, ) def _native_array_handle_plan( @@ -1454,6 +1493,7 @@ def _native_array_handle_plan( setter_action=policy.setter_action, native_assignment=policy.native_assignment, output_projection=policy.output_projection, + result_allocation=policy.result_allocation, release=policy.release, target_lifetime=policy.target_lifetime, destroy_behavior=policy.destroy_behavior, @@ -1466,6 +1506,30 @@ def _native_array_handle_plan( required_headers=policy.required_headers, array=array_plan, handoff=self._native_descriptor_handoff_plan(policy.handoff, owner_path, policy.operations), + default_handle=self._native_array_default_handle_plan(policy.default_handle, owner_path), + ) + + def _native_array_default_handle_plan( + self, + policy: NativeArrayDefaultHandlePolicy, + owner_path: str, + ) -> NativeArrayDefaultHandlePlan: + """Name completed caller-construction storage and operation roles.""" + owner_storage_role = ( + f"{owner_path}:default-owner-storage" + if policy.construction is NativeArrayDefaultConstruction.LAZY_OWNED_DESCRIPTOR + else None + ) + return NativeArrayDefaultHandlePlan( + construction=policy.construction, + descriptor_ownership=policy.descriptor_ownership, + release=policy.release, + destroy_behavior=policy.destroy_behavior, + operations=policy.operations, + owner_storage_role=owner_storage_role, + operation_roles=tuple( + (operation, f"{owner_path}:default-operation:{operation.value}") for operation in policy.operations + ), ) def _native_descriptor_handoff_plan( @@ -1559,6 +1623,8 @@ def _array_plan( order=policy.order, native_order=policy.native_order, contiguous=policy.contiguous, + flatten_python_storage=policy.flatten_python_storage, + flat_axis=policy.flat_axis, itemsize=policy.itemsize, category=policy.category, data_role=self._value_role(owner_path), diff --git a/x2py/wrapper_codegen/printers/pyi_printer.py b/x2py/wrapper_codegen/printers/pyi_printer.py index 9f45f5458..360d56d67 100644 --- a/x2py/wrapper_codegen/printers/pyi_printer.py +++ b/x2py/wrapper_codegen/printers/pyi_printer.py @@ -16,6 +16,7 @@ ADDRESS_ROLE_PROJECTION, ADDRESS_ROLE_RAW, BIND_TARGET_METADATA, + MAYBE_UNALLOCATED_METADATA, NATIVE_PROJECTION_METADATA, OPTIONAL_ABSENT_HANDLE_METADATA, SCALAR_STORAGE_CATEGORY, @@ -198,26 +199,13 @@ def _emit_function(self, func: SemanticFunction, *, name_owner: object | None = decorator = self._decorators(func, emitted_name=name) return self._emit_callable( name=name, - arguments=[self._emit_contract_argument(func, arg) for arg in self._call_arguments(func)], + arguments=[self._emit_call_argument(func, arg) for arg in self._call_arguments(func)], return_type=return_type, decorator=decorator, def_indent="", parameter_indent=" ", ) - def _emit_contract_argument(self, func: SemanticFunction, arg: SemanticArgument) -> str: - """Omit native facts already implied by the surrounding callable contract.""" - passed_object_name = func.metadata.get("fortran_passed_object_name") - if ( - func.metadata.get("fortran_type_bound_target") - and isinstance(passed_object_name, str) - and arg.name == passed_object_name - and arg.semantic_type.metadata.get("fortran_polymorphic") - ): - arg = deepcopy(arg) - arg.semantic_type.metadata.pop("fortran_polymorphic", None) - return self._emit_call_argument(func, arg) - def _visit_SemanticMethod(self, method: SemanticMethod) -> str: """Emit method syntax.""" return self._emit_method(method) @@ -259,8 +247,10 @@ def _visit_ProcedureOverloadSet(self, overload_set: ProcedureOverloadSet, *, in_ name_owner=("overload", overload_set.name), ) indent = "" - generic = self._overload_generic_argument(candidate, overload_set.name) - definitions.append(f'{indent}@{self._contract("overload")}("{target}"{generic})\n{definition}') + generic = self._overload_generic_argument(candidate, overload_set.name) if in_class else "" + bind_target = candidate.metadata.get(BIND_TARGET_METADATA) + bind = f"{indent}@{self._contract('bind')}({json.dumps(str(bind_target))})\n" if bind_target else "" + definitions.append(f'{bind}{indent}@{self._contract("overload")}("{target}"{generic})\n{definition}') return "\n\n".join(definitions) def _visit_SemanticClass(self, cls: SemanticClass) -> str: @@ -514,6 +504,8 @@ def _semantic_annotation_metadata(self, semantic_type: SemanticType) -> list[str metadata.append(self._contract("Aliased")) if semantic_type.metadata.get(PYTHON_VALUE_MUTABILITY_METADATA) == PYTHON_VALUE_IMMUTABLE: metadata.append(self._contract("Immutable")) + if semantic_type.metadata.get(MAYBE_UNALLOCATED_METADATA): + metadata.append(self._contract("MaybeUnallocated")) pointer_association = semantic_type.metadata.get("fortran_pointer_association") if pointer_association is not None and not self._is_scalar_pointer_descriptor(semantic_type): metadata.append(f"{self._contract('PointerAssociation')}({json.dumps(str(pointer_association))})") @@ -598,10 +590,16 @@ def _visible_scalar_descriptor_type(semantic_type: SemanticType) -> SemanticType return visible def _emit_prototype_argument(self, argument: SemanticArgument) -> str: - """Emit one prototype dummy with reference default and one value override.""" + """Emit one prototype dummy using the public callback transport rules.""" + if self._is_prototype_descriptor_type(argument.semantic_type): + return self._prototype_descriptor_type_text(argument.semantic_type) inner = self._prototype_argument_inner_type(argument.semantic_type) if bool(getattr(argument.origin, "metadata", {}).get("value")): + if self._is_prototype_primitive_value(argument.semantic_type): + return inner return f"{self._contract('Value')}({inner})" + if self._is_prototype_primitive_reference(argument.semantic_type): + return f"{self._contract('Addr')}({inner})" return inner def _prototype_argument_inner_type(self, semantic_type: SemanticType) -> str: @@ -618,6 +616,51 @@ def _prototype_argument_inner_type(self, semantic_type: SemanticType) -> str: return self._address_target_type(semantic_type) return self._visit(semantic_type) + def _prototype_descriptor_type_text(self, semantic_type: SemanticType) -> str: + """Render descriptor metadata without a second reference wrapper.""" + if semantic_type.metadata.get("fortran_allocatable") or semantic_type.metadata.get("fortran_pointer"): + return self._visit(semantic_type) + visible = deepcopy(semantic_type) + if visible.storage is not None and visible.storage.kind in {"reference", "address", "pointer"}: + visible.storage = None + return self._visit(visible) + + @staticmethod + def _is_prototype_primitive_value(semantic_type: SemanticType) -> bool: + storage = semantic_type.storage + return bool( + semantic_type.rank == 0 + and semantic_type.name not in {"String", "Void"} + and (semantic_type.dtype or semantic_type.name) in SEMANTIC_SCALAR_TYPE_NAMES + and (storage is None or storage.kind == "value") + and not PyiPrinter._is_prototype_descriptor_type(semantic_type) + ) + + @staticmethod + def _is_prototype_primitive_reference(semantic_type: SemanticType) -> bool: + storage = semantic_type.storage + return bool( + semantic_type.rank == 0 + and semantic_type.name not in {"String", "Void"} + and (semantic_type.dtype or semantic_type.name) in SEMANTIC_SCALAR_TYPE_NAMES + and storage is not None + and storage.kind in {"reference", "address", "pointer"} + and storage.pointer_depth == 1 + and not PyiPrinter._is_prototype_descriptor_type(semantic_type) + ) + + @staticmethod + def _is_prototype_descriptor_type(semantic_type: SemanticType) -> bool: + return any( + semantic_type.metadata.get(name) + for name in ( + "fortran_allocatable", + "fortran_pointer", + "fortran_polymorphic", + "fortran_assumed_type", + ) + ) + def _emit_data_member(self, variable: SemanticVariable) -> str: """Emit a variable in class-field context rather than argument context.""" name = self._data_member_name(variable) @@ -1391,7 +1434,7 @@ def _projected_return_annotation(self, func: SemanticFunction) -> str: else: parts.append(self._visit(self._visible_wrapped_callable_type(func.return_type))) parts.extend( - self._projected_argument_return(arg, visible=visible) + self._projected_argument_return(func, arg, visible=visible) for _, arg, visible in sorted( self._projected_return_arguments(func), key=lambda item: item[0], @@ -1434,15 +1477,32 @@ def _is_visible_projected_return(func: SemanticFunction, mapping: ProjectionMapp return False return mapping.python_position is not None - def _projected_argument_return(self, arg: SemanticArgument, *, visible: bool) -> str: + def _projected_argument_return( + self, + func_or_arg: SemanticFunction | SemanticArgument, + arg: SemanticArgument | None = None, + *, + visible: bool, + ) -> str: """Handle projected argument return for the current generation context.""" + if isinstance(func_or_arg, SemanticFunction): + if arg is None: + raise TypeError("Function projection return emission requires an argument") + func = func_or_arg + projected_arg = arg + else: + func = None + projected_arg = func_or_arg if visible: - return self._named_return(arg) - return self._plain_projected_return(arg) + return self._named_return(projected_arg, func=func) + return self._plain_projected_return(projected_arg) - def _named_return(self, arg: SemanticArgument) -> str: + def _named_return(self, arg: SemanticArgument, *, func: SemanticFunction | None = None) -> str: """Handle named return for the current generation context.""" - semantic_type = self._visible_projected_type(arg.semantic_type) + semantic_type = self._visible_projected_type( + arg.semantic_type, + unwrap_address_projection=func is not None and self._uses_address_projection(func, arg), + ) descriptor_kind = self._scalar_descriptor_kind(semantic_type) if descriptor_kind is not None: semantic_type = self._visible_scalar_descriptor_type(semantic_type) @@ -1452,13 +1512,23 @@ def _named_return(self, arg: SemanticArgument) -> str: return return_text @staticmethod - def _visible_projected_type(semantic_type: SemanticType) -> SemanticType: + def _visible_projected_type( + semantic_type: SemanticType, + *, + unwrap_address_projection: bool = False, + ) -> SemanticType: """Return the Python-visible type for address-projected scalars.""" wrapped = PyiPrinter._visible_wrapped_callable_type(semantic_type) if wrapped is not semantic_type: return wrapped storage = semantic_type.storage if ( + unwrap_address_projection + and semantic_type.rank == 0 + and storage is not None + and storage.kind in {"address", "reference"} + and storage.pointer_depth == 1 + ) or ( semantic_type.rank == 0 and storage is not None and storage.kind == "address" @@ -1838,6 +1908,8 @@ def _native_value_ref(self, value: dict[str, int | str]) -> str: @staticmethod def _requires_native_call(func: SemanticFunction) -> bool: """Return whether requires native call.""" + if isinstance(func, SemanticMethod) and func.name == "__init__" and func.metadata.get(BIND_TARGET_METADATA): + return True if PyiPrinter._scalar_descriptor_kind(func.return_type) is not None: return True if any(PyiPrinter._scalar_descriptor_kind(argument.semantic_type) is not None for argument in func.arguments):