lawspec 0.7.0 → 0.8.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 CHANGED
@@ -1,27 +1,71 @@
1
- # Compiler API migration: schema 1 → schema 2
1
+ # Compiler API migration: schema 2 → schema 3
2
2
 
3
- LawSpec 0.7.0 responses include `schemaVersion: 2`. Requests may omit the version
4
- or explicitly send `schemaVersion: 2`; unsupported versions receive a request
5
- diagnostic. Existing specification source remains compatible. Consumers of the
6
- JSON AST must migrate together with the compiler.
3
+ LawSpec 0.8 uses API schema version **3**. Requests may omit `schemaVersion` or
4
+ send `3`. An explicit `2` (or any other version) receives a request diagnostic;
5
+ it is never silently reinterpreted. LawSpec specification syntax remains compatible.
6
+ The generated `index.d.ts` describes the public protocol. Internal Haskell
7
+ constructors and record fields are no longer the wire format.
7
8
 
8
- ## Scalar values
9
+ ## Laws and typed expressions
9
10
 
10
- Example bindings and expected values are uniformly tagged. Do not coerce every
11
- numeric value to JavaScript Number.
11
+ Read `result.laws[i].examples` instead of `law.original.examples`. A binding is
12
+ now `{id, name, type, value}` rather than `[name, value]`. `value` retains the
13
+ lossless tagged scalar representation from schema 2. Expected results are typed
14
+ expressions in an assertion, for example `example.expectations[0].right.node.value`.
12
15
 
13
- ```js
14
- // Previously: ["x", 42]
15
- // Now:
16
- ["x", { type: "Int32", value: "42" }]
16
+ Every expression has `{type, origin, text, node}`. `text` is for presentation;
17
+ inspect `node` for semantics. Nodes explicitly distinguish constants, resolved
18
+ locals, declaration calls, arithmetic with capability evidence, short-circuit
19
+ operators, helpers, and conversions. Conversion mode `checked` means an exact
20
+ result must fit its adapter parameter; `explicit` records a source conversion.
21
+ There is no separate `typedExpressions` side table to match against source ASTs.
22
+
23
+ Assertions are the authoritative proposition tree:
24
+
25
+ - `{kind: "equal", evidence, left, right}`
26
+ - `{kind: "implies", guard, body}`
27
+ - `{kind: "all", items}`
28
+
29
+ The `left`, `right`, and `guards` compatibility projections on laws are removed.
30
+ Traverse the tree to preserve shared guard scope and conjunction order. Do not
31
+ flatten guards or turn refinement predicates into implications.
32
+
33
+ Inputs use `{id, name, type, predicates, bounds}`. IDs identify binders;
34
+ display names need not be globally unique. Bounds are derived generation hints
35
+ `{operator, value}` over preceding inputs. Predicates remain authoritative.
36
+ `generationPlan` and `inputRefinements` are replaced by these explicit fields.
37
+ Contracts expose typed argument/result binders, preconditions, and postconditions.
38
+ Refinement declarations expose documented parameter kinds, requirements, and a
39
+ printed definition; they are not serialized source ASTs.
40
+
41
+ ## Types, identity, and locations
17
42
 
18
- // Lossless unsigned 64-bit example:
19
- { type: "UInt64", value: "18446744073709551615" }
43
+ Types are discriminated views, not `tag`/`contents` encodings:
44
+
45
+ ```js
46
+ { kind: "constructor", name: "Int8", arguments: [] }
47
+ { kind: "constructor", name: "Optional", arguments: [
48
+ { kind: "type", type: { kind: "constructor", name: "Int8", arguments: [] } }
49
+ ] }
20
50
  ```
21
51
 
52
+ Function types use `{kind: "function", parameter, result}`; type variables use
53
+ `{kind: "variable", id}`. The argument model distinguishes types, natural indices
54
+ (encoded as decimal strings), and index variables. This representation does not
55
+ make unimplemented containers or dependent families available in 0.8.
56
+
57
+ Declaration/property/binder IDs remain stable when unrelated laws are inserted.
58
+ Origins are either `{kind: "source", span: {start, end}}` or
59
+ `{kind: "generated", declaration}`. Source ranges come from parsing, including
60
+ expressions in reused laws and refinements. Generated nodes identify their owner
61
+ instead of inventing source coordinates. Positions use one-based lines/columns;
62
+ end positions are exclusive and can include trailing parser whitespace.
63
+
64
+ ## Scalar values remain lossless
65
+
22
66
  | Domain | Payload after `type` |
23
67
  | --- | --- |
24
- | All integers | `value`: decimal string |
68
+ | Integers, including logical Integer | `value`: decimal string |
25
69
  | Bool | `value`: Boolean |
26
70
  | Decimal | `coefficient`, `exponent`: decimal strings |
27
71
  | Rational | `numerator`, `denominator`: decimal strings; reduced, denominator positive |
@@ -33,53 +77,27 @@ numeric value to JavaScript Number.
33
77
  | Unit / Null / Undefined | No payload |
34
78
  | Nullable / Optional | `value`: null for missing, otherwise a tagged scalar |
35
79
 
36
- Text is also encoded as units, making the scalar schema uniform. Raw surrogate
37
- code points never travel through JSON strings. The enclosing input type supplies
38
- the inner type of a missing presence value.
39
-
40
- `Expr.Number.contents` is now a decimal **string**. New expression nodes are
41
- `DecimalNumber` (coefficient/exponent strings), `ScalarLit`, `Binary`, `Unary`, and `Annotate`. `Type.Applied` represents Nullable
42
- and Optional. Expanded laws include `typedExpressions`, with operation operand
43
- and result types plus an explicit `requiredConversion` for checked adapter bridges. Assertion trees remain authoritative;
44
- `left`, `right`, and `guards` remain compatibility projections.
45
-
46
- ## Profiles and artifacts
47
-
48
- Requests accept `machineBits?: 32 | 64`, defaulting to 64. Successful responses
49
- include the selected `machineBits`. A profile controls language domains, not the
50
- architecture of the WASM compiler itself.
51
-
52
- Artifacts now include `placement: "source" | "test"` independently of
53
- `ownership: "user" | "generated"`. A generated runtime belongs in a source
54
- directory. Do not infer its placement from generated ownership. Continue using
55
- the manifest writer to protect edited generated files and preserve user adapters.
56
-
57
- The generated `index.d.ts` declares the complete discriminated unions. CLI
58
- `explain` prints the new scalar values without converting large integers to
59
- floating point. Native and WASM dispatch expose the same schema.
60
-
61
-
62
- ## Refinements in the unreleased 0.7.0 schema
63
-
64
- API v2 also carries parameterized refinements and executable contracts. `Integer`
65
- is a logical integer scalar tag whose `value` is a decimal string. Default integer
66
- literals and promoted integer operations now have logical type `Integer`; explicit
67
- `BigInt` declarations retain their native mappings. Both tags preserve exact values.
68
-
69
- `Law.requirements` contains `Capability` nodes (`Eq`, `Integer`, `Ordered`,
70
- `Bounded`) with their target types. Types add `Refined`, `RefinementApp`,
71
- `Qualified`, and `CheckedType` nodes. Refinement arguments explicitly distinguish
72
- `TypeArgument` from `ValueArgument`; declaration parameter kind `Type` is not a
73
- runtime scalar. Expressions add `TypeBound` and logical operators.
74
-
75
- Responses expose unit-owned `refinements` and `contracts`. Each expanded property
76
- has `propertyKind` (`law` or `contract`), `generation` settings, and a
77
- `generationPlan`. Inputs retain their underlying `inputType` and carry separate
78
- `inputRefinements`. Domain plans include derived comparison bounds; the complete
79
- predicate remains authoritative. Refinements must not be interpreted as implication
80
- guards or as permission to count rejected inputs as successful checks.
81
-
82
- Requests accept partial `generation` settings (`cases`, `maxAttempts`,
83
- `maxShrinks`, `exhaustiveLimit`). Project configuration accepts the same object.
84
- Generated TypeScript declarations define the complete wire shapes. These additions
85
- are included in schema v2 before its release; schema v1 remains unsupported.
80
+ Do not coerce integer strings to JavaScript Number. Text uses code units or code
81
+ points as appropriate to its domain; raw surrogates never pass through JSON
82
+ strings. The enclosing type supplies the inner type of a missing presence value.
83
+
84
+ ## Checking, generation, and artifacts
85
+
86
+ `check` and `expand` validate the language. `planGeneration` additionally checks
87
+ whether its input domains can be executed. For example, a well-typed empty finite
88
+ refinement can pass `check` and fail generation with an empty-domain diagnostic.
89
+ Exhausted refinement searches fail explicitly; rejected inputs do not count as
90
+ successful tests.
91
+
92
+ Requests retain `machineBits?: 32 | 64` (default 64) and partial `generation`
93
+ settings (`cases`, `maxAttempts`, `maxShrinks`, `exhaustiveLimit`). Rust joins the
94
+ seven existing targets. Artifacts retain separate `ownership` and `placement`:
95
+ generated runtime source belongs in source directories, adapters remain
96
+ user-owned, and test helpers belong in test directories. Never infer placement
97
+ from ownership or assume a fixed number of generated files. Continue using the
98
+ manifest writer to protect edited files.
99
+
100
+ All nonempty test plans now emit the portable scalar runtime. Existing Haskell
101
+ projects must include `text` and `bytestring` in the component that compiles
102
+ that source. Doctor reports the missing dependencies before generation. Build
103
+ files remain user-owned; new scaffolds already include these dependencies.
package/LANGUAGE.md ADDED
@@ -0,0 +1,136 @@
1
+ # LawSpec language and compiler boundary (0.8)
2
+
3
+ LawSpec describes portable laws, concrete examples, and adapter contracts. The
4
+ compiler is written in Haskell. Rust is an output backend alongside Java, Python,
5
+ JavaScript, TypeScript, Go, Haskell, and Kotlin.
6
+
7
+ ## Source and declarations
8
+
9
+ A source has one named `unit`, function signatures, reusable refinements, and
10
+ laws. Qualified unit names determine target module/package paths. Function
11
+ signatures are curried: `a -> b -> c` takes two inputs and returns `c`. Parentheses
12
+ group types and expressions. Comments start with `--` and run to the line end.
13
+
14
+ The following grammar summarizes the main forms; the parser and executable
15
+ compiler tests specify lexical details:
16
+
17
+ ```text
18
+ source = "unit" qualified-name declaration*
19
+ declaration = name "::" type | law | refinement
20
+ type = type-atom ["->" type]
21
+ type-atom = primitive | type-variable | "(" type ")"
22
+ | ("Nullable" | "Optional") type-atom
23
+ | refinement-name argument*
24
+ | "(" name "::" type ["where" expression] ")"
25
+ refinement = "refinement" name parameter* requirements?
26
+ "is" type "end"
27
+ parameter = "(" name "::" type ["where" expression] ")"
28
+ requirements = "requires" (capability type)+
29
+ capability = "Eq" | "Integer" | "Ordered" | "Bounded"
30
+ law = "law" quoted-name parameter* requirements? "is"
31
+ "definition" "is" proposition "end"
32
+ description? rationale? example* references? "end"
33
+ proposition = "`for all`" parameter+ "." proposition
34
+ | expression "=" expression
35
+ | expression "implies" proposition
36
+ | proposition "and" proposition
37
+ | quoted-name expression* | expression
38
+ example = "example" quoted-name "is" (name "=" literal)+
39
+ ("expect" expression "=" literal)+ "end"
40
+ ```
41
+
42
+ Law names use backticks. Text/metadata use double quotes with escapes. Named
43
+ refinement declarations state their type versus value parameter kinds, including
44
+ forward references; the compiler validates their arity and argument kinds.
45
+ Lowercase type names represent variables. `a :: Type` is a type parameter, not a
46
+ runtime value with a generator.
47
+
48
+ A generic law is specialized when invoked with concrete adapters. Its declared
49
+ capabilities must justify its operations; specialization resolves those
50
+ requirements for the actual types. `Integer` as a capability requires an integer
51
+ type. `Integer` in a concrete result signature denotes a representation-independent
52
+ mathematical integer. See [refinements and abstract integers](REFINEMENTS.md).
53
+
54
+ ## Expressions and arithmetic
55
+
56
+ Application binds most tightly. The remaining precedence, highest first, is:
57
+ unary `!`/`-`, composition `.`, multiplication/division, addition/subtraction,
58
+ comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`), `&&`, then `||`.
59
+ Comparisons do not chain. Arithmetic associates left; composition associates
60
+ right. An adjacent numeric sign remains part of an argument: `f -42` applies `f`
61
+ to negative 42. Write `x - 42` for subtraction.
62
+
63
+ Assertion `=` differs from Boolean `==`. `implies` guards its following
64
+ proposition; `and` requires both assertions. Parentheses determine the scope of a
65
+ shared guard. Both Boolean operators and implications short-circuit.
66
+
67
+ Literals acquire types from declared context. Unconstrained integers have type
68
+ `Integer`; decimal tokens have type `Decimal`. An annotation such as
69
+ `(127 :: Int8)` specifies context. A declared float context can type a decimal
70
+ token directly, but an explicitly constructed exact Decimal is not implicitly
71
+ converted into a float.
72
+
73
+ Integer `+`, `-`, `*`, and negation produce exact `Integer` results. Decimal
74
+ dominates integer/Decimal combinations; Rational dominates exact combinations
75
+ involving Rational. Exact `/` returns Rational. `prelude.quot` truncates integer
76
+ quotients toward zero; `prelude.rem` is the associated remainder. Division by
77
+ zero fails when evaluated. Explicit numeric conversions use `prelude.Type`.
78
+ Exact/inexact mixing otherwise fails type checking. IEEE arithmetic widens float
79
+ or complex precision as required; equality treats NaN as unequal and signed
80
+ zero as equal. Decimal rounding is explicit, with a scale and ties-to-even rule.
81
+
82
+ Passing a computed exact result to a bounded adapter argument performs a checked
83
+ conversion. It never wraps, truncates a fraction, or silently changes precision.
84
+ Adapter results are validated against the declared domain before use. See the
85
+ [primitive reference](PRIMITIVES.md) for all domains and helpers.
86
+
87
+ ## Refinements and executable contracts
88
+
89
+ Refinements are pure Boolean predicates over a value and preceding binders.
90
+ They cannot call user adapters. Dependent inputs are generated in order;
91
+ shrinking preserves their predicates. Bounds such as `y > Int8.max - x` are
92
+ computed with exact arithmetic and can drive a dependent generator. If a chosen
93
+ `x` has no possible `y`, generation retries earlier inputs. Search exhaustion
94
+ fails explicitly rather than passing a property with no valid cases.
95
+
96
+ Contracts check preconditions, evaluate an adapter once, validate the result,
97
+ and then check postconditions against that same result. Predicate errors are
98
+ contextual failures, not rejected samples. Small finite domains are enumerated;
99
+ other domains use target property frameworks. `machineBits: 32 | 64` controls
100
+ machine-integer domains independently of the compiler host architecture. Native
101
+ machine-sized adapter bindings additionally verify the executing architecture.
102
+
103
+ ## Compiler stages and public API
104
+
105
+ The parser retains source ranges. Resolution and inference check names, kinds,
106
+ capabilities, contextual literals, and generic specializations. Elaboration
107
+ produces typed core expressions with resolved declaration/binder IDs, explicit
108
+ arithmetic evidence and conversions, plus an authoritative proposition tree.
109
+ Refinement declarations become predicates on quantifiers and contracts; targets
110
+ do not interpret refinement syntax.
111
+
112
+ An independent core validator checks scopes, kinds, operand/result types,
113
+ capability evidence, and conversions. A pure core evaluator supports deterministic
114
+ domain checks and reference tests. The testing planner computes finite cases,
115
+ boundaries, and dependent generator requirements. All eight emitters consume
116
+ that plan and the core, without importing source syntax or inference.
117
+
118
+ `check`/`expand` report semantic validity. `planGeneration` additionally reports
119
+ execution feasibility, including empty finite domains. Source syntax errors,
120
+ semantic errors, core invariant failures, and generation errors have distinct
121
+ diagnostic codes. Runtime failures identify the law/example or adapter contract.
122
+ Parsed expressions retain real source ranges; synthesized expressions identify
123
+ the declaration that caused their creation.
124
+
125
+ API schema v3 uses separately defined wire views, with lossless tagged scalar
126
+ values. It does not serialize internal AST constructors. See the
127
+ [API migration guide](API-MIGRATION.md).
128
+
129
+ ## Next language features
130
+
131
+ `List`, algebraic `Maybe`/`Either`, user-defined sums and products, pattern
132
+ matching, GADTs, and general dependent types are outside 0.8. The core type model
133
+ supports arbitrary constructor arity and distinguishes type and index arguments
134
+ so those features can be added without target-specific surface interpretation.
135
+ `Nullable` and `Optional` retain their interoperability semantics; they do not
136
+ stand in for future algebraic sum types.
package/PRIMITIVES.md CHANGED
@@ -1,4 +1,4 @@
1
- # LawSpec scalar reference (0.7.0)
1
+ # LawSpec scalar reference (0.8.0)
2
2
 
3
3
  A scalar has a declared domain, checked literals, equality, property inputs, and
4
4
  boundary fixtures. General collections, objects, pointers, and type-only constructs
@@ -142,7 +142,7 @@ portable across architectures.
142
142
  The bundled `scalars.lawspec`, `scalar_catalog.lawspec`, and
143
143
  `scalar_adapters.lawspec` contain explicit fixtures for every scalar family.
144
144
  `tools/scalar-reference.py` produces independent Fraction-based conformance
145
- vectors; `tools/scalar-integration.mjs` executes them across the seven targets.
145
+ vectors; `tools/scalar-integration.mjs` executes them across the eight targets.
146
146
  Set `LAWSPEC_MUTANTS=1` to also verify that incorrect adapters are detected.
147
147
 
148
148
 
package/README.md CHANGED
@@ -2,19 +2,24 @@
2
2
 
3
3
  **State the law once. Check it everywhere.**
4
4
 
5
- LawSpec 0.6 compiles reusable laws into native property tests, executable examples,
5
+ LawSpec 0.8 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
- ## Portable scalars in 0.7.0
9
+ ## Rust and the typed front end in 0.8.0
10
10
 
11
- LawSpec now supports fixed and arbitrary integers, exact decimals and rationals,
11
+ Rust joins Java, Python, JavaScript, TypeScript, Go, Haskell, and Kotlin. All
12
+ eight backends consume the same typed core and testing plan. Source expressions
13
+ retain their ranges, resolved identities, arithmetic evidence, and checked
14
+ conversions. See the [Rust guide](RUST.md) and [language reference](LANGUAGE.md).
15
+
16
+ The scalar catalog includes fixed and arbitrary integers, exact decimals and rationals,
12
17
  IEEE floats and complex values, Unicode and raw-text domains, bytes, symbols,
13
- and nested absence states on all seven targets. Integer arithmetic produces representation-independent
18
+ and nested absence states on all eight targets. Integer arithmetic produces representation-independent
14
19
  `Integer` values; exact division returns Rational. See the [primitive reference](PRIMITIVES.md)
15
20
  for constructors, arithmetic, native bridges, and machine-width profiles.
16
21
 
17
- Compiler API consumers should read the [schema v2 migration guide](API-MIGRATION.md).
22
+ Compiler API consumers should read the [schema v3 migration guide](API-MIGRATION.md).
18
23
  Scalar values use tagged, lossless encodings; generated runtime placement is
19
24
  separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchanged.
20
25
 
@@ -23,7 +28,7 @@ separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchan
23
28
  Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
24
29
 
25
30
  ```sh
26
- npm install --save-dev lawspec@0.7.0
31
+ npm install --save-dev lawspec@0.8.0
27
32
  npx lawspec --version
28
33
  ```
29
34
 
@@ -37,8 +42,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
37
42
  ```sh
38
43
  mkdir lawspec-example
39
44
  cd lawspec-example
40
- npm exec --package=lawspec@0.7.0 -- lawspec init --target javascript
41
- npm install --save-dev lawspec@0.7.0
45
+ npm exec --package=lawspec@0.8.0 -- lawspec init --target javascript
46
+ npm install --save-dev lawspec@0.8.0
42
47
  npx lawspec check
43
48
  npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
44
49
  npx lawspec doctor
@@ -71,9 +76,10 @@ properties with the selected framework's shrinking and failure reporting.
71
76
  | `go` | Go modules, Go 1.22–1.26 | Rapid 1.2.0, testing | `go test ./...` |
72
77
  | `haskell` | Stack, GHC 9.10, LTS 24.58 | Hspec 2.11, Hedgehog 1.5, hspec-hedgehog 0.3 | `stack test` |
73
78
  | `kotlin` | JDK/JVM 25, Gradle 9.1–9.3, Kotlin 2.3.21 | Kotest 5.9.1 | `gradle test` |
79
+ | `rust` | Rust 1.85+, edition 2024, Cargo | Proptest 1.11.0 | `cargo test` |
74
80
 
75
81
  Java 25 and Python 3.13 are the minimum baselines. New JVM releases are admitted
76
- through compatibility profiles after testing; v0.6's current JVM profile certifies
82
+ through compatibility profiles after testing; the current JVM profile certifies
77
83
  25. Python templates declare `requires-python = ">=3.13"` and runtime checks
78
84
  currently recognize 3.13 and 3.14. Kotlin templates pin Gradle's supported build
79
85
  configuration to Kotlin 2.3.21 and target JVM 25.
@@ -244,7 +250,7 @@ reusable laws accept these curried functions, their partial applications, and
244
250
  scalar parameters. Quantified test inputs remain scalar.
245
251
 
246
252
  The prelude defines the following laws. Every row has an executable example in
247
- [algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/algebra.lawspec), including both sides of every
253
+ [algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/algebra.lawspec), including both sides of every
248
254
  combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
249
255
  `zero` are scalar parameters. All these laws require equality of the element type.
250
256
 
@@ -280,10 +286,10 @@ explicit domain predicate and conditional equations.
280
286
  `invertible` checks the supplied inverse operation. Check `identity` and
281
287
  `associative` as well when specifying a group. The prelude states contracts;
282
288
  it does not supply arithmetic implementations or prove a structure from random
283
- tests. The numeric examples use Int32 arithmetic modulo 2^32 so their laws hold
284
- at overflow boundaries on every target. JavaScript uses `Math.imul` for products,
285
- and Python explicitly wraps results into the signed Int32 range in the test
286
- adapters.
289
+ tests. The algebra and numeric currying examples use mathematical `Integer`
290
+ values and exact native arithmetic. Explicit expectations exceed Int32 and
291
+ machine bounds, so wrapping or lossy adapters fail even when their modular
292
+ arithmetic happens to satisfy the algebraic identities.
287
293
 
288
294
  Use `and` to require multiple conclusions in one law. For example:
289
295
 
@@ -312,11 +318,11 @@ their entire consequence. Every conjunct is type-checked and emitted. As with
312
318
  existing assertions, the first failure stops that individual test. `and` is now
313
319
  a reserved word.
314
320
 
315
- [Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/currying.lawspec) demonstrate a four-argument
321
+ [Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/currying.lawspec) demonstrate a four-argument
316
322
  function partially applied twice, a formatter with four heterogeneous arguments,
317
323
  and composition after partial application. Each example states its exact outputs.
318
324
  Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
319
- in all seven target languages (126 artifacts).
325
+ in all eight target languages.
320
326
 
321
327
  The expanded API's **`assertion` tree is authoritative**: `AssertEqual` contains
322
328
  two expressions, `AssertImplies` contains a condition and consequence, and
@@ -364,7 +370,7 @@ law `valid ports round trip` is
364
370
  end
365
371
  ```
366
372
 
367
- The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/parse_port.lawspec)
373
+ The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/parse_port.lawspec)
368
374
  defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
369
375
  negative values, and 65536. All explicit `expect` assertions run regardless of
370
376
  the law's condition. A false condition skips only the consequence: invalid ports
@@ -382,7 +388,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
382
388
  and `left inverse when predicate parse render` (the guarded round trip above).
383
389
  These reusable laws preserve the condition and its lexical bindings when expanded.
384
390
  `equivalent` can also compare two predicates, since `Bool` supports equality.
385
- The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/boolean_flags.lawspec)
391
+ The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/boolean_flags.lawspec)
386
392
  checks that flipping twice restores both `false` and `true`; all targets generate
387
393
  Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
388
394
  possible input combinations (up to 100), avoiding generator exhaustion.
@@ -461,7 +467,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
461
467
  The example inherits the input name `x` from the prelude. Both functions are
462
468
  user-owned adapter functions; either may delegate to your existing code.
463
469
 
464
- [The complete example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/equivalent.lawspec) compares decimal
470
+ [The complete example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/equivalent.lawspec) compares decimal
465
471
  renderers and two implementations that clamp negative integers to zero. For
466
472
  JavaScript, their adapters can be:
467
473
 
@@ -472,7 +478,7 @@ export const clamp = x => Math.max(0, x);
472
478
  export const referenceClamp = x => x < 0 ? 0 : x;
473
479
  ```
474
480
 
475
- The same specification generates native tests for all seven targets. The
481
+ The same specification generates native tests for all eight targets. The
476
482
  integration suite checks both examples with matching implementations, then
477
483
  breaks each alternative separately to verify detection. The general equivalence law alone does not establish independent correctness;
478
484
  two implementations can share the same bug. Explicit expectations additionally
@@ -498,7 +504,7 @@ law `normalizers agree` is
498
504
  end
499
505
  ```
500
506
 
501
- The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/slug.lawspec)
507
+ The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/slug.lawspec)
502
508
  compares two implementations of ASCII-space replacement. It includes empty,
503
509
  Unicode and escaped text. Each target uses its native string generator:
504
510
  JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
@@ -525,7 +531,7 @@ law `canonicalization reaches a fixed point` is
525
531
  end
526
532
  ```
527
533
 
528
- The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/canonical_url.lawspec)
534
+ The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/canonical_url.lawspec)
529
535
  uses removal of **all trailing slashes** as a small fixed-point demonstration,
530
536
  not a complete URL canonicalization algorithm. For JavaScript:
531
537
 
@@ -534,7 +540,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
534
540
  ```
535
541
 
536
542
  Removing just one trailing slash fails the supplied repeated-slash example.
537
- The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.7.0/examples/specs/mixed_inputs.lawspec)
543
+ The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.8.0/examples/specs/mixed_inputs.lawspec)
538
544
  shows `Text` and `Int32` in the same quantified property and executable example.
539
545
  The JavaScript API represents input bindings and expected values as `number | string | boolean`.
540
546
  Each example includes `expectations: { actual: Expr; expected: number | string | boolean }[]`.
@@ -550,7 +556,7 @@ npx lawspec examples --target java --output example_artifacts
550
556
  This command works without a project configuration or native build tools. It
551
557
  compiles every bundled example and writes its tests and user-owned stubs to
552
558
  `example_artifacts/<language>/`, using each target's normal source/test layout.
553
- By default it exports all seven languages; `--json` returns the file inventory.
559
+ By default it exports all eight languages; `--json` returns the file inventory.
554
560
  From a checkout, `make examples` runs the same command.
555
561
 
556
562
  These are inspection artifacts, not initialized projects: no build files are
@@ -599,7 +605,7 @@ by the JS shim.
599
605
  ## Build and verify
600
606
 
601
607
  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.7.0.tgz`.
608
+ `npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.8.0.tgz`.
603
609
  The package payload lives in `npm/`.
604
610
 
605
611
  ```sh
@@ -623,7 +629,7 @@ compiler-source and artifact hashes in `npm/build.json`; CI rejects stale WASM
623
629
  or hand-edited generated wrappers. The npm archive is a self-contained consumer
624
630
  artifact, with no install-time compilation or download hook.
625
631
 
626
- For all seven native integrations, install their build tools, then:
632
+ For all eight native integrations, install their build tools, then:
627
633
 
628
634
  ```sh
629
635
  # 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.7.0)
1
+ # Refinements and abstract integers (0.8.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
package/RUST.md ADDED
@@ -0,0 +1,97 @@
1
+ # Rust backend (0.8)
2
+
3
+ Rust is the first backend built on LawSpec's typed core and testing plan. The
4
+ compiler remains implemented in Haskell. Rust generation does not parse source
5
+ expressions, infer their types, or expand refinement declarations.
6
+
7
+ ## Project setup
8
+
9
+ Use Rust 1.85 or later, edition 2024, and Cargo. The generated project pins
10
+ Proptest 1.11.0, num-bigint 0.4.8, num-rational 0.4.2, num-complex 0.4.6, and
11
+ num-traits 0.2.19.
12
+
13
+ ```sh
14
+ lawspec init --target rust
15
+ lawspec doctor
16
+ lawspec generate
17
+ cargo test
18
+ cargo test --release
19
+ ```
20
+
21
+ Adapters are user-owned files under `src/`. LawSpec maintains
22
+ `lawspec_runtime.rs`, module declarations in `lawspec_modules.rs`, and tests
23
+ under `tests/`. The scaffolded `src/lib.rs` includes the generated declarations;
24
+ an existing library can include those declarations explicitly. Generation
25
+ preserves edited adapters and reports required signature updates.
26
+
27
+ The numeric runtime has no Proptest dependency. Framework support lives in a
28
+ separate generated file, `tests/support/lawspec_strategies.rs`.
29
+
30
+ ## Owned adapter values
31
+
32
+ Arguments and results are owned Rust values. LawSpec does not add borrowing,
33
+ lifetimes, or pointer operations to its language. Tests clone values where an
34
+ expression needs to use an input more than once.
35
+
36
+ | LawSpec domain | Rust adapter representation |
37
+ | --- | --- |
38
+ | `Bool` | `bool` |
39
+ | Fixed signed/unsigned integers | `i8`…`i64`, `u8`…`u64` |
40
+ | `IntSize`, `UIntSize`, `UIntPtr` | `isize`, `usize`, `usize` |
41
+ | `BigInt`, `BigUInt` | `BigInt`, `BigUint` |
42
+ | `Integer` input | `BigInt` |
43
+ | `Integer` result | `Integer`, with lossless `From` implementations |
44
+ | `Decimal`, `Rational` | Generated `Decimal`, `BigRational` |
45
+ | `Float32`, `Float64` | `f32`, `f64` |
46
+ | `Complex64`, `Complex128` | `Complex32`, `Complex64` from num-complex |
47
+ | `Char`, `Text` | `char`, `String` |
48
+ | `CodePoint`, `CodePointText` | Generated checked wrappers |
49
+ | `CodeUnit16`, `Utf16Text`, `Bytes` | `u16`, generated UTF-16 wrapper, `Vec<u8>` |
50
+ | `Symbol` | Generated identity type; cloning preserves identity |
51
+ | `Unit`, `Null`, `Undefined` | `()`, distinct generated absence types |
52
+ | `Nullable a`, `Optional a` | Distinct generated enums, including nested presence |
53
+
54
+ Names such as `Integer` and `Decimal` above are exported by the generated
55
+ `lawspec_runtime` module. Machine-sized native bindings check the requested
56
+ `machineBits` against the executing architecture.
57
+
58
+ For a declaration such as `successor :: Int8 -> Integer`, an implementation can
59
+ return a wider native integer through the logical result wrapper:
60
+
61
+ ```rust
62
+ use crate::lawspec_runtime as ls;
63
+
64
+ pub fn successor(value: i8) -> ls::Integer {
65
+ (i16::from(value) + 1).into()
66
+ }
67
+ ```
68
+
69
+ Exact arithmetic in laws uses arbitrary precision. Passing its result to an
70
+ `i8` adapter parameter performs a checked conversion. A fractional or
71
+ out-of-range value fails with the adapter's context.
72
+
73
+ `Decimal` stores an arbitrary integer coefficient and exponent. Decimal
74
+ arithmetic is exact; explicit rounding uses the requested scale and ties to
75
+ even. Exact-to-float conversion rounds directly to the requested IEEE precision,
76
+ including subnormal and halfway cases.
77
+
78
+ ## Refinements and generation
79
+
80
+ Small finite domains are enumerated. Larger domains use native Proptest
81
+ strategies. Integer bounds over earlier inputs become dependent strategies;
82
+ when a prefix has no possible continuation, generation retries the prefix.
83
+ Shrinking recomputes those dependent bounds and retains only valid tuples.
84
+ Refinement evaluation errors fail the test rather than being counted as rejected
85
+ samples. Generation limits prevent an empty or unreachable domain from passing
86
+ vacuously.
87
+
88
+ Contracts check preconditions, evaluate the adapter once, validate its native
89
+ result, and then check postconditions on that result.
90
+
91
+ ## Custom layouts
92
+
93
+ `sourceDir` and `testDir` move generated sources and imports together. Set Cargo's
94
+ `[lib] path` to the library entry point under the selected source directory. For
95
+ test directories other than `tests`, register generated test files using Cargo
96
+ `[[test]]` entries. Doctor checks the selected Cargo package, edition, resolved
97
+ dependencies, library directory, and custom test registration.
package/api.mjs CHANGED
@@ -2,5 +2,5 @@
2
2
  import { loadCore } from './launcher.mjs';
3
3
  export async function createCompiler() {
4
4
  const call = await loadCore();
5
- return { check: (input) => call({ ...input, method: 'check' }), expand: (input) => call({ ...input, method: 'expand' }), planGeneration: (input) => call({ ...input, method: 'planGeneration' }) };
5
+ return { check: (input) => call({ schemaVersion: 3, ...input, method: 'check' }), expand: (input) => call({ schemaVersion: 3, ...input, method: 'expand' }), planGeneration: (input) => call({ schemaVersion: 3, ...input, method: 'planGeneration' }) };
6
6
  }