Skip to content

Introduce new type #3050

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2024
Merged

Introduce new type #3050

merged 1 commit into from
May 6, 2024

Conversation

ruudk
Copy link
Contributor

@ruudk ruudk commented May 6, 2024

This makes it possible that a new instance of a class-string will be returned.

/**
 * @var array<string, class-string>
 */
private const TYPES = [
	'foo' => DateTime::class,
	'bar' => DateTimeImmutable::class,
];

/**
 * @template T of key-of<self::TYPES>
 * @param T $type
 *
 * @return new<self::TYPES[T]>
 */
public static function get(string $type) : ?object
{
	$class = self::TYPES[$type];
	return new $class('now');
}

See phpstan/phpstan#9704

The work was done by @rvanvelzen in a gist. I just created the PR for it.

Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

Made some progress.

Interestingly this works:

/**
 * @template M of self::TYPES
 * @template T of key-of<M>
 * @param T $type
 *
 * @return new<M[T]>
 */
public static function get(string $type) : object
{
	$class = self::TYPES[$type];
	return new $class('now');
}

But this doesn't:

/**
 * @template T of key-of<self::TYPES>
 * @param T $type
 *
 * @return new<self::TYPES[T]>
 */
public static function get2(string $type) : object
{
	$class = self::TYPES[$type];
	return new $class('now');
}
/**
 * @template T of class-string
 * @param T $type
 *
 * @return new<T>
 */
public static function create(string $type) : object
{
	return new $type();
}

I have the feeling this has something to do with \PHPStan\Type\NewObjectType::isResolvable:

public function isResolvable(): bool
{
	return !TypeUtils::containsTemplateType($this->type);
}

Maybe we should allow class-string there...

@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

This is already possible, without new:

/**
 * @template T of object
 * @param class-string<T> $type
 *
 * @return T
 */
public static function create(string $type) : object
{
	return new $type();
}

@ondrejmirtes
Copy link
Member

have the feeling this has something to do with \PHPStan\Type\NewObjectType::isResolvable:

I don't think it does. Other late-resolvable types look the same. You need to launch Xdebug and see what's going on :)

@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

Updated the PR. I think this is ready now.

new is only needed when you are working with templated types like:

/**
 * @template M of self::TYPES
 * @template T of key-of<M>
 * @param T $type
 *
 * @return new<M[T]>
 */
public static function get(string $type) : object
{
	$class = self::TYPES[$type];
	return new $class('now');
}

For simple cases, you can use:

/**
 * @template T of object
 * @param class-string<T> $type
 *
 * @return T
 */
public static function create(string $type) : object
{
	return new $type();
}

@ondrejmirtes
Copy link
Member

What was the problem described here? #3050 (comment)

@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

I guess writing it like this just will not be possible:

/**
 * @template T of key-of<self::TYPES>
 * @param T $type
 *
 * @return new<self::TYPES[T]>
 */
public static function get2(string $type) : object
{
	$class = self::TYPES[$type];
	return new $class('now');
}

When I use your suggested syntax (with another template), it works.

@ondrejmirtes
Copy link
Member

@ruudk Can you please send this working example with a description to this article? https://phpstan.org/blog/generics-by-examples

Source is here: https://github.com/phpstan/phpstan/blob/1.11.x/website/src/_posts/generics-by-examples.md

@rvanvelzen
Copy link
Contributor

self::TYPES[T] is a parse error currently which is why it doesn't work. Needs to be fixed in the phpdoc-parser.

Writing (self::TYPES)[T] is a workaround.

ruudk added a commit to ruudk/phpstan that referenced this pull request May 6, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

@rvanvelzen Nice! I added this example also to the test.

@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

Can you please send this working example with a description to this article? https://phpstan.org/blog/generics-by-examples

Done in phpstan/phpstan#10971

@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

self::TYPES[T] is a parse error currently which is why it doesn't work. Needs to be fixed in the phpdoc-parser.

Writing (self::TYPES)[T] is a workaround.

@rvanvelzen Interesting,

Maybe that fix will make this possible without new?

@template T of key-of<self::TYPES>
@template O of class-string<self::TYPES[T]>
@param T $type
@return O

Edit: tried it, doesn't work.

@ondrejmirtes
Copy link
Member

The improvement to phpdoc-parser has been merged here, please rebase this branch and try out what it improves (add tests for previously impossible use-cases).

You can also try it out in the playground https://phpstan.org/try, it has been updated too.

This makes it possible that a new instance of a class-string will be returned.

```php
/**
 * @var array<string, class-string>
 */
private const TYPES = [
	'foo' => DateTime::class,
	'bar' => DateTimeImmutable::class,
];

/**
 * @template T of key-of<self::TYPES>
 * @param T $type
 *
 * @return new<self::TYPES[T]>
 */
public static function get(string $type) : ?object
{
	$class = self::TYPES[$type];
	return new $class('now');
}
```

See phpstan/phpstan#9704

The work was done by @rvanvelzen in a gist. I just created the PR for it.

Co-Authored-By: Richard van Velzen <rvanvelzen1@gmail.com>
@ruudk
Copy link
Contributor Author

ruudk commented May 6, 2024

@ondrejmirtes Rebased. With the fix in phpdoc-parser we can remove the parenthesis. This simplifies the syntax. Updated the example too.

@ondrejmirtes ondrejmirtes merged commit 1f95482 into phpstan:1.11.x May 6, 2024
444 of 445 checks passed
@ondrejmirtes
Copy link
Member

Awesome, thank you!

@ondrejmirtes
Copy link
Member

Did we just remove the need for class-string-map? This looks awfully similar:

	/**
	 * @template T of key-of<self::TYPES>
	 * @param T $type
	 *
	 * @return new<self::TYPES[T]>
	 */

@ondrejmirtes
Copy link
Member

No, probably not, see phpstan/phpstan#9521

@ruudk ruudk deleted the new-type branch May 6, 2024 12:38
ondrejmirtes pushed a commit to phpstan/phpstan that referenced this pull request May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants