0

I have an interface that implements a method which takes an array of format X and returns an array with the same format X. I tried using generics to express this, but apparently I can't do so because I'm getting "ImplementedReturnTypeMismatch" and "MoreSpecificImplementedParamType" errors.

https://psalm.dev/r/5dd25daf35

<?php

interface Processor
{
    /**
     * @template TKey of array-key
     * @template TValue 
     * @param array<TKey, TValue> $data
     * @return array<TKey, TValue>
     */
    public function process(array $data): array;
}

class QueryProcessor implements Processor
{
    /**
     * @param array{string, list<int>} $data
     * @return array{string, list<int>}
     */
    public function process(array $data): array
    {
        [$query, $parameters] = $data;

        // some data process here

        return [$query, $parameters];
    }
}

class OutputProcessor implements Processor
{
    /**
     * @param list<string> $data
     * @return list<string>
     */
    public function process(array $data): array
    {
        // some data process here

        return $data;
    }
}

$query = new QueryProcessor();
$query->process(['query', [1,2,3]]);

$data = new OutputProcessor();
$data->process(['abc']);
  • Maybe the problem resides in using different structurally array types in both implementations of same method? That doesn't seem a problem to me from a classical interface point of view, but the brief documentation of MoreSpecificImplementedParamType error on psalm-php makes me wonder if it's expecting methods always with the same structure? – Bardo Aug 17 '23 at 12:03

1 Answers1

0

This has been answered on Psalm's github discussion

You just need to lift the generic type param to the interface level: https://psalm.dev/r/c82c7ea6f2

<?php
/** @template T of array<array-key, mixed> */
interface Processor
{
    /**
     * @param T $data
     * @return T
     */
    public function process(array $data): array;
}

/** @implements Processor<array{string, list<int>}> */
class QueryProcessor implements Processor
{
    public function process(array $data): array
    {
        [$query, $parameters] = $data;

        return [$query, $parameters];
    }
}

/** @implements Processor<list<string>> */
class OutputProcessor implements Processor
{
    public function process(array $data): array
    {
        return $data;
    }
}


$query = new QueryProcessor();
$query->process(['query', [1,2,3]]);
$query->process([1,2,3]); // flagged (as expected)

$data = new OutputProcessor();
$data->process(['abc']);
$data->process(['query', [1,2,3]]); // flagged (as expected)
weirdan
  • 2,499
  • 23
  • 27