Skip to content

Do not let placeholder members of intersection members override declared members - #6537

Merged
ondrejmirtes merged 3 commits into
2.2.xfrom
intersection-mixed-member-placeholders
Sep 22, 2026
Merged

ondrejmirtes merged 3 commits into
2.2.xfrom
intersection-mixed-member-placeholders

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

MixedType - and so T of mixed - answers hasMethod(), hasProperty() and hasConstant() with yes for every name and describes the member with a Dummy*Reflection placeholder declared on stdClass. IntersectionType merged those placeholders with the members declared by the other intersected types, so on T&Foo:

  • methods: since 857a5f4 IntersectionTypeMethodReflection prefers a variadic signature, so the placeholder (a variadic TrivialParametersAcceptor) always won - getDeclaringClass() returned stdClass and $x->bar(...) lost its parameters
  • properties: the placeholder provided the declaring class when it came first - errors talked about stdClass::$prop
  • constants: getConstant() returned the placeholder when the mixed member came first

Placeholders are now used only when no member declares the member.

The method part fixes the shopware/shopware integration test, red since 857a5f4 (2026-09-17): phpstan-symfony's RequestTypeSpecifyingExtension calls $methodReflection->getDeclaringClass()->getNativeMethod('getSession') and crashed with "Method getSession() was not found in reflection of class stdClass" on

static::callback(function (Request $request) {
    return $request->hasSession()
        && $request->getSession() instanceof Session;
})

where the closure parameter is CallbackInput&Request while PHPUnit's callback() template is being inferred.

On 2.3.x, IntersectionType is shadowed by turbo-ext/src/IntersectionType.cpp (unresolvedPrototype(), getConstant()), so the merge-up needs the same change there.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY

ondrejmirtes and others added 3 commits September 22, 2026 16:27
…red methods

`MixedType` (and so `T of mixed`) answers `hasMethod()` with yes for every
method name and describes it with a `DummyMethodReflection` - declared on
`stdClass`, with a variadic signature that accepts anything.
`IntersectionType::getUnresolvedMethodPrototype()` merged that placeholder
with the method declared by another member, so on `T&Foo` the combined
method could pick the placeholder. Since `IntersectionTypeMethodReflection`
prefers a variadic signature, it always did: `getDeclaringClass()` returned
`stdClass` and `$x->bar(...)` lost its parameters.

phpstan-symfony's `RequestTypeSpecifyingExtension` looks up `getSession()`
on the declaring class of `hasSession()` and crashed on
`static::callback(function (Request $request) { return $request->hasSession() && ...; })`,
where the closure parameter is `CallbackInput&Request` while PHPUnit's
`callback()` template is being inferred.

Placeholder methods are now used only when no member declares the method.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY
…clared properties

Same as for methods: on `T&Foo`, the `DummyPropertyReflection` that `T of
mixed` answers every property with was merged with `Foo`'s property and,
being first, provided the declaring class - errors talked about
`stdClass::$prop`. Placeholder properties are now used only when no member
declares the property.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY
…lared constants

`IntersectionType::getConstant()` returned the constant of the first member
answering `hasConstant()` with yes. `T of mixed` does so for every name with
a `DummyClassConstantReflection` declared on `stdClass`, so on `T&Foo` the
result depended on the order of the members. Placeholder constants are now
used only when no member declares the constant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY
@ondrejmirtes
ondrejmirtes merged commit 31470ab into 2.2.x Sep 22, 2026
512 of 515 checks passed
@ondrejmirtes
ondrejmirtes deleted the intersection-mixed-member-placeholders branch September 22, 2026 14:34
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