Skip to content

Fix Route::middleware() chain inference on Psalm dev-master#889

Merged
alies-dev merged 2 commits intomasterfrom
worktree-888-route-middleware-conditional-return
May 7, 2026
Merged

Fix Route::middleware() chain inference on Psalm dev-master#889
alies-dev merged 2 commits intomasterfrom
worktree-888-route-middleware-conditional-return

Conversation

@alies-dev
Copy link
Copy Markdown
Collaborator

Issue to Solve

On vimeo/psalm dev-master, method chains involving Illuminate\Routing\Route::middleware() infer the wrong branch of the plugin stub's conditional return type. Calls with non-null array arguments resolve to array<array-key, string> instead of $this, breaking every subsequent chained call (->withoutScopedBindings(), ->name(...), etc.) and any code passing the result to RouteCollectionInterface::add(Route).

Related

Fixes #888

Solution Description

Discriminating tests confirmed the trigger is the duplicate conditional return type between the plugin stub (($middleware is null ? string[] : $this)) and Laravel's source (($middleware is null ? array : $this)). Psalm dev-master mishandles the merge. @psalm-variadic was ruled out as the cause.

The proper layer for this is vimeo/psalm itself. As a plugin-side workaround until the upstream bug is fixed, this PR drops the conditional from the stub (@return $this) and retains @psalm-variadic. Trade-off: the no-arg getter form $route->middleware() now resolves to $this instead of array<array-key, string>. That form is rarely called from user code.

A regression test (tests/Type/tests/RouteMiddlewareTest.phpt) covers all reported failure modes (chained method call, facade chain, RouteCollectionInterface::add consumer) and pins the no-arg getter trade-off so future contributors can't silently re-trigger the bug by restoring the conditional. The new test fails on dev-master without the stub change (verified) and passes on both 7.0.0-beta19 and dev-master with it.

A short note was added to docs/contributing/README.md warning future stub authors against restating identical conditional return types from Laravel source.

Checklist

  • Tests cover the change (type test in tests/Type/)
  • Documentation is updated

…master

The plugin stub for `Illuminate\Routing\Route::middleware()` previously restated
Laravel's source `@return ($middleware is null ? array : $this)` (as `string[]`
instead of `array`). On `vimeo/psalm` dev-master, the stub/source merge of two
identical conditional return types collapses chained calls like
`Route::post(...)->middleware(['web'])->withoutScopedBindings()` to the null
branch, breaking every subsequent call.

Discriminating tests confirmed `@psalm-variadic` is not the cause; removing the
stub method entirely fixes the chain (Laravel's source resolves correctly) but
loses the variadic call form `->middleware('a', 'b')` that the plugin originally
added the stub to support.

Workaround: drop the conditional from the stub (`@return $this`), keep
`@psalm-variadic`. Trade-off: the no-arg getter form `$route->middleware()` now
resolves to `$this` instead of `array<array-key, string>` — that form is rarely
called from user code.

Proper fix lives in `vimeo/psalm`. Refs #888.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR works around a regression on vimeo/psalm dev-master where merging Laravel source + plugin stubs causes Illuminate\Routing\Route::middleware() chains to infer the wrong (array) branch, breaking fluent chaining and consumers expecting a Route.

Changes:

  • Simplifies the plugin stub for Route::middleware() to always return $this (setter branch) to avoid the conditional-return merge bug.
  • Adds a new PHPT regression test covering array/string/variadic middleware setter calls, fluent chaining after middleware([...]), facade chains, and passing the result into RouteCollectionInterface::add().
  • Documents guidance for stub authors to avoid re-declaring conditional @return types that can collide during stub/source merges.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests/Type/tests/RouteMiddlewareTest.phpt Adds regression coverage for Route::middleware() fluent chaining and facade/consumer scenarios.
stubs/common/Routing/Route.stubphp Changes Route::middleware() stub return type to $this to avoid conditional merge collapse.
docs/contributing/README.md Adds contributor guidance about conditional @return collisions during Psalm stub/source merge.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/contributing/README.md Outdated
Comment thread stubs/common/Routing/Route.stubphp Outdated
Per Copilot review on #889: 'identical conditional' was misleading because
the previous stub used `string[]` on the null branch while Laravel's source
uses `array`. The shared piece is the `$middleware is null` check itself.
Reword both the stub comment and the contributing-docs note to reflect that.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@alies-dev alies-dev added the release:fix for PRs only (used by release-drafter) label May 7, 2026
@alies-dev alies-dev merged commit d813b88 into master May 7, 2026
34 checks passed
@alies-dev alies-dev deleted the worktree-888-route-middleware-conditional-return branch May 7, 2026 07:27
@alies-dev
Copy link
Copy Markdown
Collaborator Author

Upstream issue filed: vimeo/psalm#11837 with a self-contained, Laravel-free reproducer. The stub comment in this PR was updated (commit HEAD) to reference it so a future maintainer revisiting the workaround can check upstream status. The workaround can be reverted once the upstream fix lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:fix for PRs only (used by release-drafter)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conditional return type ($middleware is null ? string[] : $this) on Route::middleware() collapses to array branch with vimeo/psalm dev-master

2 participants