lawspec 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API-MIGRATION.md +186 -67
- package/GO.md +144 -0
- package/HASKELL.md +171 -0
- package/JAVA.md +113 -0
- package/KOTLIN.md +214 -0
- package/LANGUAGE.md +361 -0
- package/PRIMITIVES.md +2 -2
- package/PYTHON.md +152 -0
- package/README.md +45 -26
- package/REFINEMENTS.md +289 -3
- package/RELEASE-0.9.md +61 -0
- package/RUST.md +211 -0
- package/WEB.md +144 -0
- package/api.mjs +8 -2
- package/bin/lawspec.mjs +22 -47
- package/build.json +198 -26
- package/compatibility.json +102 -19
- package/core.wasm +0 -0
- package/doctor.mjs +31 -2
- package/examples/specs/algebra.lawspec +36 -8
- package/examples/specs/collections.lawspec +67 -0
- package/examples/specs/currying.lawspec +1 -1
- package/examples/specs/data_types.lawspec +66 -0
- package/examples/specs/finite_data.lawspec +37 -0
- package/examples/specs/list_contracts.lawspec +136 -0
- package/examples/specs/list_refinements.lawspec +51 -0
- package/examples/specs/matching.lawspec +49 -0
- package/examples/specs/recursive_refinements.lawspec +41 -0
- package/examples/specs/refined_definitions.lawspec +44 -0
- package/examples/specs/refinements.lawspec +11 -0
- package/examples/specs/sum_refinements.lawspec +49 -0
- package/examples/specs/total_functions.lawspec +101 -0
- package/examples-command.mjs +7 -2
- package/files.mjs +10 -1
- package/index.d.ts +246 -27
- package/package.json +2 -2
- package/templates.mjs +130 -16
package/README.md
CHANGED
|
@@ -2,19 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.9 compiles reusable laws into native property tests, executable examples,
|
|
6
6
|
and implementation adapters. The compiler is Haskell, distributed as prebuilt
|
|
7
7
|
WebAssembly with a Node CLI and an asynchronous, typed JavaScript API.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Structural data and total definitions in 0.9.0
|
|
10
10
|
|
|
11
|
-
LawSpec
|
|
11
|
+
LawSpec 0.9 adds `List`, algebraic `Maybe` and `Either`, named
|
|
12
|
+
parameterized products and sums, and checked total definitions on all eight
|
|
13
|
+
backends. See the [language reference](LANGUAGE.md),
|
|
14
|
+
[collections](examples/specs/collections.lawspec),
|
|
15
|
+
[data types](examples/specs/data_types.lawspec), and
|
|
16
|
+
[total functions](examples/specs/total_functions.lawspec).
|
|
17
|
+
|
|
18
|
+
Generated code is readable by default; `--minify` explicitly selects compact
|
|
19
|
+
output. Python follows PEP 8. See the [0.9 release notes](RELEASE-0.9.md)
|
|
20
|
+
and [API migration guide](API-MIGRATION.md).
|
|
21
|
+
|
|
22
|
+
## Rust and the typed front end in 0.8.0
|
|
23
|
+
|
|
24
|
+
Rust joins Java, Python, JavaScript, TypeScript, Go, Haskell, and Kotlin. All
|
|
25
|
+
eight backends consume the same typed core and testing plan. Source expressions
|
|
26
|
+
retain their ranges, resolved identities, arithmetic evidence, and checked
|
|
27
|
+
conversions. See the [Rust guide](RUST.md) and [language reference](LANGUAGE.md).
|
|
28
|
+
|
|
29
|
+
The scalar catalog includes fixed and arbitrary integers, exact decimals and rationals,
|
|
12
30
|
IEEE floats and complex values, Unicode and raw-text domains, bytes, symbols,
|
|
13
|
-
and nested absence states on all
|
|
31
|
+
and nested absence states on all eight targets. Integer arithmetic produces representation-independent
|
|
14
32
|
`Integer` values; exact division returns Rational. See the [primitive reference](PRIMITIVES.md)
|
|
15
33
|
for constructors, arithmetic, native bridges, and machine-width profiles.
|
|
16
34
|
|
|
17
|
-
Compiler API consumers should read the [schema
|
|
35
|
+
Compiler API consumers should read the [schema v3 migration guide](API-MIGRATION.md).
|
|
18
36
|
Scalar values use tagged, lossless encodings; generated runtime placement is
|
|
19
37
|
separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchanged.
|
|
20
38
|
|
|
@@ -23,7 +41,7 @@ separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchan
|
|
|
23
41
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
24
42
|
|
|
25
43
|
```sh
|
|
26
|
-
npm install --save-dev lawspec@0.
|
|
44
|
+
npm install --save-dev lawspec@0.9.0
|
|
27
45
|
npx lawspec --version
|
|
28
46
|
```
|
|
29
47
|
|
|
@@ -37,8 +55,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
37
55
|
```sh
|
|
38
56
|
mkdir lawspec-example
|
|
39
57
|
cd lawspec-example
|
|
40
|
-
npm exec --package=lawspec@0.
|
|
41
|
-
npm install --save-dev lawspec@0.
|
|
58
|
+
npm exec --package=lawspec@0.9.0 -- lawspec init --target javascript
|
|
59
|
+
npm install --save-dev lawspec@0.9.0
|
|
42
60
|
npx lawspec check
|
|
43
61
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
44
62
|
npx lawspec doctor
|
|
@@ -71,9 +89,10 @@ properties with the selected framework's shrinking and failure reporting.
|
|
|
71
89
|
| `go` | Go modules, Go 1.22–1.26 | Rapid 1.2.0, testing | `go test ./...` |
|
|
72
90
|
| `haskell` | Stack, GHC 9.10, LTS 24.58 | Hspec 2.11, Hedgehog 1.5, hspec-hedgehog 0.3 | `stack test` |
|
|
73
91
|
| `kotlin` | JDK/JVM 25, Gradle 9.1–9.3, Kotlin 2.3.21 | Kotest 5.9.1 | `gradle test` |
|
|
92
|
+
| `rust` | Rust 1.85+, edition 2024, Cargo | Proptest 1.11.0 | `cargo test` |
|
|
74
93
|
|
|
75
94
|
Java 25 and Python 3.13 are the minimum baselines. New JVM releases are admitted
|
|
76
|
-
through compatibility profiles after testing;
|
|
95
|
+
through compatibility profiles after testing; the current JVM profile certifies
|
|
77
96
|
25. Python templates declare `requires-python = ">=3.13"` and runtime checks
|
|
78
97
|
currently recognize 3.13 and 3.14. Kotlin templates pin Gradle's supported build
|
|
79
98
|
configuration to Kotlin 2.3.21 and target JVM 25.
|
|
@@ -244,7 +263,7 @@ reusable laws accept these curried functions, their partial applications, and
|
|
|
244
263
|
scalar parameters. Quantified test inputs remain scalar.
|
|
245
264
|
|
|
246
265
|
The prelude defines the following laws. Every row has an executable example in
|
|
247
|
-
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
266
|
+
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/algebra.lawspec), including both sides of every
|
|
248
267
|
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
249
268
|
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
250
269
|
|
|
@@ -280,10 +299,10 @@ explicit domain predicate and conditional equations.
|
|
|
280
299
|
`invertible` checks the supplied inverse operation. Check `identity` and
|
|
281
300
|
`associative` as well when specifying a group. The prelude states contracts;
|
|
282
301
|
it does not supply arithmetic implementations or prove a structure from random
|
|
283
|
-
tests. The numeric examples use
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
302
|
+
tests. The algebra and numeric currying examples use mathematical `Integer`
|
|
303
|
+
values and exact native arithmetic. Explicit expectations exceed Int32 and
|
|
304
|
+
machine bounds, so wrapping or lossy adapters fail even when their modular
|
|
305
|
+
arithmetic happens to satisfy the algebraic identities.
|
|
287
306
|
|
|
288
307
|
Use `and` to require multiple conclusions in one law. For example:
|
|
289
308
|
|
|
@@ -312,11 +331,11 @@ their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
|
312
331
|
existing assertions, the first failure stops that individual test. `and` is now
|
|
313
332
|
a reserved word.
|
|
314
333
|
|
|
315
|
-
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
334
|
+
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/currying.lawspec) demonstrate a four-argument
|
|
316
335
|
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
317
336
|
and composition after partial application. Each example states its exact outputs.
|
|
318
337
|
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
319
|
-
in all
|
|
338
|
+
in all eight target languages.
|
|
320
339
|
|
|
321
340
|
The expanded API's **`assertion` tree is authoritative**: `AssertEqual` contains
|
|
322
341
|
two expressions, `AssertImplies` contains a condition and consequence, and
|
|
@@ -364,7 +383,7 @@ law `valid ports round trip` is
|
|
|
364
383
|
end
|
|
365
384
|
```
|
|
366
385
|
|
|
367
|
-
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
386
|
+
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/parse_port.lawspec)
|
|
368
387
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
369
388
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
370
389
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -382,7 +401,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
382
401
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
383
402
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
384
403
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
385
|
-
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
404
|
+
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/boolean_flags.lawspec)
|
|
386
405
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
387
406
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
388
407
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -461,7 +480,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
461
480
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
462
481
|
user-owned adapter functions; either may delegate to your existing code.
|
|
463
482
|
|
|
464
|
-
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
483
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/equivalent.lawspec) compares decimal
|
|
465
484
|
renderers and two implementations that clamp negative integers to zero. For
|
|
466
485
|
JavaScript, their adapters can be:
|
|
467
486
|
|
|
@@ -472,7 +491,7 @@ export const clamp = x => Math.max(0, x);
|
|
|
472
491
|
export const referenceClamp = x => x < 0 ? 0 : x;
|
|
473
492
|
```
|
|
474
493
|
|
|
475
|
-
The same specification generates native tests for all
|
|
494
|
+
The same specification generates native tests for all eight targets. The
|
|
476
495
|
integration suite checks both examples with matching implementations, then
|
|
477
496
|
breaks each alternative separately to verify detection. The general equivalence law alone does not establish independent correctness;
|
|
478
497
|
two implementations can share the same bug. Explicit expectations additionally
|
|
@@ -498,7 +517,7 @@ law `normalizers agree` is
|
|
|
498
517
|
end
|
|
499
518
|
```
|
|
500
519
|
|
|
501
|
-
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
520
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/slug.lawspec)
|
|
502
521
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
503
522
|
Unicode and escaped text. Each target uses its native string generator:
|
|
504
523
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -525,7 +544,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
525
544
|
end
|
|
526
545
|
```
|
|
527
546
|
|
|
528
|
-
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
547
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/canonical_url.lawspec)
|
|
529
548
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
530
549
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
531
550
|
|
|
@@ -534,7 +553,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
534
553
|
```
|
|
535
554
|
|
|
536
555
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
537
|
-
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
556
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.9.0/examples/specs/mixed_inputs.lawspec)
|
|
538
557
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
539
558
|
The JavaScript API represents input bindings and expected values as `number | string | boolean`.
|
|
540
559
|
Each example includes `expectations: { actual: Expr; expected: number | string | boolean }[]`.
|
|
@@ -550,7 +569,7 @@ npx lawspec examples --target java --output example_artifacts
|
|
|
550
569
|
This command works without a project configuration or native build tools. It
|
|
551
570
|
compiles every bundled example and writes its tests and user-owned stubs to
|
|
552
571
|
`example_artifacts/<language>/`, using each target's normal source/test layout.
|
|
553
|
-
By default it exports all
|
|
572
|
+
By default it exports all eight languages; `--json` returns the file inventory.
|
|
554
573
|
From a checkout, `make examples` runs the same command.
|
|
555
574
|
|
|
556
575
|
These are inspection artifacts, not initialized projects: no build files are
|
|
@@ -599,7 +618,7 @@ by the JS shim.
|
|
|
599
618
|
## Build and verify
|
|
600
619
|
|
|
601
620
|
For contributors working from a repository checkout, build a local archive with
|
|
602
|
-
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.
|
|
621
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.9.0.tgz`.
|
|
603
622
|
The package payload lives in `npm/`.
|
|
604
623
|
|
|
605
624
|
```sh
|
|
@@ -623,7 +642,7 @@ compiler-source and artifact hashes in `npm/build.json`; CI rejects stale WASM
|
|
|
623
642
|
or hand-edited generated wrappers. The npm archive is a self-contained consumer
|
|
624
643
|
artifact, with no install-time compilation or download hook.
|
|
625
644
|
|
|
626
|
-
For all
|
|
645
|
+
For all eight native integrations, install their build tools, then:
|
|
627
646
|
|
|
628
647
|
```sh
|
|
629
648
|
# Set LAWSPEC_GRADLE to a Gradle 9.3.0 executable if it is not on PATH.
|
package/REFINEMENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Refinements and abstract integers (0.
|
|
1
|
+
# Refinements and abstract integers (0.9.0)
|
|
2
2
|
|
|
3
3
|
A refinement restricts a scalar domain with a pure Boolean expression. LawSpec
|
|
4
4
|
checks concrete examples, generates satisfying input tuples, and checks adapter
|
|
@@ -82,7 +82,8 @@ refinement PositiveInt8 is (value :: Int8 where value > 0) end
|
|
|
82
82
|
|
|
83
83
|
Aliases preserve their underlying native representation. They can be nested in
|
|
84
84
|
`Nullable` and `Optional`; inner predicates are checked only for present values.
|
|
85
|
-
Type parameters range over
|
|
85
|
+
Type parameters range over supported value types, including structural types.
|
|
86
|
+
Their declared capabilities determine which operations are available.
|
|
86
87
|
|
|
87
88
|
`requires Integer T` describes integer capabilities in a generic declaration.
|
|
88
89
|
`Integer` entails `Eq` and `Ordered`. `Ordered` currently covers exact real numbers
|
|
@@ -108,7 +109,30 @@ Adapter calls are forbidden in refinements. Predicate errors such as division by
|
|
|
108
109
|
zero are reported as failures when evaluated; they are not ordinary rejection of
|
|
109
110
|
an input. Short-circuited branches are not evaluated by generation optimizations.
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
In the 0.9 frontend, predicates can call checked total definitions, including
|
|
113
|
+
generic definitions specialized to the predicate's concrete types:
|
|
114
|
+
|
|
115
|
+
```lawspec
|
|
116
|
+
definition nonempty (xs :: List a) :: Bool is
|
|
117
|
+
match xs with
|
|
118
|
+
| Nil -> false
|
|
119
|
+
| Cons head tail -> true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
refinement Nonempty (T :: Type) is
|
|
124
|
+
(xs :: List T where nonempty xs)
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The same closed definitions evaluate example inputs, constant refinement
|
|
129
|
+
arguments, finite domains, and boundaries, and run in generated predicate and
|
|
130
|
+
contract checks on all eight targets. Calls to adapters cannot enter that closed
|
|
131
|
+
environment. Definitions can also refine their own parameters and results.
|
|
132
|
+
Their bodies must be proved total under the ordered input preconditions, and
|
|
133
|
+
every result refinement must follow from the body.
|
|
134
|
+
|
|
135
|
+
Every refined adapter signature creates a standalone property. Calls from other
|
|
112
136
|
laws also check argument preconditions, invoke the adapter once, snapshot its
|
|
113
137
|
result, and check postconditions. Calling a function outside its precondition is
|
|
114
138
|
a test failure, not a discarded example. Ordinary `implies` guards retain their
|
|
@@ -156,3 +180,265 @@ Statically established empty executable domains and invalid examples are rejecte
|
|
|
156
180
|
See [the bundled examples](examples/specs/refinements.lawspec) for overflow,
|
|
157
181
|
abstract integer arguments/results, dependent bounds, optional refinements,
|
|
158
182
|
floating classification, and raw-byte lengths.
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
## Refined definitions
|
|
186
|
+
|
|
187
|
+
```lawspec
|
|
188
|
+
definition increment (x :: Int8 where x < 127)
|
|
189
|
+
:: (result :: Int8 where result > x)
|
|
190
|
+
is
|
|
191
|
+
prelude.Int8 (x + 1)
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Arithmetic promotes before the explicit Int8 conversion. The compiler proves
|
|
196
|
+
that the precondition makes the conversion safe and that the result exceeds x.
|
|
197
|
+
Later parameters may depend on earlier parameters. Result binders must differ
|
|
198
|
+
from argument names.
|
|
199
|
+
|
|
200
|
+
Generic definitions retain their contracts through specialization. Templates,
|
|
201
|
+
including unused ones, must pass type, capability, termination and definedness
|
|
202
|
+
checks before concrete instances are emitted. Each closed call must satisfy its
|
|
203
|
+
callee's preconditions. Contracts cannot depend cyclically on their definitions
|
|
204
|
+
or call adapters. A claim the proof checker cannot establish is rejected.
|
|
205
|
+
|
|
206
|
+
The reference evaluator and all eight native backends validate arguments, check
|
|
207
|
+
preconditions in order, evaluate the body, validate its result, and check
|
|
208
|
+
postconditions. An invalid native call fails before unsafe body arithmetic.
|
|
209
|
+
Definitions are proved during compilation; adapter contracts instead generate
|
|
210
|
+
standalone properties because their implementations are external.
|
|
211
|
+
|
|
212
|
+
See [refined definitions](examples/specs/refined_definitions.lawspec) for generic
|
|
213
|
+
reciprocals, checked narrowing and dependent bounds. Both the native compiler
|
|
214
|
+
and packaged WASM build support these contracts.
|
|
215
|
+
|
|
216
|
+
The native definition-contract harnesses accept `LAWSPEC_CONTRACT_SOURCE=1`.
|
|
217
|
+
This compiles `test/fixtures/definition_contracts.lawspec` through the public
|
|
218
|
+
frontend before emitting native code. All eight targets pass both machine
|
|
219
|
+
profiles and readable/compact modes, including ordered predicate checks, exact
|
|
220
|
+
division through a specialized generic helper, narrowing, direct logical calls,
|
|
221
|
+
and postcondition rejection of deliberately corrupted results. The default mode
|
|
222
|
+
retains the independently constructed Core fixture.
|
|
223
|
+
|
|
224
|
+
## Sum payload refinements
|
|
225
|
+
|
|
226
|
+
`Maybe (value :: Int8 where value > 0)` admits `Nothing` and positive `Just`
|
|
227
|
+
payloads. `Either (value :: Int8 where value > 0) Bool` checks the positive bound
|
|
228
|
+
only for `Left`; `Right` retains its Bool domain. Nested sums compose these
|
|
229
|
+
checks, and payload predicates may refer to earlier quantified inputs.
|
|
230
|
+
|
|
231
|
+
These refinements elaborate to exhaustive Core matches. Unselected branches are
|
|
232
|
+
not evaluated, and generated binder names preserve outer dependencies. Finite
|
|
233
|
+
domains retain distinct absence and variant states. Named data payload and constructor-field refinements are also supported. The bundled [sum refinement examples](examples/specs/sum_refinements.lawspec)
|
|
234
|
+
pass native framework execution on all eight targets under both machine profiles
|
|
235
|
+
and readable/compact layouts. The data integration harnesses include these laws
|
|
236
|
+
with their existing structural and incorrect-adapter checks.
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
## List payload refinements
|
|
240
|
+
|
|
241
|
+
`List (value :: Int8 where value > 0)` constrains every element and admits the
|
|
242
|
+
empty list. Lists compose with other Lists, Maybe, and Either. An element
|
|
243
|
+
predicate can refer to an earlier quantified input, for example:
|
|
244
|
+
|
|
245
|
+
```lawspec
|
|
246
|
+
law `elements exceed the earlier bound` is
|
|
247
|
+
definition is `for all` (floor :: Int8)
|
|
248
|
+
(xs :: List (value :: Int8 where value > floor)) .
|
|
249
|
+
prelude.length xs >= 0
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
These domains elaborate to a typed, scoped Core `AllElements` predicate. It
|
|
255
|
+
checks elements in order, stops at the first false result, and returns true for
|
|
256
|
+
an empty list. Its binder is fresh with respect to outer dependencies. There is
|
|
257
|
+
no new user-facing predicate syntax. Explicit examples outside the domain are
|
|
258
|
+
rejected; nested predicates keep their element scope during specialization.
|
|
259
|
+
See [the List refinement examples](examples/specs/list_refinements.lawspec).
|
|
260
|
+
|
|
261
|
+
Generic definition signatures can contain List payload refinements, including
|
|
262
|
+
calls to generic Boolean helpers. Native entry points check these contracts.
|
|
263
|
+
The totality prover retains universal element facts for each List identity.
|
|
264
|
+
A stronger exact numeric bound can satisfy a weaker callee contract, including
|
|
265
|
+
nested Lists. Matching calls to pure Boolean helpers can also be reused after
|
|
266
|
+
specialization. Returned inputs and explicitly constructed Lists can establish
|
|
267
|
+
universal postconditions; the empty list satisfies every element predicate.
|
|
268
|
+
See [the List contract examples](examples/specs/list_contracts.lawspec).
|
|
269
|
+
|
|
270
|
+
Proof binders are fresh and scoped. An element condition never implies that a
|
|
271
|
+
list is nonempty, and facts about one list do not transfer to another list or to
|
|
272
|
+
an unrelated scalar. In a `Cons first rest` branch, `first` inherits the element
|
|
273
|
+
predicate and `rest` retains the universal predicate with its original outer
|
|
274
|
+
dependencies. Both are strict structural subterms for termination checking. A
|
|
275
|
+
`Nil` branch knows only that its matched list is empty. These facts stay inside
|
|
276
|
+
their branch and can establish branch-specific result contracts.
|
|
277
|
+
|
|
278
|
+
For example, a definition over `List (value :: Int8 where value != 0)` can divide
|
|
279
|
+
by each matched head and recursively process the tail. The bundled List contract
|
|
280
|
+
examples include exact reciprocal sums and nested rows. More complex implications,
|
|
281
|
+
including facts requiring a callee's result contract to be unfolded, remain
|
|
282
|
+
conservative and may be rejected.
|
|
283
|
+
|
|
284
|
+
Verified callee results are available to the total-definition proof checker.
|
|
285
|
+
A helper's postcondition can establish a nonzero divisor, justify checked integer
|
|
286
|
+
narrowing, or satisfy the next helper's input contract. For example:
|
|
287
|
+
|
|
288
|
+
```lawspec
|
|
289
|
+
definition nonzero (value :: Int8 where value != 0)
|
|
290
|
+
:: (result :: Int8 where result != 0)
|
|
291
|
+
is value end
|
|
292
|
+
|
|
293
|
+
definition reciprocal (value :: Int8 where value != 0) :: Rational is
|
|
294
|
+
1 / nonzero value
|
|
295
|
+
end
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The checker first proves the call's preconditions and, for recursive calls,
|
|
299
|
+
strict structural descent. Only then does it use the result guarantee. Recursive
|
|
300
|
+
List construction can therefore prove element postconditions by induction over
|
|
301
|
+
the tail. A returned List does not itself acquire structural-subterm status.
|
|
302
|
+
Guarantees remain scoped to evaluated branches and short-circuit operands;
|
|
303
|
+
a skipped call cannot justify arithmetic elsewhere. Every helper's own result
|
|
304
|
+
contract is checked, including helpers unused by laws.
|
|
305
|
+
|
|
306
|
+
Constructor-sensitive matching also preserves payload refinements in total
|
|
307
|
+
functions. A `Maybe` payload fact is available in the `Just` branch; an `Either`
|
|
308
|
+
fact belongs to its corresponding `Left` or `Right` branch. Constructed results
|
|
309
|
+
are checked against the matching alternative, including nested sums.
|
|
310
|
+
|
|
311
|
+
Whole-value refinements on named products can relate fields explicitly:
|
|
312
|
+
|
|
313
|
+
```lawspec
|
|
314
|
+
type Range is Range lower :: Int8 upper :: Int8 end
|
|
315
|
+
|
|
316
|
+
definition gap
|
|
317
|
+
(range :: Range where match range with | Range lo hi -> hi > lo end)
|
|
318
|
+
:: Rational
|
|
319
|
+
is
|
|
320
|
+
match range with | Range lo hi -> 1 / (hi - lo) end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The field relation justifies the nonzero denominator within that branch. It
|
|
325
|
+
cannot establish a fact about a different value or another constructor's payload.
|
|
326
|
+
This example uses a refinement on the function's whole input value; direct
|
|
327
|
+
constructor-field refinements can express the relation at the data declaration.
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
## Named data payloads
|
|
331
|
+
|
|
332
|
+
A product or sum can receive refined type arguments. The predicate
|
|
333
|
+
applies wherever that parameter is stored, including recursive named
|
|
334
|
+
types, List elements, and Maybe/Either payloads. Other alternatives remain
|
|
335
|
+
unconstrained when they do not store that parameter.
|
|
336
|
+
|
|
337
|
+
```lawspec
|
|
338
|
+
unit example.positive_pair
|
|
339
|
+
|
|
340
|
+
type Pair (a :: Type) (b :: Type) is
|
|
341
|
+
Pair first :: a second :: b
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
refinement Positive is (value :: Int8 where value > 0) end
|
|
345
|
+
|
|
346
|
+
definition reciprocal (pair :: Pair Positive Bool) :: Rational is
|
|
347
|
+
match pair with
|
|
348
|
+
| Pair first second -> 1 / first
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
law `half` is
|
|
353
|
+
definition is
|
|
354
|
+
`for all` (pair :: Pair Positive Bool) . reciprocal pair > 0
|
|
355
|
+
end
|
|
356
|
+
example `two` is
|
|
357
|
+
pair = Pair 2 false
|
|
358
|
+
expect reciprocal pair = rational(1, 2)
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
The compiler lowers the payload requirement to a scoped traversal in the shared
|
|
364
|
+
Core. Generators, concrete examples, adapter contracts, and checked definition
|
|
365
|
+
entry points use that predicate. The native representation remains
|
|
366
|
+
`Pair Int8 Bool`; the refinement does not introduce a new wrapper type. Definition proofs
|
|
367
|
+
can use the selected field's predicate to establish that division is defined.
|
|
368
|
+
|
|
369
|
+
Free value names in a type argument retain the scope where the argument was
|
|
370
|
+
written. In `Pair Int8 (n :: Int8 where n > first)`, `first` refers to an earlier
|
|
371
|
+
outer input; a constructor field also named `first` does not capture it.
|
|
372
|
+
|
|
373
|
+
Recursive payloads such as `Tree Positive` use the same operation. Traversal
|
|
374
|
+
follows stored parameter positions, including mutual recursion and growing
|
|
375
|
+
arguments such as `Nest (List a)`. A fixed `Int8` field is not constrained merely
|
|
376
|
+
because another parameter is instantiated as `Int8`. Empty and phantom storage
|
|
377
|
+
satisfies its payload predicate without evaluating a callback. See the bundled
|
|
378
|
+
[recursive refinement example](examples/specs/recursive_refinements.lawspec),
|
|
379
|
+
which proves a positive result for a recursive sum and preserves outer thresholds.
|
|
380
|
+
|
|
381
|
+
The front end checks direct constructor field contracts:
|
|
382
|
+
|
|
383
|
+
```lawspec
|
|
384
|
+
type Gap is
|
|
385
|
+
Gap first :: Int8 second :: (n :: Int8 where n > first)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
definition inverseGap (gap :: Gap) :: Rational is
|
|
389
|
+
match gap with | Gap x y -> 1 / (y - x) end
|
|
390
|
+
end
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
A field predicate may refer to that field and earlier fields. Construction in a
|
|
394
|
+
total definition must prove the predicates; matching makes them available in the
|
|
395
|
+
selected branch. Concrete example inputs and expected values are checked too.
|
|
396
|
+
The reference interpreter enforces predicates on definition inputs and results.
|
|
397
|
+
All eight backends enforce these
|
|
398
|
+
contracts in generated runtime checks and native Hypothesis/fast-check/proptest/
|
|
399
|
+
JetCheck/Kotest/Rapid/Hedgehog strategies. Each generated case
|
|
400
|
+
shares one Symbol fixture context across witnesses, inputs, adapter checks and
|
|
401
|
+
assertions. Witnesses supplement native strategies; sampled alternatives may
|
|
402
|
+
retain large counterexamples even when native branches can shrink further.
|
|
403
|
+
Web and Rust generation bound whole-value retries with `maxAttempts`; an exhausted search
|
|
404
|
+
reports failure rather than claiming that the domain is empty. Input guards use
|
|
405
|
+
fast-check preconditions, preserving its skip and shrink handling. Rust carries
|
|
406
|
+
predicate evaluation errors into property failures rather than treating them as
|
|
407
|
+
rejected candidates. Java preserves native JetCheck filtering/shrinking, caps each
|
|
408
|
+
filter at the smaller of `maxAttempts` and the framework's 100-attempt limit, and
|
|
409
|
+
reports evaluator errors as contextual generation failures. It uses one native
|
|
410
|
+
session per requested constructor-contract case to avoid session-wide draw
|
|
411
|
+
uniqueness exhaustion for fixture identities. Kotlin bounds candidate sampling
|
|
412
|
+
while retaining native shrink trees; case state carries evaluator errors past
|
|
413
|
+
input guards to the property failure. Go uses Rapid's bounded native filtering
|
|
414
|
+
and replay-based shrinking, with per-sample error state that prevents later fields
|
|
415
|
+
from hiding an evaluator failure. Required conjunctive Symbol equalities bind the
|
|
416
|
+
fixture identity directly; disjunctions retain their alternatives. Go's structural
|
|
417
|
+
properties honor `cases` with scoped Rapid settings, restoring the previous flag
|
|
418
|
+
after each sequential generated test. Rapid's explicit short-test mode may reduce
|
|
419
|
+
that count. Each filter uses at most the smaller of `maxAttempts` and Rapid's
|
|
420
|
+
five-attempt native limit; enclosing native generators and the engine retain
|
|
421
|
+
their own retry limits. For structural Go properties, minimization uses Rapid's
|
|
422
|
+
`-rapid.shrinktime` setting, not the scalar refinement engine's `maxShrinks`
|
|
423
|
+
step budget. Haskell constructor properties use native Hedgehog strategies with
|
|
424
|
+
one Symbol context per case. Each dependent draw is forced before the next draw;
|
|
425
|
+
evaluator failures cannot disappear behind a later discard. Native `filterT`
|
|
426
|
+
prunes rejected shrink branches instead of searching all their descendants.
|
|
427
|
+
Hedgehog receives `cases` as its test limit, `maxAttempts` as its property discard
|
|
428
|
+
limit, and `maxShrinks` as its shrink limit. Internal generator filters retain
|
|
429
|
+
Hedgehog's own retry policy; `maxAttempts` is not a total count of candidate draws.
|
|
430
|
+
Recursively refined named payloads use scoped payload predicates that follow
|
|
431
|
+
declared type-parameter positions through constructor fields.
|
|
432
|
+
|
|
433
|
+
Common domain planning filters finite constructor domains through their field
|
|
434
|
+
predicates. For larger domains, it searches combinations of field boundaries and
|
|
435
|
+
literal values from contracts. This covers dependent fields such as `Gap` and
|
|
436
|
+
sparse Symbol fixture identities. Recursive expansion and candidate combinations
|
|
437
|
+
have finite search budgets; every returned witness satisfies the contracts.
|
|
438
|
+
Exhausting that search reports that no witness was found, rather than declaring
|
|
439
|
+
the domain empty. Bounded samples are never reported as an exhaustive domain.
|
|
440
|
+
|
|
441
|
+
Expression annotations select a representation, such as `(127 :: Int8)`; they
|
|
442
|
+
do not establish a refinement contract. Inline refinement predicates in expression
|
|
443
|
+
annotations are rejected. Put the refinement on a quantified input or function
|
|
444
|
+
signature so that generation, checking, and definition proofs enforce it.
|
package/RELEASE-0.9.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# LawSpec 0.9.0
|
|
2
|
+
|
|
3
|
+
This release adds structural data and checked total definitions across Java,
|
|
4
|
+
Python, JavaScript, TypeScript, Go, Haskell, Kotlin, and Rust.
|
|
5
|
+
|
|
6
|
+
## Language
|
|
7
|
+
|
|
8
|
+
- `List a` supports nested contextual literals, structural equality, exact
|
|
9
|
+
length, deterministic boundaries, and native framework generation/shrinking.
|
|
10
|
+
- Algebraic `Maybe a` and `Either a b` provide `Nothing`/`Just` and `Left`/`Right`.
|
|
11
|
+
They remain distinct from interoperability `Nullable` and `Optional` values.
|
|
12
|
+
- Named parameterized products and sums have constructors and ordered fields.
|
|
13
|
+
Generated native types and typed adapters preserve their structure.
|
|
14
|
+
- Total definitions support exhaustive matching and checked structural recursion.
|
|
15
|
+
The compiler rejects partial matches and recursion it cannot establish as
|
|
16
|
+
terminating. Generic definitions specialize to their concrete uses.
|
|
17
|
+
- Refined definition signatures become executable native contracts. Scoped
|
|
18
|
+
payload predicates preserve type-parameter roles through recursive and mutual
|
|
19
|
+
data declarations, including predicates that depend on preceding inputs.
|
|
20
|
+
|
|
21
|
+
Existing scalar semantics remain intact: exact arithmetic does not wrap,
|
|
22
|
+
conversions to bounded adapter parameters are checked, floating equality follows
|
|
23
|
+
IEEE rules, and Symbol equality uses identity.
|
|
24
|
+
|
|
25
|
+
## Generated output
|
|
26
|
+
|
|
27
|
+
Readable code is the default for runtimes, declarations, definitions, adapters,
|
|
28
|
+
tests, and scaffolds. Explicit `--minify` selects compact output for `init`,
|
|
29
|
+
`generate`, and `examples`; the compiler API accepts `minify: true`.
|
|
30
|
+
|
|
31
|
+
Output follows Google language-specific guidance where applicable, PEP 8 for
|
|
32
|
+
Python, standard Go and Rust formatting, and an 80-column Haskell layout.
|
|
33
|
+
Formatting is deterministic in both native and WASM compilation and requires
|
|
34
|
+
no formatter download. Compact output preserves required layout, literal
|
|
35
|
+
contents, and semantics. Changing formatting mode does not overwrite edited
|
|
36
|
+
adapters or create false adapter-signature updates.
|
|
37
|
+
|
|
38
|
+
## API and compatibility
|
|
39
|
+
|
|
40
|
+
API schema 3 adds named data declarations, structural values, checked definitions,
|
|
41
|
+
construction/matching expressions, and scoped payload predicates. Existing scalar
|
|
42
|
+
wire encodings are unchanged. Exhaustive expression visitors must handle the
|
|
43
|
+
new variants; see [API migration](API-MIGRATION.md).
|
|
44
|
+
|
|
45
|
+
Java 25+, Python 3.13+, Node 22+, and Rust 1.85+ baselines remain unchanged.
|
|
46
|
+
Machine-width profiles, custom source/test directories, generation manifests,
|
|
47
|
+
and user ownership of adapters/build files remain supported. Framework-specific
|
|
48
|
+
strategies stay separate from reusable runtime source.
|
|
49
|
+
|
|
50
|
+
GADTs, indexed families, and general dependent types remain future work. See
|
|
51
|
+
[the language reference](LANGUAGE.md) and the target guides for native type
|
|
52
|
+
representations and framework-specific refinement/shrinking limits.
|
|
53
|
+
|
|
54
|
+
## Examples and release acceptance
|
|
55
|
+
|
|
56
|
+
Bundled examples cover reverse involution, sorting idempotence/sortedness/length/
|
|
57
|
+
permutation, nested presence, products/sums, exhaustive matches, total functions,
|
|
58
|
+
and recursive payload refinements. Release acceptance includes compiler and
|
|
59
|
+
native/WASM checks, native execution on all eight targets, deliberately incorrect
|
|
60
|
+
adapters, independent formatting/syntax checks, regeneration protection, and
|
|
61
|
+
installation of the packed npm artifact. Publication is a separate step.
|