Skip to content

feat: mixed/union values at narrower declared boundaries + typed callbacks over unknown-element arrays#470

Open
chadmandoo wants to merge 1 commit into
illegalstudio:mainfrom
chadmandoo:upstream-pr/mixed-boundaries-callbacks
Open

feat: mixed/union values at narrower declared boundaries + typed callbacks over unknown-element arrays#470
chadmandoo wants to merge 1 commit into
illegalstudio:mainfrom
chadmandoo:upstream-pr/mixed-boundaries-callbacks

Conversation

@chadmandoo

Copy link
Copy Markdown
Contributor

Two related type-checker completeness fixes, both runtime-enforced PHP the checker statically rejected:

  1. types_compatible accepts a Mixed VALUE into any declared type (param and return) and a UNION value with at least one member the declaration accepts (e.g. an int|false seek result passed to an int param on its success path — a union with NO compatible member stays rejected). Scoped to the arg/return check only; interface conformance and property assignment are unaffected. The boxed-Mixed representation already funnels at the boundary — verified byte-identical.

  2. usort/uasort/array_map callbacks keep their DECLARED parameter contract when the array's element type has no literal form: Object elements (array_map's scalar dummy builder had no Object arm and fabricated Int) and Mixed/Never elements (unknown-element array-hinted values) now use a synthetic element binding.

Byte-parity vs PHP 8.5 + 4 regression tests. Known limit noted in the commit: array_map over OBJECT-element arrays now passes the checker but the EIR lowering doesn't support object elements yet (pre-existing wall).

chadmandoo added a commit to chadmandoo/elephc that referenced this pull request Jul 10, 2026
Survey-driven object/Mixed covariance at callback + interface boundaries
(85% → 88%, 262 → 269/307). illegalstudio#470 (mixed/union arg boundary) already landed
in 5052070; this completes the family:

- ARRAY-CALLBACK OBJECT/MIXED ELEMENTS (illegalstudio#519 / EC-28b): array_map fabricated
  an IntLiteral dummy for non-scalar elements, so a typed callback
  (`fn (FieldDescriptor $f) => ...`) over a `list<FieldDescriptor>` failed
  "expects Object, got Int". array_map now routes through the object-aware
  comparator_dummy_arg_for_elem (synthetic-var + env binding), and that helper
  gains a Mixed arm — a bare `array` element infers as Mixed (phpdoc generics
  aren't enforced on the PHP `array` type), and Mixed accepts any declared
  parameter at the runtime-enforced boundary. usort/uasort widen their element
  gate from Object to Object|Mixed so the same typed-comparator shape checks.
  Clears the array_map/usort/uasort `callback parameter expects Object, got
  Int` family (8).
- INTERFACE COVARIANT SELF-RETURN (#547 / EC-53): a `withX(): static`/self
  implementation of an interface-typed return (PSR-7 immutability methods)
  cannot go through type_accepts — the class is not yet registered mid-schema.
  Accept it from the facts at hand (this validation IS the proof the class
  implements the interface). Clears FileResponse et al. `withStatus incompatible
  return type` (3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…backs over unknown-element arrays

Two boundary relaxations in `types_compatible`, both runtime-enforced PHP
that the checker statically rejected, plus the typed-callback fix they enable:

- A `mixed` VALUE flowing into a narrower declared type (param or return) is
  legal PHP — the engine enforces it at runtime (TypeError on mismatch), and
  the boxed-Mixed representation already funnels at the boundary. Trust the
  declaration.
- A union VALUE with at least one member the declaration accepts crosses the
  boundary (e.g. an `int|false` seek result passed to an `int` param on the
  success path). A union with NO compatible member stays rejected.
  `types_compatible` feeds only argument/return checks, so interface
  conformance and property assignment are unaffected.
- Typed callbacks keep their declared contract over object/unknown-element
  arrays: usort/uasort/array_map fabricated an `Int` placeholder when the
  element type had no literal form, so a typed comparator/mapper failed
  ("callback parameter expects Object, got Int"). `array_element_type` now
  yields `Mixed` for a `Mixed` receiver; `comparator_dummy_arg_for_elem`
  binds a synthetic variable for Object/Mixed elements and binds `Mixed` for
  `Never` (empty/unknown) elements — PHP never invokes the callback for an
  empty array, so the declared contract stands unchecked.

Byte-parity vs PHP 8.5 on all regression tests. Known limit: array_map over
OBJECT-element arrays now passes the checker but still hits a pre-existing
EIR lowering wall — a backend follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chadmandoo chadmandoo force-pushed the upstream-pr/mixed-boundaries-callbacks branch from 59e73d9 to bcf5fed Compare July 11, 2026 14:17
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