Skip to content

Type inferrence broken for new ClassName() with a nullable generic argument #8441

Description

@oprypkhantc

Bug report

PHPStan infers the type of new GenericClassName expression based on passed arguments. There are also cases where no initial data is passed, so PHPStan infers the template arguments from the variable type:

/**
 * @template T
 */
class Collection {}

class Test {
	/** @var Collection<int> */
	private readonly Collection $collection;
	
	public function __construct() {
	        // no error here, even though PHPStan doesn't know what the generic type T is here
		$this->collection = new Collection();
	}
}

However, if you add an optional constructor parameter of generic type it breaks:

class Collection {
	/**
	 * @param T $t
	 */
	public function __construct(private readonly mixed $t = null) {}
}

class Test {
	/** @var Collection<int> */
	private readonly Collection $collection;
	
	public function __construct() {
                // error here :(
		$this->collection = new Collection(); // Property Test::$collection (Collection<int>) does not accept Collection<null>.
	}
}

Code snippet that reproduces the problem

https://phpstan.org/r/74516824-48b7-429c-9fd9-8e58cbbc20dd
https://phpstan.org/r/841b0a05-01e3-4ad1-a916-e5001c037fea

Expected output

PHPStan should always prefer inferring generic types based on the variable type (in this case - @var tag of the property), instead of the passed parameters. So instead of reporting that this value can't be assigned to this property, PHPStan should be reporting that these parameters (if any passed) don't match the inferred expected argument types.

So given this example:

/**
 * @template T
 */
class Collection {
	/**
	 * @param array<T> $items
	 */
	public function __construct(private readonly array $items) {}
}

class Test {
	/** @var Collection<int> */
	private readonly Collection $collection;
	
	public function __construct() {
		$this->collection = new Collection(["string"]);
	}
}

the error should be something like Argument #1 of Collection::__construct (int) does not accept string, when it currently reports the opposite: Property Test::$collection (Collection<int>) does not accept Collection<string>..

I'm suggesting a change in inference logic because I'm not sure if this bug can be fixed without this change. This is also how it works in Kotlin's generics - that's why they don't have this problem.

Did PHPStan help you today? Did it make you happy in any way?

It is very nice to have a baseline and finally be able to slowly sort through thousands of little problems, dozen at a time. PHPStan is slowly making all of us happy :)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions