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

InvalidTemplateParam in a complex example with nested templates #6479

Open
vudaltsov opened this issue Sep 14, 2021 · 3 comments
Open

InvalidTemplateParam in a complex example with nested templates #6479

vudaltsov opened this issue Sep 14, 2021 · 3 comments

Comments

@vudaltsov
Copy link
Contributor

Here's a snippet with all necessary for understanding comments.

https://psalm.dev/r/51e2a255ea

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/51e2a255ea
<?php

/**
 * Value object that describes a transformation.
 *
 * @psalm-immutable
 * @template TFrom
 * @template TTo
 */
interface Transformation
{
}

/**
 * Service that handles some transformation.
 *
 * @template TFrom
 * @template TTo
 * @template TTransformation of Transformation<TFrom, TTo>
 */
interface Transformer
{
    /**
     * @return class-string<TTransformation>
     */
    public static function transformation(): string;

    /**
     * @param TFrom $value
     * @param TTransformation $transformation
     * @return TTo
     */
    public function transform(mixed $value, Transformation $transformation): mixed;
}

/**
 * Locator of transfomers.
 */
interface TransformerRegistry
{
    /**
     * @template TFrom
     * @template TTo
     * @template TTransformation of Transformation<TFrom, TTo>
     * @param class-string<TTransformation> $transformation
     * @return ?Transformer<TFrom, TTo, TTransformation>
     */
    public function get(string $transformation): ?Transformer;
}

/**
 * A pipeline that holds the value, to which transformations can be applied.
 *
 * @template T
 */
final class Pipeline
{
    /**
     * @param T $value
     */
    public function __construct(
        private mixed $value,
        private TransformerRegistry $transformerRegistry,
    ) {
    }

    /**
     * @template TNew
     * @param Transformation<T, TNew> $transformation
     * @return self<TNew>
     */
    public function transform(Transformation $transformation): self
    {
        $transformer = $this
                ->transformerRegistry
                ->get($transformation::class)
            ?? throw new InvalidArgumentException('No transformer for transformation.')
        ;

        return new self(
            $transformer->transform($this->value, $transformation),
            $this->transformerRegistry,
        );
    }
}

/**
 * @psalm-immutable
 * @template T of object
 * @implements Transformation<mixed, T>
 */
final class Hydrate implements Transformation
{
    /**
     * @param class-string<T> $class
     */
    public function __construct(
        public string $class,
    ) {
    }
}

/**
 * @template T of object
 * @implements Transformer<mixed, object, Hydrate<T>>
 */
final class Hydrator implements Transformer
{
    // Symfony Denormalizer is injeceted here, for example.
    
    public static function transformation(): string
    {
        /** @var class-string<Hydrate<T>> */
        return Hydrate::class;
    }

    public function transform(mixed $value, Transformation $transformation): mixed
    {
        /** 
         * Should be $this->denormalizer->denormalize(...), for now a simple placeholder.
         *
         * @psalm-suppress MixedMethodCall
         */
        return new $transformation->class();
    }
}
Psalm output (using commit 6f1d438):

ERROR: InvalidTemplateParam - 46:16 - Extended template param TTransformation of Transformer<TFrom:fn-transformerregistry::get as mixed, TTo:fn-transformerregistry::get as mixed, TTransformation:fn-transformerregistry::get as Transformation<TFrom:fn-transformerregistry::get as mixed, TTo:fn-transformerregistry::get as mixed>> expects type Transformation<TFrom:Transformer as mixed, TTo:Transformer as mixed>, type TTransformation:fn-transformerregistry::get as Transformation<TFrom:fn-transformerregistry::get as mixed, TTo:fn-transformerregistry::get as mixed> given

@VincentLanglet
Copy link
Contributor

I think https://psalm.dev/r/1fe7ccd2fd could be a simpler example @orklah

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/1fe7ccd2fd
<?php

/** @phpstan-template T of object */
interface Bar {}

/**
 * @phpstan-template T of object
 * @phpstan-template B of Bar<T>
 */
class Foo
{
}

class Test
{
    /** @phpstan-var Foo<object, Bar<object>> */
    private $foo;
    
    public function __construct() 
    {
        $this->foo = new Foo();
    }
}
Psalm output (using commit b5b5c20):

ERROR: InvalidTemplateParam - 16:22 - Extended template param B of Foo<object, Bar<object>> expects type Bar<T:Foo as object>, type Bar<object> given

ERROR: InvalidPropertyAssignmentValue - 21:22 - $this->foo with declared type 'Foo<object, Bar<object>>' cannot be assigned type 'Foo<object, Bar<T:Foo as object>>'

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

No branches or pull requests

3 participants