← All results

array{foo: int, ...}

Group: Arrays and shapes · Category: phpdoc · File: arrays_unsealed_shape.php

A trailing ... unseals an array shape: the listed keys keep their declared types, and any further key is admitted with an unconstrained value. The two halves have to be probed separately, because an analyzer can get either one wrong on its own — dropping the ... turns every extra key into a false positive, while dropping the shape lets a missing or mistyped foo through. References: - Psalm "Unsealed array and list shapes", ... as shorthand for ...<array-key, mixed>

Analyzer results

AnalyzerVersionResultDiagnostics
phan 6.0.7Phan 6.0.7 php-ast version 1.1.3 PHP version used to run Phan: 8.5.9 Unrecognized
arrays_unsealed_shape.php:21: Saw possibly unextractable annotation for a fragment of comment '* @param array{foo: int, ...} $shape': after array, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
arrays_unsealed_shape.php:21: Saw a token Phan may have failed to parse after '* @param array{foo: int, ...} $shape': after array, saw '{' [PhanUnextractableAnnotationSuffix]
  • Recognition: spelling not resolved — reported on declaration line(s) 21
  • Enforcement: 0/3 of the expected violations reported — incidental, since the spelling was not resolved

Notes: Phan parses sealed array shapes (`array{host: string, port: int}` in arrays_shape_required_keys), but the trailing `...` makes the whole annotation unextractable, so the parameter falls back to `array` and none of the three violations is checked.

phpstan 2.2.8PHPStan - PHP Static Analysis Tool 2.2.8 Enforced reported Lv.5+
arrays_unsealed_shape.php:37: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{} given. [identifier=argument.type] [reported-from-level=5]
arrays_unsealed_shape.php:38: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{buz: 42.0} given. [identifier=argument.type] [reported-from-level=5]
arrays_unsealed_shape.php:41: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{foo: 'one'} given. [identifier=argument.type] [reported-from-level=5]
With strict-rules
arrays_unsealed_shape.php:37: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{} given. [identifier=argument.type]
arrays_unsealed_shape.php:38: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{buz: 42.0} given. [identifier=argument.type]
arrays_unsealed_shape.php:41: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{foo: 'one'} given. [identifier=argument.type]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
psalm 6.16.1Psalm 6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac
next: 7.0.0-beta19
Enforced
arrays_unsealed_shape.php:37: Argument 1 of Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...<array-key, mixed>}, but array<never, never> provided [InvalidArgument]
arrays_unsealed_shape.php:38: Argument 1 of Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...<array-key, mixed>}, but array{buz: float(42)} provided [InvalidArgument]
arrays_unsealed_shape.php:41: Argument 1 of Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...<array-key, mixed>}, but array{foo: 'one'} provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
mago 1.46.0mago 1.46.0 Enforced
arrays_unsealed_shape.php:37: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape`: expected `array{'foo': int, ...}`, but possibly received `array{}`. [possibly-invalid-argument]
arrays_unsealed_shape.php:38: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape`: expected `array{'foo': int, ...}`, but possibly received `array{'buz': float(42.0)}`. [possibly-invalid-argument]
arrays_unsealed_shape.php:41: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape`: expected `array{'foo': int, ...}`, but possibly received `array{'foo': string('one')}`. [possibly-invalid-argument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
mir 0.70.1mir 0.70.1 Enforced
arrays_unsealed_shape.php:37: InvalidArgument: Argument $shape of acceptsUnsealedShape() expects 'array{'foo': int}', got 'array{}' [MIR0201]
arrays_unsealed_shape.php:38: InvalidArgument: Argument $shape of acceptsUnsealedShape() expects 'array{'foo': int}', got 'array{'buz': 42}' [MIR0201]
arrays_unsealed_shape.php:41: InvalidArgument: Argument $shape of acceptsUnsealedShape() expects 'array{'foo': int}', got 'array{'foo': "one"}' [MIR0201]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported

Notes: mir honours the `...` -- the calls carrying undeclared keys are accepted -- but prints the expected type as `array{'foo': int}` without it, so the message reads as though the shape were sealed.

phpantom 0.9.0phpantom_lsp 0.9.0 Not enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 0/3 of the expected violations reported
intelephense 1.18.5intelephense 1.18.5 Not enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 0/3 of the expected violations reported

Notes: Not specific to `...`: Intelephense does not check arguments against array shapes at all, and leaves the sealed case in arrays_shape_required_keys unreported too.

phpy 0.2.0phpy 0.2.0 Not enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 0/3 of the expected violations reported

Notes: Not specific to `...`: phpy does not check arguments against array shapes at all, and leaves the sealed case in arrays_shape_required_keys unreported too.

qodana 262.8665.325Qodana 262.8665.325 Incidental (3/3)
arrays_unsealed_shape.php:31: Value should be one of: "foo" [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape.php:32: Value should be one of: "foo" [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape.php:32: Value should be one of: "foo" [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape.php:37: Incomplete array according to shape declaration. Missing key(s): foo [PhpMissingArrayKeyInspection]
arrays_unsealed_shape.php:38: Incomplete array according to shape declaration. Missing key(s): foo [PhpMissingArrayKeyInspection]
arrays_unsealed_shape.php:38: Value should be one of: "foo" [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape.php:41: Expected parameter of type 'int', 'string' provided [PhpParamsInspection]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported — incidental, since values the type admits were also rejected on line(s) 31, 32
Expectation diff
Line 31: Expected valid value to be accepted, got ["Value should be one of: \"foo\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]"]
Line 32: Expected valid value to be accepted, got ["Value should be one of: \"foo\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]","Value should be one of: \"foo\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]"]

Notes: Reads the shape as sealed: the trailing `...` is dropped and the undeclared keys are reported as `Value should be one of: "foo"`. The declared key and its type are still enforced, so the shape is modeled and only the opt-out is missing. The one analyzer in this matrix that rejects the extra key.

noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Unrecognized
arrays_unsealed_shape.php:23: param $shape miss matched with phpdoc type <<array{foo: int, ...}>> [funcParamTypeMissMatch]
  • Recognition: spelling not resolved — reported on declaration line(s) 23
  • Enforcement: 0/3 of the expected violations reported — incidental, since the spelling was not resolved
steins 0.1.5steins 0.1.5 (2026-08-12 revision 39b4cc1) Enforced
arrays_unsealed_shape.php:37: argument [] to acceptsUnsealedShape() violates declared @param array{foo: int, ...} $shape — declared contract violation [phpdoc.param-mismatch]
arrays_unsealed_shape.php:38: argument ['buz' => 42.0] to acceptsUnsealedShape() violates declared @param array{foo: int, ...} $shape — declared contract violation [phpdoc.param-mismatch]
arrays_unsealed_shape.php:41: argument ['foo' => 'one'] to acceptsUnsealedShape() violates declared @param array{foo: int, ...} $shape — declared contract violation [phpdoc.param-mismatch]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
phpstan-strict 2.2.8PHPStan - PHP Static Analysis Tool 2.2.8 Enforced
arrays_unsealed_shape.php:37: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{} given. [identifier=argument.type]
arrays_unsealed_shape.php:38: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{buz: 42.0} given. [identifier=argument.type]
arrays_unsealed_shape.php:41: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...}, array{foo: 'one'} given. [identifier=argument.type]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
psalm-next 7.0.0-beta19Psalm 7.0.0-beta19@7e751c06a756fa64dc4c759c09fe4a173afcb433 Enforced
arrays_unsealed_shape.php:37: Argument 1 of Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...<array-key, mixed>}, but array<never, never> provided [InvalidArgument]
arrays_unsealed_shape.php:38: Argument 1 of Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...<array-key, mixed>}, but array{buz: float(42)} provided [InvalidArgument]
arrays_unsealed_shape.php:41: Argument 1 of Conformance\Tests\ArraysUnsealedShape\acceptsUnsealedShape expects array{foo: int, ...<array-key, mixed>}, but array{foo: 'one'} provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\ArraysUnsealedShape;

/**
 * `array{foo: int, ...}`
 *
 * A trailing `...` unseals an array shape: the listed keys keep their declared
 * types, and any further key is admitted with an unconstrained value. The two
 * halves have to be probed separately, because an analyzer can get either one
 * wrong on its own — dropping the `...` turns every extra key into a false
 * positive, while dropping the shape lets a missing or mistyped `foo` through.
 *
 * References:
 * - Psalm "Unsealed array and list shapes", `...` as shorthand for `...<array-key, mixed>`
 */

/**
 * @param array{foo: int, ...} $shape
 */
function acceptsUnsealedShape(array $shape): void // T: array{foo: int, ...}
{
}

// The declared key on its own satisfies the shape.
acceptsUnsealedShape(['foo' => 1]); // V

// So does the declared key alongside undeclared ones of any type.
acceptsUnsealedShape(['foo' => 2, 'buz' => 42.0]); // V
acceptsUnsealedShape(['foo' => 3, 'buz' => 42.0, 'qux' => null]); // V

// `...` relaxes the undeclared keys only. `foo` is still required — an
// analyzer that widened the spelling to plain `array` accepts all three of
// these, so silence here says the shape was thrown away rather than honoured.
acceptsUnsealedShape([]); // E?: array{} does not have the required key foo
acceptsUnsealedShape(['buz' => 42.0]); // E?: an undeclared key does not stand in for foo

// and still an int.
acceptsUnsealedShape(['foo' => 'one']); // E?: foo is int, not string