[ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' */ class Bar { public function foo() : void { $bar = /** @return TA */ function() { return ["hello"]; }; /** @var array */ $bat = [$bar(), $bar()]; foreach ($bat as $b) { echo $b[0]; } } } /** * @psalm-type _A=array{elt:int} * @param _A $p * @return _A */ function f($p) { /** @var _A */ $r = $p; return $r; }', ], 'classTypeAliasSimple' => [ 'code' => ' "Nokia"]; } } /** @psalm-type NameType = array{name: string} */ class Name { /** @psalm-return NameType */ function toArray(): array { return ["name" => "Matt"]; } } /** * @psalm-import-type PhoneType from Phone as PhoneType2 * @psalm-import-type NameType from Name as NameType2 * * @psalm-type UserType = PhoneType2&NameType2 */ class User { /** @psalm-return UserType */ function toArray(): array { return array_merge( (new Name)->toArray(), (new Phone)->toArray() ); } }', ], 'classTypeAliasImportWithAlias' => [ 'code' => ' "Nokia"]; } } /** * @psalm-import-type PhoneType from Phone as TPhone */ class User { /** @psalm-return TPhone */ function toArray(): array { return array_merge([], (new Phone)->toArray()); } }', ], 'classTypeAliasDirectUsage' => [ 'code' => ' "Nokia"]; } } /** * @psalm-import-type PhoneType from Phone */ class User { /** @psalm-return PhoneType */ function toArray(): array { return array_merge([], (new Phone)->toArray()); } }', ], 'classTypeAliasFromExternalNamespace' => [ 'code' => ' "Nokia"]; } } } namespace Bar { /** * @psalm-import-type PhoneType from \Foo\Phone */ class User { /** @psalm-return PhoneType */ function toArray(): array { return (new \Foo\Phone)->toArray(); } } }', ], 'importTypeForParam' => [ 'code' => 'b($type); } /** * @psalm-param Type2 $type */ private function b(int $type): void { } }', ], 'usedInVarForForeach' => [ 'code' => ' [ 'code' => ' [ 'code' => ' */ class C implements A { public function output() { return "hello"; } } $instance = new C(); $output = $instance->output();', 'assertions' => [ '$output' => 'string', ], ], 'sameDocBlockTypeAliasAsTypeParameterForExtendedRegularClass' => [ 'code' => 'value = $value; } } /** * @psalm-type Foo=string * @extends A */ class C extends A {} $instance = new C("hello"); $output = $instance->value;', 'assertions' => [ '$output' => 'string', ], ], 'sameDocBlockTypeAliasAsTypeParameterForExtendedAbstractClass' => [ 'code' => 'value = $value; } } /** * @psalm-type Foo=string * @extends A */ class C extends A {} $instance = new C("hello"); $output = $instance->value;', 'assertions' => [ '$output' => 'string', ], ], 'importedTypeAliasAsTypeParameterForImplementation' => [ 'code' => ' */ class C implements A {}', ], 'importedTypeAliasAsConstrainedTypeParameterForImplementation' => [ 'code' => ' */ class C implements A {} ', ], 'importedTypeAliasAsTypeParameterForExtendedClass' => [ 'code' => ' */ class C extends A {}', ], 'importedTypeAliasAsTypeParameterForExtendedAbstractClass' => [ 'code' => ' */ class C extends A {}', ], 'importedTypeAliasRenamedAsTypeParameterForImplementation' => [ 'code' => ' */ class C implements A {}', ], 'importedTypeAliasRenamedAsTypeParameterForExtendedClass' => [ 'code' => ' */ class C extends A {}', ], 'importedTypeAliasRenamedAsTypeParameterForExtendedAbstractClass' => [ 'code' => ' */ class C extends A {}', ], 'importedTypeInsideLocalTypeAliasUsedAsTypeParameter' => [ 'code' => 'value = $value; } } /** * @psalm-type Foo=string */ class B {} /** * @psalm-import-type Foo from B * @psalm-type Baz=Foo * * @extends A */ class C extends A {} $instance = new C("hello"); $output = $instance->value;', 'assertions' => [ '$output' => 'string', ], ], 'importedTypeWithPhpstanAnnotation' => [ 'code' => 'value = $value; } } /** * @phpstan-type Foo=string */ class B {} /** * @phpstan-import-type Foo from B * @phpstan-type Baz=Foo * * @extends A */ class C extends A {} $instance = new C("hello"); $output = $instance->value;', 'assertions' => [ '$output' => 'string', ], ], 'importedTypeUsedInAssertion' => [ 'code' => 'assertFoo($input); return $input; } /** * @param mixed $value * @psalm-assert FooAlias $value */ private function assertFoo($value): void { if(!is_string($value)) { throw new \InvalidArgumentException(); } } } $instance = new B(); $output = $instance->convertToFoo("hallo"); ', 'assertions' => [ '$output' => 'string', ], ], 'importedTypeUsedInOtherType' => [ 'code' => ' */ class Main { /** @return OpeningTypeAssignment */ public function doStuff(): array { return []; } } $instance = new Main(); $output = $instance->doStuff(); ', 'assertions' => [ '$output===' => 'list<1|2>', ], ], 'callableWithReturnTypeTypeAliasWithinBackets' => [ 'code' => ' [ '$output===' => 'callable():int', ], ], 'callableWithReturnTypeTypeAlias' => [ 'code' => ' [ '$output===' => 'callable():int', ], ], 'unionOfStringsContainingBraceChar' => [ 'code' => ' [ '$t===' => '\'{\'|\'}\'', ], ], 'unionOfStringsContainingGTChar' => [ 'code' => '\' */ class Foo { /** @psalm-var T */ public static string $t; } $t = Foo::$t; ', 'assertions' => [ '$t===' => '\'<\'|\'>\'', ], ], 'unionOfStringsContainingBracketChar' => [ 'code' => ' [ '$t===' => '\'(\'|\')\'', ], ], 'bareWordsCommentAfterType' => [ 'code' => ' [ '$t===' => 'string', ], ], 'handlesTypeWhichEndsWithRoundBracket' => [ 'code' => ') */ class A {} ', ], 'commentAfterType' => [ 'code' => ' [ 'code' => ' [ '$output===' => 'array{phone: string}', ], ], 'combineAliasOfArrayAndArrayCorrectly' => [ 'code' => '|D $_doesNotWork */ public function doesNotWork($_doesNotWork): void { /** @psalm-check-type-exact $_doesNotWork = D */; } }', ], ]; } public function providerInvalidCodeParse(): iterable { return [ 'invalidTypeAlias' => [ 'code' => ' */ class A {}', 'error_message' => 'InvalidDocblock', ], 'typeAliasInTKeyedArray' => [ 'code' => ' 'InvalidReturnStatement', ], 'classTypeAliasInvalidReturn' => [ 'code' => ' "Nokia"]; } } /** @psalm-type NameType = array{name: string} */ class Name { /** @psalm-return NameType */ function toArray(): array { return ["name" => "Matt"]; } } /** * @psalm-import-type PhoneType from Phone as PhoneType2 * @psalm-import-type NameType from Name as NameType2 * * @psalm-type UserType = PhoneType2&NameType2 */ class User { /** @psalm-return UserType */ function toArray(): array { return array_merge( (new Name)->toArray(), ["foo" => "bar"] ); } }', 'error_message' => 'InvalidReturnStatement', ], 'classTypeInvalidAliasImport' => [ 'code' => ' "Matt"]; } } /** * @psalm-import-type PhoneType from Phone */ class User {}', 'error_message' => 'InvalidTypeImport', ], 'classTypeAliasFromInvalidClass' => [ 'code' => ' 'UndefinedDocblockClass', ], 'malformedImportMissingFrom' => [ 'code' => ' 'InvalidTypeImport', ], 'malformedImportMissingSourceClass' => [ 'code' => ' 'InvalidTypeImport', ], 'malformedImportMisspelledFrom' => [ 'code' => ' 'InvalidTypeImport', ], 'malformedImportMissingAlias' => [ 'code' => ' 'InvalidTypeImport', ], 'noCrashWithPriorReference' => [ 'code' => ' 'UndefinedDocblockClass', ], 'mergeImportedTypes' => [ 'code' => ' 'PossiblyUndefinedArrayOffset', ], 'noCrashWithSelfReferencingType' => [ 'code' => ' 'InvalidDocblock', ], 'invalidTypeWhenNotImported' => [ 'code' => ' */ class C implements B {}', 'error_message' => 'UndefinedDocblockClass', ], 'invalidTypeWhenNotImportedInsideAnotherTypeAlias' => [ 'code' => ' */ class C implements B {}', 'error_message' => 'UndefinedDocblockClass', ], 'duplicateKeyInArrayShapeOnInterfaceIsReported' => [ 'code' => <<<'PHP' 'InvalidDocblock', ], 'duplicateKeyInArrayShapeOnAClassIsReported' => [ 'code' => <<<'PHP' 'InvalidDocblock', ], 'duplicateKeyInArrayShapeOnATraitIsReported' => [ 'code' => <<<'PHP' 'InvalidDocblock', ], 'duplicateKeyInArrayShapeOnAnEnumIsReported' => [ 'code' => <<<'PHP' 'InvalidDocblock', 'ignored_issues' => [], 'php_version' => '8.1', ], ]; } }