mt-lang 0.4.24 → 0.4.26
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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +8 -8
- data/README.md +39 -7
- data/docs/build-guide.md +5 -0
- data/docs/index.html +61 -29
- data/docs/language-design.md +2 -2
- data/docs/language-manual.md +15 -3
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +22 -0
- data/lib/milk_tea/core/c_backend/statements.rb +118 -1
- data/lib/milk_tea/core/control_flow/builder.rb +12 -0
- data/lib/milk_tea/core/lowering/utils.rb +8 -1
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +43 -2
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +7 -1
- data/lib/milk_tea/tooling/bindgen_cli.rb +56 -0
- data/lib/milk_tea/tooling/cli/commands/std.rb +84 -0
- data/lib/milk_tea/tooling/cli.rb +27 -1
- data/lib/milk_tea/tooling/linter/fix_engine.rb +100 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +69 -11
- data/lib/milk_tea/tooling/linter/visitors.rb +58 -31
- data/lib/milk_tea/tooling/linter.rb +8 -1
- data/lib/milk_tea/tooling/std_catalog.rb +277 -0
- data/lib/milk_tea/tooling.rb +1 -0
- data/std/async/libuv_runtime.mt +20 -23
- data/std/async/mailbox.mt +2 -2
- data/std/base64.mt +3 -6
- data/std/behavior_tree.mt +1 -6
- data/std/binary.mt +2 -5
- data/std/c/stb_image.mt +15 -15
- data/std/color.mt +20 -20
- data/std/cookie.mt +1 -4
- data/std/deque.mt +2 -8
- data/std/encoding.mt +1 -4
- data/std/fmt.mt +1 -4
- data/std/graph.mt +5 -1
- data/std/htn.mt +9 -1
- data/std/http/server.mt +1 -4
- data/std/http.mt +14 -17
- data/std/jobs.mt +9 -10
- data/std/json.mt +17 -28
- data/std/linked_map.mt +3 -9
- data/std/map.mt +1 -4
- data/std/net/channel.mt +6 -6
- data/std/net/lobby.mt +3 -3
- data/std/net/manager.mt +0 -1
- data/std/net/mux.mt +1 -1
- data/std/net/packet.mt +4 -1
- data/std/net/punch.mt +3 -2
- data/std/net/rpc.mt +1 -3
- data/std/net/session.mt +12 -25
- data/std/net/sync.mt +3 -3
- data/std/net/turn.mt +7 -3
- data/std/net.mt +145 -159
- data/std/noise.mt +11 -7
- data/std/ordered_map.mt +2 -8
- data/std/ordered_set.mt +3 -5
- data/std/path.mt +1 -4
- data/std/process.mt +7 -18
- data/std/random.mt +2 -2
- data/std/sdl3/runtime.mt +1 -1
- data/std/steering.mt +27 -4
- data/std/string.mt +1 -4
- data/std/tar.mt +8 -10
- data/std/terminal.mt +21 -40
- data/std/thread.mt +3 -4
- data/std/tls.mt +22 -29
- data/std/toml.mt +23 -31
- data/std/uri.mt +2 -1
- data/std/utility.mt +2 -2
- data/std/vec.mt +7 -17
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f75e56a0679c161c076efb3dd694afe256154c512e7ffae171e13c2d98d181d
|
|
4
|
+
data.tar.gz: ff00b0459c88d4fd31d12b0fc25e953d339fe7d8f7ed10964b5d8e2b70065a6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72e4cfb94f691ace787f420cbd2b6481c0b189282d21dbe42b47ebd795844f5e42a59a95fc64f1e75f56c6ec7119723231fef4601c8d856f6d7ada5ed107d26f
|
|
7
|
+
data.tar.gz: 01ba76cc9a0aeb3ab65fbd5dea21a9995ace49a3328003352fa13b0c54c9cc42e8b3e1bb6423d703913439ca4de91b4f70348d128a6c813a835c28e9345ff88b
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.6
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -5,7 +5,7 @@ GEM
|
|
|
5
5
|
drb (2.2.3)
|
|
6
6
|
fiddle (1.1.8)
|
|
7
7
|
logger (1.7.0)
|
|
8
|
-
minitest (6.0.
|
|
8
|
+
minitest (6.0.6)
|
|
9
9
|
drb (~> 2.0)
|
|
10
10
|
prism (~> 1.5)
|
|
11
11
|
minitest-coverage (1.0.0.b3)
|
|
@@ -15,7 +15,7 @@ GEM
|
|
|
15
15
|
puma (6.6.1)
|
|
16
16
|
nio4r (~> 2.0)
|
|
17
17
|
racc (1.8.1)
|
|
18
|
-
rack (3.2.
|
|
18
|
+
rack (3.2.7)
|
|
19
19
|
rack-protection (4.2.1)
|
|
20
20
|
base64 (>= 0.1.0)
|
|
21
21
|
logger (>= 1.6.0)
|
|
@@ -34,7 +34,7 @@ GEM
|
|
|
34
34
|
rack-protection (= 4.2.1)
|
|
35
35
|
rack-session (>= 2.0.0, < 3)
|
|
36
36
|
tilt (~> 2.0)
|
|
37
|
-
tilt (2.
|
|
37
|
+
tilt (2.9.0)
|
|
38
38
|
tomlrb (2.0.4)
|
|
39
39
|
|
|
40
40
|
PLATFORMS
|
|
@@ -58,25 +58,25 @@ CHECKSUMS
|
|
|
58
58
|
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
59
59
|
fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee
|
|
60
60
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
61
|
-
minitest (6.0.
|
|
61
|
+
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
|
62
62
|
minitest-coverage (1.0.0.b3) sha256=27c8b6b8df1259325dd206baa2b143c594f8a88b7dfd97fd8d3389a0e8a6ad26
|
|
63
63
|
mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198
|
|
64
64
|
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
|
|
65
65
|
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
66
66
|
puma (6.6.1) sha256=b9b56e4a4ea75d1bfa6d9e1972ee2c9f43d0883f011826d914e8e37b3694ea1e
|
|
67
67
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
68
|
-
rack (3.2.
|
|
68
|
+
rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7
|
|
69
69
|
rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac
|
|
70
70
|
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
|
|
71
71
|
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
|
|
72
72
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
73
73
|
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
|
74
74
|
sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080
|
|
75
|
-
tilt (2.
|
|
75
|
+
tilt (2.9.0) sha256=da5735d0280bba96e9a91041bb14aee435ccad5c17b0fa519249ae543d9aa3a5
|
|
76
76
|
tomlrb (2.0.4) sha256=262f77947ac3ac9b3366a0a5940ecd238300c553e2e14f22009e2afcd2181b99
|
|
77
77
|
|
|
78
78
|
RUBY VERSION
|
|
79
|
-
ruby 4.0.
|
|
79
|
+
ruby 4.0.6
|
|
80
80
|
|
|
81
81
|
BUNDLED WITH
|
|
82
|
-
4.0.
|
|
82
|
+
4.0.16
|
data/README.md
CHANGED
|
@@ -840,6 +840,8 @@ Nullability:
|
|
|
840
840
|
- For non-pointer value bases (`int`, `bool`, `float`, structs, ...), `T?` is stored inline by value as a tagged optional (a presence flag plus the value). It copies by value with no hidden heap allocation or pointer aliasing.
|
|
841
841
|
- Use `null` for absence in any nullable context.
|
|
842
842
|
- In nullable pointer-like contexts, prefer `null` over `zero[ptr[T]]`.
|
|
843
|
+
- `null` is only a value of nullable types. Comparing a non-nullable pointer against `null` is a type error: either declare the value `T?` when null is possible, or compare against `zero[ptr[T]]` (also `zero[cstr]`, `zero[own[T]]`) when testing the raw C zero pointer at the raw ABI level. Nullable types must use `null`, not `zero[...]`.
|
|
844
|
+
- Null checks narrow flow: inside the `!= null` branch (and after an `== null` branch exits), the binding is treated as non-null. A further null check on a narrowed binding is still legal; the linter reports it as `redundant-null-check`.
|
|
843
845
|
- `ref[T]` is non-null and cannot be nullable.
|
|
844
846
|
- At an FFI boundary (`external` / `foreign function` parameters and returns), only pointer-like `T?` is allowed. A non-pointer value nullable such as `int?` is rejected — use `ptr[T]?` or pass an explicit struct.
|
|
845
847
|
|
|
@@ -911,6 +913,8 @@ A `field_handle`'s `.type` is usable directly **in type position** within a comp
|
|
|
911
913
|
|
|
912
914
|
### Standard library
|
|
913
915
|
|
|
916
|
+
The authoritative module catalog is `mtc std list [--json]`; it discovers every hand-written module shipped in `std/` with its category, description, and path. Generated binding modules (`std/c/*` and imported-binding wrappers such as `raylib` or `zstd`) are excluded from `list` but stay viewable with `mtc std show`.
|
|
917
|
+
|
|
914
918
|
Core modules in `std/`:
|
|
915
919
|
|
|
916
920
|
- `std.linear_algebra` — extends native vector/matrix/quaternion types with `dot`, `cross`, `length`, `normalized`, `lerp`, `identity`, `transpose`, `conjugate` (pure Mt, no C dependency beyond `std.math` for `sqrt`)
|
|
@@ -922,7 +926,7 @@ Core modules in `std/`:
|
|
|
922
926
|
- `std.encoding` — UTF-8 validation (`is_valid_utf8`, `utf8_codepoint_count`, `decode_utf8_codepoint`, `utf8_overlong_check`)
|
|
923
927
|
- `std.string.String` — growable owned UTF-8 text
|
|
924
928
|
- `std.mem.heap`, `std.mem.arena`, `std.mem.pool`, `std.mem.stack`, `std.mem.tracking`, `std.mem.endian` — allocators and memory utilities
|
|
925
|
-
- `std.async` — task runtime (`sleep`, `work`, `completed`, `result`, `wait`, `run`)
|
|
929
|
+
- `std.async` — task runtime (`sleep`, `work`, `completed`, `result`, `wait`, `run`, plus explicit-runtime variants `wait_on`, `work_on`, `sleep_on`, `run_on`, `pump`, `with_runtime`)
|
|
926
930
|
- `std.option.Option[T]` — optional value with `is_some`, `is_none`, `unwrap`, `expect`, `unwrap_or`, `unwrap_or_else` (auto-imported via prelude)
|
|
927
931
|
- `std.result.Result[T, E]` — fallible computation with `is_success`, `is_failure`, `unwrap`, `unwrap_error`, `unwrap_or`, `unwrap_or_else`, `ok`, `error`, `map_error` (auto-imported via prelude)
|
|
928
932
|
|
|
@@ -1241,6 +1245,13 @@ Resolve a module by name, build it, and run it:
|
|
|
1241
1245
|
mtc run-module <module> # Resolve, build, and run a module by name (e.g. std.http.server)
|
|
1242
1246
|
```
|
|
1243
1247
|
|
|
1248
|
+
Standard library inspection:
|
|
1249
|
+
|
|
1250
|
+
```
|
|
1251
|
+
mtc std list [--json] # List hand-written std modules grouped by category
|
|
1252
|
+
mtc std show MODULE # Print a module's source (dotted or slashed names)
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1244
1255
|
Toolchain maintenance:
|
|
1245
1256
|
|
|
1246
1257
|
```
|
|
@@ -1259,6 +1270,28 @@ mtc dap # Start the Debug Adapter Protocol server
|
|
|
1259
1270
|
mtc docs [--open] [--port P] # Serve the local documentation site
|
|
1260
1271
|
```
|
|
1261
1272
|
|
|
1273
|
+
`mtc bindgen` nullable policy:
|
|
1274
|
+
|
|
1275
|
+
C headers usually do not state which functions can return NULL. A nullable policy file lists those symbols so generated raw bindings expose them as `T?` and null checks work downstream. The policy feeds the same override machinery as the checked-in `std.c.*` registries and shows up in `--nullable-report`.
|
|
1276
|
+
|
|
1277
|
+
```sh
|
|
1278
|
+
mtc bindgen mylib.mt mylib.h --nullable-policy nullable_policy.json --nullable-report report.json
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
```json
|
|
1282
|
+
{
|
|
1283
|
+
"return_types": {
|
|
1284
|
+
"mylib_load": "ptr[ubyte]?",
|
|
1285
|
+
"mylib_get_error": "cstr?"
|
|
1286
|
+
},
|
|
1287
|
+
"parameters": {
|
|
1288
|
+
"mylib_read": { "out_size": "ptr[uint]?" }
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
Types must match what bindgen would otherwise emit for the symbol (minus the `?`). Bindgen rejects policy entries for unknown symbols, so a stale policy fails loudly instead of silently drifting.
|
|
1294
|
+
|
|
1262
1295
|
Build and run commands support `--profile`, `--platform`, `--cc`, `--keep-c`, `--locked`, `--frozen`, and `-I` include paths. Dependency-locked flows support `--locked` (use package.lock) and `--frozen` (require current package.lock).
|
|
1263
1296
|
|
|
1264
1297
|
Global options work with any command (before or after the subcommand, up to a `--` separator): `-h`/`--help` (also `mtc help <command>` for command-specific help), `-V`/`--version`, `-q`/`--quiet` (suppress informational output), `-v`/`--verbose` (per-file progress), and `--color auto|always|never`. Generate shell completions with `mtc completions bash|zsh|fish`. Timing breakdowns use `--timings` (on `format` and `lint`); `lint` reports warnings with per-file progress.
|
|
@@ -1266,14 +1299,13 @@ Global options work with any command (before or after the subcommand, up to a `-
|
|
|
1266
1299
|
Diagnostic output uses standard compiler format (file:line:column with source context, error codes, and caret highlighting):
|
|
1267
1300
|
|
|
1268
1301
|
```
|
|
1269
|
-
[
|
|
1270
|
-
--> file.mt:1:16
|
|
1302
|
+
error[sema/error]: unknown type floa -- /tmp/diag_probe.mt:2:12
|
|
1271
1303
|
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1304
|
+
2 | let x: floa = 1
|
|
1305
|
+
| ^~~~
|
|
1306
|
+
= help: did you mean 'float'?
|
|
1275
1307
|
|
|
1276
|
-
|
|
1308
|
+
1 error found
|
|
1277
1309
|
```
|
|
1278
1310
|
|
|
1279
1311
|
`mtc check` surfaces both errors and linter warnings:
|
data/docs/build-guide.md
CHANGED
|
@@ -351,6 +351,11 @@ Common options:
|
|
|
351
351
|
- `--bundle` for native package builds when you want a distributable app directory instead of a bare executable
|
|
352
352
|
- `--archive` for native package builds when you also want a `.tar.gz` archive of the bundle; this implies `--bundle`
|
|
353
353
|
|
|
354
|
+
Debug profile builds enable runtime debug guards:
|
|
355
|
+
|
|
356
|
+
- Loops get an iteration-limit guard that calls `mt_fatal` when a synchronous loop exceeds 50,000,000 iterations (a hang detector).
|
|
357
|
+
- Every unconditionally-evaluated raw pointer dereference — pointer field access, pointer indexing, and `read(ptr)` in `unsafe` — is preceded by a null trap that calls `mt_fatal("null pointer dereferenced in <function>")` instead of segfaulting. This catches a NULL that crossed a non-nullable FFI declaration at the source line. Derefs guarded only by flow (inside `and`/`or` right sides or `?:` branches) and `ref[T]` receivers are not trapped: conditional derefs may never execute, and refs are non-null by the type system.
|
|
358
|
+
|
|
354
359
|
The wasm platform also accepts the aliases `web`, `html5`, and `browser`.
|
|
355
360
|
|
|
356
361
|
For editor tooling, the Milk Tea VS Code extension also accepts `milkTea.lsp.platform = auto|linux|windows|wasm`.
|
data/docs/index.html
CHANGED
|
@@ -254,7 +254,7 @@ p + p{margin-top:-.25rem}
|
|
|
254
254
|
</button>
|
|
255
255
|
<span style="font-size:.75rem">
|
|
256
256
|
<a href="https://github.com/teefan/mt-lang" style="color:inherit;text-decoration:none" title="Source code on GitHub">☝ GitHub</a>
|
|
257
|
-
·
|
|
257
|
+
· v0.4
|
|
258
258
|
</span>
|
|
259
259
|
</div>
|
|
260
260
|
</aside>
|
|
@@ -2101,7 +2101,7 @@ async function main() -> int:
|
|
|
2101
2101
|
</div>
|
|
2102
2102
|
|
|
2103
2103
|
<h3>Task Helpers</h3>
|
|
2104
|
-
<p>Import <code>std.async</code> for runtime control:</p>
|
|
2104
|
+
<p>Import <code>std.async</code> for runtime control (<code>sleep</code>, <code>work</code>, <code>completed</code>, <code>result</code>, <code>wait</code>, <code>run</code>, plus explicit-runtime variants such as <code>wait_on</code>, <code>work_on</code>, and <code>with_runtime</code>):</p>
|
|
2105
2105
|
<div class="code-wrap">
|
|
2106
2106
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
2107
2107
|
<pre><code>import std.async as aio
|
|
@@ -2306,7 +2306,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2306
2306
|
<tr><td><code>std.mem.arena</code></td><td>Frame/level/scratch arena allocator</td></tr>
|
|
2307
2307
|
<tr><td><code>std.mem.pool</code></td><td>Fixed-size object pool</td></tr>
|
|
2308
2308
|
<tr><td><code>std.mem.stack</code></td><td>Explicit temporary allocator</td></tr>
|
|
2309
|
-
<tr><td><code>std.async</code></td><td>Async runtime: <code>sleep</code>, <code>work</code>, <code>completed</code>, <code>result</code>, <code>wait</code>, <code>run</code
|
|
2309
|
+
<tr><td><code>std.async</code></td><td>Async runtime: <code>sleep</code>, <code>work</code>, <code>completed</code>, <code>result</code>, <code>wait</code>, <code>run</code>, plus explicit-runtime variants (<code>wait_on</code>, <code>work_on</code>, <code>with_runtime</code>)</td></tr>
|
|
2310
2310
|
<tr><td><code>std.option</code></td><td><code>Option[T]</code> — optional value (prelude type; methods always available without import). Methods: <code>is_some</code>, <code>is_none</code>, <code>unwrap</code>, <code>expect</code>, <code>unwrap_or</code>, <code>unwrap_or_else</code></td></tr>
|
|
2311
2311
|
<tr><td><code>std.result</code></td><td><code>Result[T, E]</code> — fallible computation (prelude type; methods always available without import). Methods: <code>is_success</code>, <code>is_failure</code>, <code>unwrap</code>, <code>unwrap_error</code>, <code>unwrap_or</code>, <code>unwrap_or_else</code>, <code>ok</code>, <code>error</code>, <code>map_error</code></td></tr>
|
|
2312
2312
|
</table>
|
|
@@ -2356,6 +2356,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2356
2356
|
<tr><td><code>mtc test <path></code></td><td>Discover and run <code>@[test]</code> functions</td></tr>
|
|
2357
2357
|
<tr><td><code>mtc new <name></code></td><td>Scaffold a new package (<code>package.toml</code> + <code>src/main.mt</code>)</td></tr>
|
|
2358
2358
|
<tr><td><code>mtc run-module <module></code></td><td>Resolve a module by name, build it, and run it</td></tr>
|
|
2359
|
+
<tr><td><code>mtc std list [--json]</code></td><td>List hand-written standard library modules grouped by category (<code>--json</code> for machine-readable output)</td></tr>
|
|
2360
|
+
<tr><td><code>mtc std show <module></code></td><td>Print a standard library module's source (dotted or slashed names; platform variants resolve to the active platform)</td></tr>
|
|
2359
2361
|
<tr><td><code>mtc completions <shell></code></td><td>Print a bash/zsh/fish completion script</td></tr>
|
|
2360
2362
|
</table>
|
|
2361
2363
|
</div>
|
|
@@ -2430,46 +2432,76 @@ mtc lint path/to/file.mt --ignore line-too-long # skip selected rules</code></p
|
|
|
2430
2432
|
</div>
|
|
2431
2433
|
|
|
2432
2434
|
<h3>Inline Suppression</h3>
|
|
2433
|
-
<p>Suppress a
|
|
2435
|
+
<p>Suppress all rules on a line with <code># lint: ignore</code>, or specific rules with <code># lint: ignore(rule1, rule2)</code>:</p>
|
|
2434
2436
|
<div class="code-wrap">
|
|
2435
2437
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
2436
|
-
<pre><code
|
|
2437
|
-
|
|
2438
|
+
<pre><code>var count = 0 # lint: ignore
|
|
2439
|
+
var total = 0 # lint: ignore(prefer-let, dead-assignment)</code></pre>
|
|
2438
2440
|
</div>
|
|
2439
2441
|
|
|
2440
2442
|
<h3>Configuration</h3>
|
|
2441
|
-
<p>
|
|
2443
|
+
<p>Create a default config with <code>mtc lint --init</code>, or place a <code>.mt-lint.yml</code> file in the project root (or any ancestor directory):</p>
|
|
2442
2444
|
<div class="code-wrap">
|
|
2443
2445
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
2444
|
-
<pre><code>
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2446
|
+
<pre><code>max_line_length: 120
|
|
2447
|
+
select:
|
|
2448
|
+
- line-too-long
|
|
2449
|
+
- prefer-let
|
|
2450
|
+
- missing-return
|
|
2451
|
+
ignore:
|
|
2452
|
+
- shadow
|
|
2453
|
+
- useless-expression</code></pre>
|
|
2448
2454
|
</div>
|
|
2455
|
+
<p><code>max_line_length</code> defaults to <code>120</code> when omitted. When both <code>select</code> and <code>ignore</code> are present, <code>select</code> takes precedence.</p>
|
|
2449
2456
|
|
|
2450
2457
|
<h3>Rule Reference</h3>
|
|
2451
2458
|
<div class="table-wrap">
|
|
2452
2459
|
<table class="attr-table">
|
|
2453
|
-
<tr><th>
|
|
2454
|
-
<tr><td
|
|
2455
|
-
<tr><td
|
|
2456
|
-
<tr><td
|
|
2457
|
-
<tr><td
|
|
2458
|
-
<tr><td
|
|
2459
|
-
<tr><td
|
|
2460
|
-
<tr><td
|
|
2461
|
-
<tr><td
|
|
2462
|
-
<tr><td
|
|
2463
|
-
<tr><td
|
|
2464
|
-
<tr><td
|
|
2465
|
-
<tr><td
|
|
2466
|
-
<tr><td
|
|
2467
|
-
<tr><td
|
|
2468
|
-
<tr><td>
|
|
2469
|
-
<tr><td>
|
|
2460
|
+
<tr><th>Code</th><th>Severity</th><th>Auto-fix</th><th>Description</th></tr>
|
|
2461
|
+
<tr><td><code>borrow-and-mutate</code></td><td>warning</td><td>—</td><td>Local is borrowed with <code>ref_of</code>/<code>ptr_of</code> and also mutated in the same scope</td></tr>
|
|
2462
|
+
<tr><td><code>constant-condition</code></td><td>warning</td><td>—</td><td>Branch or loop condition is provably always true or false</td></tr>
|
|
2463
|
+
<tr><td><code>dead-assignment</code></td><td>warning</td><td>—</td><td>Assigned value is overwritten before any read</td></tr>
|
|
2464
|
+
<tr><td><code>duplicate-if-condition</code></td><td>warning</td><td>—</td><td><code>if</code>/<code>else if</code> branch repeats a previous condition and is unreachable</td></tr>
|
|
2465
|
+
<tr><td><code>directional-ffi-arg</code></td><td>hint</td><td>—</td><td>Legacy <code>ptr_of</code>/<code>ref_of</code>/<code>out</code> call-site wrapper is redundant for directional FFI parameters</td></tr>
|
|
2466
|
+
<tr><td><code>doc-tag</code></td><td>hint</td><td>—</td><td><code>##</code> doc comment tag (<code>@param</code>, <code>@return</code>, <code>@throws</code>, <code>@see</code>) is invalid or inconsistent</td></tr>
|
|
2467
|
+
<tr><td><code>event-capacity</code></td><td>warning</td><td>—</td><td>Event capacity may copy too many listeners to stack on emit</td></tr>
|
|
2468
|
+
<tr><td><code>line-too-long</code></td><td>warning</td><td>—</td><td>Source line exceeds configured maximum length</td></tr>
|
|
2469
|
+
<tr><td><code>loop-single-iteration</code></td><td>warning</td><td>—</td><td>Loop body always exits on the first iteration</td></tr>
|
|
2470
|
+
<tr><td><code>missing-return</code></td><td>error</td><td>—</td><td>Non-void function lacks a guaranteed return on all paths</td></tr>
|
|
2471
|
+
<tr><td><code>noop-compound-assignment</code></td><td>hint</td><td>—</td><td>Compound assignment uses an identity value and has no effect</td></tr>
|
|
2472
|
+
<tr><td><code>owning-release-double</code></td><td>warning</td><td>—</td><td>Owning binding may be released more than once</td></tr>
|
|
2473
|
+
<tr><td><code>owning-release-leak</code></td><td>warning</td><td>—</td><td>Owning binding is never released</td></tr>
|
|
2474
|
+
<tr><td><code>platform-api-drift</code></td><td>warning</td><td>—</td><td>Public API differs across platform-specific variants of the same module</td></tr>
|
|
2475
|
+
<tr><td><code>prefer-let</code></td><td>hint</td><td>yes</td><td><code>var</code> binding is never mutated; use <code>let</code></td></tr>
|
|
2476
|
+
<tr><td><code>prefer-let-else</code></td><td>hint</td><td>yes</td><td>Nullable guard can be rewritten as <code>let ... else:</code></td></tr>
|
|
2477
|
+
<tr><td><code>prefer-inline-methods</code></td><td>hint</td><td>yes</td><td>Methods on a struct can be written inline inside the struct declaration</td></tr>
|
|
2478
|
+
<tr><td><code>prefer-is-variant</code></td><td>hint</td><td>—</td><td>A match that maps one variant arm to a boolean can be <code>expr is Arm</code></td></tr>
|
|
2479
|
+
<tr><td><code>prefer-own-ptr</code></td><td>hint</td><td>—</td><td>Pointer binding used only inside <code>unsafe</code> could be <code>own[T]</code> for auto-deref</td></tr>
|
|
2480
|
+
<tr><td><code>prefer-or-pattern</code></td><td>hint</td><td>—</td><td>Adjacent match arms with identical bodies can merge with <code>|</code></td></tr>
|
|
2481
|
+
<tr><td><code>prefer-struct-with</code></td><td>hint</td><td>—</td><td>Field copies from another value can use <code>.with(...)</code></td></tr>
|
|
2482
|
+
<tr><td><code>prefer-try</code></td><td>hint</td><td>—</td><td>A match that only propagates the failure branch can use <code>expr?</code></td></tr>
|
|
2483
|
+
<tr><td><code>prefer-var-else</code></td><td>hint</td><td>yes</td><td>Nullable guard can be rewritten as <code>var ... else:</code></td></tr>
|
|
2484
|
+
<tr><td><code>redundant-bool-compare</code></td><td>hint</td><td>yes</td><td>Comparing a boolean expression to <code>true</code>/<code>false</code> is redundant</td></tr>
|
|
2485
|
+
<tr><td><code>redundant-cast</code></td><td>hint</td><td>yes</td><td>Explicit cast where implicit widening already works</td></tr>
|
|
2486
|
+
<tr><td><code>redundant-else</code></td><td>warning</td><td>yes</td><td><code>else</code> block is unnecessary because all prior branches return</td></tr>
|
|
2487
|
+
<tr><td><code>redundant-ignored-match-binding</code></td><td>hint</td><td>yes</td><td>Ignored <code>as _</code> match binding is redundant</td></tr>
|
|
2488
|
+
<tr><td><code>redundant-null-check</code></td><td>hint</td><td>—</td><td>Null check on a value already known non-null by flow analysis</td></tr>
|
|
2489
|
+
<tr><td><code>redundant-return</code></td><td>hint</td><td>yes</td><td>Final bare <code>return</code> in a void function is unnecessary</td></tr>
|
|
2490
|
+
<tr><td><code>redundant-type-annotation</code></td><td>hint</td><td>yes</td><td>Type annotation is redundant; the type is inferred from the initializer</td></tr>
|
|
2491
|
+
<tr><td><code>redundant-unsafe</code></td><td>hint</td><td>yes</td><td><code>unsafe</code> block contains no unsafe operations and can be removed</td></tr>
|
|
2492
|
+
<tr><td><code>reserved-primitive-name</code></td><td>warning</td><td>yes</td><td>Binding uses a reserved built-in type name in its active namespace</td></tr>
|
|
2493
|
+
<tr><td><code>self-assignment</code></td><td>warning</td><td>—</td><td>Variable is assigned to itself</td></tr>
|
|
2494
|
+
<tr><td><code>self-comparison</code></td><td>warning</td><td>—</td><td>Value is compared to itself, making the condition constant</td></tr>
|
|
2495
|
+
<tr><td><code>shadow</code></td><td>warning</td><td>—</td><td>Local binding shadows an outer binding with the same name</td></tr>
|
|
2496
|
+
<tr><td><code>trailing-list-comma</code></td><td>hint</td><td>yes</td><td>Trailing comma in call argument list is redundant</td></tr>
|
|
2497
|
+
<tr><td><code>unreachable-code</code></td><td>warning</td><td>—</td><td>Code after a guaranteed terminator cannot execute</td></tr>
|
|
2498
|
+
<tr><td><code>unused-import</code></td><td>warning</td><td>—</td><td>Import alias is never referenced (intentionally not auto-fixable: removals can drop extension methods or canonical hooks)</td></tr>
|
|
2499
|
+
<tr><td><code>unused-local</code></td><td>warning</td><td>—</td><td>Local binding is never referenced</td></tr>
|
|
2500
|
+
<tr><td><code>unused-param</code></td><td>warning</td><td>—</td><td>Parameter is never referenced</td></tr>
|
|
2501
|
+
<tr><td><code>useless-expression</code></td><td>warning</td><td>—</td><td>Expression statement has no side effects and its result is unused</td></tr>
|
|
2470
2502
|
</table>
|
|
2471
2503
|
</div>
|
|
2472
|
-
<p>Severity levels: <code>error</code> (fail build), <code>
|
|
2504
|
+
<p>Severity levels: <code>error</code> (fail build), <code>warning</code>, and <code>hint</code>.</p>
|
|
2473
2505
|
</section>
|
|
2474
2506
|
|
|
2475
2507
|
<!-- ────────────────────────────────────────────────────────────────────────── -->
|
data/docs/language-design.md
CHANGED
|
@@ -473,12 +473,12 @@ Current implemented shape:
|
|
|
473
473
|
|
|
474
474
|
- `async function` lifts its return type to `Task[T]`
|
|
475
475
|
- `await` is only valid inside async functions
|
|
476
|
-
- async entrypoint bootstrapping is compiler-owned, but async helpers stay explicit library surface; import `std.async as aio` when user code needs `sleep`, `work`, `completed`, `result`, `wait`, `run`, or runtime control
|
|
476
|
+
- async entrypoint bootstrapping is compiler-owned, but async helpers stay explicit library surface; import `std.async as aio` when user code needs `sleep`, `work`, `completed`, `result`, `wait`, `run`, `wait_on`, `with_runtime`, or runtime control
|
|
477
477
|
- `aio.wait(...)` and `aio.run(...)` accept direct task expressions as well as zero-arg task roots; the compiler rewrites the direct-task form into the deferred root shape automatically
|
|
478
478
|
- async bodies support ordinary local declarations, including `let ... else:`, assignments, returns, `if`, `while`, single-form and parallel `for`, `match`, `defer`, `unsafe`, and deferred cleanup bodies that `await`
|
|
479
479
|
- await placement is handled by the normalization pass which hoists nested awaits into `let` bindings, so `await` is supported in all expression contexts (call arguments, binary operations, if-expr, match-expr, member access, index access, format strings)
|
|
480
480
|
|
|
481
|
-
The default async model is a language-integrated entry boundary. `std.async` remains the explicit high-level helper surface for operations such as `sleep`, `work`, `completed`, `result`, `wait`, `wait_on`, and `with_runtime`, while the normal runtime model stays the single integrated libuv-backed runtime.
|
|
481
|
+
The default async model is a language-integrated entry boundary. `std.async` remains the explicit high-level helper surface for operations such as `sleep`, `work`, `completed`, `result`, `wait`, `run`, `wait_on`, and `with_runtime`, while the normal runtime model stays the single integrated libuv-backed runtime.
|
|
482
482
|
|
|
483
483
|
#### Concurrency: `parallel for` and `parallel:` blocks
|
|
484
484
|
|
data/docs/language-manual.md
CHANGED
|
@@ -1172,7 +1172,9 @@ let q = quat(x = 0.0, y = 0.0, z = 0.0, w = 1.0)
|
|
|
1172
1172
|
- for pointer-like bases (`ptr[T]`, `const_ptr[T]`, `own[T]`, `cstr`, `fn(...)`, `proc(...)`, opaque), `T?` is a nullable pointer with `null` as the absent value
|
|
1173
1173
|
- for non-pointer value bases (`int`, `bool`, `float`, structs, ...), `T?` is stored inline by value as a tagged optional (a presence flag plus the value); it copies by value with no hidden heap allocation or pointer aliasing
|
|
1174
1174
|
- `null` expresses absence in any nullable context; the explicit typed `null[...]` form's target must be pointer-like
|
|
1175
|
+
- `null` is only a value of nullable types. Comparing a non-nullable pointer, `cstr`, or opaque handle against `null` is a type error: declare the value `T?` when null is possible, or compare against `zero[ptr[T]]` (also `zero[cstr]`, `zero[own[T]]`) to test the raw C zero pointer at the raw ABI level. Nullable types must use `null`, not `zero[...]`
|
|
1175
1176
|
- in nullable pointer-like contexts, `zero[ptr[T]]` is rejected; use `null` instead
|
|
1177
|
+
- null checks narrow flow: inside the `!= null` branch (and after an `== null` branch exits), the binding is treated as non-null. A further null check on a narrowed binding is still legal; the linter reports it as `redundant-null-check`
|
|
1176
1178
|
- at an FFI boundary (`external` / `foreign function` parameters and returns), only pointer-like `T?` is allowed; a non-pointer value nullable such as `int?` is rejected — use `ptr[T]?` or pass an explicit struct
|
|
1177
1179
|
|
|
1178
1180
|
### 6.4 Generics
|
|
@@ -1523,7 +1525,7 @@ mtc lint --ignore shadow file.mt
|
|
|
1523
1525
|
|
|
1524
1526
|
### 12.2 Rules
|
|
1525
1527
|
|
|
1526
|
-
The auto-fix column corresponds to `mtc lint --fix`.
|
|
1528
|
+
The auto-fix column corresponds to `mtc lint --fix`. `unused-import` is intentionally not auto-fixable: removing an import has non-local effects (extension methods and canonical hooks) the per-file linter cannot see.
|
|
1527
1529
|
|
|
1528
1530
|
| Code | Severity | Auto-fix | Description |
|
|
1529
1531
|
|---|---|---|---|
|
|
@@ -1538,23 +1540,33 @@ The auto-fix column corresponds to `mtc lint --fix`.
|
|
|
1538
1540
|
| `loop-single-iteration` | warning | — | Loop body always exits on the first iteration |
|
|
1539
1541
|
| `missing-return` | error | — | Function with a non-void return type lacks a guaranteed return on all paths |
|
|
1540
1542
|
| `noop-compound-assignment` | hint | — | Compound assignment uses an identity value and has no effect |
|
|
1543
|
+
| `owning-release-double` | warning | — | Owning binding may be released more than once |
|
|
1544
|
+
| `owning-release-leak` | warning | — | Owning binding is never released |
|
|
1541
1545
|
| `platform-api-drift` | warning | — | Public API differs across sibling platform-specific variants of the same module |
|
|
1542
1546
|
| `prefer-let` | hint | yes | `var` binding is never mutated; use `let` instead |
|
|
1543
1547
|
| `prefer-let-else` | hint | yes | Nullable guard can be rewritten as `let ... else:` |
|
|
1548
|
+
| `prefer-inline-methods` | hint | yes | Methods on a struct can be written inline inside the struct declaration |
|
|
1549
|
+
| `prefer-is-variant` | hint | — | A match that maps one variant arm to a boolean can be `expr is Arm` |
|
|
1550
|
+
| `prefer-own-ptr` | hint | — | Pointer binding used only inside `unsafe` could be `own[T]` for auto-deref |
|
|
1551
|
+
| `prefer-or-pattern` | hint | — | Adjacent match arms with identical bodies can merge with `\|` |
|
|
1552
|
+
| `prefer-struct-with` | hint | — | Field copies from another value can use `.with(...)` |
|
|
1553
|
+
| `prefer-try` | hint | — | A match that only propagates the failure branch can use `expr?` |
|
|
1544
1554
|
| `prefer-var-else` | hint | yes | Nullable guard can be rewritten as `var ... else:` |
|
|
1545
1555
|
| `redundant-bool-compare` | hint | yes | Comparing a boolean expression to `true`/`false` is redundant |
|
|
1556
|
+
| `redundant-cast` | hint | yes | Explicit cast where implicit widening already works |
|
|
1546
1557
|
| `redundant-else` | warning | yes | `else` block is unnecessary because all prior branches return |
|
|
1547
1558
|
| `redundant-ignored-match-binding` | hint | yes | Ignored `as _` match binding is redundant |
|
|
1548
1559
|
| `redundant-null-check` | hint | — | Null check on a value already known to be non-null by flow analysis |
|
|
1549
1560
|
| `redundant-return` | hint | yes | Final bare `return` in a `void` function is unnecessary |
|
|
1561
|
+
| `redundant-type-annotation` | hint | yes | Type annotation is redundant; the type is inferred from the initializer |
|
|
1562
|
+
| `redundant-unsafe` | hint | yes | `unsafe` block contains no unsafe operations and can be removed |
|
|
1550
1563
|
| `reserved-primitive-name` | warning | yes | Binding uses a reserved built-in type name in its active namespace |
|
|
1551
1564
|
| `self-assignment` | warning | — | Variable is assigned to itself |
|
|
1552
1565
|
| `self-comparison` | warning | — | Value is compared to itself, making the condition constant |
|
|
1553
1566
|
| `shadow` | warning | — | Local binding shadows an outer binding with the same name |
|
|
1554
1567
|
| `trailing-list-comma` | hint | yes | Trailing comma in call argument list is redundant |
|
|
1555
1568
|
| `unreachable-code` | warning | — | Code after a guaranteed terminator cannot execute |
|
|
1556
|
-
| `
|
|
1557
|
-
| `unused-import` | warning | yes | Import alias is never referenced |
|
|
1569
|
+
| `unused-import` | warning | — | Import alias is never referenced |
|
|
1558
1570
|
| `unused-local` | warning | — | Local binding is never referenced |
|
|
1559
1571
|
| `unused-param` | warning | — | Parameter is never referenced |
|
|
1560
1572
|
| `useless-expression` | warning | — | Expression statement has no side effects and its result is unused |
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -141,6 +141,27 @@ module MilkTea
|
|
|
141
141
|
"GuiLoadIcons" => "ptr[ptr[char]]?",
|
|
142
142
|
).freeze
|
|
143
143
|
|
|
144
|
+
# stb_image.h documents these as returning NULL on failure (and
|
|
145
|
+
# stbi_failure_reason as returning NULL before the first failure), so
|
|
146
|
+
# the raw binding must expose them as nullable for null checks to work.
|
|
147
|
+
stb_image_function_return_overrides = {
|
|
148
|
+
"stbi_failure_reason" => "cstr?",
|
|
149
|
+
"stbi_load" => "ptr[stbi_uc]?",
|
|
150
|
+
"stbi_load_16" => "ptr[stbi_us]?",
|
|
151
|
+
"stbi_load_16_from_callbacks" => "ptr[stbi_us]?",
|
|
152
|
+
"stbi_load_16_from_memory" => "ptr[stbi_us]?",
|
|
153
|
+
"stbi_load_from_callbacks" => "ptr[stbi_uc]?",
|
|
154
|
+
"stbi_load_from_memory" => "ptr[stbi_uc]?",
|
|
155
|
+
"stbi_load_gif_from_memory" => "ptr[stbi_uc]?",
|
|
156
|
+
"stbi_loadf" => "ptr[float]?",
|
|
157
|
+
"stbi_loadf_from_callbacks" => "ptr[float]?",
|
|
158
|
+
"stbi_loadf_from_memory" => "ptr[float]?",
|
|
159
|
+
"stbi_zlib_decode_malloc" => "ptr[char]?",
|
|
160
|
+
"stbi_zlib_decode_malloc_guesssize" => "ptr[char]?",
|
|
161
|
+
"stbi_zlib_decode_malloc_guesssize_headerflag" => "ptr[char]?",
|
|
162
|
+
"stbi_zlib_decode_noheader_malloc" => "ptr[char]?",
|
|
163
|
+
}.freeze
|
|
164
|
+
|
|
144
165
|
sdl3_documented_function_param_overrides = {
|
|
145
166
|
"SDL_AcquireGPUSwapchainTexture" => {
|
|
146
167
|
"swapchain_texture_width" => "ptr[uint]?",
|
|
@@ -1269,6 +1290,7 @@ module MilkTea
|
|
|
1269
1290
|
binding_path: root.join("std/c/stb_image.mt"),
|
|
1270
1291
|
include_directives: ["stb_image.h"],
|
|
1271
1292
|
declaration_name_prefixes: ["stbi_", "STBI_"],
|
|
1293
|
+
function_return_type_overrides: stb_image_function_return_overrides,
|
|
1272
1294
|
header_candidates: [
|
|
1273
1295
|
MilkTea.data_root.join("third_party/stb-upstream/stb_image.h").to_s,
|
|
1274
1296
|
],
|
|
@@ -31,8 +31,10 @@ module MilkTea
|
|
|
31
31
|
else
|
|
32
32
|
[]
|
|
33
33
|
end
|
|
34
|
+
debug_guard_receivers = collect_debug_null_guards_for_statement(statement)
|
|
34
35
|
statement_lines = with_checked_index_aliases(aliases) do
|
|
35
|
-
|
|
36
|
+
debug_guard_lines = emit_debug_null_guard_lines(debug_guard_receivers, indent, function)
|
|
37
|
+
debug_guard_lines + case statement
|
|
36
38
|
when IR::LocalDecl
|
|
37
39
|
if array_type?(statement.type) && statement.value.is_a?(IR::Call)
|
|
38
40
|
lines = ["#{indent}#{c_declaration(statement.type, statement.linkage_name)};"]
|
|
@@ -524,6 +526,121 @@ module MilkTea
|
|
|
524
526
|
nil
|
|
525
527
|
end
|
|
526
528
|
|
|
529
|
+
# ── debug null traps ────────────────────────────────────────────────────
|
|
530
|
+
# Debug builds insert `if (ptr == NULL) mt_fatal(...)` before every
|
|
531
|
+
# unconditionally-evaluated raw pointer dereference (pointer field
|
|
532
|
+
# access, pointer indexing, read(ptr)) so a NULL that crosses the type
|
|
533
|
+
# system's non-null contract — typically from an FFI boundary — fatales
|
|
534
|
+
# with a function name instead of segfaulting. Receivers are duplicated
|
|
535
|
+
# into the guard, so only side-effect-free receivers qualify, and
|
|
536
|
+
# conditional subtrees (and/or right sides, ?: branches) are skipped so
|
|
537
|
+
# derefs that would never execute are not reported. ref[T] receivers are
|
|
538
|
+
# excluded: refs are non-null by the type system, so a trap there would
|
|
539
|
+
# be pure noise.
|
|
540
|
+
|
|
541
|
+
def collect_debug_null_guards_for_statement(statement)
|
|
542
|
+
return [] unless @debug_guards
|
|
543
|
+
|
|
544
|
+
expressions = case statement
|
|
545
|
+
when IR::LocalDecl
|
|
546
|
+
[statement.value]
|
|
547
|
+
when IR::Assignment
|
|
548
|
+
[statement.target, statement.value]
|
|
549
|
+
when IR::ExpressionStmt
|
|
550
|
+
[statement.expression]
|
|
551
|
+
when IR::ReturnStmt
|
|
552
|
+
statement.value ? [statement.value] : []
|
|
553
|
+
when IR::IfStmt
|
|
554
|
+
[statement.condition]
|
|
555
|
+
when IR::WhileStmt
|
|
556
|
+
[statement.condition]
|
|
557
|
+
when IR::ForStmt
|
|
558
|
+
[statement.init, statement.condition, statement.post].compact
|
|
559
|
+
when IR::SwitchStmt
|
|
560
|
+
[statement.expression]
|
|
561
|
+
else
|
|
562
|
+
[]
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
receivers = []
|
|
566
|
+
expressions.compact.each do |expression|
|
|
567
|
+
collect_debug_null_guard_receivers(expression, receivers)
|
|
568
|
+
end
|
|
569
|
+
receivers
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def collect_debug_null_guard_receivers(expression, receivers)
|
|
573
|
+
case expression
|
|
574
|
+
when IR::Member
|
|
575
|
+
collect_debug_null_guard_receivers(expression.receiver, receivers)
|
|
576
|
+
add_debug_null_guard_receiver(receivers, expression.receiver)
|
|
577
|
+
when IR::Index
|
|
578
|
+
collect_debug_null_guard_receivers(expression.receiver, receivers)
|
|
579
|
+
collect_debug_null_guard_receivers(expression.index, receivers)
|
|
580
|
+
add_debug_null_guard_receiver(receivers, expression.receiver)
|
|
581
|
+
when IR::Unary
|
|
582
|
+
collect_debug_null_guard_receivers(expression.operand, receivers)
|
|
583
|
+
add_debug_null_guard_receiver(receivers, expression.operand) if expression.operator == "*"
|
|
584
|
+
when IR::Call
|
|
585
|
+
collect_debug_null_guard_receivers(expression.callee, receivers) unless expression.callee.is_a?(String)
|
|
586
|
+
expression.arguments.each { |argument| collect_debug_null_guard_receivers(argument, receivers) }
|
|
587
|
+
when IR::Binary
|
|
588
|
+
collect_debug_null_guard_receivers(expression.left, receivers)
|
|
589
|
+
return if expression.operator == "and" || expression.operator == "or"
|
|
590
|
+
|
|
591
|
+
collect_debug_null_guard_receivers(expression.right, receivers)
|
|
592
|
+
when IR::Conditional
|
|
593
|
+
collect_debug_null_guard_receivers(expression.condition, receivers)
|
|
594
|
+
when IR::AddressOf, IR::Cast, IR::ReinterpretExpr
|
|
595
|
+
collect_debug_null_guard_receivers(expression.expression, receivers)
|
|
596
|
+
when IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
|
|
597
|
+
collect_debug_null_guard_receivers(expression.receiver, receivers)
|
|
598
|
+
collect_debug_null_guard_receivers(expression.index, receivers)
|
|
599
|
+
when IR::AggregateLiteral
|
|
600
|
+
expression.fields.each { |field| collect_debug_null_guard_receivers(field.value, receivers) }
|
|
601
|
+
when IR::ArrayLiteral
|
|
602
|
+
expression.elements.each { |element| collect_debug_null_guard_receivers(element, receivers) }
|
|
603
|
+
when IR::VariantLiteral
|
|
604
|
+
expression.fields.each { |field| collect_debug_null_guard_receivers(field.value, receivers) }
|
|
605
|
+
when IR::SimdLaneWith
|
|
606
|
+
collect_debug_null_guard_receivers(expression.src, receivers)
|
|
607
|
+
collect_debug_null_guard_receivers(expression.value, receivers)
|
|
608
|
+
when IR::Assignment, IR::LocalDecl
|
|
609
|
+
collect_debug_null_guard_receivers(expression.target, receivers) if expression.is_a?(IR::Assignment)
|
|
610
|
+
collect_debug_null_guard_receivers(expression.value, receivers) if expression.value
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def add_debug_null_guard_receiver(receivers, receiver_expression)
|
|
615
|
+
return unless debug_null_guardable_receiver?(receiver_expression)
|
|
616
|
+
return if receivers.any? { |existing| existing.equal?(receiver_expression) }
|
|
617
|
+
|
|
618
|
+
receivers << receiver_expression
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def debug_null_guardable_receiver?(expression)
|
|
622
|
+
type = expression.respond_to?(:type) ? expression.type : nil
|
|
623
|
+
return false if ref_type?(type)
|
|
624
|
+
|
|
625
|
+
raw_pointer_type?(type) || nullable_pointer_like_type?(type)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def emit_debug_null_guard_lines(receivers, indent, function)
|
|
629
|
+
return [] if receivers.empty?
|
|
630
|
+
|
|
631
|
+
message = "null pointer dereferenced in #{function.linkage_name}"
|
|
632
|
+
lines = []
|
|
633
|
+
emitted = Set.new
|
|
634
|
+
receivers.each do |receiver|
|
|
635
|
+
receiver_text = emit_expression(receiver)
|
|
636
|
+
next if emitted.include?(receiver_text)
|
|
637
|
+
|
|
638
|
+
emitted << receiver_text
|
|
639
|
+
lines << "#{indent}if (#{receiver_text} == NULL) mt_fatal(\"#{message}\");"
|
|
640
|
+
end
|
|
641
|
+
lines
|
|
642
|
+
end
|
|
643
|
+
|
|
527
644
|
def statements_require_scope?(statements)
|
|
528
645
|
statements.any? { |statement| statement.is_a?(IR::LocalDecl) }
|
|
529
646
|
end
|