bend-python 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,24 @@
1
+ """Build native Python extensions whose application code is written in Bend."""
2
+
3
+ from .library import get_include, library_path, vendor
4
+
5
+ BEND_VERSION = "2.0.28"
6
+
7
+ __all__ = [
8
+ "BEND_VERSION",
9
+ "BendBuildExt",
10
+ "BendExtension",
11
+ "get_include",
12
+ "library_path",
13
+ "vendor",
14
+ ]
15
+
16
+
17
+ def __getattr__(name):
18
+ # Installing a binary wheel or vendoring its Bend sources needs no build
19
+ # dependencies. Only extension authors need setuptools in their build env.
20
+ if name in {"BendBuildExt", "BendExtension"}:
21
+ from . import build
22
+
23
+ return getattr(build, name)
24
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,26 @@
1
+ """Project setup helpers: ``python -m bend_python vendor bend``."""
2
+
3
+ import argparse
4
+
5
+ from .library import get_include, vendor
6
+
7
+
8
+ def main():
9
+ parser = argparse.ArgumentParser(description=__doc__)
10
+ commands = parser.add_subparsers(dest="command", required=True)
11
+ copy = commands.add_parser("vendor", help="copy the bundled Bend library into a project")
12
+ copy.add_argument("directory", nargs="?", default="bend")
13
+ copy.add_argument("--force", action="store_true", help="replace modified library files")
14
+ commands.add_parser("include", help="print the installed Bend library directory")
15
+ args = parser.parse_args()
16
+ try:
17
+ result = (
18
+ vendor(args.directory, force=args.force) if args.command == "vendor" else get_include()
19
+ )
20
+ except (OSError, RuntimeError) as error:
21
+ parser.exit(1, f"bend-python: {error}\n")
22
+ print(result)
23
+
24
+
25
+ if __name__ == "__main__":
26
+ main()
bend_python/build.py ADDED
@@ -0,0 +1,368 @@
1
+ """Setuptools integration for the pinned Bend native compiler."""
2
+
3
+ from copy import copy
4
+ import hashlib
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import platform
9
+ import re
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ import tarfile
14
+ import tempfile
15
+ import threading
16
+ import urllib.request
17
+
18
+ from setuptools import Extension
19
+ from setuptools.command.build_ext import build_ext
20
+ from setuptools.command.build_py import build_py
21
+ from setuptools.errors import CompileError, ExecError, PlatformError
22
+
23
+ from . import BEND_VERSION
24
+ from .library import library_path, vendor
25
+
26
+ BEND_RELEASE = (
27
+ f"https://github.com/bendlang/bend/releases/download/v{BEND_VERSION}/bend-{BEND_VERSION}-linux-x64.tar.gz",
28
+ "22bb6d5f6bce8ae2c5b340371fedddcbd90edc07a48b6e2b351a944c4558a3eb",
29
+ )
30
+
31
+
32
+ class BendExtension(Extension):
33
+ """A native extension compiled from one Bend entry point and optional proofs.
34
+
35
+ The entry point's ``main`` registers functions using the vendored Python
36
+ library. Normal setuptools Extension keyword arguments remain available.
37
+ """
38
+
39
+ def __init__(self, name, source, *, proofs=(), **kwargs):
40
+ if not all(re.fullmatch(r"[A-Za-z_][A-Za-z_0-9]*", part) for part in name.split(".")):
41
+ raise ValueError("Bend extension names must be dotted ASCII Python identifiers")
42
+ if Path(source).suffix != ".bend":
43
+ raise ValueError("BendExtension source must be a .bend file")
44
+ self.bend_source = str(source)
45
+ self.bend_proofs = [str(path) for path in proofs]
46
+ defaults = {
47
+ "extra_compile_args": [
48
+ "-std=c11",
49
+ "-O2",
50
+ "-fvisibility=hidden",
51
+ "-Wno-unused-function",
52
+ "-Wno-unused-variable",
53
+ "-Wno-unreachable-code",
54
+ ],
55
+ "extra_link_args": ["-pthread"],
56
+ "libraries": ["m"],
57
+ }
58
+ for key, values in defaults.items():
59
+ kwargs[key] = values + list(kwargs.get(key, ()))
60
+ super().__init__(name, sources=[self.bend_source], **kwargs)
61
+
62
+
63
+ def _bend_version(bend):
64
+ try:
65
+ return subprocess.run([bend, "version"], text=True, capture_output=True).stdout.strip()
66
+ except OSError:
67
+ return None
68
+
69
+
70
+ def _cached_compiler():
71
+ """The pinned release, downloaded and checksum-verified into the user cache."""
72
+ cache = Path(os.environ.get("XDG_CACHE_HOME") or Path.home() / ".cache")
73
+ target = cache / "bend-python" / f"bend-{BEND_VERSION}"
74
+ executable = target / "bin" / "bend"
75
+ if executable.is_file():
76
+ return executable
77
+ if os.environ.get("BEND_PYTHON_DOWNLOAD", "1") == "0":
78
+ return None
79
+ if (sys.platform, platform.machine()) != ("linux", "x86_64"):
80
+ return None
81
+ url, digest = BEND_RELEASE
82
+ print(f"bend-python: downloading Bend {BEND_VERSION} to {target}", file=sys.stderr)
83
+ target.parent.mkdir(parents=True, exist_ok=True)
84
+ with tempfile.TemporaryDirectory(dir=target.parent) as temporary:
85
+ archive = Path(temporary) / "bend.tar.gz"
86
+ try:
87
+ with urllib.request.urlopen(url, timeout=120) as response, open(archive, "wb") as out:
88
+ shutil.copyfileobj(response, out)
89
+ except OSError as error:
90
+ raise ExecError(
91
+ f"Cannot download Bend {BEND_VERSION} from {url}: {error}; install it and set BEND"
92
+ ) from None
93
+ actual = hashlib.sha256(archive.read_bytes()).hexdigest()
94
+ if actual != digest:
95
+ raise ExecError(f"Bend download checksum mismatch: expected {digest}, got {actual}")
96
+ with tarfile.open(archive) as bundle:
97
+ # The data filter rejects absolute paths, links out of the tree, and devices.
98
+ options = {"filter": "data"} if hasattr(tarfile, "data_filter") else {}
99
+ bundle.extractall(temporary, **options)
100
+ try:
101
+ os.rename(Path(temporary) / "bend", target)
102
+ except OSError:
103
+ if not executable.is_file(): # Otherwise a concurrent build installed it first.
104
+ raise
105
+ return executable
106
+
107
+
108
+ def _compiler():
109
+ """Find Bend: $BEND, this checkout's bootstrap, PATH, or the downloaded release."""
110
+ expected = f"bend {BEND_VERSION}"
111
+ explicit = os.environ.get("BEND")
112
+ if explicit:
113
+ version = _bend_version(explicit)
114
+ if version != expected:
115
+ raise ExecError(f"Expected {expected} at BEND={explicit!r}, got {version!r}")
116
+ return explicit
117
+ # Never search relative to the cwd; only an SDK checkout has this directory.
118
+ local = Path(__file__).resolve().parents[2] / ".tools/bend/bin/bend"
119
+ for candidate in (local if local.exists() else None, shutil.which("bend")):
120
+ if candidate and _bend_version(str(candidate)) == expected:
121
+ return str(candidate)
122
+ cached = _cached_compiler()
123
+ if cached and _bend_version(str(cached)) == expected:
124
+ return str(cached)
125
+ raise ExecError(
126
+ f"Cannot find {expected}: set BEND to its executable, or allow the "
127
+ "automatic download (unset BEND_PYTHON_DOWNLOAD=0)"
128
+ )
129
+
130
+
131
+ _library_lock = threading.Lock()
132
+
133
+
134
+ def _bridge_libraries(source):
135
+ """Directories of the bend-python libraries a program reaches by relative imports.
136
+
137
+ The build must prove the library it compiles: a project's vendored copy,
138
+ which ``vendor`` preserves when modified, not necessarily the SDK's own.
139
+ A library imported but absent is vendored first, so projects need not
140
+ commit or ship a copy.
141
+ """
142
+ # A second extension must not observe an incomplete automatic vendor copy.
143
+ with _library_lock:
144
+ pending, seen, found = [Path(source).resolve()], set(), []
145
+ while pending:
146
+ path = pending.pop()
147
+ if path in seen:
148
+ continue
149
+ seen.add(path)
150
+ if path.name == "python.bend" and not path.exists():
151
+ print(
152
+ f"bend-python: vendoring the Bend library into {path.parent}", file=sys.stderr
153
+ )
154
+ vendor(path.parent)
155
+ if not path.is_file():
156
+ continue
157
+ if path.name == "python.bend" and path.with_name("python.c").is_file():
158
+ found.append(path.parent)
159
+ for match in re.finditer(r"^\s*import\s+(\.\.?/\S+\.bend)\b", path.read_text(), re.M):
160
+ pending.append((path.parent / match[1]).resolve())
161
+ return found
162
+
163
+
164
+ def _bend(bend, *args, failure, quiet=False):
165
+ # Bend prints its own diagnostic; end the build with a setuptools error
166
+ # (reported as "error: ...") rather than a CalledProcessError traceback.
167
+ # Quiet runs (the library's own proofs) print their output only on failure.
168
+ result = subprocess.run([bend, *args], capture_output=quiet, text=True)
169
+ if result.returncode != 0:
170
+ if quiet:
171
+ sys.stderr.write(result.stdout + result.stderr)
172
+ raise CompileError(f"{failure} (see Bend output above)")
173
+
174
+
175
+ def _patch_runtime(source, name):
176
+ fatal = """static void err_fail(const char* msg) {
177
+ fflush(stdout);
178
+ fprintf(stderr, "bend: %s\\n", msg);
179
+ _exit(1);
180
+ }"""
181
+ cpu_globals = """static Corpus CORPUS;
182
+ static u64 ALC[CUBE_T + 1][3 * ALC_WORDS] __attribute__((aligned(128)));
183
+ static u32 KEEP_WORDS;
184
+ // the bag: 2^CUBE_LOG groups of CUBE_T lanes (a -D constant on the device)
185
+ static u32 CUBE_LOG = 7;
186
+ static u32 bank_lock;
187
+
188
+ static u32 pool_size;"""
189
+ context_blocks = list(
190
+ re.finditer(
191
+ r"// BENDPY_RUNTIME_CONTEXT_BEGIN\n(.*?)// BENDPY_RUNTIME_CONTEXT_END\n",
192
+ source,
193
+ re.DOTALL,
194
+ )
195
+ )
196
+ if len(context_blocks) != 1:
197
+ raise CompileError(
198
+ "Bend program must import the bend-python library (python.bend) exactly once"
199
+ )
200
+ context_block = context_blocks[0]
201
+ contexts = context_block[1]
202
+ source = source[: context_block.start()] + source[context_block.end() :]
203
+ pool_open = """OUTLINE void pool_open(void) {
204
+ static bool up;
205
+ if (up) {
206
+ return;
207
+ }
208
+ up = true;
209
+ for (u32 w = 0; w < pool_size; w += 1) {
210
+ pthread_t tid;
211
+ if (pthread_create(&tid, NULL, pool_work, (void*)(uintptr_t)w)) {
212
+ err_fail("pthread_create");
213
+ }
214
+ }
215
+ }"""
216
+ for old, new in [
217
+ (fatal, "static void err_fail(const char* msg) { bendpy_panic(msg); }"),
218
+ (cpu_globals, contexts),
219
+ ("static bool io_gpu;\nstatic Stk io_stk;", ""),
220
+ ("static u64 corpus_size;", ""),
221
+ (" Corpus H = CORPUS;", " Corpus H = CORPUS;\n corpus_size = size;"),
222
+ (
223
+ pool_open,
224
+ """OUTLINE void pool_open(void) {
225
+ err_fail("Bend worker threads are unsupported by the Python embedding");
226
+ }""",
227
+ ),
228
+ (
229
+ "int main(int argc, char** argv) {",
230
+ "static int bendpy_unused_main(int argc, char** argv) {",
231
+ ),
232
+ ("#define WL_OPEN { WL_BANK u32 rn;", "#define WL_OPEN { WL_BANK u32 rn = 0;"),
233
+ # Python.h is included first and already selects the GNU feature set.
234
+ ("#define _GNU_SOURCE\n", "#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n"),
235
+ # Our WL_BANK patch below zero-initializes the forwarded registers.
236
+ # Under Clang 19+'s preserve_none, those live zeros exhaust the argument
237
+ # registers once a segment signature grows (e.g. a 5-element match):
238
+ # "ran out of registers during register allocation". Upstream leaves the
239
+ # registers uninitialized instead. Keep them defined and use the default
240
+ # convention, as Clang 14-18 (which lack preserve_none) already do.
241
+ (
242
+ "#if __has_attribute(preserve_none) && __has_attribute(preserve_most)\n"
243
+ "#define PRESERVE(A) __attribute__((A))\n#else\n#define PRESERVE(A)\n#endif\n",
244
+ "#define PRESERVE(A)\n",
245
+ ),
246
+ ]:
247
+ if source.count(old) != 1:
248
+ raise CompileError("Bend runtime changed; refusing an unreviewed embedding patch")
249
+ source = source.replace(old, new)
250
+ # The tail-call ABI forwards unused registers. Initialize those registers
251
+ # rather than passing indeterminate C values between generated segments.
252
+ source, count = re.subn(
253
+ r"#define WL_BANK Term ([\w, ]+);",
254
+ lambda match: (
255
+ "#define WL_BANK Term "
256
+ + ", ".join(register + " = 0" for register in match[1].split(", "))
257
+ + ";"
258
+ ),
259
+ source,
260
+ )
261
+ if count != 1:
262
+ raise CompileError("Bend register layout changed")
263
+ prefix = (
264
+ "#define PY_SSIZE_T_CLEAN\n#include <Python.h>\n"
265
+ "static void bendpy_panic(const char*) __attribute__((noreturn));\n"
266
+ f"#define BENDPY_MODULE_NAME {json.dumps(name)}\n"
267
+ f"#define BENDPY_INIT PyInit_{name.rsplit('.', 1)[-1]}\n"
268
+ )
269
+ return prefix + source
270
+
271
+
272
+ class BendBuildExt(build_ext):
273
+ """Proof-check, emit C, and compile BendExtension instances with Clang."""
274
+
275
+ def _clang_compiler(self):
276
+ """Give this extension its own compiler, preserving the configured flags."""
277
+ compiler = copy(self.compiler)
278
+ commands = {name: getattr(compiler, name, None) for name in ("compiler_so", "linker_so")}
279
+ if not all(commands.values()):
280
+ raise PlatformError("Bend extensions require a Unix Clang compiler")
281
+ if "CC" not in os.environ:
282
+ clang = shutil.which("clang")
283
+ if not clang:
284
+ raise PlatformError("Bend generated C requires Clang; install it or set CC=clang")
285
+ compiler.set_executables(
286
+ **{name: [clang, *command[1:]] for name, command in commands.items()}
287
+ )
288
+ try:
289
+ version = subprocess.run(
290
+ [*compiler.compiler_so, "--version"], text=True, capture_output=True
291
+ ).stdout
292
+ except OSError as error:
293
+ raise PlatformError(
294
+ f"Cannot run C compiler {compiler.compiler_so[0]!r}: {error}"
295
+ ) from error
296
+ if "clang" not in version.lower():
297
+ raise PlatformError(
298
+ f"Bend generated C requires Clang, not {compiler.compiler_so[0]!r}; set CC=clang"
299
+ )
300
+ return compiler
301
+
302
+ def build_extension(self, extension):
303
+ if not isinstance(extension, BendExtension):
304
+ return super().build_extension(extension)
305
+ if sys.platform != "linux":
306
+ raise PlatformError("The pinned Bend embedding runtime currently supports Linux only")
307
+ bend = _compiler()
308
+ if not Path(extension.bend_source).is_file():
309
+ raise CompileError(f"Bend source file not found: {extension.bend_source}")
310
+ libraries = _bridge_libraries(extension.bend_source) or [library_path()]
311
+ library_proofs = [
312
+ str(library / proof)
313
+ for library in libraries
314
+ for proof in ("PROOF.bend", "THREAD_PROOF.bend")
315
+ ]
316
+ for path in [*library_proofs, *extension.bend_proofs]:
317
+ if not Path(path).is_file():
318
+ raise CompileError(f"Bend proof file not found: {path}")
319
+ for proof in dict.fromkeys([*library_proofs, *extension.bend_proofs]):
320
+ _bend(
321
+ bend,
322
+ proof,
323
+ "--check-only",
324
+ failure=f"Bend proof check failed: {proof}",
325
+ quiet=proof in library_proofs,
326
+ )
327
+ generated = Path(self.build_temp) / (extension.name + ".c")
328
+ generated.parent.mkdir(parents=True, exist_ok=True)
329
+ _bend(
330
+ bend,
331
+ extension.bend_source,
332
+ "-o",
333
+ str(generated),
334
+ failure=f"Bend compilation failed: {extension.bend_source}",
335
+ )
336
+ generated.write_text(_patch_runtime(generated.read_text(), extension.name))
337
+ sources = extension.sources
338
+ extension.sources = [str(generated)]
339
+ try:
340
+ # build_ext --parallel shares this command. Isolate both the command
341
+ # and compiler so ordinary C extensions retain their own toolchain.
342
+ command = copy(self)
343
+ command.compiler = self._clang_compiler()
344
+ build_ext.build_extension(command, extension)
345
+ finally:
346
+ extension.sources = sources
347
+
348
+
349
+ class BuildPyWithBendLibrary(build_py):
350
+ """Bundle this repository's canonical bend/ directory when building its SDK."""
351
+
352
+ def run(self):
353
+ super().run()
354
+ target = Path(self.build_lib) / "bend_python" / "lib"
355
+ target.mkdir(parents=True, exist_ok=True)
356
+ for path in library_path().iterdir():
357
+ if path.suffix in {".bend", ".c"}:
358
+ self.copy_file(str(path), str(target / path.name))
359
+
360
+ def get_outputs(self, include_bytecode=True):
361
+ files = super().get_outputs(include_bytecode)
362
+ target = Path(self.build_lib) / "bend_python" / "lib"
363
+ files.extend(
364
+ str(target / path.name)
365
+ for path in library_path().iterdir()
366
+ if path.suffix in {".bend", ".c"}
367
+ )
368
+ return files
@@ -0,0 +1,85 @@
1
+ import Base
2
+ import ./python.bend as Python
3
+
4
+ law arity_empty:
5
+ {Python.arity([]) == 0n : Nat}
6
+
7
+ law arity_cons:
8
+ for x: Python.Object
9
+ for xs: List<Python.Object>
10
+ {Python.arity(x <> xs) == 1n+Python.arity(xs) : Nat}
11
+
12
+ law argument_head:
13
+ for +x: Python.Object
14
+ for xs: List<Python.Object>
15
+ {Python.argument(x <> xs, 0n) == Some{x} : Maybe<Python.Object>}
16
+
17
+ law argument_tail:
18
+ for x: Python.Object
19
+ for xs: List<Python.Object>
20
+ for +index: Nat
21
+ {Python.argument(x <> xs, 1n+index) == Python.argument(xs, index) : Maybe<Python.Object>}
22
+
23
+ law argument_empty:
24
+ for index: Nat
25
+ {Python.argument([], index) == None{} : Maybe<Python.Object>}
26
+
27
+ law singleton_roundtrip:
28
+ for +x: Python.Object
29
+ {Python.singleton([x]) == Some{x} : Maybe<Python.Object>}
30
+
31
+ law pair_roundtrip:
32
+ for +x: Python.Object
33
+ for +y: Python.Object
34
+ {Python.pair([x, y]) == Some{(x, y)} : Maybe<Pair(Python.Object, Python.Object)>}
35
+
36
+ law argument_at_end:
37
+ for xs: List<Python.Object>
38
+ {Python.argument(xs, Python.arity(xs)) == None{} : Maybe<Python.Object>}
39
+
40
+ law singleton_exact_arity:
41
+ for xs: List<Python.Object>
42
+ for value: Python.Object
43
+ for success: {Python.singleton(xs) == Some{value} : Maybe<Python.Object>}
44
+ {Python.arity(xs) == 1n : Nat}
45
+
46
+ law pair_exact_arity:
47
+ for xs: List<Python.Object>
48
+ for left: Python.Object
49
+ for right: Python.Object
50
+ for success: {Python.pair(xs) == Some{(left, right)} : Maybe<Pair(Python.Object, Python.Object)>}
51
+ {Python.arity(xs) == 2n : Nat}
52
+
53
+ # Converses of the exact-arity laws: every list of the right length parses.
54
+ # Together these characterize the pure parsers; the IO wrappers are covered below.
55
+ law singleton_complete:
56
+ for xs: List<Python.Object>
57
+ for exact: {Python.arity(xs) == 1n : Nat}
58
+ {Python.singleton(xs) == Python.argument(xs, 0n) : Maybe<Python.Object>}
59
+
60
+ law pair_complete:
61
+ for xs: List<Python.Object>
62
+ for exact: {Python.arity(xs) == 2n : Nat}
63
+ {Python.pair(xs) == Maybe.bind(&1, Python.Object, Pair(Python.Object, Python.Object), Python.argument(xs, 0n), left => Maybe.bind(&1, Python.Object, Pair(Python.Object, Python.Object), Python.argument(xs, 1n), right => Some{(left, right)})) : Maybe<Pair(Python.Object, Python.Object)>}
64
+
65
+ # The typed adapters' IO wrappers: a correct argument list yields exactly its
66
+ # elements, and any other length yields the TypeError effect. require_no_kwargs
67
+ # is a foreign effect checked in C and covered by tests, not here.
68
+ law singleton_accepted:
69
+ for +x: Python.Object
70
+ {Python.require_singleton(Python.singleton([x])) == IO.pure(Python.Object, x) : IO(Python.Object)}
71
+
72
+ law singleton_rejected:
73
+ for xs: List<Python.Object>
74
+ for wrong: {Nat.is_eq(Python.arity(xs), 1n) == False{} : Bool}
75
+ {Python.require_singleton(Python.singleton(xs)) == Python.type_error(Python.Object, "expected exactly one positional argument") : IO(Python.Object)}
76
+
77
+ law pair_accepted:
78
+ for +x: Python.Object
79
+ for +y: Python.Object
80
+ {Python.require_pair(Python.pair([x, y])) == IO.pure(Pair(Python.Object, Python.Object), (x, y)) : IO(Pair(Python.Object, Python.Object))}
81
+
82
+ law pair_rejected:
83
+ for xs: List<Python.Object>
84
+ for wrong: {Nat.is_eq(Python.arity(xs), 2n) == False{} : Bool}
85
+ {Python.require_pair(Python.pair(xs)) == Python.type_error(Pair(Python.Object, Python.Object), "expected exactly two positional arguments") : IO(Pair(Python.Object, Python.Object))}
@@ -0,0 +1,141 @@
1
+ import Base
2
+ import ./LAWS.bend as Laws
3
+ import ./python.bend as Python
4
+
5
+ def Laws.arity_empty():
6
+ {==}
7
+
8
+ def Laws.arity_cons(x, xs):
9
+ {==}
10
+
11
+ def Laws.argument_head(x, xs):
12
+ {==}
13
+
14
+ def Laws.argument_tail(x, xs, index):
15
+ {==}
16
+
17
+ def Laws.argument_empty(index):
18
+ {==}
19
+
20
+ def Laws.singleton_roundtrip(x):
21
+ {==}
22
+
23
+ def Laws.pair_roundtrip(x, y):
24
+ {==}
25
+
26
+ def Laws.argument_at_end(xs):
27
+ match xs:
28
+ case []:
29
+ {==}
30
+ case _ <> tail:
31
+ Laws.argument_at_end(tail)
32
+
33
+ def none_discriminator(-A: Type, value: Maybe<A>) -> Type:
34
+ match value:
35
+ case None{}:
36
+ Unit
37
+ case Some{_}:
38
+ Empty
39
+
40
+ def none_not_some(-A: Type, -value: A, success: {None{} == Some{value} : Maybe<A>}) -> Empty:
41
+ %success : none_discriminator(A, _)
42
+ Unit{}
43
+
44
+ def Laws.singleton_exact_arity(xs, value, success):
45
+ match xs:
46
+ case []:
47
+ Empty.absurd({0n == 1n : Nat}, none_not_some(Python.Object, value, success))
48
+ case [_]:
49
+ {==}
50
+ case _ <> _ <> tail:
51
+ Empty.absurd({1n+1n+Python.arity(tail) == 1n : Nat}, none_not_some(Python.Object, value, success))
52
+
53
+ def Laws.pair_exact_arity(xs, left, right, success):
54
+ match xs:
55
+ case []:
56
+ Empty.absurd({0n == 2n : Nat}, none_not_some(Pair(Python.Object, Python.Object), (left, right), success))
57
+ case [_]:
58
+ Empty.absurd({1n == 2n : Nat}, none_not_some(Pair(Python.Object, Python.Object), (left, right), success))
59
+ case [_, _]:
60
+ {==}
61
+ case _ <> _ <> _ <> tail:
62
+ Empty.absurd({1n+1n+1n+Python.arity(tail) == 2n : Nat}, none_not_some(Pair(Python.Object, Python.Object), (left, right), success))
63
+
64
+ def succ_discriminator(value: Nat) -> Type:
65
+ match value:
66
+ case 0n:
67
+ Empty
68
+ case 1n+_:
69
+ Unit
70
+
71
+ def succ_not_zero(-n: Nat, equal: {1n+n == 0n : Nat}) -> Empty:
72
+ %equal : succ_discriminator(_)
73
+ Unit{}
74
+
75
+ def predecessor(value: Nat) -> Nat:
76
+ match value:
77
+ case 0n:
78
+ 0n
79
+ case 1n+pred:
80
+ pred
81
+
82
+ def succ_injective(-a: Nat, -b: Nat, equal: {1n+a == 1n+b : Nat}) -> {a == b : Nat}:
83
+ %equal : {a == predecessor(_) : Nat}
84
+ {==}
85
+
86
+ def Laws.singleton_complete(xs, exact):
87
+ match xs:
88
+ case []:
89
+ {==}
90
+ case [_]:
91
+ {==}
92
+ case head <> _ <> tail:
93
+ Empty.absurd({None{} == Some{head} : Maybe<Python.Object>}, succ_not_zero(Python.arity(tail), succ_injective(1n+Python.arity(tail), 0n, exact)))
94
+
95
+ def Laws.pair_complete(xs, exact):
96
+ match xs:
97
+ case []:
98
+ {==}
99
+ case [_]:
100
+ {==}
101
+ case [_, _]:
102
+ {==}
103
+ case left <> right <> _ <> tail:
104
+ Empty.absurd({None{} == Some{(left, right)} : Maybe<Pair(Python.Object, Python.Object)>}, succ_not_zero(Python.arity(tail), succ_injective(1n+Python.arity(tail), 0n, succ_injective(1n+1n+Python.arity(tail), 1n, exact))))
105
+
106
+ def true_discriminator(value: Bool) -> Type:
107
+ match value:
108
+ case True{}:
109
+ Unit
110
+ case False{}:
111
+ Empty
112
+
113
+ def true_not_false(different: {True{} == False{} : Bool}) -> Empty:
114
+ %different : true_discriminator(_)
115
+ Unit{}
116
+
117
+ def Laws.singleton_accepted(x):
118
+ {==}
119
+
120
+ def Laws.singleton_rejected(xs, wrong):
121
+ match xs:
122
+ case []:
123
+ {==}
124
+ case [x]:
125
+ Empty.absurd({IO.pure(Python.Object, x) == Python.type_error(Python.Object, "expected exactly one positional argument") : IO(Python.Object)}, true_not_false(wrong))
126
+ case _ <> _ <> _:
127
+ {==}
128
+
129
+ def Laws.pair_accepted(x, y):
130
+ {==}
131
+
132
+ def Laws.pair_rejected(xs, wrong):
133
+ match xs:
134
+ case []:
135
+ {==}
136
+ case [_]:
137
+ {==}
138
+ case [x, y]:
139
+ Empty.absurd({IO.pure(Pair(Python.Object, Python.Object), (x, y)) == Python.type_error(Pair(Python.Object, Python.Object), "expected exactly two positional arguments") : IO(Pair(Python.Object, Python.Object))}, true_not_false(wrong))
140
+ case _ <> _ <> _ <> _:
141
+ {==}