lawspec 0.9.0 → 0.11.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 +78 -2
- package/HASKELL.md +1 -0
- package/KOTLIN.md +5 -0
- package/LANGUAGE.md +70 -11
- package/NATIVE-BINDINGS.md +1049 -0
- package/PRIMITIVES.md +1 -1
- package/README.md +86 -23
- package/REFINEMENTS.md +6 -1
- package/RELEASE-0.10.md +60 -0
- package/RELEASE-0.11.md +62 -0
- package/api.mjs +23 -3
- package/bin/lawspec.mjs +17 -4
- package/build.json +60 -38
- package/core.wasm +0 -0
- package/examples/native-payments/go/example/payments/domain.go +38 -0
- package/examples/native-payments/go/example/payments/native_generators_test.go +13 -0
- package/examples/native-payments/go/lawspec.json +136 -0
- package/examples/native-payments/haskell/lawspec.json +149 -0
- package/examples/native-payments/haskell/src/PaymentsDomain.hs +21 -0
- package/examples/native-payments/haskell/test/PaymentGenerators.hs +12 -0
- package/examples/native-payments/java/lawspec.json +165 -0
- package/examples/native-payments/java/src/main/java/domain/PaymentsDomain.java +32 -0
- package/examples/native-payments/java/src/test/java/domain/PaymentGenerators.java +16 -0
- package/examples/native-payments/javascript/lawspec.json +149 -0
- package/examples/native-payments/javascript/src/payments_domain.mjs +44 -0
- package/examples/native-payments/javascript/test/lawspec_generators.mjs +6 -0
- package/examples/native-payments/kotlin/lawspec.json +165 -0
- package/examples/native-payments/kotlin/src/main/kotlin/domain/PaymentsDomain.kt +17 -0
- package/examples/native-payments/kotlin/src/test/kotlin/domain/PaymentGenerators.kt +12 -0
- package/examples/native-payments/python/lawspec.json +149 -0
- package/examples/native-payments/python/src/payments_domain.py +60 -0
- package/examples/native-payments/python/tests/lawspec_generators.py +15 -0
- package/examples/native-payments/rust/lawspec.json +167 -0
- package/examples/native-payments/rust/src/domain.rs +37 -0
- package/examples/native-payments/rust/src/lib.rs +2 -0
- package/examples/native-payments/rust/tests/support/lawspec_generators.rs +11 -0
- package/examples/native-payments/typescript/lawspec.json +149 -0
- package/examples/native-payments/typescript/src/payments_domain.ts +42 -0
- package/examples/native-payments/typescript/test/lawspec_generators.ts +8 -0
- package/examples/specs/indexed_families.lawspec +59 -0
- package/examples/specs/payments.lawspec +76 -0
- package/examples-command.mjs +5 -0
- package/files.mjs +6 -6
- package/index.d.ts +53 -2
- package/native-examples.mjs +81 -0
- package/package.json +2 -2
package/PRIMITIVES.md
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.11 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
|
|
|
@@ -36,12 +36,40 @@ Compiler API consumers should read the [schema v3 migration guide](API-MIGRATION
|
|
|
36
36
|
Scalar values use tagged, lossless encodings; generated runtime placement is
|
|
37
37
|
separate from artifact ownership. Java 25+ and Python 3.13+ baselines are unchanged.
|
|
38
38
|
|
|
39
|
+
## Native domain bindings in 0.10
|
|
40
|
+
|
|
41
|
+
LawSpec 0.10 adds configuration for existing application products and sums,
|
|
42
|
+
function bindings, checked conversion hooks, and native property-generator
|
|
43
|
+
factories on all eight targets. Generators retain their framework's shrinkers;
|
|
44
|
+
explicit examples, boundaries, and finite-domain checks remain independent.
|
|
45
|
+
See the [native-binding reference and acceptance status](NATIVE-BINDINGS.md),
|
|
46
|
+
the [release notes](RELEASE-0.10.md), and
|
|
47
|
+
[schema 4 migration notes](API-MIGRATION.md#schema-4-native-bindings).
|
|
48
|
+
Existing specifications without bindings retain their behavior.
|
|
49
|
+
|
|
50
|
+
## Natural-indexed families in 0.11
|
|
51
|
+
|
|
52
|
+
LawSpec 0.11 adds data indexed by natural numbers, such as length-indexed
|
|
53
|
+
vectors and size-indexed trees. Each constructor states its index equation;
|
|
54
|
+
the compiler elaborates the family to ordinary native data, a checked measure,
|
|
55
|
+
and a refinement. Dependent signatures like
|
|
56
|
+
`append :: (xs :: Vec n a) -> (ys :: Vec m a) -> (r :: Vec (n + m) a)` become
|
|
57
|
+
checked adapter contracts, and generators construct values with a required
|
|
58
|
+
index instead of filtering for it. See the
|
|
59
|
+
[language reference](LANGUAGE.md#natural-indexed-families), the
|
|
60
|
+
[indexed example](examples/specs/indexed_families.lawspec), and the
|
|
61
|
+
[release notes](RELEASE-0.11.md).
|
|
62
|
+
|
|
39
63
|
## Install and try it
|
|
40
64
|
|
|
65
|
+
LawSpec has its own [syntax-highlighting grammar and VS Code extension](editors/vscode/README.md)
|
|
66
|
+
for keywords, types, refinements, literals, and law names. GitHub needs upstream Linguist
|
|
67
|
+
support to use it; GitHub currently uses the Haskell fallback.
|
|
68
|
+
|
|
41
69
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
42
70
|
|
|
43
71
|
```sh
|
|
44
|
-
npm install --save-dev lawspec@0.
|
|
72
|
+
npm install --save-dev lawspec@0.11.0
|
|
45
73
|
npx lawspec --version
|
|
46
74
|
```
|
|
47
75
|
|
|
@@ -55,8 +83,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
55
83
|
```sh
|
|
56
84
|
mkdir lawspec-example
|
|
57
85
|
cd lawspec-example
|
|
58
|
-
npm exec --package=lawspec@0.
|
|
59
|
-
npm install --save-dev lawspec@0.
|
|
86
|
+
npm exec --package=lawspec@0.11.0 -- lawspec init --target javascript
|
|
87
|
+
npm install --save-dev lawspec@0.11.0
|
|
60
88
|
npx lawspec check
|
|
61
89
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
62
90
|
npx lawspec doctor
|
|
@@ -192,11 +220,13 @@ Reusable laws can declare typed unary function parameters and `requires Eq a`, n
|
|
|
192
220
|
[parameterized refinements and executable contracts](REFINEMENTS.md).
|
|
193
221
|
Definitions support law application, function application/composition, universal
|
|
194
222
|
quantification, `implies`, Boolean predicates, scalar literals, and equality.
|
|
195
|
-
Function signatures and quantified inputs support the [scalar catalog](PRIMITIVES.md)
|
|
196
|
-
including mixed and multiple inputs.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
223
|
+
Function signatures and quantified inputs support the [scalar catalog](PRIMITIVES.md)
|
|
224
|
+
and [structural types](LANGUAGE.md), including mixed and multiple inputs.
|
|
225
|
+
Generic variables are supported in reusable laws. Scalar and structural values
|
|
226
|
+
can also be intermediate or compared results. Functions are synchronous and
|
|
227
|
+
support curried signatures with any positive number of arguments. Text literals
|
|
228
|
+
are double-quoted, with escapes such as `\"`, `\\`, `\n`, and `\t`; examples must
|
|
229
|
+
bind each input to a concrete value of its declared type.
|
|
200
230
|
Text values contain Unicode scalar values; surrogate code points are rejected.
|
|
201
231
|
|
|
202
232
|
Examples refer to the expanded input names, including names inherited from the
|
|
@@ -206,9 +236,10 @@ produce literal braces. Law blocks use this order:
|
|
|
206
236
|
definition, optional description, optional rationale, examples, optional references.
|
|
207
237
|
`--` starts a line comment. Names that cannot be emitted portably are diagnosed.
|
|
208
238
|
|
|
209
|
-
|
|
210
|
-
prelude, async functions
|
|
211
|
-
|
|
239
|
+
Collections beyond `List`, external law packages, cross-unit imports beyond the
|
|
240
|
+
prelude, async functions and browser hosting remain outside the language.
|
|
241
|
+
Direct existing-symbol bindings are part of LawSpec 0.10 described
|
|
242
|
+
above; the published 0.9 release uses implementation adapters.
|
|
212
243
|
|
|
213
244
|
## Algebra and currying (0.6)
|
|
214
245
|
|
|
@@ -263,7 +294,7 @@ reusable laws accept these curried functions, their partial applications, and
|
|
|
263
294
|
scalar parameters. Quantified test inputs remain scalar.
|
|
264
295
|
|
|
265
296
|
The prelude defines the following laws. Every row has an executable example in
|
|
266
|
-
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
297
|
+
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/algebra.lawspec), including both sides of every
|
|
267
298
|
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
268
299
|
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
269
300
|
|
|
@@ -331,7 +362,7 @@ their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
|
331
362
|
existing assertions, the first failure stops that individual test. `and` is now
|
|
332
363
|
a reserved word.
|
|
333
364
|
|
|
334
|
-
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
365
|
+
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/currying.lawspec) demonstrate a four-argument
|
|
335
366
|
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
336
367
|
and composition after partial application. Each example states its exact outputs.
|
|
337
368
|
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
@@ -383,7 +414,7 @@ law `valid ports round trip` is
|
|
|
383
414
|
end
|
|
384
415
|
```
|
|
385
416
|
|
|
386
|
-
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
417
|
+
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/parse_port.lawspec)
|
|
387
418
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
388
419
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
389
420
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -401,7 +432,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
401
432
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
402
433
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
403
434
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
404
|
-
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
435
|
+
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/boolean_flags.lawspec)
|
|
405
436
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
406
437
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
407
438
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -480,7 +511,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
480
511
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
481
512
|
user-owned adapter functions; either may delegate to your existing code.
|
|
482
513
|
|
|
483
|
-
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
514
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/equivalent.lawspec) compares decimal
|
|
484
515
|
renderers and two implementations that clamp negative integers to zero. For
|
|
485
516
|
JavaScript, their adapters can be:
|
|
486
517
|
|
|
@@ -517,7 +548,7 @@ law `normalizers agree` is
|
|
|
517
548
|
end
|
|
518
549
|
```
|
|
519
550
|
|
|
520
|
-
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
551
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/slug.lawspec)
|
|
521
552
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
522
553
|
Unicode and escaped text. Each target uses its native string generator:
|
|
523
554
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -544,7 +575,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
544
575
|
end
|
|
545
576
|
```
|
|
546
577
|
|
|
547
|
-
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
578
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/canonical_url.lawspec)
|
|
548
579
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
549
580
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
550
581
|
|
|
@@ -553,13 +584,31 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
553
584
|
```
|
|
554
585
|
|
|
555
586
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
556
|
-
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
587
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.11.0/examples/specs/mixed_inputs.lawspec)
|
|
557
588
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
558
|
-
The JavaScript API represents
|
|
559
|
-
|
|
589
|
+
The JavaScript API represents example bindings as typed records whose values use
|
|
590
|
+
`DataValue`: lossless tagged scalar payloads or structural constructors. Expected results are
|
|
591
|
+
typed expressions inside `Assertion` trees, not untyped JavaScript primitives.
|
|
592
|
+
See the [API migration guide](API-MIGRATION.md) for their wire representation.
|
|
560
593
|
|
|
561
594
|
## Generate all example artifacts
|
|
562
595
|
|
|
596
|
+
To use native domain bindings, export the runnable payment
|
|
597
|
+
example (omit `--target` to export all eight languages):
|
|
598
|
+
|
|
599
|
+
```sh
|
|
600
|
+
lawspec examples --example payments --target rust --output native_payments
|
|
601
|
+
cd native_payments/rust
|
|
602
|
+
# Install the dependencies listed in README.md, then:
|
|
603
|
+
lawspec check
|
|
604
|
+
lawspec generate
|
|
605
|
+
cargo test
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
These projects include application-owned types, native generators, binding
|
|
609
|
+
configuration, and build files. Re-exporting preserves edits and leaves the
|
|
610
|
+
compiler's generation manifest separate from the example export manifest.
|
|
611
|
+
|
|
563
612
|
```sh
|
|
564
613
|
npx lawspec examples
|
|
565
614
|
# Or select a target and a relative output directory:
|
|
@@ -618,7 +667,7 @@ by the JS shim.
|
|
|
618
667
|
## Build and verify
|
|
619
668
|
|
|
620
669
|
For contributors working from a repository checkout, build a local archive with
|
|
621
|
-
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.
|
|
670
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.11.0.tgz`.
|
|
622
671
|
The package payload lives in `npm/`.
|
|
623
672
|
|
|
624
673
|
```sh
|
|
@@ -657,6 +706,20 @@ unchanged. Arguments select individual targets. `LAWSPEC_PYTHON=3.14` selects th
|
|
|
657
706
|
additional Python reference environment. CI also exercises Node 22/24/26 and packs
|
|
658
707
|
and installs the npm archive. Registry publication is a separate release action.
|
|
659
708
|
|
|
709
|
+
The native-binding acceptance command uses the installed npm archive, public CLI,
|
|
710
|
+
and the selected target's normal test command:
|
|
711
|
+
|
|
712
|
+
```sh
|
|
713
|
+
node tools/native-example-integration.mjs rust
|
|
714
|
+
LAWSPEC_MACHINE_BITS=32 LAWSPEC_MINIFY=1 node tools/native-example-integration.mjs rust
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
Replace `rust` with any supported target. It exports the payment project, verifies
|
|
718
|
+
correct behavior, rejects an incorrect fee, and checks regeneration. Per-command
|
|
719
|
+
logs live in `.artifacts/native-example-integration/`. See the
|
|
720
|
+
[acceptance reference](NATIVE-BINDINGS.md) for dependency overrides and remaining
|
|
721
|
+
verification gates.
|
|
722
|
+
|
|
660
723
|
|
|
661
724
|
Refinement predicates can depend on earlier inputs. Generated tests backtrack from
|
|
662
725
|
impossible prefixes, preserve the domain during shrinking, and validate function
|
package/REFINEMENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Refinements and abstract integers (0.
|
|
1
|
+
# Refinements and abstract integers (0.11.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
|
|
@@ -154,6 +154,11 @@ seeds direct comparison values and boundaries, and checks the complete predicate
|
|
|
154
154
|
Predicates outside that analysis use bounded sampling. Small finite base-domain
|
|
155
155
|
products are enumerated exhaustively, retaining only satisfying tuples.
|
|
156
156
|
|
|
157
|
+
A refinement `m x == e` over declared data, where `m` is a linear structural
|
|
158
|
+
measure (each branch is a constant plus `m` of that branch's fields), is solved
|
|
159
|
+
rather than sampled: values are constructed with measure exactly `e`. Indexed
|
|
160
|
+
families rely on this; see [natural-indexed families](LANGUAGE.md#natural-indexed-families).
|
|
161
|
+
|
|
157
162
|
Shrinking checks refinements again and repairs dependent later inputs when an
|
|
158
163
|
earlier value changes. An overflowing counterexample can shrink to `(1, 127)`;
|
|
159
164
|
it cannot shrink to `(0, 127)` because that pair is outside the domain.
|
package/RELEASE-0.10.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# LawSpec 0.10.0
|
|
2
|
+
|
|
3
|
+
## Existing application models
|
|
4
|
+
|
|
5
|
+
Native bindings connect LawSpec products and sums to existing application types
|
|
6
|
+
on Java, Python, JavaScript, TypeScript, Go, Haskell, Kotlin and Rust. Configure
|
|
7
|
+
constructor and field mappings, bind adapter declarations to existing functions,
|
|
8
|
+
or supply paired conversion hooks for representations requiring custom code.
|
|
9
|
+
Bindings resolve against typed declaration identities; they do not change the
|
|
10
|
+
meaning of arithmetic, equality, definitions or refinements.
|
|
11
|
+
|
|
12
|
+
Checked bridges compose through generic and recursive types and the built-in
|
|
13
|
+
containers. They preserve exact numeric values, Symbol identity and distinct
|
|
14
|
+
absence states. Invalid native inputs, results, generator samples and shrink
|
|
15
|
+
candidates fail with context.
|
|
16
|
+
|
|
17
|
+
## Native generators
|
|
18
|
+
|
|
19
|
+
Factories return their framework's generator: JetCheck, Hypothesis, fast-check,
|
|
20
|
+
Rapid, Hedgehog, Kotest or Proptest. Generic factories receive child generators.
|
|
21
|
+
Composition retains native shrinking. Explicit examples, deterministic boundaries
|
|
22
|
+
and finite-domain enumeration still run when the factory's distribution excludes
|
|
23
|
+
those values.
|
|
24
|
+
|
|
25
|
+
A factory can ignore an uninhabited parameter, as in `Phantom Empty`. Requesting a
|
|
26
|
+
value from that parameter fails generation; it cannot turn a property into a
|
|
27
|
+
vacuous success. Finite inhabited containers such as `List Empty` still enumerate.
|
|
28
|
+
|
|
29
|
+
Optional `stub: true` generator bindings create user-owned implementation files.
|
|
30
|
+
Regeneration preserves edits and reports changed factory signatures for review.
|
|
31
|
+
|
|
32
|
+
## API and migration
|
|
33
|
+
|
|
34
|
+
Native binding requests negotiate schema 4. Schema 3 remains supported for
|
|
35
|
+
specifications without bindings. Structured native references and strict
|
|
36
|
+
configuration validation prevent older compilers from silently ignoring mappings.
|
|
37
|
+
Go additionally supports explicit import aliases; Rust binds tests to the
|
|
38
|
+
application library's type identities.
|
|
39
|
+
|
|
40
|
+
Existing adapter files cannot be overwritten by adopting a binding. Move their
|
|
41
|
+
implementations into the application module and save the old adapters before
|
|
42
|
+
generation. Generated source bridges, test helpers and user-owned application
|
|
43
|
+
files retain separate placement and ownership. See the
|
|
44
|
+
[migration guide](API-MIGRATION.md#schema-4-native-bindings) and
|
|
45
|
+
[ownership instructions](NATIVE-BINDINGS.md#ownership-regeneration-and-migration).
|
|
46
|
+
|
|
47
|
+
## Runnable payment projects
|
|
48
|
+
|
|
49
|
+
`lawspec examples --example payments` exports a project for every target; use
|
|
50
|
+
`--target rust` or another language to select one. Each project includes an
|
|
51
|
+
application model, native generator, configuration, build files and instructions.
|
|
52
|
+
The shared specification checks exact fees, currency preservation, sum payloads,
|
|
53
|
+
ordered archives, duplicates and absence. Re-exporting preserves application
|
|
54
|
+
edits and the separate compiler generation manifest.
|
|
55
|
+
|
|
56
|
+
Both 32-bit and 64-bit logical machine profiles and readable/compact output are
|
|
57
|
+
covered by the acceptance matrices. Java 25+, Python 3.13+ and the other published
|
|
58
|
+
toolchain baselines remain unchanged. Python output follows PEP 8.
|
|
59
|
+
|
|
60
|
+
GADTs, dependent indices and cross-unit packages remain subsequent work.
|
package/RELEASE-0.11.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# LawSpec 0.11.0
|
|
2
|
+
|
|
3
|
+
## Natural-indexed families
|
|
4
|
+
|
|
5
|
+
Data declarations may take `Natural` parameters, and each constructor states
|
|
6
|
+
its index equation:
|
|
7
|
+
|
|
8
|
+
```lawspec
|
|
9
|
+
type Vec (n :: Natural) (a :: Type) is
|
|
10
|
+
| VNil where n = 0
|
|
11
|
+
| VCons head :: a tail :: Vec m a where n = m + 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
append :: (xs :: Vec n Int8) -> (ys :: Vec m Int8) -> (r :: Vec (n + m) Int8)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
A family elaborates before inference into ordinary erased data, a checked
|
|
18
|
+
structural measure per index (`nOfVec`), and a refinement relating the measure
|
|
19
|
+
to the index. Core and all eight emitters are unchanged. Native code works with
|
|
20
|
+
the erased type, and every index claim is evidence checked at test time: a
|
|
21
|
+
dependent result such as `Vec (n + m) Int8` is an adapter postcondition.
|
|
22
|
+
|
|
23
|
+
An index variable that is otherwise unbound is implicit. The first binder whose
|
|
24
|
+
family type mentions it determines it, and later occurrences read that binder's
|
|
25
|
+
measure. Index expressions are sums of natural literals and index variables.
|
|
26
|
+
`Natural` is also available as a value type. Diagnostics use the `indexed` code.
|
|
27
|
+
|
|
28
|
+
## Index-directed generation
|
|
29
|
+
|
|
30
|
+
A quantifier constrained by a linear structural measure, including a fixed index
|
|
31
|
+
(`Vec 3 Int8`) or a shared one (`zip`'s second argument), is generated by solving
|
|
32
|
+
the constructor equations backwards instead of filtering. Multi-field equations
|
|
33
|
+
such as `Bin … where n = l + r + 1` split the remaining index across fields.
|
|
34
|
+
Python, JavaScript, TypeScript, Java, Kotlin, Go, Haskell and Rust construct these
|
|
35
|
+
values natively, and framework shrinking stays within the index. The same
|
|
36
|
+
planning applies to user-written linear measures over declared data.
|
|
37
|
+
|
|
38
|
+
`examples/specs/indexed_families.lawspec` runs on all eight targets through
|
|
39
|
+
`tools/indexed-integration.mjs`. Correct adapters pass free, fixed and shared
|
|
40
|
+
index laws; mutants that add, drop or lose elements fail their dependent
|
|
41
|
+
contracts.
|
|
42
|
+
|
|
43
|
+
## Tower-polymorphic Integer results restored
|
|
44
|
+
|
|
45
|
+
0.9 had narrowed Kotlin and Haskell adapters whose result is the abstract
|
|
46
|
+
`Integer` to `BigInteger` and `Integer`. They again return `Number` (Kotlin) and
|
|
47
|
+
`LS.IntegerValue` (Haskell), as in 0.8 and as Java always did. `Integer` is the top
|
|
48
|
+
of the integral tower: an implementation may return any integral native value,
|
|
49
|
+
and the result bridge rejects non-integral values and checks the logical domain.
|
|
50
|
+
Adapters written against 0.9 or 0.10 stubs that return `BigInteger` or `Integer`
|
|
51
|
+
still compile, because both are integral.
|
|
52
|
+
|
|
53
|
+
This regression had kept the Kotlin and Haskell CI target jobs failing since
|
|
54
|
+
0.9. Those jobs, and the scalar mutation fixtures, now pass with the documented
|
|
55
|
+
typed Symbol, Decimal, Utf16Text and Optional representations.
|
|
56
|
+
|
|
57
|
+
## Compatibility
|
|
58
|
+
|
|
59
|
+
Specifications without indexed families generate the same code as 0.10, apart
|
|
60
|
+
from the Integer result signatures above. API schemas 3 and 4 are unchanged.
|
|
61
|
+
GADTs that refine type arguments, non-linear indices and index equalities between
|
|
62
|
+
sibling fields remain future work.
|
package/api.mjs
CHANGED
|
@@ -4,9 +4,29 @@ import {loadCore} from './launcher.mjs';
|
|
|
4
4
|
export async function createCompiler() {
|
|
5
5
|
const call = await loadCore();
|
|
6
6
|
return {
|
|
7
|
-
check: (input) =>
|
|
8
|
-
|
|
7
|
+
check: (input) =>
|
|
8
|
+
call(
|
|
9
|
+
{
|
|
10
|
+
schemaVersion: input.nativeBindings === undefined ? 3 : 4,
|
|
11
|
+
...input,
|
|
12
|
+
method: 'check',
|
|
13
|
+
}
|
|
14
|
+
),
|
|
15
|
+
expand: (input) =>
|
|
16
|
+
call(
|
|
17
|
+
{
|
|
18
|
+
schemaVersion: input.nativeBindings === undefined ? 3 : 4,
|
|
19
|
+
...input,
|
|
20
|
+
method: 'expand',
|
|
21
|
+
}
|
|
22
|
+
),
|
|
9
23
|
planGeneration: (input) =>
|
|
10
|
-
call(
|
|
24
|
+
call(
|
|
25
|
+
{
|
|
26
|
+
schemaVersion: input.nativeBindings === undefined ? 3 : 4,
|
|
27
|
+
...input,
|
|
28
|
+
method: 'planGeneration',
|
|
29
|
+
}
|
|
30
|
+
),
|
|
11
31
|
};
|
|
12
32
|
}
|
package/bin/lawspec.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const options = {};
|
|
|
19
19
|
const positional = [];
|
|
20
20
|
for (let i = 0; i < args.length; i++) {
|
|
21
21
|
const arg = args[i];
|
|
22
|
-
if (["--target", "--project", "--config", "--output", "--machine-bits"].includes(arg)) {
|
|
22
|
+
if (["--target", "--project", "--config", "--output", "--machine-bits", "--example"].includes(arg)) {
|
|
23
23
|
if (!args[i + 1] || args[i + 1].startsWith("--"))
|
|
24
24
|
throw new Error(`Missing value for ${arg}`);
|
|
25
25
|
options[arg.slice(2)] = args[++i];
|
|
@@ -173,13 +173,13 @@ async function main() {
|
|
|
173
173
|
}
|
|
174
174
|
if (!verb || ["help", "--help", "-h"].includes(verb)) {
|
|
175
175
|
output(
|
|
176
|
-
"LawSpec 0.
|
|
176
|
+
"LawSpec 0.11.0\nUsage: lawspec init --target <language> [--project <directory>] [--minify]\n lawspec check | doctor | explain <unit>::<law> | generate\n lawspec examples [--example payments] [--target <language>] [--output <directory>]\nOptions: --config <path>, --target <language>, --machine-bits <32|64>, --json\nGeneration: --dry-run, --check, --minify\nTargets: " +
|
|
177
177
|
targets.join(", "),
|
|
178
178
|
);
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
if (verb === "--version") {
|
|
182
|
-
output("0.
|
|
182
|
+
output("0.11.0");
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
185
|
if (positional.length > (verb === "explain" ? 1 : 0))
|
|
@@ -191,8 +191,15 @@ async function main() {
|
|
|
191
191
|
options["dry-run"] ||
|
|
192
192
|
options.check
|
|
193
193
|
)
|
|
194
|
-
throw new Error("examples supports --target, --output, --json and --minify only");
|
|
194
|
+
throw new Error("examples supports --example, --target, --output, --machine-bits, --json and --minify only");
|
|
195
195
|
const result = await generateExamples(options);
|
|
196
|
+
if (options.example) {
|
|
197
|
+
output(options.json ? result : result.map(r =>
|
|
198
|
+
`${r.target}: payment project in ${r.directory}; ${r.preservedAdapters.length} user files preserved.` +
|
|
199
|
+
(r.adapterUpdates.length ? `\nReview changed example files: ${r.adapterUpdates.map(a => a.path).join(", ")}` : "")
|
|
200
|
+
).join("\n") + "\nOpen each project's README.md, run lawspec generate, then its native test command.");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
196
203
|
output(
|
|
197
204
|
options.json
|
|
198
205
|
? result
|
|
@@ -207,6 +214,7 @@ async function main() {
|
|
|
207
214
|
return;
|
|
208
215
|
}
|
|
209
216
|
if (options.output) throw new Error("--output is only supported by examples");
|
|
217
|
+
if (options.example) throw new Error("--example is only supported by examples");
|
|
210
218
|
if (options.minify && !["init", "generate", "examples"].includes(verb))
|
|
211
219
|
throw new Error("--minify applies to init, generate and examples");
|
|
212
220
|
if (verb === "init") return init();
|
|
@@ -251,6 +259,10 @@ async function main() {
|
|
|
251
259
|
const compiler = await createCompiler();
|
|
252
260
|
if (verb === "check") {
|
|
253
261
|
const result = diagnostics(await compiler.check(input));
|
|
262
|
+
for (const target of selected) {
|
|
263
|
+
if (target.nativeBindings !== undefined)
|
|
264
|
+
diagnostics(await compiler.check({...input, nativeBindings: target.nativeBindings}));
|
|
265
|
+
}
|
|
254
266
|
output(
|
|
255
267
|
options.json
|
|
256
268
|
? result
|
|
@@ -289,6 +301,7 @@ async function main() {
|
|
|
289
301
|
target: target.language,
|
|
290
302
|
sourceDir: target.sourceDir,
|
|
291
303
|
testDir: target.testDir,
|
|
304
|
+
nativeBindings: target.nativeBindings,
|
|
292
305
|
minify: options.minify === true,
|
|
293
306
|
}),
|
|
294
307
|
).files,
|