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 (illegalstudio#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
@github-actions github-actions Bot added area:builtins Touches PHP builtin declarations or emitters. area:types Touches type checking, inference, or compatibility. size:s Small pull request. type:feature Introduces new user-visible behavior or capabilities. labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:builtins Touches PHP builtin declarations or emitters. area:types Touches type checking, inference, or compatibility. size:s Small pull request. type:feature Introduces new user-visible behavior or capabilities.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant