Skip to content

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

Closed
phpstan/phpstan-src
#6341

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

  1. added this to the Generics milestone on Dec 16, 2022
  2. ondrejmirtes commented on Sep 1, 2026

    @ondrejmirtes
    Member

    Hi, I feel like this error is expected:

    /** @template T */
    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>.
    	}
    }

    And that errors "Argument #1 of Collection::__construct (int) does not accept string" vs. "Property Test::$collection (Collection) does not accept Collection." are roughly equivalent and it actually doesn't matter which one is reported, as long as at least one of them is reported.

    Please watch this PR phpstan/phpstan-src#6332, it improves a lot in this area.

  3. github-actions commented on Oct 3, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

  4. locked as resolved and limited conversation to collaborators on Oct 3, 2026
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