🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

unsound assignability between unsealed array shapes with overlapping optional keys. #13565

Description

@azjezz

Bug report

PHPStan incorrectly allows an array shape like array{name: string} to be assigned to a type like array{name: string, email?: string}

This is unsound because PHPStan arrays are unsealed by default. This means a value of type array{name: string} is only guaranteed to have a 'name' key of type string; it could also contain any other key, including an 'email' key of a non-string type.

When such a value is assigned to a variable expecting array{name: string, email?: string}, PHPStan fails to check for this potential conflict. It assumes the absence of the optional 'email' key, but doesn't validate that if the key is present in the source type, it has a compatible type.

<?php

class NotAString {}

/**
 * The effective return type here is `array{name: string, ...}`.
 * It might contain an 'email' key of any type.
 * 
 * @return array{name: string}
 */
function x(): array {
    return ['name' => 'string', 'email' => new NotAString()];
}

/**
 * @return array{name: string, email?: string}
 */
function y(): array { 
    // This return should be invalid.
    //
    // The type `array{name: string}` from x() is not a subtype of
    // `array{name: string, email?: string}` because x() could return an
    // array with an 'email' key of an incompatible type.
    return x();
}

Runtime failure: https://3v4l.org/espEJ

Code snippet that reproduces the problem

https://phpstan.org/r/f9f632b7-848f-4e16-b843-9aad2daec68b

Expected output

PHPStan should report an error on the return x(); line. It should recognize that array{name: string} is not safely assignable to array{name: string, email?: string} because the source type could contain an 'email' key that violates the target type's definition.

Did PHPStan help you today? Did it make you happy in any way?

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions