lawspec 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API-MIGRATION.md +102 -1
- package/GO.md +144 -0
- package/HASKELL.md +171 -0
- package/JAVA.md +113 -0
- package/KOTLIN.md +214 -0
- package/LANGUAGE.md +237 -12
- package/PRIMITIVES.md +1 -1
- package/PYTHON.md +152 -0
- package/README.md +26 -13
- package/REFINEMENTS.md +289 -3
- package/RELEASE-0.9.md +61 -0
- package/RUST.md +115 -1
- package/WEB.md +144 -0
- package/api.mjs +8 -2
- package/bin/lawspec.mjs +9 -6
- package/build.json +179 -37
- package/core.wasm +0 -0
- package/examples/specs/collections.lawspec +67 -0
- package/examples/specs/data_types.lawspec +66 -0
- package/examples/specs/finite_data.lawspec +37 -0
- package/examples/specs/list_contracts.lawspec +136 -0
- package/examples/specs/list_refinements.lawspec +51 -0
- package/examples/specs/matching.lawspec +49 -0
- package/examples/specs/recursive_refinements.lawspec +41 -0
- package/examples/specs/refined_definitions.lawspec +44 -0
- package/examples/specs/sum_refinements.lawspec +49 -0
- package/examples/specs/total_functions.lawspec +101 -0
- package/examples-command.mjs +6 -1
- package/files.mjs +10 -1
- package/index.d.ts +246 -27
- package/package.json +2 -2
- package/templates.mjs +107 -17
package/KOTLIN.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Native Kotlin data
|
|
2
|
+
|
|
3
|
+
LawSpec 0.9 emits Kotlin sealed interfaces and named generic variant classes from
|
|
4
|
+
checked Core declarations. Generated schemas and codecs share the JVM scalar
|
|
5
|
+
runtime, independently of Kotest.
|
|
6
|
+
|
|
7
|
+
```lawspec
|
|
8
|
+
unit example.trees
|
|
9
|
+
|
|
10
|
+
type Tree (a :: Type) is
|
|
11
|
+
Leaf value :: a
|
|
12
|
+
Branch children :: List (Tree a)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
echo :: Tree Int8 -> Tree Int8
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The public type is `lawspec.data.Tree<Byte>`. Its variants are
|
|
19
|
+
`Tree.LeafCase<Byte>`, with a typed `value` property, and
|
|
20
|
+
`Tree.BranchCase<Byte>`, with `children: List<Tree<Byte>>`. Recursive and mutually
|
|
21
|
+
recursive fields retain native types. Generic parameters also remain on nullary
|
|
22
|
+
variants. Empty types have a private constructor and cannot supply generated
|
|
23
|
+
arguments, but can occur inside inhabited types such as `Maybe Empty`.
|
|
24
|
+
|
|
25
|
+
A correct adapter is:
|
|
26
|
+
|
|
27
|
+
```kotlin
|
|
28
|
+
fun echo(value: lawspec.data.Tree<Byte>): lawspec.data.Tree<Byte> = value
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Adapter files remain user-owned. Duplicate type names across units receive
|
|
32
|
+
qualified generated names. Kotlin built-ins are qualified in generated signatures
|
|
33
|
+
to avoid shadowing by domain types. Adapters that collide with generated JVM
|
|
34
|
+
runtime classes are rejected before output.
|
|
35
|
+
|
|
36
|
+
## Containers and absence
|
|
37
|
+
|
|
38
|
+
`List a` uses Kotlin `List<T>`. `Maybe a` uses `LawSpecRuntime.Maybe<T>` with
|
|
39
|
+
`Nothing<T>` and `Just<T>`. `Either a b` uses `LawSpecRuntime.Either<L, R>` with
|
|
40
|
+
`Left<L, R>` and `Right<L, R>`.
|
|
41
|
+
|
|
42
|
+
Interoperability absence remains separate:
|
|
43
|
+
|
|
44
|
+
- `Nullable a`: `LawSpecKotlin.Nullable<T>`, with `Null<T>` and `Present<T>`.
|
|
45
|
+
- `Optional a`: `LawSpecKotlin.Optional<T>`, with `Undefined<T>` and `Present<T>`.
|
|
46
|
+
- `Null` and `Undefined`: distinct singleton support values.
|
|
47
|
+
- `Unit`: Kotlin `Unit`, including adapter operations without a meaningful result.
|
|
48
|
+
|
|
49
|
+
These representations preserve every state of `Nullable (Optional a)` and other
|
|
50
|
+
nested combinations. Checked codecs validate both native adapter arguments and
|
|
51
|
+
results. Containers and raw arrays are copied across the logical/native boundary.
|
|
52
|
+
LawSpec equality is schema-directed, including IEEE component equality and
|
|
53
|
+
Symbol identity; generated variant classes do not substitute Kotlin data-class
|
|
54
|
+
equality for those rules.
|
|
55
|
+
|
|
56
|
+
## Scalars
|
|
57
|
+
|
|
58
|
+
Fixed signed integers use Kotlin `Byte`, `Short`, `Int`, and `Long`. UInt8,
|
|
59
|
+
UInt16, and UInt32 use the next sufficiently wide signed JVM type. UInt64,
|
|
60
|
+
arbitrary integers, and machine-profile integers use `BigInteger` with explicit
|
|
61
|
+
domain checks. These portable machine-profile representations do not bind a
|
|
62
|
+
host machine-sized primitive.
|
|
63
|
+
|
|
64
|
+
Decimal uses exact `BigDecimal`; Rational uses the normalized
|
|
65
|
+
`LawSpecRuntime.Ratio`. Complex components use `LawSpecRuntime.Complex`, with
|
|
66
|
+
Float32 precision validated for Complex64. Raw code-point text uses `IntArray`,
|
|
67
|
+
raw UTF-16 uses `String`, and bytes use `ByteArray`. `Char` uses a one-scalar
|
|
68
|
+
`String`, allowing supplementary characters; `CodePoint` uses `Int` and
|
|
69
|
+
`CodeUnit16` uses Kotlin `Char`.
|
|
70
|
+
|
|
71
|
+
`LawSpecKotlin.Symbol` preserves the underlying fixture identity through checked
|
|
72
|
+
round trips. Its native equality compares that identity, so two Symbols with the
|
|
73
|
+
same description remain distinct.
|
|
74
|
+
|
|
75
|
+
## Total definitions
|
|
76
|
+
|
|
77
|
+
Checked source definitions produce native Kotlin entry points and shared Java
|
|
78
|
+
implementation bodies in source directories. Neither requires Kotest. For example:
|
|
79
|
+
|
|
80
|
+
```lawspec
|
|
81
|
+
unit example.total
|
|
82
|
+
|
|
83
|
+
definition increment (value :: Int8) :: BigInt is
|
|
84
|
+
value + 1
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The native call is `lawspec.definitions.example.Total.increment(symbols, value)`;
|
|
89
|
+
`value` is a Kotlin `Byte`, the result is `BigInteger`, and `symbols` is a
|
|
90
|
+
`MutableMap<String, Any>` shared by calls in one example. An input of 127 returns
|
|
91
|
+
128. Native signatures retain typed lists, custom variants, and nested presence
|
|
92
|
+
payloads. Checked codecs validate inputs and results; failures include the
|
|
93
|
+
resolved definition name.
|
|
94
|
+
|
|
95
|
+
Properties call the generated implementation directly. Definitions never become
|
|
96
|
+
user-owned adapter stubs. Recursive definitions must pass the frontend's
|
|
97
|
+
structural termination and definedness checks. Generic definitions specialize to concrete uses. Refined signatures become
|
|
98
|
+
checked contracts, and refinement predicates may call checked definitions.
|
|
99
|
+
|
|
100
|
+
`tools/kotlin-definitions-integration.mjs` checks both machine profiles, standalone
|
|
101
|
+
source calls, native type errors, recursive properties, incorrect adapters,
|
|
102
|
+
compact source and complete minified generation plans, custom layouts, and
|
|
103
|
+
regeneration protection. Java implementation
|
|
104
|
+
bodies are checked against Google Java Format; Kotlin uses structured document
|
|
105
|
+
layout checked by the independent compiler-parser audit described below.
|
|
106
|
+
|
|
107
|
+
## Generated tests
|
|
108
|
+
|
|
109
|
+
Native declarations, schema metadata, and codecs belong in source directories.
|
|
110
|
+
`LawSpecStrategies` and `LawSpecKotlinStrategies` belong in test directories and
|
|
111
|
+
compose Kotest arbitraries and shrink trees. Bounded recursive generation reserves
|
|
112
|
+
each product field's minimum cost before distributing remaining nodes. Dependent
|
|
113
|
+
generation retains source shrink trees, including list lengths and constructor
|
|
114
|
+
choices, which Kotest 5.9's flatMap otherwise discards.
|
|
115
|
+
|
|
116
|
+
`tools/kotlin-data-integration.mjs` checks native declarations, codecs, and
|
|
117
|
+
shrinking in readable/compact layouts. `tools/kotlin-data-properties.mjs` compiles
|
|
118
|
+
and runs actual compiler output through Kotest with correct and incorrect
|
|
119
|
+
adapters, both profiles, and custom source/test roots. It accepts
|
|
120
|
+
`LAWSPEC_MINIFY=1` and includes the shared arithmetic conformance vectors in its
|
|
121
|
+
scalar scenario. The refinements scenario checks dependent domains, guarded
|
|
122
|
+
arithmetic, standalone contracts and four faulty adapters. It uses cached
|
|
123
|
+
compiler/library dependencies directly.
|
|
124
|
+
|
|
125
|
+
Generated property files now use structured documents throughout: imports,
|
|
126
|
+
helpers, examples, boundaries, native Kotest strategies, dependent refinement
|
|
127
|
+
domains, guarded assertions and contract wrappers. Blocks use two-space
|
|
128
|
+
indentation and continuation arguments use four spaces. Checked native codecs,
|
|
129
|
+
short-circuiting, Symbol context, single-evaluation matches and framework
|
|
130
|
+
generation/shrinking remain intact. The corpus audit below covers both machine
|
|
131
|
+
profiles and readable/compact syntax equivalence.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
For internal checked Core definitions, shared JVM implementation bodies now
|
|
135
|
+
prove attached contracts before emission and enforce ordered preconditions and
|
|
136
|
+
validated-result postconditions at runtime. Native wrappers and direct logical
|
|
137
|
+
entry points both use those checks. `tools/jvm-definition-contract-integration.mjs`
|
|
138
|
+
verifies both JVM languages, profiles and layouts without a test-framework
|
|
139
|
+
dependency, including corrupted-result rejection. Refined source signatures
|
|
140
|
+
now produce these contracts through template proof and specialization.
|
|
141
|
+
|
|
142
|
+
## Source formatting and independent parsing
|
|
143
|
+
|
|
144
|
+
Kotlin output follows the published [Google Android Kotlin style guide](https://developer.android.com/kotlin/style-guide):
|
|
145
|
+
four-space blocks and wrapped arguments/parameters, with a 100-column code limit.
|
|
146
|
+
This corrects the earlier two-space Kotlin layout. Java support files continue to
|
|
147
|
+
use Google Java Format conventions. Python independently follows PEP 8.
|
|
148
|
+
|
|
149
|
+
`tools/kotlin-formatting-integration.mjs` generates the bundled corpus and the
|
|
150
|
+
total-definition fixture in both machine profiles and formatting modes, plus both
|
|
151
|
+
Gradle Kotlin scaffold files. An
|
|
152
|
+
independently installed Kotlin compiler parses both outputs. The checker compares
|
|
153
|
+
syntax trees, retaining operators, declaration modifiers, type arguments, and
|
|
154
|
+
string-template contents; it ignores whitespace, comments, semicolons and optional
|
|
155
|
+
trailing commas. Negative fixtures distinguish changed signs, val/var, string
|
|
156
|
+
contents and escaped dollars from template interpolation.
|
|
157
|
+
|
|
158
|
+
The audit also checks block/argument indentation, line lengths, tabs, trailing
|
|
159
|
+
whitespace, explicit ASCII-sorted imports, breaks after binary operators,
|
|
160
|
+
multiline conditional/when braces, and loop braces. Set `LAWSPEC_CORE` to the native compiler and optionally set
|
|
161
|
+
`LAWSPEC_KOTLIN_HOME` to a Kotlin installation containing `lib/kotlin-compiler.jar`.
|
|
162
|
+
The tool detects ordinary and Homebrew installations of `kotlinc`. Positional
|
|
163
|
+
arguments select specification files. All parser and formatting tooling remains
|
|
164
|
+
development-only; generated projects do not download it.
|
|
165
|
+
|
|
166
|
+
The audit checks the rules listed above. It does not compare byte-for-byte with
|
|
167
|
+
an external Kotlin formatter. No formatter is required to generate projects.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
## Constructor contracts
|
|
171
|
+
|
|
172
|
+
Kotlin data emission uses profile-aware typed JVM schema callbacks.
|
|
173
|
+
Generated named codecs and native definition arguments/results preserve a caller's
|
|
174
|
+
Symbol context through nested List, Maybe, Either, Nullable and Optional values.
|
|
175
|
+
Kotlin-specific construct/match helpers also accept an explicit context; existing
|
|
176
|
+
Kotlin calls retain overloads or default arguments. Scalar-only bridges remain
|
|
177
|
+
framework-independent.
|
|
178
|
+
|
|
179
|
+
`tools/kotlin-constructor-native-integration.mjs` compiles and executes dependent
|
|
180
|
+
fields, generic/refined lists, sums, guarded arithmetic, machine ranges, nested
|
|
181
|
+
absence, fixture Symbols and raw UTF-16 units at both widths and layouts. It also
|
|
182
|
+
checks context-reset mutants, Google-formatted Java companions and independent
|
|
183
|
+
Kotlin parsing/layout/compact syntax parity.
|
|
184
|
+
|
|
185
|
+
Public Kotlin constructor-contract properties use the checked strategies and
|
|
186
|
+
share one Symbol context across witnesses, input generation, definitions, adapter
|
|
187
|
+
bridges and structural assertions. A context is allocated once per sampled case
|
|
188
|
+
and remains stable when the framework reads its shrink tree. Required conjunctive
|
|
189
|
+
Symbol equalities can draw fixture/prior-input values; disjunctions preserve both
|
|
190
|
+
alternatives and retain their predicate check.
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
The internal `LawSpecKotlinStrategies.checkedGenerator` validates supplied witnesses
|
|
194
|
+
and indexes their typed nested values. Native Kotest list/product/choice trees
|
|
195
|
+
remain the source of candidates and shrinks. A bounded sampling wrapper retries
|
|
196
|
+
false predicates up to `maxAttempts` at each value boundary; Kotest's `RTree.filter`
|
|
197
|
+
removes invalid shrink nodes. This avoids Kotest 5.9's unbounded arbitrary-filter
|
|
198
|
+
sampling without changing global framework settings. Evaluator errors are retained
|
|
199
|
+
as checked errors, including errors discovered during shrinking. Sampled witness
|
|
200
|
+
branches can limit payload shrinking; a globally minimal result is not promised.
|
|
201
|
+
|
|
202
|
+
`tools/kotlin-checked-strategies.mjs` checks both profiles, bounded exhaustion,
|
|
203
|
+
valid shrinking, nested witnesses, error classification and Symbol identity, with
|
|
204
|
+
compiled behavioral mutants. The legacy unchecked generator rejects schemas with
|
|
205
|
+
constructor contracts rather than bypassing their checks.
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
Generated dependent-input chains retain evaluator errors as case state, skip
|
|
209
|
+
subsequent draws after an error, and report the original failure before reading
|
|
210
|
+
input bindings. Input filtering accepts failed cases for reporting instead of
|
|
211
|
+
silently discarding them. Core-proven finite domains still emit exhaustive cases.
|
|
212
|
+
`tools/kotlin-field-properties.mjs` runs public generation at both widths and
|
|
213
|
+
layouts, with custom placement, finite domains, typed native adapters, nested
|
|
214
|
+
presence and lists, disjunctive fixtures, evaluator errors and incorrect adapters.
|
package/LANGUAGE.md
CHANGED
|
@@ -1,13 +1,208 @@
|
|
|
1
|
-
# LawSpec language and compiler boundary (0.
|
|
1
|
+
# LawSpec language and compiler boundary (0.9)
|
|
2
2
|
|
|
3
3
|
LawSpec describes portable laws, concrete examples, and adapter contracts. The
|
|
4
4
|
compiler is written in Haskell. Rust is an output backend alongside Java, Python,
|
|
5
5
|
JavaScript, TypeScript, Go, Haskell, and Kotlin.
|
|
6
6
|
|
|
7
|
+
## Lists and algebraic containers
|
|
8
|
+
|
|
9
|
+
`List a` is an ordered, finite sequence of values of one type. Lists nest and
|
|
10
|
+
retain duplicates. Literals use brackets; their element type comes from the
|
|
11
|
+
surrounding signature, quantifier, or annotation. An unconstrained empty list
|
|
12
|
+
needs an annotation such as `([] :: List Int8)`.
|
|
13
|
+
|
|
14
|
+
```lawspec
|
|
15
|
+
unit guide.lists
|
|
16
|
+
|
|
17
|
+
reverse :: List Int32 -> List Int32
|
|
18
|
+
|
|
19
|
+
law `reverse preserves length` is
|
|
20
|
+
definition is
|
|
21
|
+
`for all` (xs :: List Int32) .
|
|
22
|
+
prelude.length (reverse xs) = prelude.length xs
|
|
23
|
+
end
|
|
24
|
+
example `duplicates count separately` is
|
|
25
|
+
xs = [3, 1, 3]
|
|
26
|
+
expect prelude.length xs = 3
|
|
27
|
+
expect reverse xs = [3, 1, 3]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`prelude.length` returns an exact integer. List equality compares corresponding
|
|
33
|
+
values in order and requires equal lengths. It preserves scalar equality rules:
|
|
34
|
+
NaN still differs from itself, signed zeros compare equal, and Symbols compare
|
|
35
|
+
by identity. Generic list equality requires `Eq a`.
|
|
36
|
+
|
|
37
|
+
`Maybe a` has constructors `Nothing` and `Just value`. `Either a b` has
|
|
38
|
+
constructors `Left value` and `Right value`. These are algebraic sums, separate
|
|
39
|
+
from the interoperability types `Nullable a` and `Optional a`.
|
|
40
|
+
|
|
41
|
+
```lawspec
|
|
42
|
+
unit guide.presence
|
|
43
|
+
|
|
44
|
+
echo :: Maybe (Either Int8 Bool) -> Maybe (Either Int8 Bool)
|
|
45
|
+
|
|
46
|
+
law `preserve every alternative` is
|
|
47
|
+
definition is `for all` (x :: Maybe (Either Int8 Bool)) . echo x = x end
|
|
48
|
+
example `absent` is x = Nothing expect echo x = Nothing end
|
|
49
|
+
example `left integer` is x = Just (Left 127) expect echo x = Just (Left 127) end
|
|
50
|
+
example `right boolean` is x = Just (Right false) expect echo x = Just (Right false) end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For `Maybe (Maybe Bool)`, `Nothing`, `Just Nothing`, and `Just (Just false)`
|
|
55
|
+
remain distinct. Nesting a constructor application as an argument generally
|
|
56
|
+
requires parentheses, as in `Just (Left 127)`.
|
|
57
|
+
|
|
58
|
+
The [collections example](examples/specs/collections.lawspec) combines reverse
|
|
59
|
+
involution, sorting idempotence, sortedness, length, and permutation preservation.
|
|
60
|
+
`sorted` and `permutation` in that example are adapters supplied by the user;
|
|
61
|
+
they are not built-in helpers. Sortedness and length alone cannot establish that
|
|
62
|
+
a sorting adapter retained the original elements.
|
|
63
|
+
|
|
64
|
+
## Products, sums, and pattern matching
|
|
65
|
+
|
|
66
|
+
A `type` declaration names its constructors and each constructor's fields. One
|
|
67
|
+
constructor describes a product; multiple constructors describe a sum. Type
|
|
68
|
+
parameters are declared explicitly with `:: Type`.
|
|
69
|
+
|
|
70
|
+
```lawspec
|
|
71
|
+
unit guide.trees
|
|
72
|
+
|
|
73
|
+
type Pair (a :: Type) (b :: Type) is
|
|
74
|
+
Pair
|
|
75
|
+
first :: a
|
|
76
|
+
second :: b
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
type Tree (a :: Type) is
|
|
80
|
+
Leaf value :: a
|
|
81
|
+
Branch children :: List (Tree a)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
definition rebuild (tree :: Tree a) :: Tree a is
|
|
85
|
+
match tree with
|
|
86
|
+
| Leaf value -> Leaf value
|
|
87
|
+
| Branch children -> Branch children
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
law `preserve the constructor and its fields` is
|
|
92
|
+
definition is `for all` (tree :: Tree Int8) . rebuild tree = tree end
|
|
93
|
+
example `nested branches` is
|
|
94
|
+
tree = Branch [Leaf 127, Branch [], Leaf -128]
|
|
95
|
+
expect rebuild tree = Branch [Leaf 127, Branch [], Leaf -128]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Constructors receive fields in declaration order. A match evaluates its
|
|
101
|
+
scrutinee once; each branch binds that constructor's fields in the same order.
|
|
102
|
+
Bindings are scoped to the branch. Matching must be exhaustive and cannot repeat
|
|
103
|
+
a constructor. Lists match with `Nil` and `Cons head tail`; Maybe and Either use
|
|
104
|
+
their constructors above. Recursive declarations must be strictly positive.
|
|
105
|
+
General indexed constructors and GADT result signatures are not supported.
|
|
106
|
+
|
|
107
|
+
Equality is structural and type-directed, including named fields and nested
|
|
108
|
+
containers. Native public declarations retain their names and type parameters;
|
|
109
|
+
schemas and checked codecs support them at runtime. They are not replacements
|
|
110
|
+
for the public data types. See the target guides for native representations:
|
|
111
|
+
[Java](JAVA.md), [Python](PYTHON.md), [JavaScript/TypeScript](WEB.md),
|
|
112
|
+
[Go](GO.md), [Haskell](HASKELL.md), [Kotlin](KOTLIN.md), and [Rust](RUST.md).
|
|
113
|
+
In Haskell, `Text` remains `Data.Text.Text`, while `List Char` becomes the linked
|
|
114
|
+
list `[Char]`. These are distinct LawSpec types even when they contain the same
|
|
115
|
+
characters.
|
|
116
|
+
|
|
117
|
+
Generators compose the target framework's generators and shrinkers. Recursive
|
|
118
|
+
values have a structural size budget; each constructor reserves enough budget
|
|
119
|
+
for its fields before distributing the remainder. Boundaries include empty and
|
|
120
|
+
singleton lists and constructor-specific values. Small finite domains are
|
|
121
|
+
enumerated. An empty type cannot supply a generated argument, but containers
|
|
122
|
+
such as `List Empty` and `Maybe Empty` can still be inhabited. An empty or
|
|
123
|
+
unreachable input domain never makes a property pass vacuously.
|
|
124
|
+
|
|
125
|
+
Whole-value refinements can inspect products and sums using exhaustive matches.
|
|
126
|
+
List element refinements, Maybe/Either payload refinements, and refined arguments
|
|
127
|
+
of recursive and nonrecursive named type constructors are supported; see
|
|
128
|
+
[refinements](REFINEMENTS.md#named-data-payloads). Direct refinements on named
|
|
129
|
+
constructor fields use checked constructor contracts. Recursive payload predicates
|
|
130
|
+
follow stored type arguments and preserve outer dependent inputs.
|
|
131
|
+
|
|
132
|
+
## Total definitions
|
|
133
|
+
|
|
134
|
+
A unit can supply an implementation as a checked total definition:
|
|
135
|
+
|
|
136
|
+
```lawspec
|
|
137
|
+
definition increment (x :: Int8) :: BigInt
|
|
138
|
+
requires Integer Int8
|
|
139
|
+
is
|
|
140
|
+
x + 1
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Parameters and the result have explicit types. The optional `requires` clause
|
|
145
|
+
uses the same `Eq`, `Integer`, `Ordered`, and `Bounded` capabilities as laws.
|
|
146
|
+
Requirements are checked even when the definition is unused. Bodies must have
|
|
147
|
+
exhaustive matches, proven structural descent for recursive calls, and guards
|
|
148
|
+
for operations that could otherwise fail. Calls may use other checked
|
|
149
|
+
definitions; external adapters cannot establish a definition's totality.
|
|
150
|
+
|
|
151
|
+
For exact arithmetic, the totality checker can combine linear bounds and Boolean
|
|
152
|
+
guards. For example, `x >= 0 && 1 / (x + 1) > 0` is safe for integer `x`: the
|
|
153
|
+
right side runs only when its denominator is positive. Proof arithmetic uses
|
|
154
|
+
arbitrary exact fractions, preserves strict boundaries, and has a bounded work
|
|
155
|
+
budget. An unproved obligation is rejected; IEEE expressions never acquire
|
|
156
|
+
rational identities such as `x - x = 0` from this checker.
|
|
157
|
+
|
|
158
|
+
Primitive integer ranges are available to the checker automatically, including
|
|
159
|
+
the selected machine width and BigUInt's nonnegative domain. Integer comparisons
|
|
160
|
+
retain integrality: for Int8 `x`, `x < 127 && prelude.Int8 (x + 1) > x` safely
|
|
161
|
+
narrows only on the guarded branch. The unguarded conversion is rejected because
|
|
162
|
+
`127 + 1` is outside Int8. Range bounds alone do not prove that a Rational or
|
|
163
|
+
Decimal input has no fractional part.
|
|
164
|
+
Pattern matching retains the primitive ranges of extracted fields within that
|
|
165
|
+
branch. For example, an Int8 list head still makes `head + 129` strictly positive;
|
|
166
|
+
that fact cannot be reused for an Int64 field in another constructor branch.
|
|
167
|
+
|
|
168
|
+
Definitions produce reusable generated source with checked native entry points
|
|
169
|
+
on all eight targets. They do not produce user-owned adapter stubs. Integer
|
|
170
|
+
arithmetic preserves the mathematical result; the example above returns `128`
|
|
171
|
+
for the largest Int8 input. See [the total-function example](examples/specs/total_functions.lawspec)
|
|
172
|
+
for recursive list counting, structural equality, and explicit expected values.
|
|
173
|
+
|
|
174
|
+
Definitions can quantify type variables implicitly through their signatures:
|
|
175
|
+
|
|
176
|
+
```lawspec
|
|
177
|
+
definition same (x :: a) (y :: a) :: Bool requires Eq a is x == y end
|
|
178
|
+
definition count (xs :: List a) :: BigInt is
|
|
179
|
+
match xs with
|
|
180
|
+
| Nil -> 0
|
|
181
|
+
| Cons head tail -> 1 + count tail
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Each template is checked for typing, capabilities, termination, and definedness,
|
|
187
|
+
including unused templates. Calls specialize it to concrete argument and result
|
|
188
|
+
types before Core elaboration. Different calls can use different types; recursive
|
|
189
|
+
self-calls must retain the same types. Ambiguous calls require an annotation,
|
|
190
|
+
such as `count ([] :: List Int8)` to specify an empty list's element type.
|
|
191
|
+
Unused templates emit no instances.
|
|
192
|
+
Checked definitions may also appear in refinement predicates and adapter
|
|
193
|
+
preconditions or postconditions. Their closed call graphs contain only other
|
|
194
|
+
checked definitions; adapter calls remain forbidden in predicates. The compiler
|
|
195
|
+
uses the same concrete Core definitions to check example domains and plan finite
|
|
196
|
+
cases and boundaries that generated tests use at runtime.
|
|
197
|
+
Definition parameters and results may carry refinements. The compiler proves
|
|
198
|
+
body definedness and result claims under ordered input preconditions, checks
|
|
199
|
+
callee preconditions, and preserves the contracts through specialization. Native
|
|
200
|
+
entry points enforce the same contracts. See [refined definitions](REFINEMENTS.md#refined-definitions).
|
|
201
|
+
|
|
7
202
|
## Source and declarations
|
|
8
203
|
|
|
9
|
-
A source has one named `unit`, function signatures,
|
|
10
|
-
laws. Qualified unit names determine target module/package paths. Function
|
|
204
|
+
A source has one named `unit`, function signatures, data declarations, checked
|
|
205
|
+
definitions, reusable refinements, and laws. Qualified unit names determine target module/package paths. Function
|
|
11
206
|
signatures are curried: `a -> b -> c` takes two inputs and returns `c`. Parentheses
|
|
12
207
|
group types and expressions. Comments start with `--` and run to the line end.
|
|
13
208
|
|
|
@@ -16,10 +211,17 @@ compiler tests specify lexical details:
|
|
|
16
211
|
|
|
17
212
|
```text
|
|
18
213
|
source = "unit" qualified-name declaration*
|
|
19
|
-
declaration = name "::" type | law | refinement
|
|
214
|
+
declaration = name "::" type | law | refinement | data-type | function
|
|
215
|
+
data-type = "type" name ("(" name "::" "Type" ")")*
|
|
216
|
+
"is" constructor* "end"
|
|
217
|
+
constructor = name (name "::" type)*
|
|
218
|
+
function = "definition" name parameter* "::" type requirements?
|
|
219
|
+
"is" expression "end"
|
|
20
220
|
type = type-atom ["->" type]
|
|
21
221
|
type-atom = primitive | type-variable | "(" type ")"
|
|
22
|
-
| ("Nullable" | "Optional") type-atom
|
|
222
|
+
| ("Nullable" | "Optional" | "List" | "Maybe") type-atom
|
|
223
|
+
| "Either" type-atom type-atom
|
|
224
|
+
| data-type-name type-atom*
|
|
23
225
|
| refinement-name argument*
|
|
24
226
|
| "(" name "::" type ["where" expression] ")"
|
|
25
227
|
refinement = "refinement" name parameter* requirements?
|
|
@@ -35,6 +237,10 @@ proposition = "`for all`" parameter+ "." proposition
|
|
|
35
237
|
| expression "implies" proposition
|
|
36
238
|
| proposition "and" proposition
|
|
37
239
|
| quoted-name expression* | expression
|
|
240
|
+
expression = ... | "[" [expression ("," expression)*] "]"
|
|
241
|
+
| constructor-name expression*
|
|
242
|
+
| "match" expression "with"
|
|
243
|
+
("|" constructor-name name* "->" expression)+ "end"
|
|
38
244
|
example = "example" quoted-name "is" (name "=" literal)+
|
|
39
245
|
("expect" expression "=" literal)+ "end"
|
|
40
246
|
```
|
|
@@ -126,11 +332,30 @@ API schema v3 uses separately defined wire views, with lossless tagged scalar
|
|
|
126
332
|
values. It does not serialize internal AST constructors. See the
|
|
127
333
|
[API migration guide](API-MIGRATION.md).
|
|
128
334
|
|
|
129
|
-
##
|
|
335
|
+
## Beyond 0.9
|
|
336
|
+
|
|
337
|
+
GADTs, indexed families, and general dependent types are planned after 0.9.
|
|
338
|
+
The Core type model distinguishes type arguments from index arguments, but that
|
|
339
|
+
representation is not a claim that arbitrary dependent programs are accepted.
|
|
340
|
+
User-defined products and sums have ordinary uniform type parameters. External
|
|
341
|
+
type bindings, custom generator bindings, and cross-unit packages are separate
|
|
342
|
+
future features.
|
|
343
|
+
|
|
344
|
+
## Generated project formatting
|
|
345
|
+
|
|
346
|
+
`lawspec init --target java` creates a readable Maven scaffold; Kotlin init
|
|
347
|
+
likewise expands Gradle blocks with two-space indentation. `--minify` explicitly
|
|
348
|
+
selects compact scaffolds and configuration JSON. `generate` and `examples`
|
|
349
|
+
accept the same flag for generated source. The mode is per invocation and does
|
|
350
|
+
not become a project default. Init preserves existing build files, and generation
|
|
351
|
+
preserves user-owned adapters regardless of formatting mode.
|
|
352
|
+
|
|
353
|
+
Generated runtime support and checked definitions belong in source directories;
|
|
354
|
+
framework-specific property helpers belong in test directories. Both follow
|
|
355
|
+
custom layout settings. Python uses PEP 8: four-space indentation, 79-column
|
|
356
|
+
code, and 72-column prose. Other targets follow Google language guidance where
|
|
357
|
+
applicable, with standard Rust formatting. Formatting is deterministic in the
|
|
358
|
+
native and WASM compilers; generation does not download or invoke a formatter.
|
|
130
359
|
|
|
131
|
-
|
|
132
|
-
|
|
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.
|
|
360
|
+
See [the release notes](RELEASE-0.9.md) for compatibility and scope, and the target
|
|
361
|
+
guides for formatting verification and native representation details.
|
package/PRIMITIVES.md
CHANGED
package/PYTHON.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Python backend
|
|
2
|
+
|
|
3
|
+
Python output follows [PEP 8](https://peps.python.org/pep-0008/): four-space
|
|
4
|
+
indentation, a 79-column code target, 72-column prose comments/docstrings, and
|
|
5
|
+
two blank lines between top-level definitions. Explicit `--minify` permits
|
|
6
|
+
compact layout while preserving Python indentation and semantics.
|
|
7
|
+
`tools/python-formatting-integration.mjs` checks generated runtime, adapter,
|
|
8
|
+
definition, and test files with pycodestyle 2.14.0 at both machine widths. It
|
|
9
|
+
also compares readable and compact syntax trees, including literal contents.
|
|
10
|
+
|
|
11
|
+
Use Python 3.13 or later. Generated tests use Hypothesis; generated data classes,
|
|
12
|
+
scalar operations, and schema validation do not depend on a test framework.
|
|
13
|
+
|
|
14
|
+
## Native structural values
|
|
15
|
+
|
|
16
|
+
| LawSpec type | Adapter representation |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `List a` | `list[A]` |
|
|
19
|
+
| `Maybe a` | `lawspec_schema.Maybe[A]`, with `Nothing` and `Just` variants |
|
|
20
|
+
| `Either a b` | `lawspec_schema.Either[A, B]`, with `Left` and `Right` variants |
|
|
21
|
+
| User-defined products and sums | Named generic dataclasses in `lawspec_data` |
|
|
22
|
+
| `Nullable a`, `Optional a` | Tagged `lawspec_runtime.Presence` values |
|
|
23
|
+
|
|
24
|
+
`Nothing()` differs from `Just(Nothing())`. `Left(value)` differs from
|
|
25
|
+
`Right(value)`. These algebraic variants are separate from the interoperability
|
|
26
|
+
states represented by `Nullable`, `Optional`, `Null`, and `Undefined`.
|
|
27
|
+
|
|
28
|
+
For example, this declaration:
|
|
29
|
+
|
|
30
|
+
```lawspec
|
|
31
|
+
type Tree (a :: Type) is
|
|
32
|
+
Leaf value :: a
|
|
33
|
+
Branch children :: List (Tree a)
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
produces a generic `Tree` base and `TreeLeaf` and `TreeBranch` dataclasses. A
|
|
38
|
+
product has one variant: `Pair` with constructor `Pair` produces `PairPair`.
|
|
39
|
+
When names conflict across units or between types and variants, the compiler
|
|
40
|
+
plans distinct names using their Core identities. Use the emitted declarations
|
|
41
|
+
and adapter annotations as the authoritative names.
|
|
42
|
+
|
|
43
|
+
Native pattern matching works on the generated variants:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import lawspec_data as data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def count_leaves(tree: data.Tree[int]) -> int:
|
|
50
|
+
match tree:
|
|
51
|
+
case data.TreeLeaf():
|
|
52
|
+
return 1
|
|
53
|
+
case data.TreeBranch(children=children):
|
|
54
|
+
return sum(count_leaves(child) for child in children)
|
|
55
|
+
raise TypeError("unknown Tree variant")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Classes are frozen and use slots. Container payloads are copied when crossing
|
|
59
|
+
adapter boundaries, so modifying a native list does not change another use of
|
|
60
|
+
the logical test input. An abstract base cannot be directly constructed; empty
|
|
61
|
+
types acquire no artificial variant.
|
|
62
|
+
|
|
63
|
+
LawSpec equality uses schema-directed comparisons, including IEEE NaN and
|
|
64
|
+
signed-zero rules and Symbol identity. Generated dataclasses do not derive
|
|
65
|
+
Python field equality, whose container shortcuts can change those rules.
|
|
66
|
+
|
|
67
|
+
## Checked boundaries
|
|
68
|
+
|
|
69
|
+
Generated calls validate input values, convert them to native classes, call the
|
|
70
|
+
adapter, and validate its result. A field extracted from a native product can
|
|
71
|
+
be returned directly from an adapter with the corresponding LawSpec result
|
|
72
|
+
type. The same representation is used for nested and standalone containers.
|
|
73
|
+
|
|
74
|
+
Diagnostics identify constructor fields and list indices. Checks distinguish
|
|
75
|
+
Bool from integers, enforce primitive ranges under the selected `machineBits`,
|
|
76
|
+
reject invalid scalar text, and preserve raw code units, code points, and bytes.
|
|
77
|
+
Preconditions and postconditions operate on validated logical values.
|
|
78
|
+
|
|
79
|
+
## Total definitions
|
|
80
|
+
|
|
81
|
+
Checked total definitions emit reusable Python functions separately from adapters:
|
|
82
|
+
|
|
83
|
+
```lawspec
|
|
84
|
+
unit example.total
|
|
85
|
+
|
|
86
|
+
definition increment (x :: Int8) :: BigInt is x + 1 end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from lawspec_definitions.example import total
|
|
91
|
+
|
|
92
|
+
symbols = {}
|
|
93
|
+
assert total.increment(symbols, 127) == 128
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The first argument is the Symbol fixture context shared within an example.
|
|
97
|
+
Public signatures preserve native container and variant annotations; checked
|
|
98
|
+
conversion enforces element types, ranges, machine profiles, and nested presence
|
|
99
|
+
states at runtime. Errors include the resolved definition name. Source functions
|
|
100
|
+
and implementation bodies have no Hypothesis or pytest dependency.
|
|
101
|
+
|
|
102
|
+
`lawspec_definition_bodies.py` holds checked logical implementations. Public
|
|
103
|
+
modules under `lawspec_definitions/` provide native entry points grouped by unit.
|
|
104
|
+
Both belong in source directories and are generated-owned. Properties invoke
|
|
105
|
+
those checked bodies directly; definitions do not create adapter stubs. Unit
|
|
106
|
+
modules cannot shadow support modules or another unit's package. Native function
|
|
107
|
+
names such as `str` do not shadow built-ins used by generated validation.
|
|
108
|
+
|
|
109
|
+
Definition bodies and Python properties share typed expression rendering. Match
|
|
110
|
+
inputs are evaluated once, branches remain lazy, guards short-circuit, and exact
|
|
111
|
+
Decimal arithmetic is independent of Python's ambient rounding context.
|
|
112
|
+
Definitions must pass structural termination and definedness checks. Generic
|
|
113
|
+
definitions specialize to concrete uses. Refined signatures become checked
|
|
114
|
+
contracts, and refinement predicates may call checked definitions.
|
|
115
|
+
|
|
116
|
+
`tools/python-definitions-integration.mjs` checks standalone source calls,
|
|
117
|
+
properties, incorrect adapters, both machine profiles, custom layouts, compact
|
|
118
|
+
execution, and regeneration protection. The bundled Python corpus passes PEP 8 checks at 79 code columns and 72 prose
|
|
119
|
+
columns. The style audit includes the standalone total-definition fixture.
|
|
120
|
+
|
|
121
|
+
## Generation and layouts
|
|
122
|
+
|
|
123
|
+
Hypothesis composes tuples, alternatives, lists, and dependent strategies.
|
|
124
|
+
Recursive generation reserves each field's minimum node budget before sharing
|
|
125
|
+
the remainder. List length and element budgets vary together. Shrinking remains
|
|
126
|
+
native to Hypothesis and preserves the schema and structural size bound.
|
|
127
|
+
Small finite domains are enumerated; empty domains do not pass vacuously.
|
|
128
|
+
|
|
129
|
+
`lawspec_data.py` and `lawspec_schema.py` belong in the configured source
|
|
130
|
+
directory. `lawspec_data_strategies.py` belongs in the test directory. Configure
|
|
131
|
+
Python's import paths for those directories when using a custom layout.
|
|
132
|
+
Adapters remain user-owned. Unit names cannot shadow generated support modules.
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
Internal checked Core definition contracts now run at native and logical entry
|
|
136
|
+
points. Emission proves the obligations first; argument validation precedes
|
|
137
|
+
ordered preconditions, and result validation precedes postconditions. Contract
|
|
138
|
+
binders map explicitly to body inputs and the checked result. Refined source
|
|
139
|
+
signatures now produce these contracts through template proof and specialization.
|
|
140
|
+
|
|
141
|
+
`tools/portable-definition-contract-integration.mjs` exercises Python, JavaScript
|
|
142
|
+
and strict TypeScript with both machine profiles and layouts, without property
|
|
143
|
+
frameworks. It also verifies that deliberately corrupted results are rejected.
|
|
144
|
+
The fixture in `test/DefinitionContractFixture.hs` is shared with the JVM checks.
|
|
145
|
+
|
|
146
|
+
## Checking Python style
|
|
147
|
+
|
|
148
|
+
Set `LAWSPEC_CORE` to the native compiler, `LAWSPEC_PYTHON` to Python 3.13 or
|
|
149
|
+
later, and `LAWSPEC_PYCODESTYLE` to the `pycodestyle.py` source from version
|
|
150
|
+
2.14.0. Then run `node tools/python-formatting-integration.mjs`. The checker is
|
|
151
|
+
a development dependency; generating and executing runtime code does not
|
|
152
|
+
require it. Optional positional arguments select individual specification files.
|