Skip to content

fix(types): join mismatched array/array branch merges as array<mixed> instead of bare mixed#596

Open
mirchaemanuel wants to merge 1 commit into
illegalstudio:mainfrom
mirchaemanuel:fix/587-checker-merge-array-type
Open

fix(types): join mismatched array/array branch merges as array<mixed> instead of bare mixed#596
mirchaemanuel wants to merge 1 commit into
illegalstudio:mainfrom
mirchaemanuel:fix/587-checker-merge-array-type

Conversation

@mirchaemanuel

Copy link
Copy Markdown
Contributor

Fixes #587

Root cause

The type checker's branch-merge join (merge_match_arm_result_type in
src/types/checker/inference/expr/mod.rs, shared by match, ternary, and
?:/??) had no case for two array types with different element types. A
merge like match($argc) { 1 => [1, 2], default => ["a", "b"] } fell through
to bare Mixed, while the lowering side (wider_type_for_merge,
src/ir_lower/expr/mod.rs) keeps it an array. That divergence rejected
PHP-valid array uses of the merged value at compile time:

  • passing it to a by-ref array parameter (expects Array(Mixed), got Mixed)
  • in_array() / array_sum() (argument must be array)
  • spread [...$r] (Spread operator requires an array), which additionally
    emitted a misleading follow-on Undefined variable: $s naming the
    assignment target

The homogeneous-arm versions of the same programs already compiled, because
identical arm types short-circuit before reaching the Mixed fallback.

Fix

Add a pure merge_array_branch_types helper mirroring the lowering-side
array/array and assoc/assoc rules: differing element (or key/value) types
widen elementwise to Mixed, so the merge stays array<mixed> /
array<mixed, mixed> and array operations type-check like they do for
homogeneous merges. It is consulted after the Void/nullable handling and
before object-union handling. Any pair that is not array/array or assoc/assoc —
including an indexed-vs-associative mix — returns None and falls through to
the existing Mixed handling, so scalar unions stay Mixed and object|null
merges stay nullable unions (no over-widening).

The misleading Undefined variable follow-on no longer fires for this issue
because the spread now type-checks. The underlying cascade (a failed-to-type
assignment RHS leaving its target unregistered) is a separate latent defect,
reproducible independently with <?php $x = 5; $s = [...$x]; echo count($s);,
and is left for a follow-up.

Note on runtime

This is a checker-only change and only aligns the static type. Runtime
correctness of heterogeneous array/array merges (the merge temp lowering /
segfault) is tracked by #549 and owned by PR #583; on current main a
heterogeneous merge may still fail in the EIR backend (e.g.
array_sum for PHP type Array(Str)). Accordingly, the regression tests assert
the type-check outcome only.

Test plan

  • New unit tests (src/types/checker/inference/expr/mod.rs): elementwise
    widening for heterogeneous indexed and associative merges, shared-element
    preservation, and None for indexed-vs-assoc and non-array pairs.
  • New checker-level e2e tests (tests/error_tests/type_system.rs): by-ref
    array param, array_sum/in_array, spread, and a ternary variant now
    type-check; a scalar (int vs string) merge still types as mixed and
    keeps rejecting array-only uses.
  • cargo test -p elephc --lib merge_array_branch_types — 5 passed
  • cargo test --test error_tests -- type_system narrowing — 86 passed
  • cargo test --test codegen_tests -- match ternary — 183 passed
  • cargo build clean, git diff --check clean

https://claude.ai/code/session_01HRvbcjPHa3kAojdCjNZL5L

… instead of bare mixed

The type checker collapsed a heterogeneous-element match/ternary/`??` array
merge (e.g. `[1, 2]` vs `["a", "b"]`) to bare `Mixed`, so PHP-valid array uses
of the result were rejected at compile time: passing it to a by-ref `array`
parameter, `in_array()`/`array_sum()`, and spread (`[...$r]`). The spread
rejection additionally emitted a misleading follow-on "Undefined variable"
diagnostic naming the assignment target.

`merge_match_arm_result_type` now mirrors the lowering-side `wider_type_for_merge`
array/array and assoc/assoc rules through a new pure `merge_array_branch_types`
helper: differing element (or key/value) types widen elementwise to `Mixed`, so
the merged value keeps an `array<mixed>` type and array operations type-check
just as they do for homogeneous merges. Non-array merges are untouched — scalar
unions stay `Mixed` and object|null merges stay nullable unions — and an
indexed-vs-associative mix still falls through to `Mixed`, matching lowering.

Runtime correctness of heterogeneous merges is tracked by illegalstudio#549 / PR illegalstudio#583; this
change only aligns the static type. The residual "Undefined variable" cascade
for genuinely-failing spreads (repro: `$x = 5; $s = [...$x]; count($s);`) is a
separate latent defect left for a follow-up.

Claude-Session: https://claude.ai/code/session_01HRvbcjPHa3kAojdCjNZL5L
@mirchaemanuel

Copy link
Copy Markdown
Contributor Author

The misleading Undefined variable follow-on mentioned in the description is a separate error-recovery defect (a failed assignment RHS leaves the target unregistered) — reverified on current main with an independent repro and filed as #597.

@github-actions github-actions Bot added area:types Touches type checking, inference, or compatibility. size:s Small pull request. type:fix Corrects broken or incompatible behavior. labels Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:types Touches type checking, inference, or compatibility. size:s Small pull request. type:fix Corrects broken or incompatible behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Checker types a heterogeneous array/array merge as mixed, rejecting valid array uses (by-ref, in_array, array_sum, spread)

1 participant