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

How to allow filter-like functions make restrictions on types? #7944

Open
someniatko opened this issue May 11, 2022 · 3 comments
Open

How to allow filter-like functions make restrictions on types? #7944

someniatko opened this issue May 11, 2022 · 3 comments

Comments

@someniatko
Copy link
Contributor

someniatko commented May 11, 2022

Consider the following example:
https://psalm.dev/r/9b8357ef92

I'd like to somehow infer that filterIterable('is_int', generateIntsOrNulls()) returns iterable<int>. Of course I cannot do it using the current annotations on the function. Is there a way to type it more precisely so that it infers type restriction from how callable behaves? I guess similar behavior is hard-coded for array_filter function type inference.

BTW it's a mystery for me, how did it infer iterable<mixed, int|mixed|null> instead of iterable<mixed, int|null>

@psalm-github-bot
Copy link

psalm-github-bot bot commented May 11, 2022

I found these snippets:

https://psalm.dev/r/9b8357ef92
<?php

/**
 * @template T
 * @param callable(T):bool $filter
 * @param iterable<T> $it
 * @return iterable<T>
 */
function filterIterable(callable $filter, iterable $it): iterable
{
    foreach ($it as $el) {
        if ($filter($el)) {
            yield $el;
        }
    }                   
}

/** @return iterable<int|null> */
function generateIntsOrNulls(): iterable
{
    for ($i = 0; ; $i++) {
        yield ($i % 2 === 0)
            ? $i
			: null;
    }
}

/**
 * @param iterable<int> $it
 * @return iterable<int>
 */
function takesInts(iterable $it): iterable
{
    foreach ($it as $el) {
        yield $el * 2;
    }
}

$onlyInts = filterIterable('is_int', generateIntsOrNulls());
takesInts($onlyInts);
Psalm output (using commit f960d71):

ERROR: InvalidArgument - 40:11 - Argument 1 of takesInts expects iterable<mixed, int>, iterable<mixed, int|mixed|null> provided

@AndrolGenhald
Copy link
Collaborator

AndrolGenhald commented May 11, 2022

BTW it's a mystery for me, how did it infer iterable<mixed, int|mixed|null> instead of iterable<mixed, int|null>

Because is_int takes mixed. As to why it doesn't collapse to just mixed, well our type system needs a bit of rework, but I haven't had the time to finish my improvements lately.

Using an extra template actually works for this case, but in general it's been rather buggy to declare a template as a template constraint.

For your actual problem, I'm not sure if it's currently possible, I'll have to think about it.

Edit: It's definitely not possible right now, I think we'd need to allow templated conditionals in closure types and do something like this, but there might be more difficulties I haven't thought of yet.

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/69e63992a6
<?php

/**
 * @template TFilterParam
 * @template T of TFilterParam
 * @param callable(TFilterParam):bool $filter
 * @param iterable<T> $it
 * @return iterable<T>
 */
function filterIterable(callable $filter, iterable $it): iterable
{
    foreach ($it as $el) {
        if ($filter($el)) {
            yield $el;
        }
    }                   
}

/** @return iterable<int|null> */
function generateIntsOrNulls(): iterable
{
    for ($i = 0; ; $i++) {
        yield ($i % 2 === 0)
            ? $i
			: null;
    }
}

/**
 * @param iterable<int> $it
 * @return iterable<int>
 */
function takesInts(iterable $it): iterable
{
    foreach ($it as $el) {
        yield $el * 2;
    }
}

$onlyInts = filterIterable('is_int', generateIntsOrNulls());
takesInts($onlyInts);
Psalm output (using commit f960d71):

ERROR: InvalidArgument - 41:11 - Argument 1 of takesInts expects iterable<mixed, int>, iterable<mixed, int|null> provided

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

2 participants