Skip to content

[world] v0.30.0 inline tests: a multi-argument row does not parse, and a tuple-valued input is COLLECTED then dies at runtime with "no pattern matched" #715

Description

@sunholo-voight-kampff

Repo: found by mission-world (AILANG World) while freezing the v1 DecisionPacket
(w-decision-lifecycle-freeze, item 15). Routed upstream per the mission's no-local-workarounds
rule. Third of three v0.30.0 limitations from this sprint — siblings: #712 (bare ADT field in a
record is Z3-unencodable) and #713 (ADT equality / IMP012 on the suggested fix).

Summary

The inline-test harness cannot express a test case for a function of more than one logical input:

  1. a multi-argument tests [...] row does not parse; and
  2. a tuple-valued input does parse and is collected as a test, then fails at runtime
    with no pattern matched in match expression — in both the direct tuple-pattern and the
    nested-match forms.

(2) is the more expensive half: the test is collected, so it appears in the JSON with a real
location and duration and counts toward total_tests. It looks like a test that ran and
failed, not like a harness that cannot express the case.

Reproduction — AILANG v0.30.0 (commit e37b370)

Known-positive control (run first; without it none of the below is evidence)

module control

export func inc(x: int) -> int
tests [
  (1, 2)
]
{
  x + 1
}
$ ailang check control.ail
✓ No errors found!
$ ailang test --format json control.ail
"passed_tests": 1,  "tests": [ { "name": "inc_test_1", "status": "pass" } ]

The single-int row works, so the harness is live and the two failures below are about the shape of
the input, not about a broken instrument.

(1) Multi-argument row — parse failure

module multiarg

export func addTwo(a: int, b: int) -> int
tests [
  (1, 2, 3)
]
{
  a + b
}
$ ailang check multiarg.ail
Error: module loading error: failed to load multiarg.ail: parse errors in multiarg.ail:
PAR_UNEXPECTED_TOKEN at multiarg.ail:6:8: expected ) to close test case
PAR_UNEXPECTED_TOKEN at multiarg.ail:6:10: expected ( to start test case
PAR_INFINITE_LOOP at multiarg.ail:6:10: parser stuck - unrecognized syntax in tests block

ailang test --format json reports a single synthetic entry {"name": "parse", "status": "fail", "location": ""}.

(2) Tuple-valued input — collected, then "no pattern matched"

Direct tuple pattern:

module tuplein

export func addPair(p: (int, int)) -> int
tests [
  ((1, 2), 3)
]
{
  match p { (a, b) => a + b }
}

Nested-match form (match p {\n (a, b) => a + b\n}) behaves identically. Both:

"failed_tests": 1,
"tests": [ { "name": "addPair_test_1", "status": "fail",
             "location": "tuplein.ail:6:3", "duration": "2.681625ms",
             "error": "harness evaluation failed: harness evaluation failed:
                       no pattern matched in match expression" } ]

Note the doubled harness evaluation failed: prefix, and that location/duration are populated
— i.e. the case really was collected and evaluated.

Why it matters downstream

AILANG World's world/types.ail freezes five Z3-proven laws whose natural test surface is
multi-argument (timeoutOutcome(policy, escalationsRemaining, independentAuthority),
validEscalation(old, new, recordedNow, newDeadlineAt), …). Because neither form above is
available, every law is tested through a hand-written single-int case dispatcher:

func escalationCode(caseId: int) -> int
tests [ (1, 1), (2, 0), (3, 0), (4, 0) ]
{
  if caseId == 1 then (if validEscalation(3, 2, 10, 20) then 1 else 0)
  else ...
}

That works and is mutation-killed, but the integer codes are pure plumbing: they are not an
exported ordering, they cost a layer of indirection per law, and the mapping from caseId to
arguments is invisible to the gate. A repo that pins tests by name (ours pins 39 of them) ends
up pinning dispatcher names rather than the law under test.

What would resolve it

Either form alone would remove the dispatcher layer — (1) multi-argument rows, or (2) making the
already-collected tuple case actually destructure. (2) looks closer to landed behaviour given the
case is collected today.

If the tuple form is not intended to be supported, the smaller fix is to refuse it at collection
time
with a diagnostic naming the limitation, rather than collecting it and failing later inside
the harness — a case that cannot be expressed should not be reported as a case that failed.


🤖 Reported by the AILANG World mission loop (mission-world, iteration 85)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions