← All results

array{name: string, age: int, ...<Foo>}

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

A trailing ...<T> unseals an array shape and types every extra value as T (the extra key type is implied array-key). Bare ... is arrays_unsealed_shape; this file asks whether the remainder type is kept — both when an extra key is passed in, and when a value is read back out of one. References: - PHPStan 2.2 unsealed array shapes, array{foo: int, ...<Bar>} https://phpstan.org/blog/phpstan-2-2-unsealed-array-shapes-safer-array-keys - Psalm "Unsealed array and list shapes", ...<TKey, TValue> - Intelephense Type-System, extra elements of form ...<TKey, TValue>

Analyzer results

AnalyzerVersionResultDiagnostics
phan 6.0.10Phan 6.0.10 php-ast version 1.1.3 PHP version used to run Phan: 8.5.11 Unrecognized
arrays_unsealed_shape_typed_remainder.php:36: Saw possibly unextractable annotation for a fragment of comment '* @param array{name: string, age: int, ...<Foo>} $row': after array, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
arrays_unsealed_shape_typed_remainder.php:36: Saw a token Phan may have failed to parse after '* @param array{name: string, age: int, ...<Foo>} $row': after array, saw '{' [PhanUnextractableAnnotationSuffix]
arrays_unsealed_shape_typed_remainder.php:43: Saw possibly unextractable annotation for a fragment of comment '* @param array{name: string, age: int, ...<Foo>} $row': after array, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
arrays_unsealed_shape_typed_remainder.php:43: Saw a token Phan may have failed to parse after '* @param array{name: string, age: int, ...<Foo>} $row': after array, saw '{' [PhanUnextractableAnnotationSuffix]
  • Recognition: spelling not resolved — reported on declaration line(s) 36, 43
  • Enforcement: 1/6 of the expected violations reported — incidental, since the spelling was not resolved

Notes: Same annotation-extraction failure as the bare unsealed shape: the trailing `...` makes the whole `@param` unextractable, so the parameter falls back to `array` and neither the remainder type nor the listed keys are checked.

phpstan 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Enforced reported Lv.5+
arrays_unsealed_shape_typed_remainder.php:51: Parameter #1 $value of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesString expects string, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo given. [reported-from-level=5]
arrays_unsealed_shape_typed_remainder.php:66: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 'Ada', age: 36, extra: 'nope'} given. [reported-from-level=5]
arrays_unsealed_shape_typed_remainder.php:69: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 'Ada'} given. [reported-from-level=5]
arrays_unsealed_shape_typed_remainder.php:70: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{extra: Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo} given. [reported-from-level=5]
arrays_unsealed_shape_typed_remainder.php:71: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 1, age: 36} given. [reported-from-level=5]
With strict-rules
arrays_unsealed_shape_typed_remainder.php:51: Parameter #1 $value of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesString expects string, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo given.
arrays_unsealed_shape_typed_remainder.php:66: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 'Ada', age: 36, extra: 'nope'} given.
arrays_unsealed_shape_typed_remainder.php:69: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 'Ada'} given.
arrays_unsealed_shape_typed_remainder.php:70: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{extra: Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo} given.
arrays_unsealed_shape_typed_remainder.php:71: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 1, age: 36} given.
  • Recognition: spelling resolved
  • Enforcement: 6/6 of the expected violations reported
psalm 6.20.0Psalm 6.20.0@804f03c0c183d149fcd4b8c7c0c9b3fbf836d34c
next: 7.0.0-rc2
Unrecognized (pzoom≠)
arrays_unsealed_shape_typed_remainder.php:36: Must have exactly two extra fields! in docblock for Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow [InvalidDocblock]
arrays_unsealed_shape_typed_remainder.php:43: Must have exactly two extra fields! in docblock for Conformance\Tests\ArraysUnsealedShapeTypedRemainder\inspectRow [InvalidDocblock]
arrays_unsealed_shape_typed_remainder.php:50: Argument 1 of Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesFoo cannot be mixed, expecting Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo [MixedArgument]
arrays_unsealed_shape_typed_remainder.php:51: Argument 1 of Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesString cannot be mixed, expecting string [MixedArgument]
pzoom (Psalm port)
arrays_unsealed_shape_typed_remainder.php:51: Argument 1 of takesString expects string, but Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo provided [InvalidArgument]
arrays_unsealed_shape_typed_remainder.php:66: Argument 1 of acceptsRow expects array{age: int, name: string, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, but array{age: 36, extra: 'nope', name: 'Ada'} provided [InvalidArgument]
arrays_unsealed_shape_typed_remainder.php:69: Argument 1 of acceptsRow expects array{age: int, name: string, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, but array{name: 'Ada'} provided [InvalidArgument]
arrays_unsealed_shape_typed_remainder.php:70: Argument 1 of acceptsRow expects array{age: int, name: string, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, but array{extra: Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo} provided [InvalidArgument]
arrays_unsealed_shape_typed_remainder.php:71: Argument 1 of acceptsRow expects array{age: int, name: string, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, but array{age: 36, name: 1} provided [InvalidArgument]
  • Recognition: spelling not resolved — reported on declaration line(s) 36, 43
  • Enforcement: 1/6 of the expected violations reported — incidental, since the spelling was not resolved

Notes: InvalidDocblock: "Must have exactly two extra fields!". Psalm documents only `...<TKey, TValue>`; the PHPStan 2.2 single-type form `...<Foo>` (value type, key implied `array-key`) is rejected and the parameter falls back to mixed.

mago 1.57.0mago 1.57.0 Unrecognized
arrays_unsealed_shape_typed_remainder.php:38: Docblock type `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}` for parameter `$row` is incompatible with native type `array<array-key, mixed>`. [docblock-type-mismatch]
arrays_unsealed_shape_typed_remainder.php:45: Docblock type `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}` for parameter `$row` is incompatible with native type `array<array-key, mixed>`. [docblock-type-mismatch]
arrays_unsealed_shape_typed_remainder.php:50: Invalid argument type for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesFoo`: expected `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo`, but found `nonnull`. [mixed-argument]
arrays_unsealed_shape_typed_remainder.php:51: Invalid argument type for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesString`: expected `string`, but found `nonnull`. [mixed-argument]
arrays_unsealed_shape_typed_remainder.php:59: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}`, but possibly received `array{'age': int(36), 'extra': Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, 'name': string('Ada')}`. [possibly-invalid-argument]
arrays_unsealed_shape_typed_remainder.php:60: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}`, but possibly received `array{'a': Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, 'age': int(36), 'b': Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, 'name': string('Ada')}`. [possibly-invalid-argument]
arrays_unsealed_shape_typed_remainder.php:61: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\inspectRow`: expected `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}`, but possibly received `array{'age': int(36), 'extra': Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, 'name': string('Ada')}`. [possibly-invalid-argument]
arrays_unsealed_shape_typed_remainder.php:66: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}`, but possibly received `array{'age': int(36), 'extra': string('nope'), 'name': string('Ada')}`. [possibly-invalid-argument]
arrays_unsealed_shape_typed_remainder.php:69: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}`, but possibly received `array{'name': string('Ada')}`. [possibly-invalid-argument]
arrays_unsealed_shape_typed_remainder.php:70: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}`, but possibly received `array{'extra': Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo}`. [possibly-invalid-argument]
arrays_unsealed_shape_typed_remainder.php:71: Possible argument type mismatch for argument #1 of `Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, mixed>}`, but possibly received `array{'age': int(36), 'name': int(1)}`. [possibly-invalid-argument]
  • Recognition: spelling not resolved — reported on declaration line(s) 38, 45
  • Enforcement: 5/6 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 59: Expected valid value to be accepted, got ["Possible argument type mismatch for argument #1 of `Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\Foo, mixed>}`, but possibly received `array{'age': int(36), 'extra': Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\Foo, 'name': string('Ada')}`. [possibly-invalid-argument]"]
Line 60: Expected valid value to be accepted, got ["Possible argument type mismatch for argument #1 of `Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\acceptsRow`: expected `array{'age': int, 'name': string, ...<Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\Foo, mixed>}`, but possibly received `array{'a': Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\Foo, 'age': int(36), 'b': Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\Foo, 'name': string('Ada')}`. [possibly-invalid-argument]"]
Line 61: Expected valid value to be accepted, got ["Possible argument type mismatch for argument #1 of `Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\inspectRow`: expected `array{'age': int, 'name': string, ...<Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\Foo, mixed>}`, but possibly received `array{'age': int(36), 'extra': Conformance\\Tests\\ArraysUnsealedShapeTypedRemainder\\Foo, 'name': string('Ada')}`. [possibly-invalid-argument]"]

Notes: Parses `...<Foo>` as `...<Foo, mixed>`: the single type is the extra *key* type, values are mixed. Extra string keys carrying Foo values are then rejected, and the resulting shape is reported as incompatible with native `array`. PHPStan's reading is the opposite (`...<array-key, Foo>`).

mir 0.87.0mir 0.87.0 Widened to array{name: string, age: int}
arrays_unsealed_shape_typed_remainder.php:50: MixedArgument: Argument $value of takesFoo() is mixed [MIR1200]
arrays_unsealed_shape_typed_remainder.php:51: MixedArgument: Argument $value of takesString() is mixed [MIR1200]
arrays_unsealed_shape_typed_remainder.php:69: InvalidArgument: Argument $row of acceptsRow() expects 'array{'name': string, 'age': int}', got 'array{'name': "Ada"}' [MIR0201]
arrays_unsealed_shape_typed_remainder.php:70: InvalidArgument: Argument $row of acceptsRow() expects 'array{'name': string, 'age': int}', got 'array{'extra': Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo}' [MIR0201]
arrays_unsealed_shape_typed_remainder.php:71: InvalidArgument: Argument $row of acceptsRow() expects 'array{'name': string, 'age': int}', got 'array{'name': 1, 'age': 36}' [MIR0201]
  • Recognition: spelling resolved
  • Enforcement: 4/6 of the expected violations reported

Notes: Drops `...<Foo>` from the type it prints and from checking: extra Foo and extra string both go through, extra-key reads are mixed. The listed keys stay required and typed.

phpantom 0.11.1phpantom_lsp 0.11.1 Partly enforced (5/6)
arrays_unsealed_shape_typed_remainder.php:50: Argument 1 ($value) expects Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo, got string|int|Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo (string|int does not satisfy Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo) [type_mismatch_argument]
arrays_unsealed_shape_typed_remainder.php:51: Argument 1 ($value) expects string, got string|int|Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo (int|Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo does not satisfy string) [type_mismatch_argument]
arrays_unsealed_shape_typed_remainder.php:66: Argument 1 ($row) expects array{name: string, age: int, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, got array{name: 'Ada', age: 36, extra: 'nope'} [type_mismatch_argument]
arrays_unsealed_shape_typed_remainder.php:69: Argument 1 ($row) expects array{name: string, age: int, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, got array{name: 'Ada'} (missing required key 'age') [type_mismatch_argument]
arrays_unsealed_shape_typed_remainder.php:70: Argument 1 ($row) expects array{name: string, age: int, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, got array{extra: Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo} (missing required keys 'name', 'age') [type_mismatch_argument]
arrays_unsealed_shape_typed_remainder.php:71: Argument 1 ($row) expects array{name: string, age: int, ...<array-key, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, got array{name: 1, age: 36} [type_mismatch_argument]
  • Recognition: spelling resolved
  • Enforcement: 5/6 of the expected violations reported

Notes: Inbound remainder is PHPStan's reading (`...<array-key, Foo>`): extra Foo is admitted, extra string is rejected. The extra-key read after isset is `string|int|Foo` — every value in the shape — rather than remainder-only Foo, so `takesFoo($row['extra'])` still fails.

intelephense 1.18.5intelephense 1.18.5 Partly enforced (2/6)
arrays_unsealed_shape_typed_remainder.php:51: Expected type 'string'. Found 'Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo'. [P1006]
  • Recognition: spelling resolved
  • Enforcement: 2/6 of the expected violations reported

Notes: Remainder values are Foo on offset access (`Found 'Foo'` on takesString), but inbound extra keys are not checked against the shape — the same argument-checking hole as arrays_unsealed_shape / arrays_shape_required_keys.

phpactor 2026.10.07.0Phpactor 2026.10.07.0 Partly enforced (1/6)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 1/6 of the expected violations reported

Notes: Not specific to `...<Foo>`: phpactor does not check arguments against array shapes at all. The 1/6 is the quiet extraction probe, which silent tools honour vacuously.

phpy 1.0.19409phpy 1.0.19409 Partly enforced (1/6)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 1/6 of the expected violations reported

Notes: Not specific to `...<Foo>`: phpy does not check arguments against array shapes at all. The 1/6 is the quiet extraction probe, which silent tools honour vacuously.

qodana 262.10968.76PhpStorm 262.10968.76 Incidental (5/6)
arrays_unsealed_shape_typed_remainder.php:56: Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:59: Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:59: Value should be one of: "name", "age", "..." [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_typed_remainder.php:60: Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:60: Value should be one of: "name", "age", "..." [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_typed_remainder.php:60: Value should be one of: "name", "age", "..." [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_typed_remainder.php:61: Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:61: Value should be one of: "name", "age", "..." [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_typed_remainder.php:66: Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:66: Value should be one of: "name", "age", "..." [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_typed_remainder.php:69: Incomplete array according to shape declaration. Missing key(s): age, ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:70: Incomplete array according to shape declaration. Missing key(s): name, age, ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:70: Value should be one of: "name", "age", "..." [PhpArrayKeyDoesNotMatchArrayShapeInspection]
arrays_unsealed_shape_typed_remainder.php:71: Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]
arrays_unsealed_shape_typed_remainder.php:71: Expected parameter of type 'string', 'int' provided [PhpParamsInspection]
  • Recognition: spelling resolved
  • Enforcement: 5/6 of the expected violations reported — incidental, since values the type admits were also rejected on line(s) 56, 59, 60, 61
Expectation diff
Line 56: Expected valid value to be accepted, got ["Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]"]
Line 59: Expected valid value to be accepted, got ["Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]","Value should be one of: \"name\", \"age\", \"...\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]"]
Line 60: Expected valid value to be accepted, got ["Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]","Value should be one of: \"name\", \"age\", \"...\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]","Value should be one of: \"name\", \"age\", \"...\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]"]
Line 61: Expected valid value to be accepted, got ["Incomplete array according to shape declaration. Missing key(s): ... [PhpMissingArrayKeyInspection]","Value should be one of: \"name\", \"age\", \"...\" [PhpArrayKeyDoesNotMatchArrayShapeInspection]"]

Notes: Reads `...` as a required literal key named `"..."` (`Value should be one of: "name", "age", "..."`, `Missing key(s): ...`). `<Foo>` is dropped, so extra Foo values are rejected and even the known-keys-only payload is incomplete. The extra-key read is untyped. Bare `...` in arrays_unsealed_shape is dropped instead; the `...<T>` form is what becomes a key name.

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_typed_remainder.php:38: param $row miss matched with phpdoc type <<array{name: string, age: int, ...<Foo>}>> [funcParamTypeMissMatch]
arrays_unsealed_shape_typed_remainder.php:45: param $row miss matched with phpdoc type <<array{name: string, age: int, ...<Foo>}>> [funcParamTypeMissMatch]
arrays_unsealed_shape_typed_remainder.php:50: potential null array access in parameter value of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesFoo [notNullSafetyFunctionArgumentArrayDimFetch]
arrays_unsealed_shape_typed_remainder.php:51: potential null array access in parameter value of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesString [notNullSafetyFunctionArgumentArrayDimFetch]
  • Recognition: spelling not resolved — reported on declaration line(s) 38, 45
  • Enforcement: 1/6 of the expected violations reported — incidental, since the spelling was not resolved

Notes: Reports a phpdoc/native mismatch on the declaration (`funcParamTypeMissMatch`) and does not check extras or listed keys at the call site. The extraction hits are null-safety on the dim-fetch, not the remainder type.

steins 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) Partly enforced (5/6)
arrays_unsealed_shape_typed_remainder.php:66: argument ['name' => 'Ada', 'age' => 36, 'extra' => 'nope'] to acceptsRow() violates declared @param array{name: string, age: int, ...<Foo>} $row — declared contract violation [phpdoc.param-mismatch]
arrays_unsealed_shape_typed_remainder.php:69: argument ['name' => 'Ada'] to acceptsRow() violates declared @param array{name: string, age: int, ...<Foo>} $row — declared contract violation [phpdoc.param-mismatch]
arrays_unsealed_shape_typed_remainder.php:70: argument ['extra' => <expr>] to acceptsRow() violates declared @param array{name: string, age: int, ...<Foo>} $row — declared contract violation [phpdoc.param-mismatch]
arrays_unsealed_shape_typed_remainder.php:71: argument ['name' => 1, 'age' => 36] to acceptsRow() violates declared @param array{name: string, age: int, ...<Foo>} $row — declared contract violation [phpdoc.param-mismatch]
  • Recognition: spelling resolved
  • Enforcement: 5/6 of the expected violations reported

Notes: Inbound remainder is enforced (extra `'nope'` is a contract violation). The extra-key read is not typed as Foo: both takesFoo and takesString stay silent.

phpstan-strict 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Enforced
arrays_unsealed_shape_typed_remainder.php:51: Parameter #1 $value of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesString expects string, Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo given.
arrays_unsealed_shape_typed_remainder.php:66: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 'Ada', age: 36, extra: 'nope'} given.
arrays_unsealed_shape_typed_remainder.php:69: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 'Ada'} given.
arrays_unsealed_shape_typed_remainder.php:70: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{extra: Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo} given.
arrays_unsealed_shape_typed_remainder.php:71: Parameter #1 $row of function Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow expects array{name: string, age: int, ...<Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo>}, array{name: 1, age: 36} given.
  • Recognition: spelling resolved
  • Enforcement: 6/6 of the expected violations reported
psalm-next 7.0.0-rc2Psalm 7.0.0-rc2@20339e3fe5b8d03bcd8061030eb2a83ad50e5783 Unrecognized
arrays_unsealed_shape_typed_remainder.php:36: Must have exactly two extra fields! in docblock for Conformance\Tests\ArraysUnsealedShapeTypedRemainder\acceptsRow [InvalidDocblock]
arrays_unsealed_shape_typed_remainder.php:43: Must have exactly two extra fields! in docblock for Conformance\Tests\ArraysUnsealedShapeTypedRemainder\inspectRow [InvalidDocblock]
arrays_unsealed_shape_typed_remainder.php:50: Argument 1 of Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesFoo cannot be mixed, expecting Conformance\Tests\ArraysUnsealedShapeTypedRemainder\Foo [MixedArgument]
arrays_unsealed_shape_typed_remainder.php:51: Argument 1 of Conformance\Tests\ArraysUnsealedShapeTypedRemainder\takesString cannot be mixed, expecting string [MixedArgument]
  • Recognition: spelling not resolved — reported on declaration line(s) 36, 43
  • Enforcement: 1/6 of the expected violations reported — incidental, since the spelling was not resolved

Notes: Same InvalidDocblock as Psalm 6: "Must have exactly two extra fields!". The 7.x line still requires `...<TKey, TValue>` and does not accept the single-type `...<Foo>` form.

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\ArraysUnsealedShapeTypedRemainder;

/**
 * `array{name: string, age: int, ...<Foo>}`
 *
 * A trailing `...<T>` unseals an array shape and types every extra value as
 * `T` (the extra key type is implied `array-key`). Bare `...` is
 * `arrays_unsealed_shape`; this file asks whether the remainder type is kept
 * — both when an extra key is passed in, and when a value is read back out
 * of one.
 *
 * References:
 * - PHPStan 2.2 unsealed array shapes, `array{foo: int, ...<Bar>}`
 *   https://phpstan.org/blog/phpstan-2-2-unsealed-array-shapes-safer-array-keys
 * - Psalm "Unsealed array and list shapes", `...<TKey, TValue>`
 * - Intelephense Type-System, extra elements of form `...<TKey, TValue>`
 */

final class Foo
{
}

function takesFoo(Foo $value): void
{
}

function takesString(string $value): void
{
}

/**
 * @param array{name: string, age: int, ...<Foo>} $row
 */
function acceptsRow(array $row): void // T: array{name: string, age: int, ...<Foo>}
{
}

/**
 * @param array{name: string, age: int, ...<Foo>} $row
 */
function inspectRow(array $row): void // T: array{name: string, age: int, ...<Foo>}
{
    // Extra keys are Foo. The isset() keeps "the offset exists" out of the
    // question; what remains is the remainder type.
    if (isset($row['extra'])) {
        takesFoo($row['extra']); // Q?: remainder values are Foo
        takesString($row['extra']); // E?: extra is Foo, not string
    }
}

// The declared keys on their own satisfy the shape.
acceptsRow(['name' => 'Ada', 'age' => 36]); // V

// So does an extra key whose value is Foo.
acceptsRow(['name' => 'Ada', 'age' => 36, 'extra' => new Foo()]); // V
acceptsRow(['name' => 'Ada', 'age' => 36, 'a' => new Foo(), 'b' => new Foo()]); // V
inspectRow(['name' => 'Ada', 'age' => 36, 'extra' => new Foo()]); // V

// `...<Foo>` constrains the extras, not the declared keys. An analyzer that
// read this as bare `...` accepts the string extra; one that sealed the
// shape rejects the Foo extras above as well.
acceptsRow(['name' => 'Ada', 'age' => 36, 'extra' => 'nope']); // E?: extra is Foo, not string

// The listed keys stay required and keep their types.
acceptsRow(['name' => 'Ada']); // E?: age is still required
acceptsRow(['extra' => new Foo()]); // E?: extras do not stand in for name and age
acceptsRow(['name' => 1, 'age' => 36]); // E?: name is string, not int