Not sure was this discussed or not. It would be great to make Psalm less to report ...cannot be mixed or ...has no provided type.
In the following example, Psalm will report multiple info messages about missing type:
https://psalm.dev/r/01e39c763b
function foo($s) {
return "foo" . $s;
}
$s = foo(1);
$s + 1;
INFO: MixedOperand - 4:20 - Right operand cannot be mixed
INFO: MissingParamType - 3:14 - Parameter $s has no provided type
INFO: MissingReturnType - 3:10 - Method foo does not have a return type, expecting string
INFO: MixedAssignment - 7:1 - Cannot assign $s to a mixed type
INFO: MixedOperand - 8:1 - Left operand cannot be mixed
md5-086552ee908ee4bbd31b5e985fbf1c27
ERROR: InvalidScalarArgument - 11:10 - Argument 1 of foo expects string, int(1) provided
ERROR: InvalidOperand - 12:1 - Cannot perform a numeric operation with a non-numeric type string
```
Psalm already do types inference.Probably, it might be possible to infer types for functions signatures.
In some particular cases, the type templates can be inferred which are already supported by Psalm:
Dart's type system is a great example. I can guess that such type inference system might be very complex. For example, Python's mypy does not support types inference for dynamically typed functions and there might be some a reason for this.
Hey!
Psalm doesn't do this for three reasons:
If you want a tool that does this, I recommend Phan which has always had it, I think? cc @TysonAndre on that.
If you have a solution that's not obviously slower (or which could be hidden behind an opt-in flag) I would welcome a PR. I'm happy to also provide pointers on how you might go about things. But I'm not going to add this myself.
It's not quite bidirectional - The parameter types aren't inferred from the method body. Instead, types are inferred from usage, with a limit on how deeply Phan would recurse. (This was easier to implement)
--quick, which makes it only analyze method bodies once, non-recursively. (similar to Psalm)array{0:'string literal'} becomes array<int,string>@return), the types from return statements within the method body get added to the return type of the method. (So the return type you get currently depends on the order in which uses get analyzed, which is a situation that could probably get improved on)@muglug another much simpler example of type inference of return type that would be useful:
https://psalm.dev/r/2d72825662
All current static analyzer Psalm, Phan, Phpstan reports about a not existing method:
Psalm output (using commit 19faa31):
ERROR: UndefinedMethod - 37:34 - Method ParentClassTranslation::setextrafield does not exist
Surprisingly, PHPStorm can infer the type correctly and recognize the method properly.
I found these snippets:
https://psalm.dev/r/2d72825662
<?php
class ParentClassTranslation
{
}
class ParentClass
{
public function getTranslation() : ParentClassTranslation
{
return $this->createTranslation();
}
protected function createTranslation() : ParentClassTranslation
{
return new ParentClassTranslation();
}
}
class ChildClassTranslation extends ParentClassTranslation
{
/** @var string|null */
private $extraField;
public function setExtraField(string $extraField) : void
{
$this->extraField = $extraField;
}
}
class ChildClass extends ParentClass
{
public function setExtraField(string $extraField) : void
{
$this->getTranslation()->setExtraField($extraField);
}
// Starting from version 7.4 PHP supports covariant return types but it does not help as well
protected function createTranslation() : ParentClassTranslation
{
return new ChildClassTranslation();
}
}
$obj = new ChildClass();
$obj->setExtraField('some text');
Psalm output (using commit 19faa31):
ERROR: UndefinedMethod - 37:34 - Method ParentClassTranslation::setextrafield does not exist
You can achieve decent results with templates here: https://psalm.dev/r/e16fcbab0a
I found these snippets:
https://psalm.dev/r/e16fcbab0a
<?php
class ParentClassTranslation
{
}
/**
* @template T as ParentClassTranslation
*/
abstract class ParentClass
{
/** @return T */
public function getTranslation() : ParentClassTranslation
{
return $this->createTranslation();
}
/** @return T */
abstract protected function createTranslation() : ParentClassTranslation;
}
class ChildClassTranslation extends ParentClassTranslation
{
/** @var string|null */
private $extraField;
public function setExtraField(string $extraField) : void
{
$this->extraField = $extraField;
}
}
/**
* @extends ParentClass<ChildClassTranslation>
*/
class ChildClass extends ParentClass
{
public function setExtraField(string $extraField) : void
{
$this->getTranslation()->setExtraField($extraField);
}
// Starting from version 7.4 PHP supports covariant return types but it does not help as well
protected function createTranslation() : ParentClassTranslation
{
return new ChildClassTranslation();
}
}
$obj = new ChildClass();
$obj->setExtraField('some text');
Psalm output (using commit 366e2d3):
No issues!
@muglug I agree that it is not a problem to cover code with types. Unfortunately, these cases are mostly related to third-party libraries. A much simpler workaround can be done with simple assert(... instanceof ChildClassTranslation ), but I just use @psalm-suppress to avoid additional variable creation.