You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Psalm reports MixedMethodCall when chaining withoutGlobalScopes() directly onto a belongsTo() call inside a relationship definition. The receiver of withoutGlobalScopes() (the BelongsTo returned by belongsTo()) is treated as mixed, breaking the fluent chain.
ERROR: MixedMethodCall
at src/Cart/Models/AbandonedCart.php:40:51
Cannot determine the type of the object on the left hand side of this expression (see https://psalm.dev/015)
return $this->belongsTo(Customer::class)->withoutGlobalScopes();
The column points at ->withoutGlobalScopes(). Psalm has lost the type of the $this->belongsTo(Customer::class) receiver.
Expected behaviour
belongsTo() is stubbed (in stubs/common/Database/Eloquent/Concerns/HasRelationships.phpstub) to return \Illuminate\Database\Eloquent\Relations\BelongsTo<TRelatedModel, $this>, and withoutGlobalScopes(?array $scopes = null): $this is declared in the Builder stub. Via the Relation's @mixin Builder<TRelatedModel>, the chain should resolve to BelongsTo<Customer, AbandonedCart> so the method's @return BelongsTo<Customer, self> is satisfied without a Mixed cascade.
Notes / context
Likely the same family as Support hasMany(...)->orderBy() #190 (Support hasMany(...)->orderBy()), which was fixed in Add MethodForwardingHandler for Relation method forwarding #642 by adding MethodForwardingHandler. That handler covers QueryBuilder-only methods that come in through __call (Path 2: orderBy, limit, groupBy, etc.) and Builder-mixin methods (Path 1). withoutGlobalScopes() is declared directly in the Builder stub with @return $this, so it's a Path 1 / mixin-interception candidate — but the chain still falls back to Mixed at the immediate belongsTo() receiver, not just at later links.
Workaround: assign the relation to a typed local first, then call the chained method:
Forcing the type with the intermediate variable resolves the error, which confirms the regression is in how the chained call infers the receiver type rather than in the Builder stub's withoutGlobalScopes() signature itself.
I haven't dug further into whether the gap is in MethodForwardingHandler's mixin-interception path, in the stubbed belongsTo()@return ... <TRelatedModel, $this> template resolution under chaining, or in core Psalm 7's handling of $this through @mixin. Happy to provide more debug info if helpful.
Summary
Psalm reports
MixedMethodCallwhen chainingwithoutGlobalScopes()directly onto abelongsTo()call inside a relationship definition. The receiver ofwithoutGlobalScopes()(theBelongsToreturned bybelongsTo()) is treated asmixed, breaking the fluent chain.Versions
psalm/plugin-laravel:dev-master@9304208713a57542c08b14ed12e073cddbef978f(composer constraint^4.0@RC)vimeo/psalm:7.0.0-beta19psalm/plugin-phpunit:0.20.1laravel/framework: v12Reproducer
Reported error:
The column points at
->withoutGlobalScopes(). Psalm has lost the type of the$this->belongsTo(Customer::class)receiver.Expected behaviour
belongsTo()is stubbed (instubs/common/Database/Eloquent/Concerns/HasRelationships.phpstub) to return\Illuminate\Database\Eloquent\Relations\BelongsTo<TRelatedModel, $this>, andwithoutGlobalScopes(?array $scopes = null): $thisis declared in the Builder stub. Via the Relation's@mixin Builder<TRelatedModel>, the chain should resolve toBelongsTo<Customer, AbandonedCart>so the method's@return BelongsTo<Customer, self>is satisfied without a Mixed cascade.Notes / context
Support hasMany(...)->orderBy()), which was fixed in AddMethodForwardingHandlerfor Relation method forwarding #642 by addingMethodForwardingHandler. That handler covers QueryBuilder-only methods that come in through__call(Path 2:orderBy,limit,groupBy, etc.) and Builder-mixin methods (Path 1).withoutGlobalScopes()is declared directly in the Builder stub with@return $this, so it's a Path 1 / mixin-interception candidate — but the chain still falls back to Mixed at the immediatebelongsTo()receiver, not just at later links.withoutGlobalScopes()signature itself.I haven't dug further into whether the gap is in
MethodForwardingHandler's mixin-interception path, in the stubbedbelongsTo()@return ... <TRelatedModel, $this>template resolution under chaining, or in core Psalm 7's handling of$thisthrough@mixin. Happy to provide more debug info if helpful.