php-generics-standard icon indicating copy to clipboard operation
php-generics-standard copied to clipboard

How are constructor types inferred?

Open nikic opened this issue 4 years ago • 2 comments

The description in https://github.com/DaveLiddament/php-generics-standard#constructor is a bit unclear to me. If you are passing a literal integer value, it's obvious that T = int, but what if the value is dynamic?

function test(?int $foo) {
    $valueHolder = new ValueHolder($foo);
}

In this case, $foo can be int or null at runtime. However, I would assume that the actual inferred type is T = ?int. Is that correct?

If it is, then what about this variant?

function test(?int $foo) {
    if (null !== $foo) {
        $valueHolder = new ValueHolder($foo);
    }
}

Here, $foo is known to be int, but only through flow-sensitive type analysis. Would T = int here?

nikic avatar Jan 11 '21 09:01 nikic

Well, from a static analysis point of view, there is two possibilities:

  • The analysis tool try to infer the type of T from the value. This is delicate because there is a granularity of type to consider. For example, new ValueHolder(21); could very well be a ValueHolder<21>. On the opposite direction, it could be a ValueHolder<scalar> where the first value happen to be a literal int. (this example could make more sense with objects where we push a Child class and the tool has to decide whether it's a ValueHolder<Child> or a ValueHolder<Parent_> with a Child as first element)
  • The analysis tool can require the user to document the type of the templated class. This is the choice made by psalm and phpstan if I'm not mistaken. Psalm will consider T = empty in the absence of an explicit /** @var ValueHolder<int> */

orklah avatar Jan 11 '21 09:01 orklah

Great question. My initial response is that the type must be explicitly stated:

e.g.

/** @var ValueHolder<int> $intHolder */
$intHolder = new ValueHolder($value) ;

This maps to what would happen in Java (based on my Java knowledge from 5 years ago!) ...

intHolder = new ValueHolder<int>();

@orklah raises an interesting point when inferring the type. Is new ValueHolder(new Dog()); holding Dog or Animal. Psalm assumes Dog, which I tend to agree with.

Either way the ambiguity suggests adds weight the argument that the type should be explicitly stated.

Thoughts?

DaveLiddament avatar Jan 11 '21 22:01 DaveLiddament