phpstan-src icon indicating copy to clipboard operation
phpstan-src copied to clipboard

Implement TypeSpecifierContext->getReturnType()

Open staabm opened this issue 8 months ago • 6 comments
trafficstars

trying a minimal approach to see whether the idea works as a whole


for expression like

if (doFoo() > 0)

we want to pass the context specific return-type information into type-specifying extensions so they can decide not just on true/truethy/false/falsy but also on "remaining return type values" (e.g. preg_match returns 0|1|false)

this should help to reduce the number of hardcoded hacks within the TypeSpecifier und move over some cases into extensions (which also allows 3rd party extensions todo similar things, as they can't hard-code hack the TypeSpecifier)

ondrej quoted:

There should be a new method on the context, can be getReturnType

Let’s say we have doFoo(): int if (doFoo() > 0), in the then branch, this should return int<1, max>, in the else branch it should return int<min, 0>

staabm avatar Mar 14 '25 09:03 staabm

the case failling in webmozart-assert is effectively

<?php

declare(strict_types=1);

class HelloWorld
{
	public function sayHello($m): void
	{
		\PHPStan\dumpType(
			is_string($m)
			&& strlen($m) >= 1
			&& strlen($m) <= 0
		);
	}
}

not sure what todo with it atm.

edit: fixed with 19749e8

staabm avatar Mar 14 '25 11:03 staabm

debugging some more, I think more precisely the problem is here:

<?php

function sayHello(string $m): void
{
	if (strlen($m) >= 1) {
		if (strlen($m) <= 0) {
			\PHPStan\dumpType(
				$m
			);
		}
	}
}

it dumps non-empty-string instead of *NEVER*

edit: fixed with 19749e8

staabm avatar Mar 15 '25 08:03 staabm

This pull request has been marked as ready for review.

phpstan-bot avatar Mar 15 '25 09:03 phpstan-bot

//cc @herndlm

staabm avatar Mar 15 '25 09:03 staabm

Yeah, this works, but I'd like to "burn more bridges" in the first PR about this change. I don't mean "breaking backward compatibility", but what I mean is to use the new code path as much as possible.

  1. TypeSpecifierContext should be created with the return type. So the constructor should accept ?Type, not ?int $value.
  2. We probably don't need the constants with bitmasks at all. The createTrue etc. methods should simply create the object with a type that makes sense and continues to be correct.
  3. The negate method is tricky. Maybe we need to create the object with both the "if" path type and the "else" path type.

ondrejmirtes avatar May 05 '25 16:05 ondrejmirtes

I use this approach often. It forces the code to be correct and generally uncovers any flaws about the changes 😊

ondrejmirtes avatar May 05 '25 16:05 ondrejmirtes