Skip to content

Conversation

yt-catpaw
Copy link

@yt-catpaw yt-catpaw commented Aug 17, 2025

closes phpstan/phpstan#13358
closes phpstan/phpstan#11441

Summary

@phpstan-assert-if-true on $this with union types produced incorrect type inference.

Repro

See added test: tests/PHPStan/Rules/Methods/AssertIfTrueOnThisTest.php.

Cause

Union handling for $this assertions wasn’t propagated into TypeSpecifier (assert-based refinement path).

Fix

Normalize/refine $this union members before producing SpecifiedTypes from assert info.

Tests

  • Added/updated tests in AssertIfTrueOnThisTest.php.
  • Full test suite passes locally.

Also fixes #11441

  • Added regression test in tests/PHPStan/Analyser/nsrt/bug-11441.php.
  • This ensures that @phpstan-assert !null $this->getParam() works correctly when called on union types (Foo|Bar), where each class defines its own method.
  • Previously, the null type was not removed in such cases. After the fix, int|string is correctly inferred.

@staabm
Copy link
Contributor

staabm commented Aug 18, 2025

please have a look at the issue-bot github action job summary. it seems this PR also affects other issues.

please add regression tests for those which are getting fixed

@yt-catpaw
Copy link
Author

Thanks, @staabm. I’ll review the issue-bot GitHub Action job summary and add regression tests for the additionally affected issues. This is my first time contributing to open source (and my first PR to PHPStan), so I’ll update this PR in small batches as I add tests. If you prefer a specific location or pattern for these tests, please let me know.

@staabm
Copy link
Contributor

staabm commented Aug 18, 2025

Welcome 🤗

Type related tests go usually into the https://github.com/phpstan/phpstan-src/tree/2.1.x/tests/PHPStan/Analyser/nsrt/ directory.

Files located in this directory will automatically checked against using assertType().

@ondrejmirtes
Copy link
Member

Regression tests are typically added to existing test classes for rules that reported the (disappeared) errors.

Alternatively covering some bugs with simple type inference tests (in tests/PHPStan/Analyser/nsrt) is good enough.

@@ -0,0 +1,40 @@
<?php declare(strict_types=1);

namespace Bug13358;
Copy link
Contributor

Choose a reason for hiding this comment

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

You can move this also into nsrt/ folder

Copy link
Author

@yt-catpaw yt-catpaw Aug 19, 2025

Choose a reason for hiding this comment

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

Done.
31b55ac


use PHPStan\Testing\TypeInferenceTestCase;

class AssertIfTrueOnThisTest extends TypeInferenceTestCase
Copy link
Contributor

Choose a reason for hiding this comment

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

This file can be dropped when everything else moved into nsrt/

Copy link
Author

@yt-catpaw yt-catpaw Aug 19, 2025

Choose a reason for hiding this comment

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

Done.
31b55ac

@yt-catpaw yt-catpaw marked this pull request as draft August 19, 2025 04:02
@yt-catpaw
Copy link
Author

@staabm
Hi, I’ve moved the test case for bug-13358 into tests/PHPStan/Analyser/nsrt/bug-13358.php, but now I’m not sure how to run just this specific test file.

The reason I want to run it individually is because I’d like to debug a specific function and check only the information related to this test case, without running everything else.

Could you tell me how to do that?

Thanks!

@yt-catpaw yt-catpaw marked this pull request as ready for review August 21, 2025 15:56
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@yt-catpaw yt-catpaw requested a review from staabm August 21, 2025 16:25
@yt-catpaw
Copy link
Author

Added regression test for #11441 (bug-11441.php) and updated the description accordingly.
Let me know if anything else is needed!

$assertions = Assertions::createEmpty();

foreach ($this->methods as $method) {
$assertions = $assertions->intersectWith($method->getAsserts());
Copy link
Member

Choose a reason for hiding this comment

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

This will merge assertions from both types. Which is a wrong operation. (The method has a misleading name but it's used in IntersectionTypeMethodReflection where it's appropriate.)

In a union type what needs to be done is we need to look at assertions from all places and do some kind of intersection, figure out the common assertions.

Let's say we have Foo|Bar and we call a method that's on both types.

One method does some kind of assertion and the other method does a different assertion. The correct performed assertion on the union is none because they have nothing in common.

So we need to look at on which expression the assertion is performed (in this case $this->getParam()) and if all involved methods on the union are doing an assertion on the same expression.

Then we need to take a look at the asserted type and also take it into account only if it's the same. (It's possible that they don't have to be the same but we'd have to figure out the right operation that we can be sure about.)

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.

Narrowing type using @phpstan-assert-if-true doesn't work with $this on abstract class @phpstan-assert isn't working with Union
5 participants