Skip to content

refactor(core): improvements on types#335

Open
Guikingone wants to merge 4 commits into
illegalstudio:mainfrom
Guikingone:feat/type-guard-narrowing
Open

refactor(core): improvements on types#335
Guikingone wants to merge 4 commits into
illegalstudio:mainfrom
Guikingone:feat/type-guard-narrowing

Conversation

@Guikingone
Copy link
Copy Markdown
Contributor

No description provided.

An untyped parameter was specialized to the last non-int argument type and then frozen, so a function called as f(5) and f("s") typed $x as string and coerced the integer argument to a string at the call site; is_int()/gettype() and other type-dependent operations then misbehaved at runtime.

Accumulate the argument types observed across all call sites per undeclared parameter and widen to a union once a non-int/bool type appears, so each argument keeps its own runtime type (Union lowers to the boxed Mixed shape, so args are boxed at the call site). Int/bool-only observations are recorded but do not, on their own, drive specialization, preserving the int fallback and keeping a bool param's strict === true/false guard intact for the optimizer.
A method call on a receiver whose static type does not name a single class — a
`mixed` value, or a union of object classes like `Foo|false` — previously emitted
no dispatch and left a garbage value in the result register, because the receiver
class could not be resolved to a vtable slot at compile time.

Such calls now dispatch dynamically: the receiver is evaluated once and unboxed to
an object pointer (fataling if it is not an object), then its runtime class id
selects the matching class's method via the normal static-dispatch path. Candidate
classes are the user classes that declare the method; intrinsic-method classes are
excluded since their argument shapes differ, so a `mixed` value holding such an
object faults cleanly as "undefined method" rather than miscompiling.

The checker now also accepts a regular `->` call on a union with a single object
class (e.g. `Foo|false`); codegen handles it via the same runtime dispatch. The
nullsafe `?->` and property-access paths are unchanged.

Adds 6 regression tests; full suite green.
Inside an `if` guarded by is_int/is_float/is_string/is_bool (and aliases) on a variable, or `$x instanceof Class`, the guarded variable is narrowed to that type in the then-branch and its complement in the else-branch; a leading `!` swaps them, and a guard with no else whose body diverges narrows the statements after the if. This makes the common int|object "overload" shape type-check. Narrowing is a type-checker step: the value keeps its boxed Mixed runtime, and codegen coerces scalars (existing unbox path) and dispatches methods on a Mixed receiver by runtime class id.

Also extends the heterogeneous-call union parameter inference to instance- and static-method parameters (previously free functions only), guarded so only the inferred Int fallback (or an already-tracked parameter) widens and intrinsic Mixed-typed parameters (Fiber, Generator, ...) are left untouched. Closure parameters are unchanged.

Adds narrowing and method-param-union codegen tests, a type-narrowing example, and docs.
…rowing

Type narrowing covers function and method parameters (inferred as unions across heterogeneous call sites); closure parameters called with incompatible types are still rejected at compile time. Documents the limitation pending a dedicated fix (span-keyed persistent closure signatures).
@Guikingone Guikingone marked this pull request as ready for review June 4, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant