Skip to content

fix: support higher-order method calls on expectations - #17

Open
imKenjo18 wants to merge 1 commit into
pestphp:5.xfrom
imKenjo18:higher-order-methods
Open

fix: support higher-order method calls on expectations#17
imKenjo18 wants to merge 1 commit into
pestphp:5.xfrom
imKenjo18:higher-order-methods

Conversation

@imKenjo18

@imKenjo18 imKenjo18 commented Sep 2, 2026

Copy link
Copy Markdown

Fixes pestphp/pest#1896

Problem

When chaining method calls directly on an expectation:

$policy = new UserPolicy;

expect($policy)
    ->view()->toBeTrue()
    ->update()->toBeTrue();

It causes two PHPStan errors:

  • Call to an undefined method Pest\Expectation<UserPolicy>::view()
  • Calling toBeTrue() on Expectation<UserPolicy>; assertion is impossible.

Solution

  • Type Inference (HigherOrderExpectationTypeExtension & ExpectationChainStateResolver):
    • Resolves higher-order method return types from the underlying value type (TValue).
    • Correctly resets the target back to the original value type after intermediate assertions (toBeTrue(), etc.).
    • Returns MixedType wrapped in HigherOrderExpectation when methods are unresolvable or reflection fails to ensure chain stability.
  • Typo & Undefined Method Handling (HigherOrderExpectationMethodIgnoreExtension):
    • Implements IgnoreErrorExtension to ignore method.notFound only if the method exists on TValue.
    • Typos and invalid calls (e.g. expect($post)->typoMethod() or expect('string')->foo()) are properly reported as errors by PHPStan.

@imKenjo18
imKenjo18 force-pushed the higher-order-methods branch from a07b9e7 to bca9ad6 Compare September 2, 2026 21:38

@MrPunyapal MrPunyapal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tests pass locally. The main case is fixed. Requesting small changes below.

src/Type/Pest/ExpectationMethodsExtension.php
hasMethod returns true for any method name. I checked and expect($post)->typoMethod() no longer errors. Same for expect('string')->foo(). Can we only skip the error when the method exists on the value type? Right now typos are hidden.

src/Type/Pest/HigherOrderExpectationTypeExtension.php
When the method is missing you return null. When getMethod throws you return Mixed wrapped in HigherOrderExpectation. Pick one. I think wrap in Mixed in both cases so the chain type stays the same.

tests/Type/data/higher-order-expectations.php
Please add the exact case from the issue. One method plus toBe is covered. Missing is method plus toBe plus method again:
expect($policy)->view()->toBeTrue()->update()->toBeTrue()
That is what broke before. Without it the reset logic is not tested.

src/Type/Pest/PestConfigReader.php
src/Type/Pest/PestHookPropertyReader.php
These ??= changes are unrelated to this fix. Please remove them from this PR or split them out.

src/Type/Pest/ExpectationMethodReflection.php
This accepts any args and returns Mixed. So wrong args like typedMethod('string') for int param pass silently. Same for protected methods. I am ok to keep it for now since properties work the same way, but please note it as a limit or follow up.

Style:
Run pint on the new files. There are spacing and brace issues in the new extension files.

@imKenjo18
imKenjo18 force-pushed the higher-order-methods branch from 7b76b98 to df83a63 Compare September 3, 2026 18:31
@imKenjo18

Copy link
Copy Markdown
Author

@MrPunyapal, I have updated the PR as per your comment.

I reverted the rector fixes as it was out of scope and will probably submit another PR for that.

Sorry for the mess, still learning how everything works. 😅

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.

[Bug]: Higher-order expectation method calls fail with method.notFound and pest.expectation.impossible

2 participants