vimeo / psalm

A static analysis tool for finding errors in PHP applications
https://psalm.dev
MIT License
5.55k stars 659 forks source link

Psalter inferring incorrect parameter type when unioning with mixed #9279

Open aboyton opened 1 year ago

aboyton commented 1 year ago

When running Psalter on a method with a parameter type which is missing it appears tries to infer the type from usage, taking a union of the types that have been used to call the function.

But when one of these types is mixed the union of int and mixed is being converted to int not mixed (nor mixed | int).

This caused a bug in our production code. I've made a greatly simplified example in https://psalm.dev/r/d81512ff7e which exposes the bug (and also has a lot more types added than our real code did).

The type of $value is incorrectly being inferred as int|null not mixed or similar. This causes the code to break at runtime.

It is the case that by Psalter changing the type of $value in the function setInternal to be int|null I do get an error Argument 2 of ConfigContainer::setInternal expects int|null, but list{1, 2, 3, 4}|null provided but in a huge codebase that I'm trying to introduce Psalm to I already have a large number of similar errors and so sadly this gets lost in the noise.

As a new user of Psalm, I'm unsure if this is expected behaviour or not. I have a fairly large PHP codebase with very little type information and I'm hoping to have Psalm infer as many types as possible, either inline as PHP types like Psalm is doing here, or potentially preferably entirely as doc-blocks so that incorrect types don't break things (which might be safer on large legacy codebases with poor typing to avoid breaking code like this).

psalm-github-bot[bot] commented 1 year ago

I found these snippets:

https://psalm.dev/r/d81512ff7e ```php setInternal($name, $value); } public function setFoo(int $value): void { $this->setInternal('foo', $value); } private function setInternal(string $name, $value = null) { $this->values[$name] = $value; } public function getValues(): array { return $this->values; } } $config = new ConfigContainer(); $config->setValue('userIds', [1,2,3,4]); $config->setFoo(5); print_r($config->getValues()); ``` ``` Psalm output (using commit 08f5b35): INFO: MixedArrayAssignment - 19:9 - Cannot access array value on mixed variable $this->values[$name] INFO: MissingParamType - 17:48 - Parameter $value has no provided type INFO: MissingReturnType - 17:22 - Method ConfigContainer::setInternal does not have a return type, expecting void INFO: MixedReturnStatement - 24:16 - Could not infer a return type INFO: MixedInferredReturnType - 22:34 - Could not verify return type 'array' for ConfigContainer::getValues INFO: MissingPropertyType - 5:13 - Property ConfigContainer::$values does not have a declared type - consider array|mixed ```