Skip to content

Commit 1ea9069

Browse files
committed
improve coverage and remove dead branches
1 parent d98a2f1 commit 1ea9069

14 files changed

Lines changed: 541 additions & 1064 deletions

File tree

README.md

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -520,40 +520,6 @@ X2PY_C_DOCS_END -->
520520
For native projects with macros, includes, or target flags, use the
521521
compiler-preprocessed CLI path or an equivalent preprocessing configuration.
522522

523-
## Supported Scope
524-
525-
x2py preserves wrapper-relevant declarations, signatures, types, source
526-
locations, include/use relationships, diagnostics, and semantic metadata.
527-
Current support includes:
528-
529-
- free-form and fixed-form Fortran, procedures, modules, derived types,
530-
imports, arrays, and wrapper-relevant declaration attributes;
531-
- language-neutral semantic IR and editable `.pyi` interfaces;
532-
- compiled Python extensions from one or more ordered fixed-form or free-form
533-
Fortran sources, with an optional GNU Make build;
534-
- documented runtime wrapper behavior for scalar and array calls, strings,
535-
module state, derived types, generic interfaces, optional and output
536-
arguments, and immediate call-scoped Python callbacks. The
537-
[language feature matrix](docs/user/language-support/feature-matrix.md) is the
538-
authoritative support-status summary.
539-
540-
<!-- X2PY_C_DOCS_START
541-
- compiled CPython extensions from one or more ordered fixed-form or free-form
542-
Fortran sources, including generated Fortran/C bridges and an optional GNU
543-
Make build;
544-
- C declarations and definitions, variables, typedefs, aggregates, enums,
545-
pointers, arrays, function pointers, includes, and preprocessing facts;
546-
X2PY_C_DOCS_END -->
547-
548-
<!-- X2PY_C_DOCS_START
549-
Runtime wrapper generation from user C inputs is not implemented yet. It will
550-
reuse the shared semantic contracts after the C backend and its ownership,
551-
ABI, and runtime tests are complete.
552-
X2PY_C_DOCS_END -->
553-
554-
x2py is not a full compiler frontend. It does not silently infer pointer
555-
ownership, callback lifetime, ABI shims, or Python-visible projections.
556-
557523
## Documentation
558524

559525
- **[Documentation](https://pynumlab.github.io/x2py/)** — Complete published documentation

docs/maintainer/roadmap/wrapper-plan-migration-checklist.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ summary, the exhaustive matrix, and the test tree disagree.
594594

595595
| Status | Collected nodes |
596596
| --- | ---: |
597-
| `wrapper-plan` | 366 |
597+
| `wrapper-plan` | 369 |
598598
| `dual-route` | 0 |
599599
| `legacy` | 0 |
600600
| `not-applicable` | 76 |
@@ -749,6 +749,9 @@ already covered by the new generator.
749749
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_free_form_standalone_external_runtime_parity[*]` | source/generated-.pyi parity or parametrized route | scalar external symbol; explicit bridge interface | `wrapper-plan` |
750750
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_generated_external_contracts_are_non_empty_root_fragments` | direct wrapper/build route | external symbols/native linkage | `wrapper-plan` |
751751
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_handwritten_c_order_flat_contract_passes_rank_preserving_bridge_view` | direct wrapper/build route | external symbols/native linkage; ordinary arrays | `wrapper-plan` |
752+
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_handwritten_fortran_order_flat_contract_flattens_the_final_python_axes` | direct wrapper/build route | external symbols/native linkage; flat arrays; scalar storage | `wrapper-plan` |
753+
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_external_allocatable_argument_accepts_a_caller_created_handle` | direct wrapper/build route | external symbols/native linkage; native allocatable descriptors | `wrapper-plan` |
754+
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_optional_flat_contracts_preserve_present_and_absent_calls` | direct wrapper/build route | optional/presence; F-order and C-order flat arrays | `wrapper-plan` |
752755
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_module_procedure_bridge_uses_native_module_scope` | direct wrapper/build route | external symbols/native linkage | `wrapper-plan` |
753756
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_namespace_imported_module_rejects_external_marker_before_codegen` | non-generating: validation/failure-path assertion | external symbols/native linkage | `not-applicable` |
754757
| `tests/wrapper/fortran/external_routines/test_external_procedures.py::test_one_source_with_several_standalone_externals_exports_each_at_root[*]` | source/generated-.pyi parity or parametrized route | scalar external symbols; explicit bridge interfaces | `wrapper-plan` |

tests/runtime/handles/test_array_actual_abi.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,67 @@ def test_array_actual_argument_abi_packer_flattens_leading_edge_before_checked_s
467467
) == (values.ctypes.data, 6, 4)
468468

469469

470+
@pytest.mark.parametrize(
471+
("values", "expected_rank", "expected_shape", "flat_axis", "message"),
472+
[
473+
(
474+
np.array(1.0, dtype=np.float64),
475+
1,
476+
(None,),
477+
0,
478+
"expects NumPy array rank 1 through 15",
479+
),
480+
(
481+
np.ones((2,), dtype=np.float64),
482+
2,
483+
(2, None),
484+
1,
485+
"expects NumPy array rank at least 2",
486+
),
487+
(
488+
np.ones((2, 3, 4), dtype=np.float64),
489+
3,
490+
(2, None, 4),
491+
1,
492+
"axis must be the first or final contract dimension",
493+
),
494+
(
495+
np.ones((2, 3, 4), dtype=np.float64),
496+
2,
497+
(3, None),
498+
1,
499+
"incompatible shape at axis 0",
500+
),
501+
(
502+
np.ones((2, 3, 4), dtype=np.float64),
503+
2,
504+
(None, 3),
505+
0,
506+
"incompatible shape at axis 2",
507+
),
508+
],
509+
)
510+
def test_array_actual_argument_abi_packer_rejects_invalid_flat_shapes(
511+
values,
512+
expected_rank,
513+
expected_shape,
514+
flat_axis,
515+
message,
516+
):
517+
with pytest.raises((TypeError, ValueError), match=message):
518+
_native_array_actual_argument_for_binding_positional(
519+
values,
520+
expected_dtype=np.float64,
521+
expected_rank=expected_rank,
522+
expected_shape=expected_shape,
523+
require_native_byte_order=True,
524+
require_aligned=True,
525+
require_contiguous=True,
526+
flatten_storage=True,
527+
flat_axis=flat_axis,
528+
)
529+
530+
470531
def test_array_actual_argument_abi_packer_flattens_native_handle_shape():
471532
actual = _handoff(252)
472533
handle = AllocatableArray(

tests/runtime/test_contract_constructors.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
AllocatableArray,
99
PointerArray,
1010
_bind_contract_native_array_handle,
11+
_native_array_actual_for_binding,
1112
_native_array_descriptor_argument_for_binding,
1213
_native_array_descriptor_handoff_for_binding,
1314
)
@@ -111,6 +112,7 @@ def source_nullify(_handle):
111112
assert target.associated is True
112113
assert target.shape == (3,)
113114
np.testing.assert_array_equal(target.to_numpy(), value)
115+
assert _native_array_actual_for_binding(target).address == value.ctypes.data
114116

115117
source.nullify()
116118
assert source.associated is False
@@ -232,6 +234,107 @@ def bind_default(value):
232234
assert calls == [("destroy", owner)]
233235

234236

237+
@pytest.mark.parametrize(
238+
("prepare", "descriptor_kind", "dtype", "rank", "error", "message"),
239+
[
240+
(
241+
lambda: AllocatableArray(
242+
dtype="float64",
243+
rank=1,
244+
ops={
245+
"shape": lambda _handle: None,
246+
"array_actual": lambda _handle: None,
247+
"descriptor": lambda _handle: None,
248+
"allocated": lambda _handle: False,
249+
},
250+
to_numpy_policy="unsupported",
251+
),
252+
"allocatable",
253+
"float64",
254+
1,
255+
TypeError,
256+
"fresh contract handle",
257+
),
258+
(
259+
lambda: contracts.Allocatable[contracts.Float64[:]](),
260+
"pointer",
261+
"float64",
262+
1,
263+
TypeError,
264+
"cannot attach pointer descriptor storage",
265+
),
266+
(
267+
lambda: contracts.Allocatable[contracts.Float64[:]](),
268+
"allocatable",
269+
"float64",
270+
2,
271+
ValueError,
272+
"does not match generated rank 2",
273+
),
274+
(
275+
lambda: contracts.Allocatable[contracts.Float64[:]](),
276+
"allocatable",
277+
"int32",
278+
1,
279+
TypeError,
280+
"does not match generated dtype",
281+
),
282+
],
283+
)
284+
def test_generated_storage_rejects_incompatible_contract_handles(
285+
prepare,
286+
descriptor_kind,
287+
dtype,
288+
rank,
289+
error,
290+
message,
291+
):
292+
handle = prepare()
293+
294+
with pytest.raises(error, match=message):
295+
_bind_contract_native_array_handle(
296+
handle,
297+
descriptor_kind,
298+
dtype,
299+
rank,
300+
{},
301+
object(),
302+
"owned",
303+
"unsupported",
304+
)
305+
306+
307+
def test_generated_storage_rejects_a_closed_contract_handle():
308+
handle = contracts.Allocatable[contracts.Float64[:]]()
309+
handle.close()
310+
311+
with pytest.raises(ReferenceError, match="handle is closed"):
312+
_bind_contract_native_array_handle(
313+
handle,
314+
"allocatable",
315+
"float64",
316+
1,
317+
{},
318+
object(),
319+
"owned",
320+
"unsupported",
321+
)
322+
323+
324+
def test_pointer_association_rejects_closed_handles():
325+
target = contracts.Pointer[contracts.Float64[:]]()
326+
source = contracts.Pointer[contracts.Float64[:]]()
327+
target.close()
328+
329+
with pytest.raises(ReferenceError, match="pointer handle is closed"):
330+
target.associate(source)
331+
332+
target = contracts.Pointer[contracts.Float64[:]]()
333+
source.close()
334+
with pytest.raises(ReferenceError, match="source pointer handle is closed"):
335+
target.associate(source)
336+
337+
235338
def test_non_array_descriptor_and_ordinary_array_annotations_are_not_factories():
236339
with pytest.raises(TypeError, match="scalar allocatable contracts"):
237340
contracts.Allocatable[contracts.Float64]()

tests/semantics/policy/test_wrapper_policy.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,32 @@ def raw_labels(n: Int32, labels: Addr(String[8][n])) -> None: ...
906906
assert labels.array.itemsize == 8
907907

908908

909+
def test_scalar_storage_rejects_incompatible_explicit_ownership_metadata():
910+
module = parse_pyi_text(
911+
"""
912+
def invalid(
913+
value: Annotated[
914+
Int32[()],
915+
Ownership("python"),
916+
Transfer("snapshot_copy"),
917+
Destruction("python_refcount"),
918+
],
919+
) -> None: ...
920+
""",
921+
module_name="invalid_scalar_storage_ownership",
922+
)
923+
complete_semantic_policies(module)
924+
policy = module.functions[0].metadata[RESOLVED_FUNCTION_WRAPPER_POLICY_METADATA]
925+
926+
assert policy.supported is False
927+
assert policy.blockers[:4] == (
928+
"argument 'value' scalar-storage owner is python, not caller",
929+
"argument 'value' scalar-storage transfer is snapshot_copy, not in_place",
930+
"argument 'value' scalar-storage destruction is python_refcount, not caller",
931+
"argument 'value' scalar-storage action is snapshot_copy, not a storage-address action",
932+
)
933+
934+
909935
def test_wrapper_policy_keeps_optional_raw_array_addresses_blocked():
910936
module = parse_pyi_text(
911937
"def optional_raw(n: Int32, values: Addr(Float64[n]) = ...) -> None: ...",

tests/wrapper/CHECKLIST_COVERAGE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ recorded progression, not in the live ledger.
4747
| External bridge placement and module-procedure contrast | `external_routines/test_external_procedures.py::test_classic_external_bridge_uses_implicit_declaration_and_no_module_use`, `external_routines/test_external_procedures.py::test_module_procedure_bridge_uses_native_module_scope` |
4848
| `@external` with `@bind` and handwritten source-free contracts | `external_routines/test_external_procedures.py::test_external_bind_renames_python_export_without_changing_native_call` |
4949
| C-order flat storage over assumed-size native external buffers | `external_routines/test_external_procedures.py::test_handwritten_c_order_flat_contract_passes_rank_preserving_bridge_view` |
50+
| Handwritten flat, scalar-storage, optional-presence, and allocatable external contracts compile and run through their completed plans | `external_routines/test_external_procedures.py::test_handwritten_fortran_order_flat_contract_flattens_the_final_python_axes`, `external_routines/test_external_procedures.py::test_optional_flat_contracts_preserve_present_and_absent_calls`, `external_routines/test_external_procedures.py::test_external_allocatable_argument_accepts_a_caller_created_handle` |
5051
| Invalid root/module placement edits fail before code generation | `external_routines/test_external_procedures.py::test_package_entry_rejects_non_external_root_declaration_before_codegen`, `external_routines/test_external_procedures.py::test_namespace_imported_module_rejects_external_marker_before_codegen` |
5152

5253
## Stage 5 — Full Generated-Contract Runtime Parity

0 commit comments

Comments
 (0)