← All results

array{foo: int, bar?: bool, ...}

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

An optional key and a trailing ... in one shape. Both say something about keys that may be absent, and an analyzer that collapses them reads bar? as just another extra key — which is silent until bar shows up with a value the declaration forbids. References: - PHPStan array shapes, array{'foo': int, "bar"?: string} for the optional key - Psalm "Unsealed array and list shapes" for the trailing ...

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_optional_key.php:21: Saw possibly unextractable annotation for a fragment of comment '* @param array{foo: int, bar?: bool, ...} $shape': after array, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
arrays_unsealed_shape_optional_key.php:21: Saw a token Phan may have failed to parse after '* @param array{foo: int, bar?: bool, ...} $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: Same annotation-extraction failure as the plain unsealed shape: Phan handles sealed shapes with optional keys, but stops at the `...` and drops the `@param` entirely.

phpstan 2.2.8PHPStan - PHP Static Analysis Tool 2.2.8 Enforced reported Lv.5+
arrays_unsealed_shape_optional_key.php:40: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{foo: 1, bar: 'yes'} given. [identifier=argument.type] [reported-from-level=5]
arrays_unsealed_shape_optional_key.php:44: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{bar: true} given. [identifier=argument.type] [reported-from-level=5]
arrays_unsealed_shape_optional_key.php:45: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{buz: 42.0} given. [identifier=argument.type] [reported-from-level=5]
With strict-rules
arrays_unsealed_shape_optional_key.php:40: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{foo: 1, bar: 'yes'} given. [identifier=argument.type]
arrays_unsealed_shape_optional_key.php:44: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{bar: true} given. [identifier=argument.type]
arrays_unsealed_shape_optional_key.php:45: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{buz: 42.0} 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_optional_key.php:40: Argument 1 of Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{bar?: bool, foo: int, ...<array-key, mixed>}, but array{bar: 'yes', foo: 1} provided [InvalidArgument]
arrays_unsealed_shape_optional_key.php:44: Argument 1 of Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{bar?: bool, foo: int, ...<array-key, mixed>}, but array{bar: true} provided [InvalidArgument]
arrays_unsealed_shape_optional_key.php:45: Argument 1 of Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{bar?: bool, foo: int, ...<array-key, mixed>}, but array{buz: float(42)} provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
mago 1.46.0mago 1.46.0 Enforced
arrays_unsealed_shape_optional_key.php:40: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey`: expected `array{'bar'?: bool, 'foo': int, ...}`, but possibly received `array{'bar': string('yes'), 'foo': int(1)}`. [possibly-invalid-argument]
arrays_unsealed_shape_optional_key.php:44: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey`: expected `array{'bar'?: bool, 'foo': int, ...}`, but possibly received `array{'bar': true}`. [possibly-invalid-argument]
arrays_unsealed_shape_optional_key.php:45: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey`: expected `array{'bar'?: bool, 'foo': int, ...}`, but possibly received `array{'buz': float(42.0)}`. [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_optional_key.php:40: InvalidArgument: Argument $shape of acceptsUnsealedShapeWithOptionalKey() expects 'array{'foo': int, 'bar'?: bool}', got 'array{'foo': 1, 'bar': "yes"}' [MIR0201]
arrays_unsealed_shape_optional_key.php:44: InvalidArgument: Argument $shape of acceptsUnsealedShapeWithOptionalKey() expects 'array{'foo': int, 'bar'?: bool}', got 'array{'bar': true}' [MIR0201]
arrays_unsealed_shape_optional_key.php:45: InvalidArgument: Argument $shape of acceptsUnsealedShapeWithOptionalKey() expects 'array{'foo': int, 'bar'?: bool}', got 'array{'buz': 42}' [MIR0201]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported

Notes: Enforces the shape correctly, but as with the plain unsealed shape the `...` is missing from the type it prints.

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_optional_key.php:29: Value should be one of: "foo", "bar" [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_optional_key.php:33: Value should be one of: "foo", "bar" [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_optional_key.php:40: Expected parameter of type 'bool', 'string' provided [PhpParamsInspection]
arrays_unsealed_shape_optional_key.php:44: Incomplete array according to shape declaration. Missing key(s): foo [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_optional_key.php:45: Incomplete array according to shape declaration. Missing key(s): foo [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_optional_key.php:45: Value should be one of: "foo", "bar" [PhpArrayKeyDoesNotMatchArrayShapeInspection]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported — incidental, since values the type admits were also rejected on line(s) 29, 33
Expectation diff
Line 29: Expected valid value to be accepted, got ["Value should be one of: \"foo\", \"bar\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]"]
Line 33: Expected valid value to be accepted, got ["Value should be one of: \"foo\", \"bar\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]"]

Notes: Same as the plain unsealed shape: `...` is dropped, so both calls carrying an undeclared key are rejected. The optional `bar` is handled correctly -- absent is fine, present as a string is caught.

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_optional_key.php:23: param $shape miss matched with phpdoc type <<array{foo: int, bar?: bool, ...}>> [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_optional_key.php:40: argument ['foo' => 1, 'bar' => 'yes'] to acceptsUnsealedShapeWithOptionalKey() violates declared @param array{foo: int, bar?: bool, ...} $shape — declared contract violation [phpdoc.param-mismatch]
arrays_unsealed_shape_optional_key.php:44: argument ['bar' => true] to acceptsUnsealedShapeWithOptionalKey() violates declared @param array{foo: int, bar?: bool, ...} $shape — declared contract violation [phpdoc.param-mismatch]
arrays_unsealed_shape_optional_key.php:45: argument ['buz' => 42.0] to acceptsUnsealedShapeWithOptionalKey() violates declared @param array{foo: int, bar?: bool, ...} $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_optional_key.php:40: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{foo: 1, bar: 'yes'} given. [identifier=argument.type]
arrays_unsealed_shape_optional_key.php:44: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{bar: true} given. [identifier=argument.type]
arrays_unsealed_shape_optional_key.php:45: Parameter #1 $shape of function Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{foo: int, bar?: bool, ...}, array{buz: 42.0} 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_optional_key.php:40: Argument 1 of Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{bar?: bool, foo: int, ...<array-key, mixed>}, but array{bar: 'yes', foo: 1} provided [InvalidArgument]
arrays_unsealed_shape_optional_key.php:44: Argument 1 of Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{bar?: bool, foo: int, ...<array-key, mixed>}, but array{bar: true} provided [InvalidArgument]
arrays_unsealed_shape_optional_key.php:45: Argument 1 of Conformance\Tests\ArraysUnsealedShapeOptionalKey\acceptsUnsealedShapeWithOptionalKey expects array{bar?: bool, foo: int, ...<array-key, mixed>}, but array{buz: float(42)} provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\ArraysUnsealedShapeOptionalKey;

/**
 * `array{foo: int, bar?: bool, ...}`
 *
 * An optional key and a trailing `...` in one shape. Both say something about
 * keys that may be absent, and an analyzer that collapses them reads `bar?` as
 * just another extra key — which is silent until `bar` shows up with a value
 * the declaration forbids.
 *
 * References:
 * - PHPStan array shapes, `array{'foo': int, "bar"?: string}` for the optional key
 * - Psalm "Unsealed array and list shapes" for the trailing `...`
 */

/**
 * @param array{foo: int, bar?: bool, ...} $shape
 */
function acceptsUnsealedShapeWithOptionalKey(array $shape): void // T: array{foo: int, bar?: bool, ...}
{
}

// The optional key may be left out,
acceptsUnsealedShapeWithOptionalKey(['foo' => 1]); // V
acceptsUnsealedShapeWithOptionalKey(['foo' => 2, 'buz' => 42.0]); // V

// supplied,
acceptsUnsealedShapeWithOptionalKey(['foo' => 1, 'bar' => true]); // V
acceptsUnsealedShapeWithOptionalKey(['foo' => 1, 'bar' => false, 'buz' => 42.0]); // V

// or supplied out of declaration order.
acceptsUnsealedShapeWithOptionalKey(['bar' => true, 'foo' => 1]); // V

// Optional is not the same as unconstrained: when `bar` is there it is a bool,
// and `...` does not take over the key the shape already spoke for.
acceptsUnsealedShapeWithOptionalKey(['foo' => 1, 'bar' => 'yes']); // E?: bar is bool, not string

// `foo` carries no `?`, so it stays required. An analyzer that widened the
// whole spelling to plain `array` says nothing on either of these.
acceptsUnsealedShapeWithOptionalKey(['bar' => true]); // E?: array{bar: true} does not have the required key foo
acceptsUnsealedShapeWithOptionalKey(['buz' => 42.0]); // E?: only undeclared keys, so foo is still missing