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/README.md CHANGED
@@ -2,10 +2,23 @@
2
2
 
3
3
  **State the law once. Check it everywhere.**
4
4
 
5
- LawSpec 0.8 compiles reusable laws into native property tests, executable examples,
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.8.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.8.0 -- lawspec init --target javascript
46
- npm install --save-dev lawspec@0.8.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.8.0/examples/specs/algebra.lawspec), including both sides of every
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.8.0/examples/specs/currying.lawspec) demonstrate a four-argument
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.8.0/examples/specs/parse_port.lawspec)
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.8.0/examples/specs/boolean_flags.lawspec)
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.8.0/examples/specs/equivalent.lawspec) compares decimal
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.8.0/examples/specs/slug.lawspec)
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.8.0/examples/specs/canonical_url.lawspec)
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.8.0/examples/specs/mixed_inputs.lawspec)
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.8.0.tgz`.
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.8.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 the supported scalar types, including presence types.
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
- Every refined function signature creates a standalone property. Calls from other
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 (0.8)
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.