You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -187,6 +187,7 @@ Read `skills/writing_skills.md` first — it carries the full checklist. The thr
187
187
188
188
- No implicit type promotion: `int + float` is a compile error — both sides must match
189
189
- No `bool(int)` cast — use `x != 0`; no `string(bool)` — use `"{flag}"`
190
+
-**`length` and `empty` on a string need no `require`** — both live in the base module, so `length(s)` compiles in a bare file (the rest of the string surface still needs `require strings`). 64-bit forms: `long_length(s)`. The `int`-returning `length` panics past 2^31 rather than wrapping — same always-on guard array/table length already carry
190
191
-`int("123")` does NOT work — use `to_int` from `require strings`. **`to_int` silently returns `0` on garbage** (`to_int("foo")` → `0`, `to_int("12abc")` → `12`). When you need to validate user/external input — including any string that flows into a shell command, file path, or system call — use `try_to_int` / `try_to_float` from `daslib/strings_convert` instead. Those return `Result<T; ConversionError>` distinguishing `invalid_argument` / `out_of_range` / `trailing_garbage`, so `";rm -rf;"` rejects cleanly instead of becoming `0`. Same for `to_float` → `try_to_float`
191
192
- Hex literals are `uint` by default — use `int(0x3F)` for int
192
193
-**16/8-bit type lattice**: `float16` scalar (`half` = builtin alias; literal suffix `1.5h`) + packed vectors `half2/3/4/8`, `short2/3/4/8`, `ushort2/3/4/8`, `byte2/3/4/8/16` (**byte = SIGNED int8**), `ubyte2/3/4/8/16`. Tightly packed (half3 = 6B), element-aligned, pass by value. fp16 family has **closed arithmetic** (per-op promote-to-float-round-back — bit-identical to native fp16); int families are **storage + converts only** (`byte4 + byte4` is a compile error; widen via `int4(b4)`, narrow via ctor = C-truncate or `short4_sat(i4)`-style saturating forms). `.s`-hex swizzles on every vector (`v.s3210`, `b16.sf`), `.lo`/`.hi` on 8/16-lane forms; `xyzw` stays ≤4 lanes. Conformance harness: `tests/type_lattice/` (GENERATED — regen via `daslang utils/dasgen/gen_type_conformance.das`). Lattice vectors have NO ExprConst nodes by design (`isFoldable` false; `Program::makeConst` returns null — callers must handle)
@@ -354,6 +355,9 @@ A generic that should accept `array<T>`, `array<array<T>>`, … (any nesting)
354
355
| hand-rolled `is X` / `as X` / null-guard / `ExprRef2Value`-peel ladders in macro code |`qmatch(e, $e(a) + $e(b))` for source-syntax shapes; `match (e) { if (ExprField(name = "key", value = ExprVar(...))) { ... } }` for node-class shapes | both matchers peel `ExprRef2Value` automatically; `\|\|` alternation, `&&` guards, and `match_expr(local)` cover most ladders. Limits + the qmatch↔match division of labor: `skills/das_macros.md` "`match` (daslib/match)" |
355
356
|`unsafe(reinterpret<T?>(unsafe(addr(x))))` (reinterpret-of-addr, pointer target) |`unsafe(addr<T?>(x))`| STYLE034: same AST, one unsafe gate instead of two. Non-pointer puns (`reinterpret<uint64>(addr(x))`) have no `addr<T?>` spelling and stay silent; the sugar's own desugared output is exempt via `castFlags.fromAddrSugar`|
356
357
|`b == T('(')` where `b : T` and T is a non-`int` built-in numeric scalar (also `!=`, ranges, and Yoda forms) | make `b : int`, then write `b == '('`| STYLE035: character literals are `int`; changing the plain variable once removes every repeated numeric cast. The warning is deduplicated at the declaration. |
358
+
|`int64(length(x))` / `uint64(capacity(x))` (also `count`, `find_index`, `fread`, `fwrite`) |`long_length(x)` / `long_capacity(x)` / … | LINT017: the inner call returns `int`, so the 2^31 limit is hit *before* the widening cast — as a wrap, or as the panic guard on array/table/string length — and the cast buys nothing. Pair table is hardcoded (an exists-check is meaningless for user functions, since a macro can add/remove them) and gated on receiver type, which keeps same-named user overloads and the fixed-array `length` generic (no `long_` twin) silent |
359
+
|`memcpy(d, s, int(n))` / `memcmp(d, s, int(n))` where `n` is `int64`/`uint64`/`uint`|`memcpy(d, s, n)`| LINT018: `memcpy`/`memcmp` now carry `uint`/`int64`/`uint64` size overloads, so the `int(...)` narrowing is pure loss — above 2^31 it copies the wrong byte count |
360
+
|`cast<T -const>(x)` / `reinterpret<T -#>(x)` / `addr<void? -const>(x)` — any of `-const``-&``-[]``-#``==const``==&` on a **concrete** cast target | drop the contract | STYLE036: these are *substitution* contracts — they act only while a generic binds, and infer clears them once consumed, so a flag still set at lint time proves it did nothing. `auto`/unresolved-alias targets are excluded (there substitution hasn't run and the contract is real); a concrete typedef is NOT excluded (`reinterpret<CI? -const>` with `typedef CI = int const` keeps the const) |
357
361
358
362
For path/filename ops use `fio` helpers (`base_name`/`dir_name`/`path_join`/etc.) — see `skills/filesystem.md`. Never hand-roll `rfind("/")` / slice — misses Windows separators.
// Receiver-gated, so a same-named user overload or the fixed-array `length` generic in daslib/builtin.das (which has no long_ twin) stays silent.
395
+
letnargs=length(icall.arguments)
396
+
if (nargs==0||icall.arguments[0]._type==null) return""
397
+
letbt=icall.arguments[0]._type.baseType
398
+
if (fname=="length"&& (bt==Type.tArray||bt==Type.tTable||bt==Type.tString)) return"long_length"
399
+
if (fname=="capacity"&& (bt==Type.tArray||bt==Type.tTable)) return"long_capacity"
400
+
if (fname=="count"&& (bt==Type.tArray||bt==Type.tIterator)) return"long_count"
401
+
if (fname=="find_index"&&bt==Type.tArray&&nargs==2) return"long_find_index"
402
+
// fio's `file` is a pointer-to-handle; the type gate keeps a user-defined 2-arg fread/fwrite from being told to call a long_ twin that does not exist.
403
+
if ((fname=="fread"||fname=="fwrite") &&bt==Type.tPointer&&nargs==2) return"long_{fname}"
lint_error("LINT017: {expr.name}({iname}(...)) widens an already-32-bit result, so the 2^31 limit is still hit inside {iname} (as a wrap, or as the panic guard on array/table/string length) before the cast runs; call {twin}(...) instead", expr.at)
420
+
}
421
+
422
+
// --- LINT018: narrowing the size argument of memcpy / memcmp ---
Copy file name to clipboardExpand all lines: daslib/style_lint.das
+20Lines changed: 20 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -2367,6 +2367,26 @@ class StyleLintVisitor : AstVisitor {
2367
2367
&&!expr.subexpr.genFlags.generated) {
2368
2368
style_warning("STYLE034: reinterpret<{describe(expr.castType)}>(addr(...)) collapses to addr<{describe(expr.castType)}>(...); one unsafe() covers both halves", expr.at)
2369
2369
}
2370
+
// ===== STYLE036 — type contract that cannot act on a resolved cast type =====
2371
+
if (!expr.genFlags.generated&&expr.castType!=null) {
2372
+
letinert=style036_inert_contract(expr.castType)
2373
+
if (!empty(inert)) {
2374
+
lettail=inert=="-const" ? "; drop it, along with any 'const' it was meant to cancel" : "; drop it"
2375
+
style_warning("STYLE036: '{inert}' does nothing on the already-resolved cast type {describe(expr.castType)}{tail}", expr.at)
// Substitution contracts: infer clears them when a generic binds (ast_typedecl.cpp:223,422), so one still set on a concrete target was never consumed — but an auto/unresolved-alias target has simply not been substituted yet (linq's `reinterpret<ARGT -const>` does strip const).
0 commit comments