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
feat(typing): introduce TypingNullT for sound null literal handling
Before this change, the `null` literal synthesized to Dynamic, which
made `a: int = null` (and every other typed-slot-with-null) silently
type-check. Reassignments to null left the narrowed type as Dynamic
too, so subsequent reads didn't reflect "this is definitely null."
Inferred returns mixing a value and null produced int|dynamic instead
of int?.
New leaf type TypingNullT:
- LitNull synth uses TypingNullT instead of Dynamic.
- TypingNullT only flows into slots that admit null - Optional<T>,
unions containing null, any/dynamic. Non-nullable slots emit
RAD30001 (the existing nullable-suggestion diagnostic). User-
facing name is "null"; users never write it as a standalone
annotation (T? remains the canonical spelling).
- TypingOptionalT.IsAssignableFrom learns to accept TypingNullT
directly (in addition to T and Optional<T>).
Narrowing now has a definite answer on the null side:
- narrowNullEquality returns TypingNullT on the "x == null" arm,
replacing the previous no-op. Downstream type_of("null") dispatch
and "subtract the non-null arm" patterns see x as definitely null.
- narrowByTypeOf for Optional<T> returns TypingNullT on the null
arm (truthy when target == "null", falsy when target matched the
non-null inner type).
- joinNarrowArms and unionTypesForJoin both collapse `T | null` to
`T?` so two-arm narrowing results match how users spell nullable
themselves (e.g. type_of-narrowing `int?|str` produces `str?` on
the non-int branch instead of `null|str`).
The existing matchesTypeOf("null") returned false unconditionally;
now it returns true for TypingNullT leaves so the new narrowing
arms compose with downstream type_of guards.
Snapshots in narrow/null.snap: a: int = null fires, a: int? = null
clean, else-of-!=null narrows to TypingNullT, and a fn mixing
return 5 / return null synthesizes to int?. Drift in
narrow/nested_fn.snap (x reassigned to null now types null, not
dynamic) and narrow/type_of.snap (b in the else branch collapses
to str?) is the expected canonicalisation.
0 commit comments