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

Prevent array{a: Foo} going cleanly into array<Foo> #8691

Merged
merged 5 commits into from Nov 10, 2022

Conversation

muglug
Copy link
Collaborator

@muglug muglug commented Nov 10, 2022

With this PR the following unsound code:

/**
 * @param array{a: float, b: float} $params
 */
function avg(array $params): void {
  takesArrayOfFloats($params);
}

/**
 * @param array<array-key, float> $arr
 */
function takesArrayOfFloats(array $arr): void {
    foreach ($arr as $a) {
        echo $a;
    }
}

now generates the error:

Argument 1 of takesArrayOfFloats expects array<array-key, float>, but parent type array{a: float, b: float} provided

Internal changes

TKeyedArray gets a simplified signature since $sealed always implied a null key/value param.

Future work

We need to decide if we want to change the behaviour of array{a: float, b: float} to be closed by default.

@muglug muglug added release:fix The PR will be included in 'Fixes' section of the release notes release:internal The PR will be included in 'Internal changes' section of the release notes labels Nov 10, 2022
@othercorey
Copy link
Contributor

@muglug Could you explain why a string key isn't valid for array-key?

@weirdan
Copy link
Collaborator

weirdan commented Nov 10, 2022

@othercorey it's not about the key type (in this example). It's about unknown values that may be present in the unsealed shape. E.g. Psalm currently sees nothing wrong with https://psalm.dev/r/fb73a41c80, but it actually produces a fatal error: https://3v4l.org/b7XcPK

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/fb73a41c80
<?php declare(strict_types=1);
/**
 * @param array{a: float, b: float} $params
 */
function f(array $params): void {
  mapAtan($params);
}

/**
 * @param array<array-key, float> $arr
 * @return array<array-key, float>
 */
function mapAtan(array $arr): array {
    $ret = [];
    foreach ($arr as $k => $v) {
        $ret[$k] = atan($v);
    }
    return $ret;
}

f(['a' => 0.5, 'b' => 10, 'c' => new Exception()]);
Psalm output (using commit 0cd1f13):

No issues!

@othercorey
Copy link
Contributor

@weirdan ah, so array{} doesn't exclude keys not specified? I thought it was always exclusive.

@@ -41,7 +40,6 @@
*/
final class Union implements TypeNode, Stringable
{
use ImmutableNonCloneableTrait;
Copy link
Collaborator

@danog danog Nov 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?
Full Union immutability was recently introduced by one of my PRs, since the class is marked as psalm-immutable it makes no sense to clone it (and after I removed all clones in a later-merged PR performance improved by some 15%) :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great! Sadly that PR failed our end-to-end tests, because PSL apparently has a Psalm plugin that calls Union::__clone. Not sure how to work around that@azjezz do you have ideas?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the meantime I'm going to merge this — we can always add the trait back once the end-to-end test has been fixed.

Copy link
Collaborator

@danog danog Nov 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@azjezz Fixing this is actually pretty simple, just replace the last three lines with return $argument_type->setPossiblyUndefined(true) :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danog Thanks! will do so :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@azjezz You should generally also remove all clones of Unions and atomics from your plugin code :)

Copy link
Collaborator

@danog danog Nov 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Please note that this is only valid for Psalm v5, Psalm v4 still generally requires clones and your legacy logic)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ops! didn't think of that :D not released broken 2.0.3 :D

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, this can't be fixed until Psalm v5 is released, psalm uses a different repository for testing psl ( github.com/psalm/endtoend-test-psl ), which is still in PSL 1.x, the plugin used is also 1.x, so fixing those two for Psalm 5 doesn't seem worth it to me, maybe disable testing on psl for 5.0 for now? i can add it back when psalm 5 is released.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually already did this in #8697 (basically provided a custom patch file for our version), this was just a heads-up of the required changes for v5 :)

@muglug muglug merged commit d63da1f into vimeo:master Nov 10, 2022
@muglug muglug deleted the muglug-unsealed-array-shape branch November 10, 2022 14:18
azjezz added a commit to php-standard-library/psalm-plugin that referenced this pull request Nov 10, 2022
azjezz added a commit to php-standard-library/psalm-plugin that referenced this pull request Nov 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release:fix The PR will be included in 'Fixes' section of the release notes release:internal The PR will be included in 'Internal changes' section of the release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants