Skip to content

Adds type specifier for settype.#2920

Merged
ondrejmirtes merged 4 commits intophpstan:1.10.xfrom
ChrisBrenton:bug-3250
Feb 19, 2024
Merged

Adds type specifier for settype.#2920
ondrejmirtes merged 4 commits intophpstan:1.10.xfrom
ChrisBrenton:bug-3250

Conversation

@ChrisBrenton
Copy link
Copy Markdown
Contributor

I'm fairly new to PHPStan's code base and I've been looking through some of the issues marked "Easy fixes" for a way to get started. I'm not certain if I am going about this the right way or if its completely mad - particularly the test.

I don't expect this to be accepted as is, but it'd be good to get some pointers to how I might otherwise go about it if/when you have time?

@ChrisBrenton ChrisBrenton force-pushed the bug-3250 branch 3 times, most recently from d163a46 to f5f7545 Compare February 16, 2024 23:11
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.

I think a 2nd test function which works with a non-constant $x would be helpful.

Like

function doFoo(int $i, string $s ...)

And assert similar to what you did in this method

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.

If I caught the intention of your message correctly, this is just a mistake on my part when writing this test initially. The function was supposed to receive a mixed type for the first argument to match the signature of settype. I've changed this now, but if that wasn't the intention, please correct me.

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.

I would add a test-functions like

function doBoolean(int $i, string $s) {
	settype($i, 'boolean');
	assertType('bool', $x);

	settype($s, 'boolean');
	assertType('bool', $x);
}

since for this non-constant values, different types are returned.
there are lots of combinations and I think it doesn't make sense to test all combinations.. but at least a few most common ones should be tested

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.

Updated - I've added the functions as you described to the existing test cases.

@ChrisBrenton ChrisBrenton changed the title RFC: Adds type specifier for settype. Adds type specifier for settype. Feb 17, 2024
@ChrisBrenton ChrisBrenton force-pushed the bug-3250 branch 2 times, most recently from 421162e to 9685155 Compare February 17, 2024 19:45
$valueType = $scope->getType($value);
$castType = $scope->getType($node->getArgs()[1]->value);

$constantStrings = $castType->getConstantStrings();
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.

If the constant string is empty, we should return empty new SpecifiedTypes here.

}
}

return new SpecifiedTypes(['$value' => [$value, TypeCombinator::union(...$types)]], [], true);
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.

$value is wrong here, it cannot work. It'd only work if the call looked like this: settype($value, 'int').

You need to call TypeSpecifier::create(), as all the other type-specifying extensions do. That will create the correct SpecifiedTypes object for the right expression.

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've made the change as requested, but I am not confident that I have implemented it correctly. The tests pass as I'd expect but I am very unsure as to the purpose/behaviour of $context. I would appreciate feedback on this PR if possible, and if you have time and are willing, an explanation of $context - for me this is one of the biggest unknowns I have come across so far.

Copy link
Copy Markdown
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

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

Otherwise it looks fine.

public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool
{
return strtolower($functionReflection->getName()) === 'settype'
&& count($node->getArgs()) > 1;
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.

This needs to check that $context->null().

switch ($constantString->getValue()) {
case 'bool':
case 'boolean':
var_dump('converting to bool');
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.

Forgot var_dump calls there

$value,
TypeCombinator::union(...$types),
TypeSpecifierContext::createTruthy(),
true,
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.

No overwriting - this needs to be false.

@ondrejmirtes
Copy link
Copy Markdown
Member

Yeah, and a lot of tests are failing, please fix them :)

Also, I've got an idea for a future PR: There could be a rule that checks whether the performed settype makes sense - e.g. whether it doesn't lead to ErrorType.

@ondrejmirtes
Copy link
Copy Markdown
Member

Oh I'm sorry, $overwrite needs to be true in this case, it's a very rare case where this value makes sense :)

@ChrisBrenton
Copy link
Copy Markdown
Contributor Author

No worries. Fixed. My understanding is this is true in this scenario because it is effecting the passed in value by reference?

@ondrejmirtes
Copy link
Copy Markdown
Member

Let's say that the original type is non-empty-string|int. When the extension specified the type to StringType, then:

  • With overwrite=false the resulting type is non-empty-string
  • With overwrite=true the resulting type string

Most type-specifying functions are about type-checking and narrowing, not about casting (changing) the type. That's why overwrite=false is there in 99 % of cases.

@ondrejmirtes ondrejmirtes merged commit d2c30d1 into phpstan:1.10.x Feb 19, 2024
@ondrejmirtes
Copy link
Copy Markdown
Member

Thank you.

@ondrejmirtes
Copy link
Copy Markdown
Member

FYI I thought you meant to fix phpstan/phpstan#3250 but I don't think it does that. Please try that with a regression test in a new PR.

@ChrisBrenton ChrisBrenton deleted the bug-3250 branch February 19, 2024 17:17
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.

3 participants