lawspec 0.8.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 +102 -1
- package/GO.md +144 -0
- package/HASKELL.md +171 -0
- package/JAVA.md +113 -0
- package/KOTLIN.md +214 -0
- package/LANGUAGE.md +237 -12
- package/PRIMITIVES.md +1 -1
- package/PYTHON.md +152 -0
- package/README.md +26 -13
- package/REFINEMENTS.md +289 -3
- package/RELEASE-0.9.md +61 -0
- package/RUST.md +115 -1
- package/WEB.md +144 -0
- package/api.mjs +8 -2
- package/bin/lawspec.mjs +9 -6
- package/build.json +179 -37
- package/core.wasm +0 -0
- package/examples/specs/collections.lawspec +67 -0
- 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/sum_refinements.lawspec +49 -0
- package/examples/specs/total_functions.lawspec +101 -0
- package/examples-command.mjs +6 -1
- package/files.mjs +10 -1
- package/index.d.ts +246 -27
- package/package.json +2 -2
- package/templates.mjs +107 -17
package/README.md
CHANGED
|
@@ -2,10 +2,23 @@
|
|
|
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
|
+
## Structural data and total definitions in 0.9.0
|
|
10
|
+
|
|
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
|
+
|
|
9
22
|
## Rust and the typed front end in 0.8.0
|
|
10
23
|
|
|
11
24
|
Rust joins Java, Python, JavaScript, TypeScript, Go, Haskell, and Kotlin. All
|
|
@@ -28,7 +41,7 @@ separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchan
|
|
|
28
41
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
29
42
|
|
|
30
43
|
```sh
|
|
31
|
-
npm install --save-dev lawspec@0.
|
|
44
|
+
npm install --save-dev lawspec@0.9.0
|
|
32
45
|
npx lawspec --version
|
|
33
46
|
```
|
|
34
47
|
|
|
@@ -42,8 +55,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
42
55
|
```sh
|
|
43
56
|
mkdir lawspec-example
|
|
44
57
|
cd lawspec-example
|
|
45
|
-
npm exec --package=lawspec@0.
|
|
46
|
-
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
|
|
47
60
|
npx lawspec check
|
|
48
61
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
49
62
|
npx lawspec doctor
|
|
@@ -250,7 +263,7 @@ reusable laws accept these curried functions, their partial applications, and
|
|
|
250
263
|
scalar parameters. Quantified test inputs remain scalar.
|
|
251
264
|
|
|
252
265
|
The prelude defines the following laws. Every row has an executable example in
|
|
253
|
-
[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
|
|
254
267
|
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
255
268
|
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
256
269
|
|
|
@@ -318,7 +331,7 @@ their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
|
318
331
|
existing assertions, the first failure stops that individual test. `and` is now
|
|
319
332
|
a reserved word.
|
|
320
333
|
|
|
321
|
-
[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
|
|
322
335
|
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
323
336
|
and composition after partial application. Each example states its exact outputs.
|
|
324
337
|
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
@@ -370,7 +383,7 @@ law `valid ports round trip` is
|
|
|
370
383
|
end
|
|
371
384
|
```
|
|
372
385
|
|
|
373
|
-
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)
|
|
374
387
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
375
388
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
376
389
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -388,7 +401,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
388
401
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
389
402
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
390
403
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
391
|
-
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)
|
|
392
405
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
393
406
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
394
407
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -467,7 +480,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
467
480
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
468
481
|
user-owned adapter functions; either may delegate to your existing code.
|
|
469
482
|
|
|
470
|
-
[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
|
|
471
484
|
renderers and two implementations that clamp negative integers to zero. For
|
|
472
485
|
JavaScript, their adapters can be:
|
|
473
486
|
|
|
@@ -504,7 +517,7 @@ law `normalizers agree` is
|
|
|
504
517
|
end
|
|
505
518
|
```
|
|
506
519
|
|
|
507
|
-
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)
|
|
508
521
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
509
522
|
Unicode and escaped text. Each target uses its native string generator:
|
|
510
523
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -531,7 +544,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
531
544
|
end
|
|
532
545
|
```
|
|
533
546
|
|
|
534
|
-
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)
|
|
535
548
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
536
549
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
537
550
|
|
|
@@ -540,7 +553,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
540
553
|
```
|
|
541
554
|
|
|
542
555
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
543
|
-
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)
|
|
544
557
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
545
558
|
The JavaScript API represents input bindings and expected values as `number | string | boolean`.
|
|
546
559
|
Each example includes `expectations: { actual: Expr; expected: number | string | boolean }[]`.
|
|
@@ -605,7 +618,7 @@ by the JS shim.
|
|
|
605
618
|
## Build and verify
|
|
606
619
|
|
|
607
620
|
For contributors working from a repository checkout, build a local archive with
|
|
608
|
-
`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`.
|
|
609
622
|
The package payload lives in `npm/`.
|
|
610
623
|
|
|
611
624
|
```sh
|
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.
|
package/RUST.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Rust backend
|
|
1
|
+
# Rust backend
|
|
2
2
|
|
|
3
3
|
Rust is the first backend built on LawSpec's typed core and testing plan. The
|
|
4
4
|
compiler remains implemented in Haskell. Rust generation does not parse source
|
|
@@ -27,6 +27,59 @@ preserves edited adapters and reports required signature updates.
|
|
|
27
27
|
The numeric runtime has no Proptest dependency. Framework support lives in a
|
|
28
28
|
separate generated file, `tests/support/lawspec_strategies.rs`.
|
|
29
29
|
|
|
30
|
+
## Total definitions (0.9)
|
|
31
|
+
|
|
32
|
+
Unit-level definitions supply executable implementations alongside laws:
|
|
33
|
+
|
|
34
|
+
```lawspec
|
|
35
|
+
unit example.total
|
|
36
|
+
|
|
37
|
+
definition size (xs :: List Int8) :: BigInt is
|
|
38
|
+
match xs with
|
|
39
|
+
| Nil -> 0
|
|
40
|
+
| Cons head tail -> 1 + size tail
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The compiler checks types, exhaustive matching, structural termination, and
|
|
46
|
+
potentially failing operations before emission. Definitions may call other
|
|
47
|
+
checked definitions, including forward references; they cannot call adapters.
|
|
48
|
+
Generic definitions specialize to concrete uses. Refinement predicates may call
|
|
49
|
+
checked definitions. Refined definition signatures become checked native contracts.
|
|
50
|
+
|
|
51
|
+
Rust emits their implementations into the generated source file
|
|
52
|
+
`lawspec_definitions.rs`. They are not user-owned adapter stubs. Typed native
|
|
53
|
+
entry points are grouped by unit, for example:
|
|
54
|
+
|
|
55
|
+
```rust
|
|
56
|
+
let mut context = lawspec_runtime::Context::default();
|
|
57
|
+
let count = lawspec_definitions::example_total::size(&mut context, vec![1, 2, 3])?;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
These entry points return `lawspec_runtime::Result<T>` and check native values
|
|
61
|
+
at the boundary. Share the context when Symbol fixture identities must match.
|
|
62
|
+
Native machine-sized bindings check the architecture, including fields in
|
|
63
|
+
unselected data variants. The implementation has no Proptest dependency and can
|
|
64
|
+
be used by the application library. Generated properties invoke the same checked
|
|
65
|
+
implementation; remaining external declarations still receive adapter stubs.
|
|
66
|
+
|
|
67
|
+
`tools/rust-definitions-integration.mjs` verifies both machine profiles, custom
|
|
68
|
+
source/test roots, native calls, readable and compact source, exact `rustfmt`
|
|
69
|
+
layout, incorrect adapters, and regeneration ownership.
|
|
70
|
+
|
|
71
|
+
## Formatting (0.9)
|
|
72
|
+
|
|
73
|
+
Rust source uses four-space indentation and structured line wrapping. Request
|
|
74
|
+
compact layout explicitly with `lawspec generate --minify`, or `minify: true`
|
|
75
|
+
in the compiler API. The mode is not saved in project configuration. Formatting
|
|
76
|
+
switches preserve user-owned adapters and their canonical interface references.
|
|
77
|
+
|
|
78
|
+
`tools/rust-formatting-integration.mjs` compares all Rust artifacts generated
|
|
79
|
+
from the bundled examples and the total-definition fixture against rustfmt,
|
|
80
|
+
under both machine profiles. Formatting is implemented in the compiler; generated
|
|
81
|
+
projects do not need rustfmt to run. The bundled WASM uses the same layout.
|
|
82
|
+
|
|
30
83
|
## Owned adapter values
|
|
31
84
|
|
|
32
85
|
Arguments and results are owned Rust values. LawSpec does not add borrowing,
|
|
@@ -50,6 +103,10 @@ expression needs to use an input more than once.
|
|
|
50
103
|
| `Symbol` | Generated identity type; cloning preserves identity |
|
|
51
104
|
| `Unit`, `Null`, `Undefined` | `()`, distinct generated absence types |
|
|
52
105
|
| `Nullable a`, `Optional a` | Distinct generated enums, including nested presence |
|
|
106
|
+
| `List a` | `Vec<A>` |
|
|
107
|
+
| `Maybe a` | `Option<A>` |
|
|
108
|
+
| `Either a b` | Generated `Either<A, B>` |
|
|
109
|
+
| User-defined products and sums | Named generic enums in `lawspec_data` |
|
|
53
110
|
|
|
54
111
|
Names such as `Integer` and `Decimal` above are exported by the generated
|
|
55
112
|
`lawspec_runtime` module. Machine-sized native bindings check the requested
|
|
@@ -75,6 +132,38 @@ arithmetic is exact; explicit rounding uses the requested scale and ties to
|
|
|
75
132
|
even. Exact-to-float conversion rounds directly to the requested IEEE precision,
|
|
76
133
|
including subnormal and halfway cases.
|
|
77
134
|
|
|
135
|
+
## Native data declarations
|
|
136
|
+
|
|
137
|
+
User-defined data emits reusable `lawspec_data.rs` and `lawspec_schema.rs` in the
|
|
138
|
+
source directory. Adapters receive native enum values with named, typed fields.
|
|
139
|
+
A product uses an enum with one record variant. Parameterized fields retain
|
|
140
|
+
Rust generic types; recursive fields use `Box` where an inline cycle requires
|
|
141
|
+
indirection, while `Vec` already supplies it. Mutually recursive declarations
|
|
142
|
+
are resolved together. When two units use the same type name, generated names
|
|
143
|
+
are qualified by their unit identities.
|
|
144
|
+
|
|
145
|
+
The runtime's `FromValue` and `IntoValue` traits provide conversion bridges.
|
|
146
|
+
Generated calls validate the complete logical value before conversion and the
|
|
147
|
+
native result before checking postconditions. This preserves raw UTF-16 units,
|
|
148
|
+
bytes, nested presence, and primitive range checks inside custom fields.
|
|
149
|
+
Machine-width checks also apply to machine integers inside custom data.
|
|
150
|
+
|
|
151
|
+
Generated enums derive `Clone` and `Debug`. LawSpec equality remains a runtime
|
|
152
|
+
operation: it compares fields structurally, retains IEEE NaN and signed-zero
|
|
153
|
+
rules, and compares Symbols by identity. It does not replace these rules with a
|
|
154
|
+
derived Rust `Eq` implementation.
|
|
155
|
+
|
|
156
|
+
Unused or exclusively recursive type parameters use a `PhantomData` marker.
|
|
157
|
+
Types with no constructors remain uninhabited; they do not acquire a synthetic
|
|
158
|
+
variant. Generated schemas and conversion support have no Proptest dependency.
|
|
159
|
+
The test support composes native Proptest strategies for recursive values and
|
|
160
|
+
shrinking, with deterministic boundary cases and finite-domain enumeration.
|
|
161
|
+
The structural budget counts each scalar, container, and constructor as one
|
|
162
|
+
node. Generation reserves every product field's minimum before distributing
|
|
163
|
+
remaining nodes. List length and element budgets vary together, so a deep
|
|
164
|
+
singleton remains reachable and lists have no hidden four-element limit.
|
|
165
|
+
Native shrinking preserves the schema and the structural budget.
|
|
166
|
+
|
|
78
167
|
## Refinements and generation
|
|
79
168
|
|
|
80
169
|
Small finite domains are enumerated. Larger domains use native Proptest
|
|
@@ -95,3 +184,28 @@ result, and then check postconditions on that result.
|
|
|
95
184
|
test directories other than `tests`, register generated test files using Cargo
|
|
96
185
|
`[[test]]` entries. Doctor checks the selected Cargo package, edition, resolved
|
|
97
186
|
dependencies, library directory, and custom test registration.
|
|
187
|
+
|
|
188
|
+
Internal typed Core definitions with attached contracts are proved before Rust
|
|
189
|
+
emission. Generated source checks preconditions after argument validation and
|
|
190
|
+
postconditions after result validation, without property-framework dependencies.
|
|
191
|
+
Checks sequence through Result, preserving ordered predicates and contextual
|
|
192
|
+
failures. Native wrappers share logical enforcement. Readable contract bodies
|
|
193
|
+
match rustfmt exactly.
|
|
194
|
+
The shared native fixture passes both machine profiles and formatting modes,
|
|
195
|
+
including exact division, narrowing, nested calls, direct logical entry checks,
|
|
196
|
+
and rejection of corrupted results. Refined source definition signatures now
|
|
197
|
+
produce these contracts through template proof and specialization.
|
|
198
|
+
|
|
199
|
+
## Constructor field contracts
|
|
200
|
+
|
|
201
|
+
Rust checks constructor predicates at logical definition and native adapter
|
|
202
|
+
boundaries. Generated proptest strategies validate complete candidates, retain
|
|
203
|
+
native shrinking, and share each case's Symbol context with witness literals and
|
|
204
|
+
assertions. Only false predicates trigger retries; evaluation errors fail the
|
|
205
|
+
property. `maxAttempts` bounds local and global rejection, and exhaustion does
|
|
206
|
+
not prove an empty domain.
|
|
207
|
+
|
|
208
|
+
Validated witnesses seed nested payloads. Builtin lists and presence/sum
|
|
209
|
+
containers retain native structural shrinking; sampled named witnesses can
|
|
210
|
+
still produce larger counterexamples than native branches. Recursive named
|
|
211
|
+
refined payloads remain outside the currently supported contract fragment.
|