expectExceptionMessage('NullableReturnStatement'); $this->expectException(\Psalm\Exception\CodeException::class); Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'x = null; } } } class X { public ?int $x = null; public function getX(): int { $this->x = 5; XCollector::modify(); return $this->x; } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testForgetPropertyAssignmentsPassesNormally(): void { $this->addFile( 'somefile.php', 'x = null; } } } class X { public ?int $x = null; public function getX(): int { $this->x = 5; XCollector::modify(); return $this->x; } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testForgetPropertyAssignmentsInBranch(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'x = null; } } } class X { public ?int $x = null; } function testX(X $x): void { $x->x = 5; if (rand(0, 1)) { XCollector::modify(); } if ($x->x === null) {} }' ); $this->analyzeFile('somefile.php', new Context()); } public function testForgetFinalMethodCalls(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'x = null; } } } class X { public ?int $x = null; public function __construct(?int $x) { $this->x = $x; } public final function getX() : ?int { return $this->x; } } function testX(X $x): void { if ($x->getX()) { XCollector::modify(); if ($x->getX() === null) {} } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testRememberImmutableMethodCalls(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->expectExceptionMessage('TypeDoesNotContainNull - somefile.php:22:29'); $this->expectException(\Psalm\Exception\CodeException::class); $this->addFile( 'somefile.php', 'x = $x; } public function getX() : ?int { return $this->x; } } function testX(X $x): void { if ($x->getX()) { XCollector::modify(); if ($x->getX() === null) {} } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testRememberImmutableProperties(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->expectExceptionMessage('TypeDoesNotContainNull - somefile.php:18:29'); $this->expectException(\Psalm\Exception\CodeException::class); $this->addFile( 'somefile.php', 'x = $x; } } function testX(X $x): void { if ($x->x) { XCollector::modify(); if ($x->x === null) {} } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testNoCrashInTryCatch(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'f = 1; maybeMutates(); } } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testUniversalObjectCrates(): void { Config::getInstance()->addUniversalObjectCrate(\DateTime::class); $this->addFile( 'somefile.php', 'bar; // sets are fine $f->buzz = false; ' ); $this->analyzeFile('somefile.php', new Context()); } public function testForgetPropertyAssignmentsInBranchWithThrowNormally(): void { $this->addFile( 'somefile.php', 'x = null; } } } class X { /** @var ?int **/ public $x; public function getX(bool $b): int { $this->x = 5; if ($b) { XCollector::modify(); throw new \Exception("bad"); } return $this->x; } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return iterable,error_levels?:string[]}> */ public function providerValidCodeParse(): iterable { return [ 'newVarInIf' => [ 'foo = []; } if (!is_array($this->foo)) { // do something } } }', ], 'propertyWithoutTypeSuppressingIssue' => [ 'foo;', 'assertions' => [], 'error_levels' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'propertyWithoutTypeSuppressingIssueAndAssertingNull' => [ 'foo === null && rand(0,1); echo $this->foo->baz; } }', 'assertions' => [], 'error_levels' => [ 'UndefinedThisPropertyFetch', 'MixedAssignment', 'MixedArgument', 'MixedMethodCall', 'MixedPropertyFetch', ], ], 'sharedPropertyInIf' => [ 'foo; }', 'assertions' => [ '$b' => 'int|null|string', ], ], 'sharedPropertyInElseIf' => [ 'foo; }', 'assertions' => [ '$b' => 'int|null|string', ], ], 'nullablePropertyCheck' => [ 'bb) && $b->bb->aa === "aa") { echo $b->bb->aa; }', ], 'nullablePropertyAfterGuard' => [ 'aa) { $a->aa = "hello"; } echo substr($a->aa, 1);', ], 'nullableStaticPropertyWithIfCheck' => [ ' [ 'name . " - " . $a->class;', ], 'grandparentReflectedProperties' => [ 'ownerDocument;', 'assertions' => [ '$owner' => 'DOMDocument|null', ], ], 'propertyMapHydration' => [ 'attributes->length; }', ], 'genericTypeFromPropertyMap' => [ 'attributes->item(0); }' ], 'goodArrayProperties' => [ ' */ public $is = []; } $c = new C1; $c->is = [new A1]; $c->is = [new A1, new A1]; $c->is = [new A1, new B1];', 'assertions' => [], 'error_levels' => ['MixedAssignment'], ], 'issetPropertyDoesNotExist' => [ 'bar)) { }', ], 'notSetInConstructorButHasDefault' => [ ' [ 'foo(); } private function foo(): void { $this->a = 5; } }', ], 'definedInTraitSetInConstructor' => [ 'a = "hello"; } }', ], 'propertySetInNestedPrivateMethod' => [ 'foo(); } private function foo(): void { $this->bar(); } private function bar(): void { $this->a = 5; } }', ], 'propertyArrayIssetAssertion' => [ ' */ public $a = []; private function foo(): void { if (isset($this->a["hello"])) { bar($this->a["hello"]); } } }', ], 'propertyArrayIssetAssertionWithVariableOffset' => [ ' */ public $a = []; private function foo(): void { $b = "hello"; if (!isset($this->a[$b])) { return; } bar($this->a[$b]); } }', ], 'staticPropertyArrayIssetAssertionWithVariableOffset' => [ ' */ public static $a = []; } function foo(): void { $b = "hello"; if (!isset(A::$a[$b])) { return; } bar(A::$a[$b]); }', ], 'staticPropertyArrayIssetAssertionWithVariableOffsetAndElse' => [ ' */ public static $a = []; } function foo(): void { $b = "hello"; if (!isset(A::$a[$b])) { $g = "bar"; } else { bar(A::$a[$b]); $g = "foo"; } bar($g); }', ], 'traitConstructor' => [ 'foo = "hello"; } } class A { use T; }', ], 'abstractClassWithNoConstructor' => [ ' [ 'foo = ""; } } class B extends A { public function __construct() { parent::__construct(); } }', ], 'abstractClassConstructorAndImplicitChildConstructor' => [ 'foo = (string)$bar; } } class B extends A {} class E extends \Exception{}', ], 'notSetInEmptyConstructor' => [ ' [ 'aString = "aa"; echo($this->aString); } } class Descendant extends Base { /** * @var bool */ private $aBool; public function __construct() { parent::__construct(); $this->aBool = true; } }', ], 'extendsClassWithPrivateAndException' => [ 'p = $p; } } final class B extends A {}', ], 'setInAbstractMethod' => [ 'foo(); } } class B extends A { public function foo(): void { $this->bar = "hello"; } }', 'assertions' => [], 'error_levels' => [ 'PropertyNotSetInConstructor' => Config::REPORT_INFO, ], ], 'callsPrivateParentMethodThenUsesParentInitializedProperty' => [ 'setBar(); } private function setBar(): void { $this->bar = "hello"; } } class B extends A { public function __construct() { parent::__construct(); echo $this->bar; } }', ], 'setInFinalMethod' => [ 'setOptions($opts); } /** * @param string[] $opts * @psalm-param array{a:string,b:string} $opts */ final public function setOptions(array $opts): void { $this->a = $opts["a"] ?? "defaultA"; $this->b = $opts["b"] ?? "defaultB"; } }', ], 'setInFinalClass' => [ 'setOptions($opts); } /** * @param string[] $opts * @psalm-param array{a:string,b:string} $opts */ public function setOptions(array $opts): void { $this->a = $opts["a"] ?? "defaultA"; $this->b = $opts["b"] ?? "defaultB"; } }', ], 'selfPropertyType' => [ 'next = new Node(); } } } $node = new Node(); $next = $node->next;', 'assertions' => [ '$next' => 'Node|null', ], ], 'perPropertySuppress' => [ ' [ ' $stmts Statements * @param array $attributes Additional attributes */ public function __construct(array $stmts = array(), array $attributes = array()) { parent::__construct($attributes); $this->stmts = $stmts; } public function getSubNodeNames() : array { return array("stmts"); } public function getType() : string { return "Stmt_Finally"; } }', 'assertions' => [], ], 'privatePropertyAccessible' => [ 'foo = $foo; } private function bar() : void {} } class B extends A { /** @var string */ private $foo; public function __construct(string $foo) { $this->foo = $foo; parent::__construct($foo); } }', ], 'privatePropertyAccessibleDifferentType' => [ 'foo = 5; } private function bar() : void {} } class B extends A { /** @var string */ private $foo; public function __construct(string $foo) { $this->foo = $foo; parent::__construct($foo); } }', ], 'privatePropertyAccessibleInTwoSubclasses' => [ 'prop = 1; } } class C extends A { /** * @var int */ private $prop; public function __construct() { parent::__construct(); $this->prop = 2; } }', ], 'noIssueWhenSuppressingMixedAssignmentForProperty' => [ 'foo = $a; } }', 'assertions' => [], 'error_levels' => [ 'MixedAssignment', ], ], 'propertyAssignmentToMixed' => [ 'foo = $a; }', 'assertions' => [], 'error_levels' => [ 'MixedAssignment', ], ], 'propertySetInBothIfBranches' => [ 'status = 1; } else { $this->status = $in; } } }', ], 'propertySetInPrivateMethodWithIfAndElse' => [ 'foo(); } else { $this->bar(); } } private function foo(): void { $this->a = 5; } private function bar(): void { $this->a = 5; } }', ], 'allowMixedAssignmetWhenDesired' => [ 'mixed = $value; } }', ], 'suppressUndefinedThisPropertyFetch' => [ 'bar = rand(0, 1) ? "hello" : null; } /** @psalm-suppress UndefinedThisPropertyFetch */ public function foo() : void { if ($this->bar === null && rand(0, 1)) {} } }', ], 'suppressUndefinedPropertyFetch' => [ 'bar = rand(0, 1) ? "hello" : null; } } $a = new A(); /** @psalm-suppress UndefinedPropertyFetch */ if ($a->bar === null && rand(0, 1)) {}', ], 'setPropertiesOfSpecialObjects' => [ 'b = "c"; $d = new SimpleXMLElement(""); $d->e = "f";', 'assertions' => [ '$a' => 'stdClass', '$a->b' => 'string', '$d' => 'SimpleXMLElement', '$d->e' => 'mixed', ], ], 'allowLessSpecificReturnTypeForOverriddenMethod' => [ ' [ ' [ 'bar(); echo self::$instance->bat; } } public function bar() : void {} } $a = new A(); if ($a->instance) { $a->instance->bar(); echo $a->instance->bat; }', ], 'nonStaticPropertyMethodCall' => [ 'instance) { $this->instance->bar(); echo $this->instance->bat; } } public function bar() : void {} } $a = new A(); if ($a->instance) { $a->instance->bar(); echo $a->instance->bat; }', ], 'staticPropertyOfStaticTypeMethodCall' => [ 'instance) { $this->instance->bar(); echo $this->instance->bat; } } public function bar() : void {} }', ], 'classStringPropertyType' => [ ' */ public $member = [ InvalidArgumentException::class => 1, ]; }', ], 'allowPrivatePropertySetAfterInstanceof' => [ 'foo = "hello"; } } class B extends A {}', ], 'noCrashForAbstractConstructorWithInstanceofInterface' => [ 'a = $this->bar(); } else { $this->a = 6; } } } interface I { public function bar() : int; }', ], 'SKIPPED-abstractConstructorWithInstanceofClass' => [ 'a = $this->bar(); } else { $this->a = 6; } } } class B extends A { public function bar() : int { return 3; } }', [], 'error_levels' => [], ], 'inheritDocPropertyTypes' => [ 'a = "hello"; echo (new Y)->a; Y::$b = "bar"; echo Y::$b;', ], 'subclassPropertySetInParentConstructor' => [ 'prop = $s; } } class Child extends Base { /** @var string */ protected $prop; }', ], 'callInParentContext' => [ 'foo = ""; $this->bar(); } public function bar(): void { \usort($this->as, function (A $a, A $b): int { return $b->i <=> $a->i; }); } } class C extends B { public function __construct() { parent::__construct(); } }', ], 'staticVarSelf' => [ ' 'Foo', ], ], 'noMixedErrorWhenAssignmentExpectsMixed' => [ ' $bar */ public $bar = []; /** @param mixed $b */ public function foo($b) : void { $this->bar["a"] = $b; } }', ], 'propertySetInGrandparentExplicitly' => [ 's = $s; } } class B extends A {} class C extends B { public function __construct(string $s) { A::__construct($s); } }', ], 'propertySetInGrandparentImplicitly' => [ 's = $s; } } class B extends A {} class C extends B {}', ], 'unitializedPropertySuppressPropertyNotSetInConstructor' => [ 'setFoo(); // public method that circumvents checks echo strlen($this->foo); } public function setFoo() : void { $this->foo = "foo"; } }', [], ['PropertyNotSetInConstructor'], ], 'setTKeyedArrayPropertyType' => [ ' false, "to" => false, ]; /** * @psalm-param "from"|"to" $property */ public function ChangeThing(string $property) : void { $this->changed[$property] = true; } }', ], 'noRedundantConditionWhenCheckingInitializations' => [ 'y) { return true; } return false; } public function func2 (): int { if ($this->y) { return 1; } return 2; } public function __construct () { $this->x = false; if ($this->func1()) { $this->y = $this->func2(); } $this->func2(); } }', ], 'noRedundantConditionWhenCheckingInitializationsEdgeCases' => [ 'y !== 0) { return true; } return false; } public function func2 (): int { if ($this->y !== 0) { return $this->y; } return 2; } public function __construct () { $this->x = false; if ($this->func1()) { $this->y = $this->func2(); } $this->func2(); } }', ], 'propertySetInProtectedMethodWithConstant' => [ 'foo(); } protected function foo(): void { $this->a = 5; } } class B extends A { const HELLO = "HELLO"; protected function foo() : void { $this->a = 6; echo self::HELLO; } }', ], 'setPropertyInParentProtectedMethodExplicitCall' => [ 'overriddenByB(); } protected function overriddenByB(): void { // do nothing } } class B extends A { /** @var int */ private $foo; /** @var int */ protected $bar; public function __construct() { parent::__construct(); } protected final function overriddenByB(): void { $this->foo = 1; $this->bar = 1; } }', ], 'setPropertyInParentProtectedMethodImplicitCall' => [ 'overriddenByB(); } protected function overriddenByB(): void { // do nothing } } class B extends A { /** @var int */ private $foo; /** @var int */ protected $bar; protected final function overriddenByB(): void { $this->foo = 1; $this->bar = 1; } }', ], 'setPropertyInParentWithPrivateConstructor' => [ 'setA(); } private function setA() : void { $this->a = 5; } public static function getInstance(): self { return new static; } } class Concrete extends Base {}', ], 'preventCrashWhenCallingInternalMethodInPropertyInitialisationChecks' => [ 'serializableTrace = $this->getTrace(); } } class Bar extends Foo {}', ], 'parentSetsWiderTypeInConstructor' => [ 'foo = $foo; } } class BarMore extends Bar { /** @var FooMore */ protected $foo; public function __construct(FooMore $foo) { parent::__construct($foo); $this->foo->something(); } }', ], 'inferPropertyTypesForSimpleConstructors' => [ 'foo = $foot; $this->bar = $bart; } public function getFoo() : int { return $this->foo; } public function getBar() : string { return $this->bar; } }', ], 'nullableDocblockTypedPropertyNoConstructor' => [ ' [ ' [ 'foo; } }', ], 'dontAlterClosureParams' => [ 'i = [ function (Exception $e): void {}, function (LogicException $e): void {}, ]; } }', ], 'inferSpreadParamType' => [ 'tags = $tags; } }', ], 'readonlyPropertySetInConstructor' => [ 'bar = "hello"; } } echo (new A)->bar;' ], 'readonlyWithPrivateMutationsAllowedPropertySetInAnotherMEthod' => [ 'bar = $s; } } echo (new A)->bar;' ], 'readonlyPublicPropertySetInAnotherMEthod' => [ 'bar = $s; } } echo (new A)->bar;' ], 'readonlyPropertySetChildClass' => [ 'bar = "hello"; } } echo (new B)->bar;' ], 'staticPropertyDefaultWithStaticType' => [ ' */ private static $t1 = []; /** @var array */ private $t2 = []; }' ], 'propagateIgnoreNullableOnPropertyFetch' => [ 's !== null) {} echo $foo->s ?? "bar"; takesString($foo->s);', ], 'noMissingPropertyWhenArrayTypeProvided' => [ 'bar = $bar; } public function bar(): void { echo $this->bar["key"]; } }', ], 'rememberThisPropertyAsssignmentsInMethod' => [ 'foo = false; $this->maybeChange(); if ($this->foo) {} } public function maybeChange() : void { if (rand(0, 1)) { $this->foo = true; } } }' ], 'testRemoveClauseAfterReassignment' => [ 'foo = false; $this->bar(); if ($this->foo === true) {} } private function bar(): void { if (mt_rand(0, 1)) { $this->foo = true; } } }', ], 'allowIssetOnTypedProperty' => [ 'a = "hello"; } if (isset($this->a)) { echo $this->a; $this->a = "bello"; } $this->a = "bar"; } }' ], 'allowGoodArrayPushOnArrayValue' => [ 'prop, 5); } }', ], 'someConditionalCallToParentConstructor' => [ 'val = "hello"; if (rand(0, 1)) { parent::__construct(); } } } class ChildClass extends ParentClassDefinesVar { public function __construct() { parent::__construct(); } }' ], 'noConditionalCallToParentConstructor' => [ 'val = "hello"; parent::__construct(); } } class ChildClass extends ParentClassDefinesVar { public function __construct() { parent::__construct(); } }' ], 'allowByReferenceAssignmentToUninitializedNullableProperty' => [ 'foo($this->onCancel); } /** * @param mixed $onCancel * @param-out \Closure $onCancel */ public function foo(&$onCancel) : void { $onCancel = function (): void {}; } }' ], 'dontCarryAssertionsOver' => [ 'network = $s; $this->firstCheck(); $this->secondCheck(); } public function firstCheck(): void { if ($this->network === "x") { return; } } public function secondCheck(): void { if ($this->network === "x") { return; } } }' ], 'useVariableAccessInStatic' => [ ' [ 'foo = $bar; } public function baz(): Bar { return $this->foo; } }' ], 'aliasedFinalMethod' => [ 'prop = $prop; } } class B { use A { setProp as setPropFinal; } public function __construct() { $this->setPropFinal(1); } }' ], 'aliasedAsFinalMethod' => [ 'prop = $prop; } } class B { use A { setProp as final setPropFinal; } public function __construct() { $this->setPropFinal(1); } }' ], 'staticPropertyAssertion' => [ ' [ 'i; if ($o instanceof A) { echo strlen($o->i); } }' ], 'unionPropertyType' => [ 'i = 5; $this->i = "hello"; } } $a = new A(); if ($a->i === 3) {} if ($a->i === "foo") {}' ], 'setClassStringOfStatic' => [ ' [ 'foo;' ], 'promotedPublicPropertyWitoutDefault' => [ 'foo;' ], 'promotedProtectedProperty' => [ 'foo; } }' ], 'skipConstructor' => [ 's = "hello"; } } class B extends A {} class C extends B { public function __construct() { parent::__construct(); echo $this->s; } }' ], 'getPropertyThatMayNotBeSet' => [ 'bar)) { return "hello"; } return $this->bar; } public function getBarAgain() : string { /** @psalm-suppress RedundantPropertyInitializationCheck */ if (isset($this->bar)) { return $this->bar; } return "hello"; } }', ], 'memoizePropertyAfterSetting' => [ 'b = "c"; echo strlen($this->b); } }' ], 'noErrorForSplatArgs' => [ 'b = $bb; } } class Bar extends Foo {}' ], 'noUndefinedPropertyIssueAfterSuppressingOnInterface' => [ 'foo; }' ], 'noRedundantCastWhenCheckingProperties' => [ 'map = []; $this->map["test"] = "test"; $this->useMap(); } public function useMap(): void { $keys = array_keys($this->map); $key = reset($keys); echo (string) $key; } }' ], 'ignoreUndefinedMethodOnUnion' => [ 'name; if ($name === null) {} return $name; }', [], [], '8.0' ], 'dynamicPropertyFetch' => [ '{$b} ?? null; }' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse(): iterable { return [ 'undefinedPropertyAssignment' => [ 'foo = "cool";', 'error_message' => 'UndefinedPropertyAssignment', ], 'undefinedPropertyFetch' => [ 'foo;', 'error_message' => 'UndefinedPropertyFetch', ], 'undefinedThisPropertyAssignment' => [ 'foo = "cool"; } }', 'error_message' => 'UndefinedThisPropertyAssignment', ], 'undefinedStaticPropertyAssignment' => [ ' 'UndefinedPropertyAssignment', ], 'undefinedThisPropertyFetch' => [ 'foo; } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'missingPropertyType' => [ 'foo = 5; } }', 'error_message' => 'MissingPropertyType - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:32 - Property A::$foo does not have a ' . 'declared type - consider int|null', ], 'missingPropertyTypeWithConstructorInit' => [ 'foo = 5; } }', 'error_message' => 'MissingPropertyType - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:32 - Property A::$foo does not have a ' . 'declared type - consider int', ], 'missingPropertyTypeWithConstructorInitAndNull' => [ 'foo = 5; } public function makeNull(): void { $this->foo = null; } }', 'error_message' => 'MissingPropertyType - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:32 - Property A::$foo does not have a ' . 'declared type - consider int|null', ], 'missingPropertyTypeWithConstructorInitAndNullDefault' => [ 'foo = 5; } }', 'error_message' => 'MissingPropertyType - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:32 - Property A::$foo does not have a ' . 'declared type - consider int|null', ], 'missingPropertyTypeWithConstructorInitConditionallySet' => [ 'foo = 5; } } }', 'error_message' => 'MissingPropertyType - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:32 - Property A::$foo does not have a ' . 'declared type - consider int|null', ], 'badAssignment' => [ 'foo = 5; } }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'badStaticAssignment' => [ ' 'InvalidPropertyAssignmentValue', ], 'typeCoercion' => [ 'foo = $a; } } class B extends A {}', 'error_message' => 'PropertyTypeCoercion', ], 'mixedTypeCoercion' => [ ' */ public $foo = []; /** @param A[] $arr */ public function barBar(array $arr): void { $this->foo = $arr; } }', 'error_message' => 'MixedPropertyTypeCoercion', ], 'staticTypeCoercion' => [ ' 'PropertyTypeCoercion', ], 'staticMixedTypeCoercion' => [ ' */ public static $foo = []; /** @param A[] $arr */ public static function barBar(array $arr): void { self::$foo = $arr; } }', 'error_message' => 'MixedPropertyTypeCoercion', ], 'possiblyBadAssignment' => [ 'foo = rand(0, 1) ? 5 : "hello"; } }', 'error_message' => 'PossiblyInvalidPropertyAssignmentValue', ], 'possiblyBadStaticAssignment' => [ ' 'PossiblyInvalidPropertyAssignmentValue', ], 'badAssignmentAsWell' => [ 'foo = "bar";', 'error_message' => 'InvalidPropertyAssignment', ], 'badFetch' => [ 'foo;', 'error_message' => 'InvalidPropertyFetch', ], 'possiblyBadFetch' => [ ' 3 ? "hello" : new stdClass; echo $a->foo;', 'error_message' => 'PossiblyInvalidPropertyFetch', ], 'mixedPropertyFetch' => [ 'foo;', 'error_message' => 'MixedPropertyFetch', 'error_levels' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'mixedPropertyAssignment' => [ 'foo = "hello";', 'error_message' => 'MixedPropertyAssignment', 'error_levels' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'possiblyNullablePropertyAssignment' => [ 'foo = "hello";', 'error_message' => 'PossiblyNullPropertyAssignment', ], 'nullablePropertyAssignment' => [ 'foo = "hello";', 'error_message' => 'NullPropertyAssignment', ], 'possiblyNullablePropertyFetch' => [ 'foo;', 'error_message' => 'PossiblyNullPropertyFetch', ], 'nullablePropertyFetch' => [ 'foo;', 'error_message' => 'NullPropertyFetch', ], 'badArrayProperty' => [ ' */ public $bb; } $c = new C; $c->bb = [new A, new B];', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'possiblyBadArrayProperty' => [ 'bb = ["hello", "world"];', 'error_message' => 'PossiblyInvalidPropertyAssignmentValue', ], 'notSetInEmptyConstructor' => [ ' 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'noConstructor' => [ ' 'MissingConstructor', ], 'abstractClassInheritsNoConstructor' => [ ' 'MissingConstructor', ], 'abstractClassInheritsPrivateConstructor' => [ 'foo = "hello"; } } class B extends A {} $b = new B();', 'error_message' => 'InaccessibleMethod - src' . DIRECTORY_SEPARATOR . 'somefile.php:13', ], 'classInheritsPrivateConstructorWithImplementedConstructor' => [ 'foo = "hello"; } } class B extends A { public function __construct() {} }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:11', ], 'notSetInAllBranchesOfIf' => [ 'a = 5; } } }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'propertySetInProtectedMethod' => [ 'foo(); } protected function foo(): void { $this->a = 5; } } class B extends A { protected function foo() : void {} }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:15', ], 'definedInTraitNotSetInEmptyConstructor' => [ ' 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:6', ], 'propertySetInPrivateMethodWithIf' => [ 'foo(); } } private function foo(): void { $this->a = 5; } }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'privatePropertySameNameNotSetInConstructor' => [ 'b = "foo"; } } class B extends A { /** @var string */ private $b; }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:13', ], 'privateMethodCalledInParentConstructor' => [ 'publicMethod(); } public function publicMethod() : void { $this->privateMethod(); } private function privateMethod() : void {} }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:2', ], 'privatePropertySetInParentConstructorReversedOrder' => [ 'b = "foo"; } } }', 'error_message' => 'InaccessibleProperty', ], 'privatePropertySetInParentConstructor' => [ 'b = "foo"; } } } class B extends A { /** @var string */ private $b; } ', 'error_message' => 'InaccessibleProperty', ], 'undefinedPropertyClass' => [ ' 'UndefinedDocblockClass', ], 'abstractClassWithNoConstructorButChild' => [ ' 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:7', ], 'badAssignmentToUndefinedVars' => [ '$y = 4;', 'error_message' => 'UndefinedGlobalVariable', ], 'echoUndefinedPropertyFetch' => [ '$y;', 'error_message' => 'UndefinedGlobalVariable', ], 'toStringPropertyAssignment' => [ 'foo = new B;', 'error_message' => 'ImplicitToStringCast', ], 'noInfiniteLoop' => [ 'doThing(); } private function doThing(): void { if (rand(0, 1)) { $this->doOtherThing(); } } private function doOtherThing(): void { if (rand(0, 1)) { $this->doThing(); } } }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'invalidPropertyDefault' => [ ' 'InvalidPropertyAssignmentValue', ], 'prohibitMixedAssignmentNormally' => [ 'mixed = $value; } }', 'error_message' => 'MixedAssignment', ], 'assertPropertyTypeHasImpossibleType' => [ 'foo)) {}', 'error_message' => 'DocblockTypeContradiction', ], 'impossiblePropertyCheck' => [ 'bar = new Bar(); } public function getBar(): void { if (!$this->bar) {} } }', 'error_message' => 'DocblockTypeContradiction', ], 'staticPropertyOfStaticTypeMethodCallWithUndefinedMethod' => [ 'instance) { $this->instance->bar(); } } } class B extends A { public function bar() : void {} }', 'error_message' => 'UndefinedMethod', ], 'misnamedPropertyByVariable' => [ '$var_name; } return null; } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'inheritDocPropertyTypesIncorrectAssignmentToInstanceProperty' => [ 'a = 5; echo (new Y)->a; Y::$b = "bar"; echo Y::$b;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'inheritDocPropertyTypesIncorrectAssignmentToStaticProperty' => [ ' 'InvalidPropertyAssignmentValue', ], 'unitializedProperty' => [ 'foo); $this->foo = "foo"; } }', 'error_message' => 'UninitializedProperty', ], 'unitializedPropertyWithoutType' => [ 'foo); $this->foo = "foo"; } }', 'error_message' => 'UninitializedProperty', ['MixedArgument', 'MissingPropertyType'] ], 'unitializedObjectProperty' => [ 'foo->bar); $this->foo = $foo; } }', 'error_message' => 'UninitializedProperty', ], 'possiblyNullArg' => [ 'foo); $this->foo = "foo"; } }', 'error_message' => 'PossiblyNullArgument', ], 'noCrashOnMagicCall' => [ 'setA(); } public function __call(string $var, array $args) {} }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'reportGoodLocationForPropertyError' => [ 'setS(); } public function setS() : void { $this->s = "hello"; } } class D extends C { public function setS() : void { // nothing happens here } }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:15', ], 'noCrashWhenUnsettingPropertyWithoutDefaultInConstructor' => [ 'foo); } }', 'error_message' => 'PropertyNotSetInConstructor', ], 'parentSetsWiderTypeInConstructor' => [ 'foo = $foo; } } class BarMore extends Bar { /** @var FooMore */ protected $foo; public function __construct(FooMore $foo) { parent::__construct($foo); $this->foo->something(); } }', 'error_message' => 'UndefinedInterfaceMethod', ], 'nullableTypedPropertyNoConstructor' => [ ' 'MissingConstructor', ], 'nullableTypedPropertyEmptyConstructor' => [ ' 'PropertyNotSetInConstructor', ], 'nullableTypedPropertyUseBeforeInitialised' => [ 'foo; } }', 'error_message' => 'UninitializedProperty', ], 'nullableTypedPropertyNoConstructorWithDocblock' => [ ' 'MissingConstructor', ], 'nullableTypedPropertyEmptyConstructorWithDocblock' => [ ' 'PropertyNotSetInConstructor', ], 'nullableTypedPropertyUseBeforeInitialisedWithDocblock' => [ 'foo; } }', 'error_message' => 'UninitializedProperty', ], 'badStaticPropertyDefault' => [ ' */ public static $test = ["string-key" => 1]; }', 'error_message' => 'InvalidPropertyAssignmentValue' ], 'readonlyPropertySetInConstructorAndAlsoAnotherMethodInsideClass' => [ 'bar = "hello"; } public function setBar() : void { $this->bar = "goodbye"; } }', 'error_message' => 'InaccessibleProperty', ], 'readonlyPropertySetInConstructorAndAlsoAnotherMethodInSublass' => [ 'bar = "hello"; } } class B extends A { public function setBar() : void { $this->bar = "hello"; } }', 'error_message' => 'InaccessibleProperty', ], 'readonlyPropertySetInConstructorAndAlsoOutsideClass' => [ 'bar = "hello"; } } $a = new A(); $a->bar = "goodbye";', 'error_message' => 'InaccessibleProperty', ], 'readonlyPropertySetInConstructorAndAlsoOutsideClassWithAllowPrivate' => [ 'bar = "hello"; } public function setAgain() : void { $this->bar = "hello"; } } $a = new A(); $a->bar = "goodbye";', 'error_message' => 'InaccessibleProperty - src' . DIRECTORY_SEPARATOR . 'somefile.php:19:21', ], 'readonlyPublicPropertySetInConstructorAndAlsoOutsideClass' => [ 'bar = "hello"; } public function setAgain() : void { $this->bar = "hello"; } } $a = new A(); $a->bar = "goodbye";', 'error_message' => 'InaccessibleProperty - src' . DIRECTORY_SEPARATOR . 'somefile.php:18:21', ], 'addNullToMixedAfterNullablePropertyFetch' => [ 'foo); }', 'error_message' => 'PossiblyNullArgument', ], 'catchBadArrayStaticProperty' => [ ' */ public array $map = []; /** * @param string $class */ public function get(string $class) : void { $this->map[$class] = 5; } }', 'error_message' => 'InvalidPropertyAssignmentValue' ], 'preventArrayPushOnArrayValue' => [ 'prop, "bad"); } }', 'error_message' => 'InvalidPropertyAssignmentValue' ], 'overriddenConstructorCalledMethod' => [ 'init(); } public function init(): void { $this->prop = "zxc"; } } class ChildClass extends ParentClass { public function init(): void {} }', 'error_message' => 'PropertyNotSetInConstructor' ], 'propertyWithSameNameUndefined' => [ 'id; } }', 'error_message' => 'UndefinedPropertyFetch', ], 'missingPropertyTypeWithDocblock' => [ ' 'MissingPropertyType', ], 'promotedPrivateProperty' => [ 'foo;', 'error_message' => 'InaccessibleProperty', ], 'overwritePropertyType' => [ ' 'MismatchingDocblockPropertyType', ], ]; } }