Skip to content
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

Cannot assert parameter to be template container of other parameter #4339

Closed
ChristophWurst opened this issue Oct 15, 2020 · 10 comments
Closed
Labels

Comments

@ChristophWurst
Copy link

Hello.

I'm trying to Psalm-type an API of an event dispatcher where you pass the event class as class-string as well as the listener as class-string. The listener is a template class with the event as template argument.

I simplified the example into a container that wraps an element and wrote this snippet: https://psalm.dev/r/49995ab4a2

The goal is to assert that the $container argument of wrap is a Container<T> and not a Container<object> but it looks like Psalm does not bother mixed template types. See the last line.

Is this a limitation of how far Psalm can work with the type system or am I doing something that is not solvable by types?

PS: if anyone has a better title, shoot.

Cheers ✌️

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/49995ab4a2
<?php

class A {}
class B {}

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

/**
 * @template T
 * @param class-string<T> $class
 * @param class-string<Container<T>> $container
 */
function wrap(string $class, string $container): void {
        
}

/**
 * @template-extends Container<A>
 */
class AC extends Container {
    
}

/**
 * @template-extends Container<B>
 */
class BC extends Container {
    
}

wrap(A::class, AC::class);
wrap(A::class, BC::class);
Psalm output (using commit 8a2983e):

INFO: UnusedParam - 18:22 - Param $class is never referenced in this method

INFO: UnusedParam - 18:37 - Param $container is never referenced in this method

@weirdan
Copy link
Collaborator

weirdan commented Oct 15, 2020

I think something like this should work: https://psalm.dev/r/f407561f3e (but doesn't).

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/f407561f3e
<?php

class A {}
class B {}

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

/**
 * @template T
 * @template TC of Container<T>
 * @param class-string<T> $_class
 * @param class-string<TC> $_container
 */
function wrap(string $_class, string $_container): void {}

/**
 * @template-extends Container<A>
 */
class AC extends Container {
    
}

/**
 * @template-extends Container<B>
 */
class BC extends Container {
    
}

wrap(A::class, AC::class);
wrap(A::class, BC::class);
Psalm output (using commit bf569d1):

ERROR: InvalidArgument - 35:16 - Argument 2 of wrap expects class-string<Container<T>>, AC::class provided

ERROR: InvalidArgument - 36:16 - Argument 2 of wrap expects class-string<Container<T>>, BC::class provided

@weirdan weirdan added the bug label Oct 15, 2020
@muglug
Copy link
Collaborator

muglug commented Oct 16, 2020

I don't think that's really possible, though I could see this being supported: https://psalm.dev/r/07d0b932d5

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/07d0b932d5
<?php

class A {}
class B {}

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

/**
 * @template T
 * @param class-string<T> $_class
 * @return callable(class-string<Container<T>>):void
 */
function wrap(string $_class): callable {
    return function(string $container) : void {};
}

/**
 * @template-extends Container<A>
 */
class AC extends Container {
    
}

/**
 * @template-extends Container<B>
 */
class BC extends Container {
    
}

wrap(A::class)(AC::class);
wrap(A::class)(BC::class);
Psalm output (using commit aee4311):

ERROR: InvalidArgument - 36:16 - Argument 1 expects class-string<Container<T>>, AC::class provided

ERROR: InvalidArgument - 37:16 - Argument 1 expects class-string<Container<T>>, BC::class provided

1 similar comment
@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/07d0b932d5
<?php

class A {}
class B {}

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

/**
 * @template T
 * @param class-string<T> $_class
 * @return callable(class-string<Container<T>>):void
 */
function wrap(string $_class): callable {
    return function(string $container) : void {};
}

/**
 * @template-extends Container<A>
 */
class AC extends Container {
    
}

/**
 * @template-extends Container<B>
 */
class BC extends Container {
    
}

wrap(A::class)(AC::class);
wrap(A::class)(BC::class);
Psalm output (using commit aee4311):

ERROR: InvalidArgument - 36:16 - Argument 1 expects class-string<Container<T>>, AC::class provided

ERROR: InvalidArgument - 37:16 - Argument 1 expects class-string<Container<T>>, BC::class provided

@ChristophWurst
Copy link
Author

I don't think that's really possible, though I could see this being supported: https://psalm.dev/r/07d0b932d5

Yeah, to be honest I wasn't quite sure if the type system would allow this, given that there might be some tricky edge cases.

though I could see this being supported: https://psalm.dev/r/07d0b932d5

Interesting approach, though it wouldn't help with the functions I'm typing right now as I can't change their signature because they are part of a stable API.

I'm fine with closing this, just figured it might be worth reporting in case it's a bug :)

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/07d0b932d5
<?php

class A {}
class B {}

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

/**
 * @template T
 * @param class-string<T> $_class
 * @return callable(class-string<Container<T>>):void
 */
function wrap(string $_class): callable {
    return function(string $container) : void {};
}

/**
 * @template-extends Container<A>
 */
class AC extends Container {
    
}

/**
 * @template-extends Container<B>
 */
class BC extends Container {
    
}

wrap(A::class)(AC::class);
wrap(A::class)(BC::class);
Psalm output (using commit b4a2391):

ERROR: InvalidArgument - 36:16 - Argument 1 expects class-string<Container<T>>, AC::class provided

ERROR: InvalidArgument - 37:16 - Argument 1 expects class-string<Container<T>>, BC::class provided

@muglug muglug closed this as completed Oct 19, 2020
@ChristophWurst
Copy link
Author

Loading https://psalm.dev/r/49995ab4a2 with 4.9.2 gives me

ERROR: InvalidArgument - 37:1 - Incompatible types found for T

so this "feature" now works 🚀

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/49995ab4a2
<?php

class A {}
class B {}

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

/**
 * @template T
 * @param class-string<T> $class
 * @param class-string<Container<T>> $container
 */
function wrap(string $class, string $container): void {
        
}

/**
 * @template-extends Container<A>
 */
class AC extends Container {
    
}

/**
 * @template-extends Container<B>
 */
class BC extends Container {
    
}

wrap(A::class, AC::class);
wrap(A::class, BC::class);
Psalm output (using commit 2e7763c):

INFO: UnusedParam - 18:22 - Param $class is never referenced in this method

INFO: UnusedParam - 18:37 - Param $container is never referenced in this method

ERROR: InvalidArgument - 37:1 - Incompatible types found for T

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants