Commit Graph
1055 Commits
Author SHA1 Message Date
TermiNexus b78cddc401 feat(obs): precise return-borrow summaries and owned-local escape check
Each `-> &T` / `-> &mut T` function now records which parameter it borrows (a parameter index, `self`, or unknown) in a return-borrow registry, keyed by symbol and by `Class.method`. A reference initialized from a call registers only the summarized argument (the receiver for methods), and `return f(...)` checks only that argument; callees without a summary keep the conservative "all borrowed arguments" fallback. Borrowing a compiler-owned (`own`) local cannot escape the frame any more: `_OwnerOutlivesFrame` rejects `own`/slife bindings and only accepts reference parameters, `self`, statics, and non-owned heap pointers, so `return id(&ownLocal)` and `return refBinding` are rejected while `return id(&heapPtr)` still passes. The LSP mirrors the owned-local rule.
2026-11-05 17:55:54 +08:00
TermiNexus d4c1e3f535 fix(obs): resolve method/module-qualified return annotations and static returns
`ret_annot_ref_kind` now resolves plain calls (current module, from-imports, re-exports), method calls (receiver class from its annotation, `.vpi` method entry), and module-qualified calls (`mod.fn` through the module alias). A reference binding or a reference return now requires a verified `-> &T` / `-> &mut T` callee: an unresolvable callee is reported instead of being silently accepted, and `return <call>` first checks the callee's return kind, then its borrowed arguments. Module globals may be returned as `&g` (returning the value is rejected with that hint), since static storage outlives the frame. The per-file OBS flag is passed explicitly to `reject_ref_marker_position`, so a `-> &T` in one file is no longer rejected when another file's stub has reset the module-level flag. `&temp T` participates in borrow tracking, `obj.f = obj.f` no longer frees the value it stores back, and own-field chains release up to 32 levels. The LSP mirrors the method/module return checks and the static-return rules.
2026-11-05 17:43:08 +08:00
TermiNexus 5c6c6c9619 fix(obs): track field/element borrows and check call returns
A borrow of `&obj.field` / `&arr[i]` now registers the root binding, so deleting that object is blocked while the reference lives. A reference binding may only be initialized by a call whose return annotation is `&T` / `&mut T` (resolved through the module's vpi); a `-> U` or `-> U*` call is rejected. Returning a call from a reference-returning function checks its borrowed arguments: borrowing a by-value parameter or a frame-local value is rejected, while a reference parameter, `self`, or a heap-owned value passes. The LSP mirrors all three rules.
2026-11-05 17:17:49 +08:00
TermiNexus 1d668497a5 fix(obs): close reference-return and ownership soundness holes
Reference returns are checked structurally instead of relying on the frame tag, so `return &x` and `return x` for a by-value parameter are rejected while a reference parameter, `self`, and a call still pass. Borrows created by a call result now register the borrowed arguments, so the owner cannot be deleted while the reference lives. `own` rejects `new memgr(...)` storage (pool memory is not freed by `free`) and raw-pointer adoption; `own` arguments reject memgr too. Overwriting an `own` field releases the value it held (not during construction), and `del` of an `own` field is rejected. Reference parameters registered under their source name as well, so passing a borrow to an `&T` parameter no longer trips the stack-escape check. The LSP mirrors the new return and adoption rules.
2026-11-05 16:43:48 +08:00
TermiNexus a2a04f567e feat(obs): check reference argument kinds
A read-only borrow (or a `&T` binding) cannot fill a `&mut T` parameter; the call site now reports it, and the LSP mirrors the same rule.
2026-11-05 15:56:09 +08:00
TermiNexus 38a8d338c1 feat(lsp): mirror mutable borrows and reference fields
Diagnostics follow the new rules: `&mut` exclusivity conflicts, a read-only borrow assigned to a `&mut` slot, borrows through nested field paths (so `del` is reported), `Box[T]` as an ownership spelling, and reference bindings initialized from calls.
2026-11-05 15:49:27 +08:00
TermiNexus 3764afbaa6 feat(obs): borrow exclusivity, Box ownership and full own release
Borrows carry a kind: while a value is mutably borrowed no new borrow starts, a mutable borrow is rejected while read-only borrows are alive, and a `&mut T` slot only accepts `&mut value`. Reference fields record their path (`B.n`) as the borrower, so `del` is blocked by nested and self borrows and released on `del`/reassign of the root. The table grows to 1024 rows and fails fast instead of silently dropping rows, and each function clears its rows at entry. `Box[T]`/`BoxT[T]` count as ownership spellings; own fields are released at scope exit (not only on `del`), for forward-declared and self-referential types too, behind a null guard. A move is only allowed into an `own` slot. Reference results use borrow elision (a reference parameter or `self`) instead of the `@life` advice, reference parameters no longer trip the stack-escape check, and `&` on an owned pointer yields the heap pointer (LIFE_HEAP).
2026-11-05 15:49:10 +08:00
TermiNexus 88b3724303 feat(lsp): mirror own parameters, reference borrows and del
Call arguments are checked against `own T` parameters (a borrow or a non-owned binding is reported, an owned argument is moved); reference-field and reference-binding assignments register a borrow so `del` reports a still-live reference; `del` of a reference binding itself is reported; own parameters count as owned bindings for the move-on-assign rules.
2026-11-04 23:03:24 +08:00
TermiNexus e7ef23b432 refactor(types): drop retired t/c module type spellings
The `t`/`c` modules are gone, so their C type aliases (CInt*, CUnsigned*, CSizeT, CBool, CVoid, CFloat, CDouble, CChar, t.State, t.DefineT, t.VolatileT, the c.CIf family, @c.CReturn) are no longer recognised or reported anywhere: sign inference, enum widths, stub generation/reading, expression lookups and the LSP type-alias check. `import t` / `import c` no longer gets a special message.
2026-11-04 22:49:57 +08:00
TermiNexus a746952997 feat(obs): ownership for own params, returns and fields
`own` adds a pointer layer in the resolver; own parameters are released by the callee and callers move owned arguments (borrows are rejected); returning a local own binding transfers it; own fields are released when the containing object is deleted; reference-field assignments register a borrow so the owner cannot be del-ed while the object lives.
2026-11-04 22:48:45 +08:00
TermiNexus 04aa27d8a0 feat(lsp): flag owners assigned to reference fields
An attribute target whose field annotation starts with `&` only accepts `&value`, another reference or None; anything else is reported like the compiler does.
2026-11-04 18:52:07 +08:00
TermiNexus 4ff813f5dc feat(obs): reference fields only accept borrows
`A.n = B` where `n: &U?` is now rejected: a reference field must be initialized from `&value` / another reference (None stays allowed). Fields carry a RefKind, `ref_kind` looks through the nullable wrapper, and nullable checks treat RefT/MutRefT as transparent.
2026-11-04 18:34:19 +08:00
TermiNexus e650d8dd45 feat(obs): require an explicit borrow to create a reference
`b: &U = a` now fails with a borrow hint; only `&value` (or `&ownedPtr`) / another reference may initialize a reference. Copying a reference extends the original borrow to the new binding.
2026-11-04 18:13:46 +08:00
TermiNexus 6f45fc016b feat(obs): move own values into fields and elements
`A.n = B` / `arr[i] = B` now transfer ownership: the source shadow stops owning the object (no double free) and later uses of B fail as moved. The language server marks the source moved too.
2026-11-04 18:04:36 +08:00
TermiNexus 17c7e1f896 feat(obs): isolate __obs__ library imports in includes_obs
Each configured include root also maps to an includes_obs sibling: OBS files may only import modules that exist there, and non-OBS files may not import includes_obs-only modules. Scanning and stub resolution use the combined list, while the Gen layer enforces the direction.
2026-11-04 17:36:15 +08:00
TermiNexus 2e5218abc6 feat(obs): bind case Some(&mut v) as a mutable reference
A Store-context capture becomes RefKind=2 (writable); `&v` stays read-only. Both still register a borrow, so the owner cannot be del-ed inside the arm.
2026-11-04 17:07:21 +08:00
TermiNexus 40691567f5 feat(obs): bind case Some(&v) by reference
The capture is a read-only reference to the payload (RefKind=1) and registers a borrow of the subject base, so the owner cannot be del-ed while the arm runs and writes through the capture are rejected. The language server binds the capture so v.a resolves.
2026-11-04 16:01:42 +08:00
TermiNexus b9d84c6bda feat(obs): first-class &T / &mut T references
In __obs__ files references are ordinary pointer values (no call-site auto-address), &T is read-only and &mut T writable. A reference borrows its owner: the owner cannot be del-ed while the reference variable is still in scope, references are never owners, and reassigning a reference drops the old borrow. Outside __obs__, &mut T is rejected.
2026-11-04 15:48:21 +08:00
TermiNexus dbee021cd1 fix(lsp): walk assignment target bases for name diagnostics
`_x.a = 1`, `_x.a += 2`, `_x[0] = 3` and `_x.a: T = v` only visited the assigned value, so an unresolved target base never produced the unknown-name warning.
2026-11-03 22:01:24 +08:00
TermiNexus 2c65cea97f feat(lsp): parse own/parenthesized/nullable types and report implicit conversions
ParseType now understands `own T`, `(T)` and `T?`, so assignment checks reuse the existing int/pointer conversion codes (integer<->pointer, pointer mismatch). Value-typed targets reject pointer values, and duplicate nullable reporting is gone.
2026-11-03 19:49:04 +08:00
TermiNexus 295d78aa56 feat(lsp): report nullable flow and pointer/value mismatches
Returns, call arguments (from the callee signature) and constructor arguments (from __init__ or field order) now flag a nullable value flowing into a non-nullable slot, and a pointer value stored into a value-typed target is reported like the compiler does.
2026-11-03 19:34:27 +08:00
TermiNexus 3bc7afaf64 feat(obs): reject nullable constructor and variant payload arguments
OOP constructors read the nullability mask of their __init__ (self offset included), plain structs and renum variants read their field flags; generic variant payloads are read from the normalized type argument. A nullable value can no longer be packed into a non-nullable payload.
2026-11-03 18:26:50 +08:00
TermiNexus 65e03c823f feat(obs): reject nullable call arguments for non-nullable parameters
Compiled callees register a per-parameter nullable mask (self included for methods, specializations included); cross-module callees fall back to their vpi param annotations. A nullable binding/field argument now fails instead of silently narrowing.
2026-11-03 18:12:20 +08:00
TermiNexus b17de2bb65 feat(obs): reject nullable values in non-nullable bindings
In strict mode a `T?` binding or field may hold None, so assigning it to an `own T`, a plain pointer, a non-nullable field or a non-nullable return now fails instead of silently narrowing. Unwrap with `match` (or declare the target as `T?`). The language server publishes the same diagnostic for annotated/plain assignments.
2026-11-03 18:01:21 +08:00
TermiNexus 6073f60321 fix(own): read nullability through the own wrapper
`own (T?)` resolves to the nullable pointer, so a `None` initializer is valid there.
2026-11-03 17:21:35 +08:00
TermiNexus 1077830be9 fix(lsp): stop emitting string tokens
The TextMate grammar already colors strings, and a semantic `string` token makes VS Code classify the adjacent call parentheses as string content, so bracket pair colorization skips them.
2026-11-03 16:48:10 +08:00
TermiNexus d8884ff7dd feat(match): enforce nullable match exhaustiveness in the compiler
The language server reported a missing `None` arm while the compiler accepted the same match. Both paths now require `Some(...)` and the empty branch unless a wildcard covers them, and a wildcard arm without a `Some` arm runs for the non-null branch instead of falling through.
2026-11-03 16:31:41 +08:00
TermiNexus 273792e1be fix(lsp): leave None to the TextMate grammar
The semantic `class` token overrode the grammar and colored `None` green. Emitting nothing restores `constant.language.null`, which uses the same dark blue as `def`.
2026-11-03 16:22:38 +08:00
TermiNexus 0e5933ce09 feat(lsp): color None like a constructor
`None` is a value singleton, not a control keyword, so it now uses the same token type as `Some` instead of the `case`/`own` keyword color.
2026-11-03 16:18:10 +08:00
TermiNexus 7917992deb fix(match): reject a bare Nil pattern
A bare `Nil` parses as a capture, so a nullable match silently treated it as the null branch. Both the compiler and the language server now reject it and point at `None` (nullable `T?`) or `Nil()` (builtin `Option[T]`). Exhaustiveness reports `None` as the missing nullable branch, and the builtin generic type names no longer trigger the unknown-name warning.
2026-11-03 16:11:22 +08:00
TermiNexus 7289b027ec feat(lsp): unknown-name warnings, Nil validation and match exhaustiveness
`Nil` is rejected outside nullable/`Option` matches in both the compiler and the language server, non-exhaustive nullable matches report the missing branch names, and unresolved plain names are published as warnings. Diagnostics now honour ParseError.severity.
2026-11-03 16:00:13 +08:00
TermiNexus 2781ba7466 feat(lsp): flag direct dot access on nullable values
Attribute reads now check the base symbol detail (`T?` annotation) and emit the same error as the compiler when `?.` is missing. `?.` and pattern-bound non-null captures stay clean.
2026-11-03 15:41:14 +08:00
TermiNexus 1911f70818 feat(option): reject direct dot access on nullable values
A field annotated `own T?` / `T?` (and a local declared nullable) now fails fast on `base.field`: it may be None. Use `base?.field` or check against None first. FieldEntry carries a Nullable flag (appended), populated from the annotation during struct registration and inherited by flattened parent fields; both read and write attribute paths check it.
2026-11-03 15:30:16 +08:00
TermiNexus 98d4da7602 feat(option): allow case None on nullable pointers
None stays a keyword literal; the pattern parser already produced MatchSingleton, and the nullable-pointer match now treats it as the Nil branch. LSP colors None as a keyword.
2026-11-03 15:19:53 +08:00
TermiNexus c7ab075594 feat(option): match nullable pointers with Some(v)/Nil; align LSP captures
Compiler:
- `match p:` where p is a nullable pointer (`own U?` / `T?`) now treats
  `Some(v)` as the non-null branch (v is the pointer, so `v.field` works)
  and `Nil` / `Nil()` as the null branch. The subject's pointee must be a
  plain struct (a renum pointee keeps the tag-based renum path).
- Guards are rejected for this form (not implemented), and unsupported
  patterns fail fast.

LSP:
- `case Some(v)` captures are declared and bound to the match subject's
  class in a second pass, so `v.a` resolves and is colored as a property.
- `case Nil:` is no longer treated as a capture variable; `Some` / `Nil`
  get class/property semantic colors.

Probe: the reported ViperTestProject snippet compiles and prints
1 / 8 / 5 (v.a = 5); semantic tokens show Some=class, v=variable,
v.a property, Nil=property. Self-host 110/110 and the regression set
(EnumTest, ListTest, GenericTest/3/4, NewTest, NewLifetimeTest,
StructTest) pass.
2026-11-02 22:40:23 +08:00
TermiNexus c81ebaa7d4 feat(option): builtin Option uses the Nil variant
The lazily registered builtin template now declares Nil = 0 instead of None_.
2026-11-02 22:15:15 +08:00
TermiNexus 695c886ed0 fix(lsp): walk MatchClass sub-patterns as patterns so case captures bind
case Some(v): v is now declared and colored as a variable in the arm body.
2026-11-02 22:07:52 +08:00
TermiNexus 1f491c6dba feat(renum): generic Option[T] and Option pattern matching
- Generic renum specialization: `Option[int].Some(42)` / `Option[int].None_()`
  are intercepted before method dispatch (which would translate the
  subscript type argument as a value), specialize the renum template and
  construct the variant. `_ctor_call_agg_ty` recognizes the generic
  qualifier so construction materializes the renum value.
- Builtin Option: the generator registers `renum Option[T]` (None_ |
  Some(T)) lazily on first `Option[...]` use, with a reserved synthetic
  module id. The copy in includes/builtins.vp is skipped during Phase1
  template/variant registration so a user-defined `renum Option` (EnumTest)
  keeps precedence; constructor/annotation lookup prefers the current
  module's Option.
- Match: the renum class is derived from the concrete subject struct
  (authoritative for `Option[int]`) instead of only the annotation name.
  Both `case Option[int].Some(v)` and `case Some(v)` bind the payload;
  `None_()` works qualified or bare.

LSP: pattern captures (`case Some(v)`) are declared as variables.

Probe: builtin Option without importing builtins prints 42 for Some and
hits the None_ arm; EnumTest (user renum Option) passes; self-host
110/110; full regression set passes.
2026-11-02 22:05:09 +08:00
TermiNexus 51c0c7dae9 fix(option): base?.field yields the field value; resolve chained members in LSP
- A value field under `?.` now yields its value (zero when the base is
  null) instead of a pointer to the field. `print(_U3.n?.d)` prints the
  stored `d` (8), not an address. Pointer fields still yield the pointer
  (null on a null base), so `a?.next?.x` keeps working.
- LSP `_LocalMember` follows an intermediate field's declared type when a
  chain continues (`_U3.n.d` / `_U3.n?.d`), so `d` resolves and is colored
  as a property instead of being dropped as unknown.

Probe: the reported snippet prints 1 and 8; semantic tokens for
`print(_U3.n?.d)` include `_U3` (variable), `n` and `d` (property);
completion after `_U3.n?.` offers the U members. Self-host 110/110;
ListTest, GenericTest, GenericTest3/4, PtrOpProbe, HashLibTest, NewTest,
NewLifetimeTest, StructTest and EscapeOkTest pass.
2026-11-02 20:34:03 +08:00
TermiNexus faf1f17b22 feat(option): base?.field optional access and nullable own
Option access:
- `base?.field` evaluates to `Option[field]`: a null base yields null.
  A value field yields its address (`Option[int]` = `int*`); a pointer
  field is loaded only on the non-null branch and selected with null.
  Chaining (`n?.next?.a`) works through the pointer result.
- The AST `Attribute.optional` flag drives this in translate_attribute.

`own` nullable fix:
- `n: own U? = None` was rejected because the own-None check ignored the
  `?` wrapper. The check now skips nullable annotations, so nullable owned
  bindings accept None; non-null `own T = None` still fails.

LSP:
- member completion treats `n?.` as `n.` (base `n`, not `n?`), so fields
  and methods are offered after `?.`; semantic tokens and diagnostics
  already resolve through the optional attribute.

Probe: null `n?.a` prints the None branch; after `n = new U(5)`,
`q: Option[int] = n?.a; *q` = 5; `n?.next` null-checks; `U.__sizeof__()`
= 16; LSP completion after `n?.` offers `a`/`next` and diagnostics are
clean. Self-host 110/110; ListTest, GenericTest, GenericTest3/4,
PtrOpProbe, HashLibTest, NewTest, NewLifetimeTest, StructTest and
EscapeOkTest pass.
2026-11-02 20:24:34 +08:00
TermiNexus 3b8dc1377d feat(lsp): leave moved variables and their members uncolored
Semantic tokens now drop a variable token whose name was moved before that
line, and a property token whose base (`x` in `x.a`) is such a name. The
transfer statement itself keeps its colors (moves on the same line are
ignored for this filter), so only later uses turn plain/white.

With
  _U2: own U = _U
  _U3: own U = _U2
  print(_U2.a)
line 3 emits only `print`; `_U2` and `a` have no semantic token. The
move diagnostic still marks `_U2` separately.

Completion filtering is unchanged (`_` offers only `_U3`).
2026-11-02 20:09:54 +08:00
TermiNexus 6e6b94fdfb feat(lsp): hide moved names from completion, flag pointer-to-value writes
- Completion now computes the names moved before the cursor in the
  enclosing function and omits them, so after
  `_U2: own U = _U` typing `_` no longer offers `_U`.
- Diagnostics report a pointer/`own` value stored into a value-typed
  annotation (`_U3: U = _U2`) as an error, matching the compiler's
  CastError; pointer/own/optional targets stay clean.

Verified over textDocument/completion and textDocument/diagnostic:
`_` offers only `_U2`; `_U3: U = _U2` yields one severity-1 diagnostic on
`_U2`; `_U3: own U = _U2` yields none.
2026-11-02 20:02:46 +08:00
TermiNexus f31c1dbc78 feat(own): enforce move use-after-move and reject pointer-to-value transfer
Compiler:
- moving an owned value (`b: own T = a`) marks the source released; this
  now also applies outside OBS files when the target is declared `own`.
  Later reads/field access fail with a LifetimeError.
- transferring an owned pointer into a value-typed target
  (`_U3: U = _U2`) now fails with CastError before codegen. Previously it
  emitted a bitcast of the struct value to a pointer and died in mlir-opt.

LSP:
- track `own` declarations and moves per function and report
  "cannot use 'x': its value was moved" as an error diagnostic on later
  uses (severity 1, so editors paint it red).
- a rebinding clears the moved state.

Probe: `_U: own U = new U(1); _U2: own U = _U; print(_U2.a)` is clean;
`print(_U.a)` produces the move diagnostic and the compiler LifetimeError;
`_U3: U = _U2` produces the CastError. Self-host 110/110; ListTest,
GenericTest, GenericTest3/4, PtrOpProbe, HashLibTest, NewTest,
NewLifetimeTest, StructTest, EscapeOkTest and OopInheritTest pass.
2026-11-02 19:52:59 +08:00
TermiNexus 6fa98cf303 fix(lsp): resolve own/Box/Option types and color builtins
- Treat OwnT / OptionT / NullableT / Option / Box / BoxT / RawT as type
  wrappers in `_IsTypeWrapperName`, so `_U: own U` binds type U and
  `_U.a` resolves to the field (property color, hover, goto).
- Emit function semantic tokens for builtin calls (print, len, range,
  malloc, free, sizeof, ...), which have no declaration symbol; `print`
  was previously colored only by the grammar and lost it inside files the
  grammar could not scope.

Verified over a real semanticTokens/full request: `own` = keyword,
`print` = function, `_U.a` = property on the same line.
2026-11-02 19:01:40 +08:00
TermiNexus c95290f913 fix(lsp): color the own soft keyword
`own` is consumed by the annotation parser, so it never appears as a
symbol and the editor showed it uncolored. Emit a keyword semantic token
for the name `own`; identifiers/keywords otherwise stay with the TextMate
grammar.

This also unblocks symbol colors after an `own` annotation once the
server is rebuilt (the old binary could not parse `own` at all and lost
every later symbol).
2026-11-02 18:55:15 +08:00
TermiNexus c37b8dc52c feat(obs): drop default Box[T], add explicit own T ownership
Revert the implicit boxing pass: a bare `T` annotation is a raw value
again (`_U: U = new U(...)` fails with a pointer/value CastError), and
struct layouts are untouched.

`own T` is the explicit owned reference:
- resolves to one pointer layer (`Ptr(T)`); `own T?` stays nullable.
- a variable declared `own` is registered as an owned slot; a `new`
  initializer is always taken over (outside the NLL frame-confined
  predicate), so scope exit runs `__del__` + `free`.
- `new stack` is rejected for `own` (its storage dies with the frame).
- `own` bindings reject `None`; moves (`b: own T = a`) release the source
  slot.
- a bare identifier named `own` still works as a name/type.

`Box[T]`, `RawT[T]`, `Option[T]` / `T?` stay available as explicit forms.
LSP completion lists `own`.

Probe: `struct U { ..., n: own U }` resolves and `U.__sizeof__()` = 24;
`_U: own U = new U(1,2,3,4); _B: own U = _U` runs and `print(_U.a)` fails
with LifetimeError; `own U = None`, `own U = new stack U(...)` fail fast;
without `own` the old CastError returns; `own` works outside OBS too.
Self-host 110/110; ListTest, GenericTest, GenericTest3, GenericTest4,
PtrOpProbe, HashLibTest, NewTest, NewLifetimeTest, StructTest and
EscapeOkTest pass.
2026-11-02 18:26:00 +08:00
TermiNexus 1581aa0d84 feat(obs): default T to Box[T], explicit Box/RawT/Option, move analysis
Rename the per-file opt-in from `__cwa__` to `from __future__ import __obs__`
(internal StrictCWA becomes StrictOBS).

OBS type model:
- `Box[T]` / `BoxT[T]` is one owned indirection (`Ptr(T)`); a user generic
  class named Box keeps precedence.
- `RawT[T]` is the unboxed value type; primitives, arrays and pointers stay raw.
- `Option[T]` is the explicit spelling of the existing `T?` suffix
  (parser marker renamed to OptionT; NullableT still accepted) and resolves
  to a nullable pointer.
- Declaration annotations (locals, globals, parameters, returns) get an
  `AutoBoxT[...]` wrapper in OBS files before the vpi pass, so struct/class
  types become `Box[T]` while primitives stay raw; struct field declarations
  are left untouched, preserving the layout (`U.__sizeof__()` stays 16).
  Explicit `Box[...]`/`RawT[...]`/`Option[...]`/`T?` are not wrapped again.

Ownership:
- an OBS move (`b: T = a` where `a` owns a heap object) marks the source
  slot as released, so later uses (including `a.field`) fail fast; rebinding
  clears the state.

LSP: `Box`, `RawT` and `Option` are offered as builtin completions.

Probe: `_U: U = new U(1,2,3,4); _B: U = _U; print(U.__sizeof__())` prints 16 and
`print(_U.a)` fails with LifetimeError; Box[U]/Option[U]/U? declarations work;
`Box[U] = None` is rejected; `__cwa__` is rejected and `__obs__` accepted.
Self-host 110/110; ListTest, GenericTest, GenericTest3, GenericTest4,
PtrOpProbe, HashLibTest, NewTest, NewLifetimeTest, StructTest and
EscapeOkTest pass.
2026-11-02 18:10:34 +08:00
TermiNexus 8c72adde88 feat(gen): nullable T? types and non-null analysis in strict CWA files
A strict CWA file (`from __future__ import __cwa__`) now distinguishes
nullable from non-nullable types:

- `name: T?` resolves to `Ptr(T)` and records the slot as nullable
  (a per-function slot table, like the released-slot table).
- assigning `None` to a non-nullable declaration, plain assignment,
  annotated attribute/subscript target or return type fails fast with a
  TypeError; `T?` accepts it.
- class-name extraction unwraps `NullableT[...]`, so field lookup,
  method dispatch and `AnnotClassName` still see the class.

Non-strict files keep the previous behavior (`T* = None` is accepted);
`T?` also parses there as `Ptr(T)`, it just carries no enforcement.

Probe: strict `p: A? = None` / `p = new heap hlife A()` / `p = None`
works and null comparisons run; `p: A = None`, `p: A* = None`,
`q = None` on a non-nullable variable and `-> T*` + `return None` all
fail fast; `-> A?` and field `next: A?` work; the same `T* = None`
still compiles outside strict mode. Self-host 110/110; ListTest,
GenericTest3, PtrOpProbe, HashLibTest, NewTest, NewLifetimeTest,
StructTest and EscapeOkTest pass.
2026-11-02 15:47:36 +08:00
TermiNexus 5f981d7878 feat(gen): del obj reclaims a heap object (__del__ then free)
`del name` now destroys a class/struct instance:

- requires a pointer variable (T*) to a class/struct; value-typed T and
  non-struct types fail fast,
- rejects frame-allocated pointers (`new stack` / borrows) and repeat
  `del` of the same binding,
- calls `__del__` (MRO-aware, skipped for a null pointer) and then
  `free`s the object,
- cancels the automatic scope-exit release when the binding is a
  compiler-owned `slife` object, so the destructor and free still happen
  exactly once,
- marks the slot released: later reads, field access, subscript access
  and `&name` fail with a LifetimeError until the variable is rebound
  (rebinding clears the mark, as with willFree).

The released-variable diagnostic now names both causes (`del` or
`@life(willFree(...))`) and drops the obsolete `c.deref(p)` spelling.

Probe: hlife object prints `__del__` output once and exits 0; slife
object prints once (no second call at scope exit); field read/write and
plain use after `del` fail fast; rebound variable is usable again.
Self-host 110/110; ListTest, GenericTest3, PtrOpProbe, HashLibTest,
NewTest, NewLifetimeTest, StructTest and EscapeOkTest pass.
2026-11-02 15:15:20 +08:00
TermiNexus d9511a54ea fix(gen): keep new T() construction in the storage it allocated
For a plain struct with no field defaults and no constructor arguments,
the `A()` call path returned `zeroinitializer` directly instead of
constructing into the storage provided by `new`. Consequences for
`p: A* = new A()`:

- the malloc block was leaked,
- the struct value was materialized into an entry alloca by coercion,
  so `p` pointed at the frame,
- the slife scope-exit free then freed a stack address (heap corruption / 0xC0000374).

Reuse the constructor path whenever a `new` storage descriptor is
pending, so `_translate_struct_ctor` zero-initializes the heap block and
returns its pointer.

Also guard the compiler-owned `new ... slife` binding: the target slot
must be a pointer variable, otherwise a value-typed `T` target would make
the scope-exit free load the struct value and bitcast it to a pointer.

Probe: `new heap A()`, `new A()`, `new stack A()` and `new heap hlife A()`
on a field-only class all print the expected values and exit 0; the
generated IR frees the malloc pointer instead of a stack slot.
Self-host 110/110; ListTest, GenericTest3, PtrOpProbe and HashLibTest
pass; NewTest, NewLifetimeTest, StructTest, EscapeOkTest and
OopInheritTest pass. NewAstTest and GenericOopTest fail the same way on
the previous compiler (pre-existing).
2026-11-02 14:45:44 +08:00