pythsol 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.
Files changed (76) hide show
  1. pythsol/__init__.py +317 -0
  2. pythsol/cli/__init__.py +7 -0
  3. pythsol/cli/cache.py +78 -0
  4. pythsol/cli/cluster.py +45 -0
  5. pythsol/cli/commands/build.py +214 -0
  6. pythsol/cli/commands/check.py +54 -0
  7. pythsol/cli/commands/client_cmd.py +97 -0
  8. pythsol/cli/commands/deploy.py +103 -0
  9. pythsol/cli/commands/docs_cmd.py +62 -0
  10. pythsol/cli/commands/doctor.py +211 -0
  11. pythsol/cli/commands/emit_c.py +69 -0
  12. pythsol/cli/commands/faucet.py +66 -0
  13. pythsol/cli/commands/idl_cmd.py +69 -0
  14. pythsol/cli/commands/idl_publish.py +80 -0
  15. pythsol/cli/commands/init.py +65 -0
  16. pythsol/cli/commands/port_cmd.py +42 -0
  17. pythsol/cli/commands/profile.py +57 -0
  18. pythsol/cli/commands/serve.py +52 -0
  19. pythsol/cli/commands/test_cmd.py +217 -0
  20. pythsol/cli/commands/verify.py +96 -0
  21. pythsol/cli/commands/wallet.py +128 -0
  22. pythsol/cli/config.py +84 -0
  23. pythsol/cli/main.py +72 -0
  24. pythsol/cli/rpc.py +56 -0
  25. pythsol/cli/template_programs/__init__.py +1 -0
  26. pythsol/cli/template_programs/counter.py +52 -0
  27. pythsol/cli/template_programs/escrow.py +75 -0
  28. pythsol/cli/template_programs/ledger.py +68 -0
  29. pythsol/cli/template_programs/multisig.py +91 -0
  30. pythsol/cli/template_programs/poll.py +70 -0
  31. pythsol/cli/template_programs/vault.py +67 -0
  32. pythsol/cli/templates.py +192 -0
  33. pythsol/client/__init__.py +5 -0
  34. pythsol/client/from_idl.py +356 -0
  35. pythsol/client/gen.py +496 -0
  36. pythsol/client/port.py +355 -0
  37. pythsol/compiler/__init__.py +39 -0
  38. pythsol/compiler/diagnostics.py +130 -0
  39. pythsol/compiler/emit.py +3550 -0
  40. pythsol/compiler/front.py +84 -0
  41. pythsol/compiler/idl.py +465 -0
  42. pythsol/compiler/model.py +1329 -0
  43. pythsol/compiler/modules.py +320 -0
  44. pythsol/compiler/parse.py +59 -0
  45. pythsol/compiler/pipeline.py +152 -0
  46. pythsol/compiler/resolve.py +518 -0
  47. pythsol/compiler/subset.py +1061 -0
  48. pythsol/compiler/toolchain.py +438 -0
  49. pythsol/compiler/typecheck.py +2447 -0
  50. pythsol/playground/__init__.py +7 -0
  51. pythsol/playground/app.css +182 -0
  52. pythsol/playground/app.js +260 -0
  53. pythsol/playground/apple-touch-icon.png +0 -0
  54. pythsol/playground/docs.html +61 -0
  55. pythsol/playground/favicon-32.png +0 -0
  56. pythsol/playground/favicon.ico +0 -0
  57. pythsol/playground/favicon.svg +24 -0
  58. pythsol/playground/hero-bg.webp +0 -0
  59. pythsol/playground/index.html +66 -0
  60. pythsol/playground/landing.html +810 -0
  61. pythsol/playground/playground.html +77 -0
  62. pythsol/playground/robots.txt +4 -0
  63. pythsol/playground/server.py +336 -0
  64. pythsol/playground/site-bg.svg +146 -0
  65. pythsol/playground/site.css +1105 -0
  66. pythsol/py.typed +0 -0
  67. pythsol/refrt/__init__.py +200 -0
  68. pythsol/runtime/__init__.py +17 -0
  69. pythsol/runtime/pythsol_rt.h +1216 -0
  70. pythsol/testing/__init__.py +12 -0
  71. pythsol/testing/plugin.py +437 -0
  72. pythsol-0.1.0.dist-info/METADATA +178 -0
  73. pythsol-0.1.0.dist-info/RECORD +76 -0
  74. pythsol-0.1.0.dist-info/WHEEL +4 -0
  75. pythsol-0.1.0.dist-info/entry_points.txt +6 -0
  76. pythsol-0.1.0.dist-info/licenses/LICENSE +202 -0
pythsol/client/port.py ADDED
@@ -0,0 +1,355 @@
1
+ """Turn an Anchor-legacy IDL into a PythSol program skeleton.
2
+
3
+ The bodies are ``pass``. Account wrappers are inferred: a ``seeds`` constraint
4
+ becomes ``Seeds[Init[T], "seed"]``, a mutable account on ``initialize`` /
5
+ ``init`` / ``make`` / ``create`` / ``open`` becomes ``Init[T]``, and other
6
+ mutable accounts become ``Mut[T]``. The compiler still has to accept the
7
+ result; fill the bodies in before the program can run.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ __all__ = ["render_port"]
15
+
16
+ _INIT_NAMES = {"initialize", "init", "make", "create", "open"}
17
+ _SCALARS = {"u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128"}
18
+ _WRAPPERS = ("Seeds[", "HasOne[", "CloseTo[", "Address[", "Owner[")
19
+
20
+
21
+ class PortError(Exception):
22
+ """The IDL is too incomplete to sketch a program."""
23
+
24
+
25
+ def render_port(idl: dict[str, Any]) -> str:
26
+ """Return Python source for a skeleton program."""
27
+ program = str(idl.get("name") or "program")
28
+ types = _type_map(idl)
29
+ accounts = [item for item in idl.get("accounts") or [] if isinstance(item, dict)]
30
+ if not accounts:
31
+ raise PortError("IDL has no accounts to port")
32
+ imports: set[str] = {"account", "instruction"}
33
+ blocks: list[str] = []
34
+ for info in types.values():
35
+ body = info.get("type")
36
+ if isinstance(body, dict) and body.get("kind") == "enum":
37
+ imports.add("enum")
38
+ blocks.append(_enum_block(info))
39
+ for info in types.values():
40
+ if _is_user_struct(info):
41
+ imports.add("struct")
42
+ blocks.append(_struct_block(info, types, imports))
43
+ for account in accounts:
44
+ blocks.append(_account_block(account, types, imports))
45
+ errors = [item for item in idl.get("errors") or [] if isinstance(item, dict)]
46
+ if errors:
47
+ imports.update({"error", "ProgramError"})
48
+ blocks.append(_error_block(program, errors))
49
+ instructions = [item for item in idl.get("instructions") or [] if isinstance(item, dict)]
50
+ if not instructions:
51
+ raise PortError("IDL has no instructions to port")
52
+ for instr in instructions:
53
+ blocks.append(_instruction_block(instr, accounts, types, imports))
54
+ header = [
55
+ f'"""Ported from the `{program}` IDL.',
56
+ "",
57
+ "Each instruction body is a placeholder. Replace ``pass`` with the program.",
58
+ '"""',
59
+ "",
60
+ "from pythsol import (",
61
+ ]
62
+ for name in _import_lines(imports):
63
+ header.append(f" {name},")
64
+ header.append(")")
65
+ header.append("")
66
+ return "\n".join([*header, "", *blocks]).rstrip() + "\n"
67
+
68
+
69
+ def _import_lines(names: set[str]) -> list[str]:
70
+ order = [
71
+ "Account",
72
+ "Address",
73
+ "CloseTo",
74
+ "FixedArray",
75
+ "FixedString",
76
+ "HasOne",
77
+ "Init",
78
+ "Mut",
79
+ "Option",
80
+ "Owner",
81
+ "ProgramError",
82
+ "Pubkey",
83
+ "Seeds",
84
+ "Signer",
85
+ "account",
86
+ "enum",
87
+ "error",
88
+ "instruction",
89
+ "struct",
90
+ "u8",
91
+ "u16",
92
+ "u32",
93
+ "u64",
94
+ "u128",
95
+ "i8",
96
+ "i16",
97
+ "i32",
98
+ "i64",
99
+ "i128",
100
+ ]
101
+ return [name for name in order if name in names]
102
+
103
+
104
+ def _type_map(idl: dict[str, Any]) -> dict[str, dict[str, Any]]:
105
+ found: dict[str, dict[str, Any]] = {}
106
+ for item in idl.get("types") or []:
107
+ if isinstance(item, dict) and isinstance(item.get("name"), str):
108
+ found[item["name"]] = item
109
+ return found
110
+
111
+
112
+ def _snake(name: str) -> str:
113
+ out: list[str] = []
114
+ for char in name:
115
+ if char.isupper() and out and out[-1] != "_":
116
+ out.append("_")
117
+ out.append(char.lower())
118
+ return "".join(out)
119
+
120
+
121
+ def _pascal(name: str) -> str:
122
+ parts = name.replace("-", "_").split("_")
123
+ return "".join(part[:1].upper() + part[1:] for part in parts if part) or "Program"
124
+
125
+
126
+ def _is_user_struct(info: dict[str, Any]) -> bool:
127
+ body = info.get("type")
128
+ if not isinstance(body, dict) or body.get("kind") != "struct":
129
+ return False
130
+ fields = body.get("fields")
131
+ if not isinstance(fields, list):
132
+ return False
133
+ if (
134
+ len(fields) == 2
135
+ and isinstance(fields[0], dict)
136
+ and isinstance(fields[1], dict)
137
+ and fields[0].get("name") == "tag"
138
+ and fields[1].get("name") == "value"
139
+ ):
140
+ return False
141
+ return not (
142
+ len(fields) == 2
143
+ and isinstance(fields[0], dict)
144
+ and isinstance(fields[1], dict)
145
+ and fields[0].get("name") == "len"
146
+ and fields[1].get("name") == "data"
147
+ )
148
+
149
+
150
+ def _annotation(ty: Any, types: dict[str, dict[str, Any]], imports: set[str]) -> str:
151
+ if ty == "publicKey":
152
+ imports.add("Pubkey")
153
+ return "Pubkey"
154
+ if ty == "bool":
155
+ return "bool"
156
+ if isinstance(ty, str) and ty in _SCALARS:
157
+ imports.add(ty)
158
+ return ty
159
+ if isinstance(ty, dict) and isinstance(ty.get("array"), list) and len(ty["array"]) == 2:
160
+ inner, count = ty["array"]
161
+ imports.add("FixedArray")
162
+ return f"FixedArray[{_annotation(inner, types, imports)}, {count}]"
163
+ if isinstance(ty, dict) and isinstance(ty.get("option"), (str, dict)):
164
+ imports.add("Option")
165
+ return f"Option[{_annotation(ty['option'], types, imports)}]"
166
+ if isinstance(ty, dict) and isinstance(ty.get("defined"), str):
167
+ return _defined(ty["defined"], types, imports)
168
+ imports.add("u64")
169
+ return "u64"
170
+
171
+
172
+ def _defined(name: str, types: dict[str, dict[str, Any]], imports: set[str]) -> str:
173
+ info = types.get(name)
174
+ body = info.get("type") if isinstance(info, dict) else None
175
+ if not isinstance(body, dict):
176
+ return name
177
+ if body.get("kind") == "enum":
178
+ return name
179
+ fields = body.get("fields")
180
+ if isinstance(fields, list) and len(fields) == 2:
181
+ first, second = fields[0], fields[1]
182
+ if (
183
+ isinstance(first, dict)
184
+ and isinstance(second, dict)
185
+ and first.get("name") == "tag"
186
+ and second.get("name") == "value"
187
+ ):
188
+ imports.add("Option")
189
+ return f"Option[{_annotation(second.get('type'), types, imports)}]"
190
+ if (
191
+ isinstance(first, dict)
192
+ and isinstance(second, dict)
193
+ and first.get("name") == "len"
194
+ and second.get("name") == "data"
195
+ and isinstance(second.get("type"), dict)
196
+ and isinstance(second["type"].get("array"), list)
197
+ ):
198
+ imports.add("FixedString")
199
+ return f"FixedString[{second['type']['array'][1]}]"
200
+ return name
201
+
202
+
203
+ def _enum_block(info: dict[str, Any]) -> str:
204
+ name = str(info.get("name") or "Enum")
205
+ body = info.get("type") if isinstance(info.get("type"), dict) else {}
206
+ variants = body.get("variants") if isinstance(body, dict) else []
207
+ lines = ["@enum", f"class {name}:"]
208
+ if not isinstance(variants, list) or not variants:
209
+ lines.append(" pass")
210
+ return "\n".join(lines)
211
+ for index, variant in enumerate(variants):
212
+ label = "Variant"
213
+ if isinstance(variant, dict) and isinstance(variant.get("name"), str):
214
+ label = variant["name"]
215
+ lines.append(f" {label} = {index}")
216
+ return "\n".join(lines)
217
+
218
+
219
+ def _struct_block(info: dict[str, Any], types: dict[str, dict[str, Any]], imports: set[str]) -> str:
220
+ name = str(info.get("name") or "Struct")
221
+ body = info.get("type") if isinstance(info.get("type"), dict) else {}
222
+ fields = body.get("fields") if isinstance(body, dict) else []
223
+ lines = ["@struct", f"class {name}:"]
224
+ if not isinstance(fields, list) or not fields:
225
+ lines.append(" pass")
226
+ return "\n".join(lines)
227
+ for field in fields:
228
+ if not isinstance(field, dict):
229
+ continue
230
+ field_name = str(field.get("name") or "field")
231
+ lines.append(f" {field_name}: {_annotation(field.get('type'), types, imports)}")
232
+ return "\n".join(lines)
233
+
234
+
235
+ def _account_block(
236
+ account: dict[str, Any],
237
+ types: dict[str, dict[str, Any]],
238
+ imports: set[str],
239
+ ) -> str:
240
+ name = str(account.get("name") or "Account")
241
+ body = account.get("type") if isinstance(account.get("type"), dict) else {}
242
+ fields = body.get("fields") if isinstance(body, dict) else []
243
+ lines = ["@account", f"class {name}:"]
244
+ if not isinstance(fields, list) or not fields:
245
+ lines.append(" pass")
246
+ return "\n".join(lines)
247
+ for field in fields:
248
+ if not isinstance(field, dict):
249
+ continue
250
+ field_name = str(field.get("name") or "field")
251
+ lines.append(f" {field_name}: {_annotation(field.get('type'), types, imports)}")
252
+ return "\n".join(lines)
253
+
254
+
255
+ def _error_block(program: str, errors: list[dict[str, Any]]) -> str:
256
+ lines = [
257
+ "@error",
258
+ f"class {_pascal(program)}Error(ProgramError):",
259
+ ' """Ported error names. Codes on chain are 6000 + declaration index."""',
260
+ ]
261
+ for index, error in enumerate(errors):
262
+ label = str(error.get("name") or f"Error{index}")
263
+ lines.append(f" {label} = {index}")
264
+ return "\n".join(lines)
265
+
266
+
267
+ def _match_account(param: str, accounts: list[dict[str, Any]]) -> str | None:
268
+ for account in accounts:
269
+ name = str(account.get("name") or "")
270
+ if name == param or _snake(name) == param:
271
+ return name
272
+ return None
273
+
274
+
275
+ def _constraint(account: dict[str, Any], kind: str, key: str) -> str | None:
276
+ raw = account.get("constraints")
277
+ if not isinstance(raw, list):
278
+ return None
279
+ for item in raw:
280
+ if isinstance(item, dict) and item.get("kind") == kind and isinstance(item.get(key), str):
281
+ return str(item[key])
282
+ return None
283
+
284
+
285
+ def _account_annotation(
286
+ instr_name: str,
287
+ account: dict[str, Any],
288
+ accounts: list[dict[str, Any]],
289
+ imports: set[str],
290
+ ) -> str:
291
+ if account.get("isSigner"):
292
+ imports.add("Signer")
293
+ return "Signer"
294
+ matched = _match_account(str(account.get("name") or ""), accounts)
295
+ if matched is None:
296
+ imports.add("Pubkey")
297
+ return "Pubkey"
298
+ seed = _constraint(account, "seeds", "seed")
299
+ has_one = _constraint(account, "has_one", "field")
300
+ close = _constraint(account, "close", "account")
301
+ owner = _constraint(account, "owner", "account")
302
+ address = _constraint(account, "address", "pubkey")
303
+ if seed is not None:
304
+ imports.update({"Seeds", "Init"})
305
+ return f'Seeds[Init[{matched}], "{seed}"]'
306
+ if has_one is not None:
307
+ imports.update({"HasOne", "Mut"})
308
+ return f'HasOne[Mut[{matched}], "{has_one}"]'
309
+ if close is not None:
310
+ imports.update({"CloseTo", "Mut"})
311
+ return f"CloseTo[Mut[{matched}], {close}]"
312
+ if owner is not None:
313
+ imports.update({"Owner", "Mut"})
314
+ return f"Owner[Mut[{matched}], {owner}]"
315
+ if address is not None:
316
+ imports.update({"Address", "Mut"})
317
+ return f"Address[Mut[{matched}], {address}]"
318
+ if instr_name in _INIT_NAMES and account.get("isMut"):
319
+ imports.add("Init")
320
+ return f"Init[{matched}]"
321
+ if account.get("isMut"):
322
+ imports.add("Mut")
323
+ return f"Mut[{matched}]"
324
+ imports.add("Account")
325
+ return f"Account[{matched}]"
326
+
327
+
328
+ def _instruction_block(
329
+ instr: dict[str, Any],
330
+ accounts: list[dict[str, Any]],
331
+ types: dict[str, dict[str, Any]],
332
+ imports: set[str],
333
+ ) -> str:
334
+ name = str(instr.get("name") or "instruction")
335
+ params: list[str] = []
336
+ for account in instr.get("accounts") or []:
337
+ if not isinstance(account, dict):
338
+ continue
339
+ param = str(account.get("name") or "account")
340
+ ann = _account_annotation(name, account, accounts, imports)
341
+ suffix = ""
342
+ if ann.startswith(_WRAPPERS):
343
+ suffix = " # type: ignore[name-defined] # noqa: F821"
344
+ params.append(f" {param}: {ann},{suffix}")
345
+ for arg in instr.get("args") or []:
346
+ if not isinstance(arg, dict):
347
+ continue
348
+ param = str(arg.get("name") or "arg")
349
+ params.append(f" {param}: {_annotation(arg.get('type'), types, imports)},")
350
+ lines = ["@instruction", f"def {name}("]
351
+ lines.extend(params)
352
+ lines.append(") -> None:")
353
+ lines.append(f' """TODO: port {name}."""')
354
+ lines.append(" pass")
355
+ return "\n".join(lines)
@@ -0,0 +1,39 @@
1
+ """PythSol compiler — front-end and toolchain modules.
2
+
3
+ Pipeline: ``parse → subset → resolve → typecheck`` produce
4
+ :class:`~pythsol.compiler.diagnostics.Diagnostic` objects; ``toolchain`` then
5
+ compiles the generated C (Phase 3) to native SBF with clang + ld.lld.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pythsol.compiler.diagnostics import PS4010, Diagnostic, PysolError
11
+ from pythsol.compiler.front import check_source, check_tree
12
+ from pythsol.compiler.toolchain import (
13
+ ToolchainPaths,
14
+ compile_to_sbf,
15
+ find_cargo_build_sbf,
16
+ find_clang,
17
+ find_lld,
18
+ find_solana_cli,
19
+ locate_platform_tools,
20
+ sbpf_compile_args,
21
+ sbpf_link_args,
22
+ )
23
+
24
+ __all__ = [
25
+ "PS4010",
26
+ "Diagnostic",
27
+ "PysolError",
28
+ "ToolchainPaths",
29
+ "check_source",
30
+ "check_tree",
31
+ "compile_to_sbf",
32
+ "find_cargo_build_sbf",
33
+ "find_clang",
34
+ "find_lld",
35
+ "find_solana_cli",
36
+ "locate_platform_tools",
37
+ "sbpf_compile_args",
38
+ "sbpf_link_args",
39
+ ]
@@ -0,0 +1,130 @@
1
+ """Stable diagnostic codes and helpers shared by every compiler stage.
2
+
3
+ Codes follow the pattern used in ``TRD.md`` §5.3:
4
+
5
+ * ``PS1xxx`` — subset/parse
6
+ * ``PS2xxx`` — type check
7
+ * ``PS3xxx`` — semantic/aliasing
8
+ * ``PS9xxx`` — internal compiler error (always a bug: report it)
9
+
10
+ The registry below is written for Phase 2, but Phase 1 already hands back
11
+ structured objects for toolchain errors.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class Diagnostic:
21
+ """One problem with a stable code and 1-based source position.
22
+
23
+ ``render()`` produces the clang-style string the CLI prints:
24
+
25
+ PS1003 program.py:4:5: unsupported statement: Try
26
+
27
+ The shape of :meth:`as_dict` is the ``--json`` contract; keep it additive.
28
+ """
29
+
30
+ code: str
31
+ message: str
32
+ file: str = "<program>"
33
+ line: int = 1
34
+ col: int = 1
35
+ fix: str | None = None
36
+
37
+ def render(self) -> str:
38
+ text = f"{self.code} {self.file}:{self.line}:{self.col}: {self.message}"
39
+ if self.fix:
40
+ return f"{text}\n fix: {self.fix}"
41
+ return text
42
+
43
+ def position_key(self) -> tuple[int, int]:
44
+ return (self.line, self.col)
45
+
46
+ def as_dict(self) -> dict[str, str | int | None]:
47
+ return {
48
+ "code": self.code,
49
+ "message": self.message,
50
+ "file": self.file,
51
+ "line": self.line,
52
+ "col": self.col,
53
+ "fix": self.fix,
54
+ }
55
+
56
+
57
+ class PysolError(Exception):
58
+ """A structured compiler/toolchain error raised back to the CLI.
59
+
60
+ Not a dataclass: exceptions must accept ``__traceback__`` writes when
61
+ raised (a frozen dataclass blocks click's re-raise machinery).
62
+ """
63
+
64
+ def __init__(self, code: str, message: str, fix: str | None = None) -> None:
65
+ super().__init__(message)
66
+ self.code = code
67
+ self.message = message
68
+ self.fix = fix
69
+
70
+ def render(self) -> str:
71
+ text = f"{self.code}: {self.message}"
72
+ if self.fix:
73
+ return f"{text}\nsuggested fix: {self.fix}"
74
+ return text
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Subset / parse problems (PS1xxx)
79
+ # ---------------------------------------------------------------------------
80
+ PS1000 = "PS1000" # unparseable syntax
81
+ PS1001 = "PS1001" # unsupported import
82
+ PS1002 = "PS1002" # floats/complex in program code
83
+ PS1003 = "PS1003" # unsupported statement (try/except, with, yield…)
84
+ PS1004 = "PS1004" # dynamic `import` or `__import__`
85
+ PS1005 = "PS1005" # `*args`/`**kwargs`/defaults on instruction functions are not callable.
86
+ PS1006 = "PS1006" # missing annotation on a parameter/field local.
87
+ PS1007 = "PS1007" # unsupported type annotation (unbounded containers, Any).
88
+ PS1008 = "PS1008" # unsupported attribute access / dynamic attribute assignment.
89
+ PS1009 = "PS1009" # unsupported operator (is, in, not-in on non-enums).
90
+ PS1010 = "PS1010" # unsupported call (dynamic callee).
91
+ PS1011 = "PS1011" # unsupported comprehension/generator.
92
+ PS1012 = "PS1012" # unsupported `class` (only @account/@error/@enum programs).
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # Type-check problems (PS2xxx)
96
+ # ---------------------------------------------------------------------------
97
+ PS2000 = "PS2000" # missing annotation on anything not inferable.
98
+ PS2001 = "PS2001" # unsupported/unknown type annotation.
99
+ PS2002 = "PS2002" # `float`/`complex` literal in program code.
100
+ PS2003 = "PS2003" # unbounded integers (int) are not allowed.
101
+ PS2004 = "PS2004" # bytes/str/list/tuple/dict/set in a program.
102
+ PS2005 = "PS2005" # unsupported assignment target.
103
+ PS2006 = "PS2006" # TypeError / unexpected operand.
104
+ PS2007 = "PS2007" # int width mismatch/overflow on constant expression.
105
+ PS2008 = "PS2008" # unsupported decorator target.
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # Semantic / account-model problems (PS3xxx)
109
+ # ---------------------------------------------------------------------------
110
+ PS3000 = "PS3000" # aliasing refusal (mutable data used for a live aliasing reference).
111
+ PS3001 = "PS3001" # unsupported constraint combination.
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # Toolchain problems (PS40xx) — see toolchain.py for exact text.
115
+ # ---------------------------------------------------------------------------
116
+ PS4010 = "PS4010"
117
+
118
+ # Model/elaboration problems (PS41xx) — raised by model.py when a type-checked
119
+ # program still contains something the emitter model cannot represent.
120
+ PS4001 = "PS4001" # unsupported construct in the emitter model
121
+
122
+ # Emitter problems (PS5xxx) — raised by emit.py while lowering an AST body.
123
+ PS5001 = "PS5001" # unsupported statement in an instruction body
124
+ PS5002 = "PS5002" # unsupported expression in an instruction body
125
+ PS5003 = "PS5003" # Init[...] without a `bump: u8` data parameter / payer signer
126
+
127
+ # ---------------------------------------------------------------------------
128
+ # Internal compiler errors (PS9xxx)
129
+ # ---------------------------------------------------------------------------
130
+ PS9000 = "PS9000" # internal bug: unhandled construct in emitter.