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

Psalm's inference of array keys as an int<min, max> range clashes with invariance of templated collection types #10985

Open
Ocramius opened this issue May 22, 2024 · 1 comment

Comments

@Ocramius
Copy link
Contributor

Psalm seems to behave incorrectly with Collection<TKey, ...> types when TKey is inferred to be int<0, n>, rather than int, 1|2|3, and a Collection<int> is requested. I lack the proper terminology/keywords to explain and search this better, so here's an example:

<?php

/** @template TKey */
final class Collection
{
    /** @param array<TKey, mixed> $items */
    function __construct(array $items) {}
}

/** @param Collection<int> $c */
function usesIntCollection(Collection $c): void {
    echo var_export($c, true); // irrelevant
}

// ok
$empty = new Collection([]);
/** @psalm-trace $empty */
usesIntCollection($empty);


// ok
$keysInferredAsUnionType = new Collection([1 => 'a', 2 => 'b']);
/** @psalm-trace $keysInferredAsUnionType */
usesIntCollection($keysInferredAsUnionType);


$keysInferredAsRange = new Collection(['a', 'b']);
/** @psalm-trace $keysInferredAsRange */
usesIntCollection($keysInferredAsRange);
Psalm output (using commit [16b24bd](https://github.com/vimeo/psalm/commit/16b24bd)): 

INFO: [Trace](https://psalm.dev/224) - 18:1 - $empty: Collection<int>

INFO: [Trace](https://psalm.dev/224) - 24:1 - $keysInferredAsUnionType: Collection<int>

ERROR: [InvalidArgument](https://psalm.dev/004) - 29:19 - Argument 1 of usesIntCollection expects Collection<int>, but Collection<int<0, 1>> provided

INFO: [Trace](https://psalm.dev/224) - 29:1 - $keysInferredAsRange: Collection<int<0, 1>>

https://psalm.dev/r/8a5261da42

In the above example, all 3 examples should probably pass the @param Collection<int> $c parameter type required by usesIntCollection().

Refs

Related: doctrine/orm#11451
Related: doctrine/orm#11454
Longer explanation @ doctrine/orm#11454 (comment)

/cc @greg0ire @MatteoFeltrin

Copy link

I found these snippets:

https://psalm.dev/r/8a5261da42
<?php

/** @template TKey */
final class Collection
{
    /** @param array<TKey, mixed> $items */
    function __construct(array $items) {}
}

/** @param Collection<int> $c */
function usesIntCollection(Collection $c): void {
    echo var_export($c, true); // irrelevant
}

// ok
$empty = new Collection([]);
/** @psalm-trace $empty */
usesIntCollection($empty);


// ok
$keysInferredAsUnionType = new Collection([1 => 'a', 2 => 'b']);
/** @psalm-trace $keysInferredAsUnionType */
usesIntCollection($keysInferredAsUnionType);


$keysInferredAsRange = new Collection(['a', 'b']);
/** @psalm-trace $keysInferredAsRange */
usesIntCollection($keysInferredAsRange);
Psalm output (using commit 16b24bd):

INFO: Trace - 18:1 - $empty: Collection<int>

INFO: Trace - 24:1 - $keysInferredAsUnionType: Collection<int>

ERROR: InvalidArgument - 29:19 - Argument 1 of usesIntCollection expects Collection<int>, but Collection<int<0, 1>> provided

INFO: Trace - 29:1 - $keysInferredAsRange: Collection<int<0, 1>>

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

No branches or pull requests

1 participant