Skip to content

reproduce specifier bug - #1396

Closed
staabm wants to merge 1 commit into
phpstan:1.10.xfrom
staabm:repro-specifier-bug
Closed

staabm wants to merge 1 commit into
phpstan:1.10.xfrom
staabm:repro-specifier-bug

Conversation

@staabm

@staabm staabm commented Jun 3, 2022

Copy link
Copy Markdown
Contributor

with https://github.com/phpstan/phpstan-src/pull/1383/files#r887647098 I was running into this bug.

the problem is, that phpstan should dump the type string but reports bool|float|int|string for the dumpType call seen here:

$ php bin/phpstan analyze src/Analyser/TypeSpecifier.php
Note: Using configuration file C:\dvl\Workspace\phpstan-src-staabm\phpstan.neon.dist.
 1/1 [============================] 100%

 ------ ------------------------------------
  Line   TypeSpecifier.php
 ------ ------------------------------------
  274    Dumped type: bool|float|int|string
 ------ ------------------------------------

I am not yet sure how to debug this issue further. I also tried reproducing the case in a smaler repro script, but this did not work out yet.

any ideas/hints?

@herndlm

herndlm commented Jun 3, 2022

Copy link
Copy Markdown
Contributor

I guess I'd try using xdebug here, in case you didn't do that already.

@staabm

staabm commented Jun 3, 2022

Copy link
Copy Markdown
Contributor Author

I tried, but wasn't sure where to look tbh

@staabm

staabm commented Jun 4, 2022 •

Copy link
Copy Markdown
Contributor Author

after a bit more trying, I was able to reduce the repro further

this PR now includes a proper NodeScopeResolverTest for the problem

ConstantScalarType $constantType
): SpecifiedTypes
{
if ($constantType->getValue() === null) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it seems this nullcheck is somehow messing it up

maybe @rvanvelzen has an idea?

@herndlm herndlm Jun 4, 2022 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's interesting. Null isn't even a scalar, right? Is this ever triggered and is there even a ConstantType that implements returning null via getValue?
Might be unrelated to the bug though.. 😅
nevermind NullType implements this of course..

@herndlm herndlm Jun 4, 2022 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah one more thing came to mind: could it be related to the fact that phpstan remembers what the method returned? Maybe this needs to he invalidated below in the if scope.
The bug might then be that method return types are falsely remembered / not invalidated, even if the class instance changes. Or smth like that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@rajyan do you have an idea on how to approach this problem? maybe you even have time for a fix?

@rajyan rajyan Jun 9, 2022 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://phpstan.org/r/a7200b8c-8482-4fd2-b782-ecdb8052a28a

This bug looks very interesting, the commented out line changes the result. Rough guess, the type specification with instanceof is not working or moreSpecificType $var->getValue() remembered by !== null is breaking something.
I don’t have time this week (and this weekend) but I think I can fix it next week.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another guess is that the moreSpecificType is not correctly invalidated correctly on type specification by instanceof.

@rvanvelzen

Copy link
Copy Markdown
Contributor

Check this strange behavior as well:

@rajyan rajyan mentioned this pull request Jul 5, 2022
@staabm

staabm commented Jul 7, 2022

Copy link
Copy Markdown
Contributor Author

@rvanvelzen @rajyan might one of you guys has time to look into this problem?

@rvanvelzen

Copy link
Copy Markdown
Contributor

I've looked into this recently but it didn't look trivial to solve. I'm a bit swamped at work right now but I'll look into it more when that settles down again :)

@rajyan

rajyan commented Jul 8, 2022

Copy link
Copy Markdown
Contributor

@staabm
This was harder to solve than I thought.
You can see #1495
when $var is specified, you might narrow down $var->call() more.
In this case, $var->call() is specified as non-null at first, and the next instanceof ConstantString type can specify $var->call() more. Currently, these types of type specification is not handled.

@staabm
staabm changed the base branch from 1.7.x to 1.8.x September 22, 2022 16:08
@clxmstaab
clxmstaab force-pushed the repro-specifier-bug branch 3 times, most recently from db11b2d to 815caf2 Compare September 22, 2022 17:13
@staabm

staabm commented Sep 23, 2022 •

Copy link
Copy Markdown
Contributor Author

after working thru this I implemented several different solutions, which made the test pass, but regressed other things.

by doing this I got a better understanding how things work and how a possible solution could look like.

atm I think we need to adjust this block:

foreach ($moreSpecificTypeHolders as $specifiedExprString => $specificTypeHolder) {
if (!$specificTypeHolder->getCertainty()->yes()) {
continue;
}
$specifiedExprString = (string) $specifiedExprString;
$specifiedExpr = $this->exprStringToExpr($specifiedExprString);
if ($specifiedExpr === null) {
continue;
}
if (!$specifiedExpr instanceof Expr\ArrayDimFetch) {
continue;
}
if (!$specifiedExpr->dim instanceof Variable) {
continue;
}
if (!is_string($specifiedExpr->dim->name)) {
continue;
}
if ($specifiedExpr->dim->name !== $variableName) {
continue;
}
$moreSpecificTypeHolders[$specifiedExprString] = VariableTypeHolder::createYes($this->getType($specifiedExpr->var)->getOffsetValueType($type));
unset($nativeTypes[$specifiedExprString]);
}

.. so it takes into account when a variable get specified to a new ObjectType - e.g. via $constantType instanceof ConstantStringType.
We need to update all moreSpecificTypeHolders which depend on $constantType , e.g. $constantType->getValue() and merge the existing type of $constantType->getValue() with the new knowledge that $var was specified to ConstantStringType and therefore we have a better idea of $constantType->getValue().

class HelloWorld
{
	public function doFoo(
		ConstantScalarType $constantType
	)
	{
		if ($constantType->getValue() === null) {
			return;
		}
		assertType('bool|float|int|string', $constantType->getValue());

		if (
			$constantType instanceof ConstantStringType
		) {
			assertType('string', $constantType->getValue());
		}
	}
}

in the end I think this is a similar thing, this line achieves for array-offset-types:

$moreSpecificTypeHolders[$specifiedExprString] = VariableTypeHolder::createYes($this->getType($specifiedExpr->var)->getOffsetValueType($type));

@clxmstaab
clxmstaab force-pushed the repro-specifier-bug branch 2 times, most recently from 6f7c064 to befadc3 Compare September 23, 2022 09:57
@staabm
staabm marked this pull request as ready for review September 23, 2022 10:05
@staabm

staabm commented Sep 23, 2022

Copy link
Copy Markdown
Contributor Author

finally, this one is ready for review.

Comment thread src/Analyser/MutatingScope.php Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you please explain what the bug is and why this change fixes it? It seems too specific and too hardcoded for this specific test case...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried explaining it here: #1396 (comment)

@staabm staabm Sep 23, 2022 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it boils down to

  • phpstan knows a moreSpecificType for a method call $constant->getValue(), based on $constant beeing of type ConstantScalarType
  • after that the $constant object-type get narrowed to ConstantStringType via instanceof
  • phpstans already known moreSpecificType is not changed, and therefore phpstan doesn't realize that $constant->getValue() can only return string after the instanceof ConstantStringType case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ondrejmirtes

Copy link
Copy Markdown
Member

Is this still a problem?

@staabm

staabm commented Oct 16, 2022 •

Copy link
Copy Markdown
Contributor Author

The underlying bug should still be present, but in phpstan-src I worked arround it in a related PR, so the phpstan-src codebase is no longer blocked by it right now

@staabm
staabm changed the base branch from 1.8.x to 1.10.x June 21, 2023 15:34
@clxmstaab
clxmstaab force-pushed the repro-specifier-bug branch from f511470 to 14452a8 Compare June 21, 2023 15:34
@staabm
staabm marked this pull request as draft June 21, 2023 15:38
@staabm
staabm force-pushed the repro-specifier-bug branch from 1d8abd5 to d590419 Compare September 9, 2023 08:57
@staabm
staabm force-pushed the repro-specifier-bug branch from 085e094 to 15b02f4 Compare September 9, 2023 09:00
@staabm
staabm force-pushed the repro-specifier-bug branch from 36dea2b to 8f37360 Compare September 9, 2023 09:01
@ondrejmirtes

Copy link
Copy Markdown
Member

It'd be better to reproduce this on phpstan.org/try and open an issue.

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.

6 participants