Skip to content

feat(types): accept an array of any object at an array<Object> parameter#519

Open
chadmandoo wants to merge 1 commit into
illegalstudio:mainfrom
chadmandoo:upstream-pr/array-element-object-covariance
Open

feat(types): accept an array of any object at an array<Object> parameter#519
chadmandoo wants to merge 1 commit into
illegalstudio:mainfrom
chadmandoo:upstream-pr/array-element-object-covariance

Conversation

@chadmandoo

Copy link
Copy Markdown
Contributor

What

PHP array parameters carry no element-type enforcement (there are no generics), so a declared array<Object> should accept an array of any object — a sibling or a subtype alike.

Previously rejected (two concrete types implementing different interfaces, passed to the same array param):

final class QCond implements CondA { /* ... */ }
final class CCond implements CondB { /* ... */ }

final class QB {
    /** @var list<QCond|CCond> */
    private array $conditions;
    private function withConditions(array $conditions): self { /* ... */ }
    public function where(string $s): self   { return $this->withConditions([new QCond($s)]); }
    public function compound(string $s): self { return $this->withConditions([new CCond($s)]); } // rejected
}

How

In type_accepts, the Array(expected_elem) arm now also accepts an Array(actual_elem) when both element types are objects. Both sides share the same run representation (a boxed object pointer), so there is no element re-typing — the callee reads elements through the class table regardless of the static element name.

Test

test_array_element_object_sibling_covariance_at_param — a withConditions-style builder passing an array of a sibling concrete object, byte-parity vs PHP 8.5.

🤖 Generated with Claude Code

PHP `array` parameters carry no element-type enforcement (there are no
generics), so a declared `array<Object>` accepts an array of ANY object — a
sibling or a subtype alike. Both sides share the same run representation (a
boxed object pointer), so there is no element re-typing and the callee reads
them through the class table regardless of the static element name.

This is what a query-builder-style `withConditions(array $conditions)` needs
when one call passes `array<QueryCondition>` and another `array<CompoundCondition>`
(two concrete types implementing different interfaces), matching the docblock
`list<QueryConditionInterface|CompoundConditionInterface>`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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