expectExceptionMessage('NullableReturnStatement'); $this->expectException(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 testFooBar(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'bar = null; foreach (range(1, 5) as $part) { if ($part === 3) { $this->foo(); } } if ($this->bar === null) {} } private function foo() : void { $this->bar = 5; } }', ); $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(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(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 testAssertionInsideWhileOne(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'a) { $has_changes = true; $this->alter(); } return $has_changes; } public function alter() : void { if (rand(0, 1)) { array_pop($this->a); } if (rand(0, 1)) { array_pop($this->b); } if (rand(0, 1)) { array_pop($this->c); } } }', ); $this->analyzeFile('somefile.php', new Context()); } public function testAssertionInsideWhileTwo(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'a || $this->b) { $has_changes = true; $this->alter(); } return $has_changes; } public function alter() : void { if (rand(0, 1)) { array_pop($this->a); } if (rand(0, 1)) { array_pop($this->b); } } }', ); $this->analyzeFile('somefile.php', new Context()); } public function testAssertionInsideWhileThree(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'a || $this->b || $this->c) { $has_changes = true; $this->alter(); } return $has_changes; } public function alter() : void { if (rand(0, 1)) { array_pop($this->a); } if (rand(0, 1)) { array_pop($this->b); } if (rand(0, 1)) { array_pop($this->c); } } }', ); $this->analyzeFile('somefile.php', new Context()); } public function testAssertionInsideWhileFour(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'a && $this->b) || $this->c) { $has_changes = true; $this->alter(); } return $has_changes; } public function alter() : void { if (rand(0, 1)) { array_pop($this->a); } if (rand(0, 1)) { array_pop($this->b); } if (rand(0, 1)) { array_pop($this->c); } } }', ); $this->analyzeFile('somefile.php', new Context()); } public function testAssertionInsideWhileFive(): void { Config::getInstance()->remember_property_assignments_after_call = false; $this->addFile( 'somefile.php', 'a || ($this->b && $this->c)) { $has_changes = true; $this->alter(); } return $has_changes; } public function alter() : void { if (rand(0, 1)) { array_pop($this->a); } if (rand(0, 1)) { array_pop($this->b); } if (rand(0, 1)) { array_pop($this->c); } } }', ); $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()); } public function providerValidCodeParse(): iterable { return [ 'newVarInIf' => [ 'code' => 'foo = []; } if (!is_array($this->foo)) { // do something } } }', ], 'propertyWithoutTypeSuppressingIssue' => [ 'code' => 'foo;', 'assertions' => [], 'ignored_issues' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'propertyWithoutTypeSuppressingIssueAndAssertingNull' => [ 'code' => 'foo === null && rand(0,1); echo $this->foo->baz; } }', 'assertions' => [], 'ignored_issues' => [ 'UndefinedThisPropertyFetch', 'MixedAssignment', 'MixedArgument', 'MixedMethodCall', 'MixedPropertyFetch', ], ], 'sharedPropertyInIf' => [ 'code' => 'foo; }', 'assertions' => [ '$b' => 'int|null|string', ], ], 'sharedPropertyInElseIf' => [ 'code' => 'foo; }', 'assertions' => [ '$b' => 'int|null|string', ], ], 'nullablePropertyCheck' => [ 'code' => 'bb) && $b->bb->aa === "aa") { echo $b->bb->aa; }', ], 'nullablePropertyAfterGuard' => [ 'code' => 'aa) { $a->aa = "hello"; } echo substr($a->aa, 1);', ], 'nullableStaticPropertyWithIfCheck' => [ 'code' => ' [ 'code' => 'name . " - " . $a->class;', ], 'grandparentReflectedProperties' => [ 'code' => 'ownerDocument;', 'assertions' => [ '$owner' => 'DOMDocument|null', ], ], 'propertyMapHydration' => [ 'code' => 'attributes->length; }', ], 'genericTypeFromPropertyMap' => [ 'code' => 'attributes->item(0); }', ], 'goodArrayProperties' => [ 'code' => ' */ public $is = []; } $c = new C1; $c->is = [new A1]; $c->is = [new A1, new A1]; $c->is = [new A1, new B1];', 'assertions' => [], 'ignored_issues' => ['MixedAssignment'], ], 'issetPropertyDoesNotExist' => [ 'code' => 'bar)) { }', ], 'notSetInConstructorButHasDefault' => [ 'code' => ' [ 'code' => 'foo(); } private function foo(): void { $this->a = 5; } }', ], 'definedInTraitSetInConstructor' => [ 'code' => 'a = "hello"; } }', ], 'propertySetInNestedPrivateMethod' => [ 'code' => 'foo(); } private function foo(): void { $this->bar(); } private function bar(): void { $this->a = 5; } }', ], 'propertyArrayIssetAssertion' => [ 'code' => ' */ public $a = []; private function foo(): void { if (isset($this->a["hello"])) { bar($this->a["hello"]); } } }', ], 'propertyArrayIssetAssertionWithVariableOffset' => [ 'code' => ' */ public $a = []; private function foo(): void { $b = "hello"; if (!isset($this->a[$b])) { return; } bar($this->a[$b]); } }', ], 'staticPropertyArrayIssetAssertionWithVariableOffset' => [ 'code' => ' */ public static $a = []; } function foo(): void { $b = "hello"; if (!isset(A::$a[$b])) { return; } bar(A::$a[$b]); }', ], 'staticPropertyArrayIssetAssertionWithVariableOffsetAndElse' => [ 'code' => ' */ 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' => [ 'code' => 'foo = "hello"; } } class A { use T; }', ], 'abstractClassWithNoConstructor' => [ 'code' => ' [ 'code' => 'foo = ""; } } class B extends A { public function __construct() { parent::__construct(); } }', ], 'abstractClassConstructorAndImplicitChildConstructor' => [ 'code' => 'foo = (string)$bar; } } class B extends A {} class E extends \Exception{}', ], 'notSetInEmptyConstructor' => [ 'code' => ' [ 'code' => 'aString = "aa"; echo($this->aString); } } class Descendant extends Base { /** * @var bool */ private $aBool; public function __construct() { parent::__construct(); $this->aBool = true; } }', ], 'extendsClassWithPrivateAndException' => [ 'code' => 'p = $p; } } final class B extends A {}', ], 'setInAbstractMethod' => [ 'code' => 'foo(); } } class B extends A { public function foo(): void { $this->bar = "hello"; } }', ], 'callsPrivateParentMethodThenUsesParentInitializedProperty' => [ 'code' => 'setBar(); } private function setBar(): void { $this->bar = "hello"; } } class B extends A { public function __construct() { parent::__construct(); echo $this->bar; } }', ], 'setInFinalMethod' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => 'next = new Node(); } } } $node = new Node(); $next = $node->next;', 'assertions' => [ '$next' => 'Node|null', ], ], 'perPropertySuppress' => [ 'code' => ' [ 'code' => ' Statements */ public $stmts; /** * Constructs a finally node. * * @param list $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' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => 'prop = 1; } } class C extends A { /** * @var int */ private $prop; public function __construct() { parent::__construct(); $this->prop = 2; } }', ], 'noIssueWhenSuppressingMixedAssignmentForProperty' => [ 'code' => 'foo = $a; } }', 'assertions' => [], 'ignored_issues' => [ 'MixedAssignment', ], ], 'propertyAssignmentToMixed' => [ 'code' => 'foo = $a; }', 'assertions' => [], 'ignored_issues' => [ 'MixedAssignment', ], ], 'propertySetInBothIfBranches' => [ 'code' => 'status = 1; } else { $this->status = $in; } } }', ], 'propertySetInPrivateMethodWithIfAndElse' => [ 'code' => 'foo(); } else { $this->bar(); } } private function foo(): void { $this->a = 5; } private function bar(): void { $this->a = 5; } }', ], 'allowMixedAssignmetWhenDesired' => [ 'code' => 'mixed = $value; } }', ], 'suppressUndefinedThisPropertyFetch' => [ 'code' => 'bar = rand(0, 1) ? "hello" : null; } /** @psalm-suppress UndefinedThisPropertyFetch */ public function foo() : void { if ($this->bar === null && rand(0, 1)) {} } }', ], 'suppressUndefinedPropertyFetch' => [ 'code' => 'bar = rand(0, 1) ? "hello" : null; } } $a = new A(); /** @psalm-suppress UndefinedPropertyFetch */ if ($a->bar === null && rand(0, 1)) {}', ], 'setPropertiesOfStdClass' => [ 'code' => 'b = "c";', 'assertions' => [ '$a' => 'stdClass', '$a->b' => 'string', ], ], 'getPropertiesOfSimpleXmlElement' => [ 'code' => '"); $b = $a->b;', 'assertions' => [ '$a' => 'SimpleXMLElement', '$a->b' => 'SimpleXMLElement|null', '$b' => 'SimpleXMLElement|null', ], ], 'allowLessSpecificReturnTypeForOverriddenMethod' => [ 'code' => ' [ 'code' => ' [ 'code' => 'bar(); echo self::$instance->bat; } } public function bar() : void {} } if (A::$instance) { A::$instance->bar(); echo A::$instance->bat; }', ], 'nonStaticPropertyMethodCall' => [ 'code' => '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' => [ 'code' => 'instance) { $this->instance->bar(); echo $this->instance->bat; } } public function bar() : void {} }', ], 'classStringPropertyType' => [ 'code' => ' */ public $member = [ InvalidArgumentException::class => 1, ]; }', ], 'allowPrivatePropertySetAfterInstanceof' => [ 'code' => 'foo = "hello"; } } class B extends A {}', ], 'noCrashForAbstractConstructorWithInstanceofInterface' => [ 'code' => 'a = $this->bar(); } else { $this->a = 6; } } } interface I { public function bar() : int; }', ], 'SKIPPED-abstractConstructorWithInstanceofClass' => [ 'code' => 'a = $this->bar(); } else { $this->a = 6; } } } class B extends A { public function bar() : int { return 3; } }', 'assertions' => [], 'ignored_issues' => [], ], 'inheritDocPropertyTypes' => [ 'code' => 'a = "hello"; echo (new Y)->a; Y::$b = "bar"; echo Y::$b;', ], 'subclassPropertySetInParentConstructor' => [ 'code' => 'prop = $s; } } class Child extends Base { /** @var string */ protected $prop; }', ], 'callInParentContext' => [ 'code' => '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' => [ 'code' => ' [ '$a' => 'Foo', ], ], 'noMixedErrorWhenAssignmentExpectsMixed' => [ 'code' => ' $bar */ public $bar = []; /** @param mixed $b */ public function foo($b) : void { $this->bar["a"] = $b; } }', ], 'propertySetInGrandparentExplicitly' => [ 'code' => 's = $s; } } class B extends A {} class C extends B { public function __construct(string $s) { A::__construct($s); } }', ], 'propertySetInGrandparentImplicitly' => [ 'code' => 's = $s; } } class B extends A {} class C extends B {}', ], 'unitializedPropertySuppressPropertyNotSetInConstructor' => [ 'code' => 'setFoo(); // public method that circumvents checks echo strlen($this->foo); } public function setFoo() : void { $this->foo = "foo"; } }', 'assertions' => [], 'ignored_issues' => ['PropertyNotSetInConstructor'], ], 'unitializedPropertySuppressPropertyNotSetInAbstractConstructor' => [ 'code' => ' [ 'code' => ' false, "to" => false, ]; /** * @psalm-param "from"|"to" $property */ public function ChangeThing(string $property) : void { $this->changed[$property] = true; } }', ], 'noRedundantConditionWhenCheckingInitializations' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => 'setA(); } private function setA() : void { $this->a = 5; } public static function getInstance(): self { return new static; } } class Concrete extends Base {}', ], 'preventCrashWhenCallingInternalMethodInPropertyInitialisationChecks' => [ 'code' => 'serializableTrace = $this->getTrace(); } } class Bar extends Foo {}', ], 'inferPropertyTypesForSimpleConstructors' => [ 'code' => 'foo = $foot; $this->bar = $bart; } public function getFoo() : int { return $this->foo; } public function getBar() : string { return $this->bar; } }', ], 'nullableDocblockTypedPropertyNoConstructor' => [ 'code' => ' [ 'code' => ' [ 'code' => 'foo; } }', ], 'dontAlterClosureParams' => [ 'code' => 'i = [ function (Exception $e): void {}, function (LogicException $e): void {}, ]; } }', ], 'inferSpreadParamType' => [ 'code' => 'tags = $tags; } }', ], 'staticPropertyDefaultWithStaticType' => [ 'code' => ' */ private static $t1 = []; /** @var array */ private $t2 = []; }', ], 'propagateIgnoreNullableOnPropertyFetch' => [ 'code' => 's !== null) {} echo $foo->s ?? "bar"; takesString($foo->s);', ], 'noMissingPropertyWhenArrayTypeProvided' => [ 'code' => 'bar = $bar; } public function bar(): void { echo $this->bar["key"]; } }', ], 'rememberThisPropertyAsssignmentsInMethod' => [ 'code' => 'foo = false; $this->maybeChange(); if ($this->foo) {} } public function maybeChange() : void { if (rand(0, 1)) { $this->foo = true; } } }', ], 'testRemoveClauseAfterReassignment' => [ 'code' => 'foo = false; $this->bar(); if ($this->foo === true) {} } private function bar(): void { if (mt_rand(0, 1)) { $this->foo = true; } } }', ], 'allowIssetOnTypedProperty' => [ 'code' => 'a = "hello"; } if (isset($this->a)) { echo $this->a; $this->a = "bello"; } $this->a = "bar"; } }', ], 'allowGoodArrayPushOnArrayValue' => [ 'code' => 'prop, 5); } }', ], 'someConditionalCallToParentConstructor' => [ 'code' => 'val = "hello"; if (rand(0, 1)) { parent::__construct(); } } } class ChildClass extends ParentClassDefinesVar { public function __construct() { parent::__construct(); } }', ], 'noConditionalCallToParentConstructor' => [ 'code' => 'val = "hello"; parent::__construct(); } } class ChildClass extends ParentClassDefinesVar { public function __construct() { parent::__construct(); } }', ], 'allowByReferenceAssignmentToUninitializedNullableProperty' => [ 'code' => 'foo($this->onCancel); } /** * @param mixed $onCancel * @param-out \Closure $onCancel */ public function foo(&$onCancel) : void { $onCancel = function (): void {}; } }', ], 'dontCarryAssertionsOver' => [ 'code' => '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' => [ 'code' => ' [ 'code' => 'foo = $bar; } public function baz(): Bar { return $this->foo; } }', ], 'aliasedFinalMethod' => [ 'code' => 'prop = $prop; } } class B { use A { setProp as setPropFinal; } public function __construct() { $this->setPropFinal(1); } }', ], 'aliasedAsFinalMethod' => [ 'code' => 'prop = $prop; } } class B { use A { setProp as final setPropFinal; } public function __construct() { $this->setPropFinal(1); } }', ], 'staticPropertyAssertion' => [ 'code' => ' [ 'code' => 'i; if ($o instanceof A) { echo strlen($o->i); } }', ], 'unionPropertyType' => [ 'code' => 'i = 5; $this->i = "hello"; } } $a = new A(); if ($a->i === 3) {} if ($a->i === "foo") {}', 'assertions' => [], 'ignored_issues' => [], 'php_version' => '8.0', ], 'setClassStringOfStatic' => [ 'code' => ' [ 'code' => 'foo;', ], 'promotedPublicPropertyWitoutDefault' => [ 'code' => 'foo;', ], 'promotedProtectedProperty' => [ 'code' => 'foo; } }', ], 'skipConstructor' => [ 'code' => 's = "hello"; } } class B extends A {} class C extends B { public function __construct() { parent::__construct(); echo $this->s; } }', ], 'getPropertyThatMayNotBeSet' => [ 'code' => 'bar)) { return "hello"; } return $this->bar; } public function getBarAgain() : string { /** @psalm-suppress RedundantPropertyInitializationCheck */ if (isset($this->bar)) { return $this->bar; } return "hello"; } }', ], 'memoizePropertyAfterSetting' => [ 'code' => 'b = "c"; echo strlen($this->b); } }', ], 'noErrorForSplatArgs' => [ 'code' => 'b = $bb; } } class Bar extends Foo {}', ], 'noUndefinedPropertyIssueAfterSuppressingOnInterface' => [ 'code' => 'foo; }', ], 'noRedundantCastWhenCheckingProperties' => [ 'code' => 'map = []; $this->map["test"] = "test"; $this->useMap(); } public function useMap(): void { $keys = array_keys($this->map); $key = reset($keys); echo (string) $key; } }', ], 'ignoreUndefinedMethodOnUnion' => [ 'code' => 'name; if ($name === null) {} return $name; }', 'assertions' => [], 'ignored_issues' => [], 'php_version' => '8.0', ], 'dynamicPropertyFetch' => [ 'code' => '{$b} ?? null; }', ], 'nullCoalesceWithNullablePropertyAccess' => [ 'code' => 'a ?? "default"; }', 'assertions' => [], 'ignored_issues' => [], 'php_version' => '8.0', ], 'possiblyNullOnFunctionCallCoalesced' => [ 'code' => '{$b} ?? null; }', ], 'dontMemoizeConditionalAssignment' => [ 'code' => 'a) { $this->mayBeSetA(); } if ($this->a instanceof A) { } } protected function mayBeSetA(): void { if (mt_rand(0, 1)) { $this->a = new A(); } } }', ], 'allowDefaultForTemplatedProperty' => [ 'code' => ' */ public $foo = []; } /** * @extends A */ class AChild extends A { public $foo = ["hello"]; }', ], 'allowBuiltinPropertyDocblock' => [ 'code' => ' [ 'code' => 'isAutoCommitEnabled = true; } public function disableAutoCommit(): void { $this->isAutoCommitEnabled = false; } public function isAutoCommitEnabled(): bool { return $this->isAutoCommitEnabled; } } $mode = new ExecutionMode(); $mode->disableAutoCommit(); assert($mode->isAutoCommitEnabled() === false); $mode->enableAutoCommit(); assert($mode->isAutoCommitEnabled() === true);', ], 'promotedInheritedPropertyWithDocblock' => [ 'code' => ' [ 'code' => 'nullableSelf?->self->self; } }', 'assertions' => [], 'ignored_issues' => [], 'php_version' => '8.0', ], 'impossibleIntersection' => [ 'code' => ' [], 'ignored_issues' => [], 'php_version' => '8.1', ], 'intersectionPropertyAccess' => [ 'code' => 'test1; $test2 = $r->test2;', 'assertions' => [ '$test1===' => 'int', '$test2===' => "'lmao'", ], ], ]; } public function providerInvalidCodeParse(): iterable { return [ 'undefinedPropertyAssignment' => [ 'code' => 'foo = "cool";', 'error_message' => 'UndefinedPropertyAssignment', ], 'undefinedPropertyFetch' => [ 'code' => 'foo;', 'error_message' => 'UndefinedPropertyFetch', ], 'undefinedThisPropertyAssignment' => [ 'code' => 'foo = "cool"; } }', 'error_message' => 'UndefinedThisPropertyAssignment', ], 'undefinedStaticPropertyAssignment' => [ 'code' => ' 'UndefinedPropertyAssignment', ], 'undefinedThisPropertyFetch' => [ 'code' => 'foo; } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'missingPropertyType' => [ 'code' => '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' => [ 'code' => 'foo = 5; } }', 'error_message' => 'MissingPropertyType - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:32 - Property A::$foo does not have a ' . 'declared type - consider int', ], 'missingPropertyTypeWithConstructorInitAndNull' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => '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' => [ 'code' => 'foo = 5; } }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'badStaticAssignment' => [ 'code' => ' 'InvalidPropertyAssignmentValue', ], 'typeCoercion' => [ 'code' => 'foo = $a; } } class B extends A {}', 'error_message' => 'PropertyTypeCoercion', ], 'mixedTypeCoercion' => [ 'code' => ' */ public $foo = []; /** @param A[] $arr */ public function barBar(array $arr): void { $this->foo = $arr; } }', 'error_message' => 'MixedPropertyTypeCoercion', ], 'staticTypeCoercion' => [ 'code' => ' 'PropertyTypeCoercion', ], 'staticMixedTypeCoercion' => [ 'code' => ' */ public static $foo = []; /** @param A[] $arr */ public static function barBar(array $arr): void { self::$foo = $arr; } }', 'error_message' => 'MixedPropertyTypeCoercion', ], 'possiblyBadAssignment' => [ 'code' => 'foo = rand(0, 1) ? 5 : "hello"; } }', 'error_message' => 'PossiblyInvalidPropertyAssignmentValue', ], 'possiblyBadStaticAssignment' => [ 'code' => ' 'PossiblyInvalidPropertyAssignmentValue', ], 'badAssignmentAsWell' => [ 'code' => 'foo = "bar";', 'error_message' => 'InvalidPropertyAssignment', ], 'badFetch' => [ 'code' => 'foo;', 'error_message' => 'InvalidPropertyFetch', ], 'possiblyBadFetch' => [ 'code' => ' 3 ? "hello" : new stdClass; echo $a->foo;', 'error_message' => 'PossiblyInvalidPropertyFetch', ], 'mixedPropertyFetch' => [ 'code' => 'foo;', 'error_message' => 'MixedPropertyFetch', 'ignored_issues' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'mixedPropertyAssignment' => [ 'code' => 'foo = "hello";', 'error_message' => 'MixedPropertyAssignment', 'ignored_issues' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'possiblyNullablePropertyAssignment' => [ 'code' => 'foo = "hello";', 'error_message' => 'PossiblyNullPropertyAssignment', ], 'nullablePropertyAssignment' => [ 'code' => 'foo = "hello";', 'error_message' => 'NullPropertyAssignment', ], 'possiblyNullablePropertyFetch' => [ 'code' => 'foo;', 'error_message' => 'PossiblyNullPropertyFetch', ], 'nullablePropertyFetch' => [ 'code' => 'foo;', 'error_message' => 'NullPropertyFetch', ], 'badArrayProperty' => [ 'code' => ' */ public $bb; } $c = new C; $c->bb = [new A, new B];', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'possiblyBadArrayProperty' => [ 'code' => 'bb = ["hello", "world"];', 'error_message' => 'PossiblyInvalidPropertyAssignmentValue', ], 'notSetInEmptyConstructor' => [ 'code' => ' 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'noConstructor' => [ 'code' => ' 'MissingConstructor', ], 'abstractClassInheritsNoConstructor' => [ 'code' => ' 'MissingConstructor', ], 'abstractClassInheritsPrivateConstructor' => [ 'code' => 'foo = "hello"; } } class B extends A {} $b = new B();', 'error_message' => 'InaccessibleMethod - src' . DIRECTORY_SEPARATOR . 'somefile.php:13', ], 'classInheritsPrivateConstructorWithImplementedConstructor' => [ 'code' => 'foo = "hello"; } } class B extends A { public function __construct() {} }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:11', ], 'notSetInAllBranchesOfIf' => [ 'code' => 'a = 5; } } }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'propertySetInProtectedMethod' => [ 'code' => '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' => [ 'code' => ' 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:6', ], 'propertySetInPrivateMethodWithIf' => [ 'code' => 'foo(); } } private function foo(): void { $this->a = 5; } }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'privatePropertySameNameNotSetInConstructor' => [ 'code' => 'b = "foo"; } } class B extends A { /** @var string */ private $b; }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:13', ], 'privateMethodCalledInParentConstructor' => [ 'code' => 'publicMethod(); } public function publicMethod() : void { $this->privateMethod(); } private function privateMethod() : void {} }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:2', ], 'privatePropertySetInParentConstructorReversedOrder' => [ 'code' => 'b = "foo"; } } }', 'error_message' => 'InaccessibleProperty', ], 'privatePropertySetInParentConstructor' => [ 'code' => 'b = "foo"; } } } class B extends A { /** @var string */ private $b; } ', 'error_message' => 'InaccessibleProperty', ], 'undefinedPropertyClass' => [ 'code' => ' 'UndefinedDocblockClass', ], 'abstractClassWithNoConstructorButChild' => [ 'code' => ' 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:7', ], 'badAssignmentToUndefinedVars' => [ 'code' => '$y = 4;', 'error_message' => 'UndefinedGlobalVariable', ], 'echoUndefinedPropertyFetch' => [ 'code' => '$y;', 'error_message' => 'UndefinedGlobalVariable', ], 'toStringPropertyAssignment' => [ 'code' => 'foo = new B;', 'error_message' => 'ImplicitToStringCast', ], 'noInfiniteLoop' => [ 'code' => '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' => [ 'code' => ' 'InvalidPropertyAssignmentValue', ], 'prohibitMixedAssignmentNormally' => [ 'code' => 'mixed = $value; } }', 'error_message' => 'MixedAssignment', ], 'assertPropertyTypeHasImpossibleType' => [ 'code' => 'foo)) {}', 'error_message' => 'DocblockTypeContradiction', ], 'impossiblePropertyCheck' => [ 'code' => 'bar = new Bar(); } public function getBar(): void { if (!$this->bar) {} } }', 'error_message' => 'DocblockTypeContradiction', ], 'staticPropertyOfStaticTypeMethodCallWithUndefinedMethod' => [ 'code' => 'instance) { $this->instance->bar(); } } } class B extends A { public function bar() : void {} }', 'error_message' => 'UndefinedMethod', ], 'misnamedPropertyByVariable' => [ 'code' => '$var_name; } return null; } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'inheritDocPropertyTypesIncorrectAssignmentToInstanceProperty' => [ 'code' => 'a = 5; echo (new Y)->a; Y::$b = "bar"; echo Y::$b;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'inheritDocPropertyTypesIncorrectAssignmentToStaticProperty' => [ 'code' => ' 'InvalidPropertyAssignmentValue', ], 'unitializedProperty' => [ 'code' => 'foo); $this->foo = "foo"; } }', 'error_message' => 'UninitializedProperty', ], 'unitializedPropertyWithoutType' => [ 'code' => 'foo); $this->foo = "foo"; } }', 'error_message' => 'UninitializedProperty', 'ignored_issues' => ['MixedArgument', 'MissingPropertyType'], ], 'unitializedObjectProperty' => [ 'code' => 'foo->bar); $this->foo = $foo; } }', 'error_message' => 'UninitializedProperty', ], 'possiblyNullArg' => [ 'code' => 'foo); $this->foo = "foo"; } }', 'error_message' => 'PossiblyNullArgument', ], 'noCrashOnMagicCall' => [ 'code' => 'setA(); } public function __call(string $var, array $args) {} }', 'error_message' => 'PropertyNotSetInConstructor - src' . DIRECTORY_SEPARATOR . 'somefile.php:4', ], 'reportGoodLocationForPropertyError' => [ 'code' => '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' => [ 'code' => 'foo); } }', 'error_message' => 'PropertyNotSetInConstructor', ], 'nullableTypedPropertyNoConstructor' => [ 'code' => ' 'MissingConstructor', ], 'nullableTypedPropertyEmptyConstructor' => [ 'code' => ' 'PropertyNotSetInConstructor', ], 'nullableTypedPropertyUseBeforeInitialised' => [ 'code' => 'foo; } }', 'error_message' => 'UninitializedProperty', ], 'nullableTypedPropertyNoConstructorWithDocblock' => [ 'code' => ' 'MissingConstructor', ], 'nullableTypedPropertyEmptyConstructorWithDocblock' => [ 'code' => ' 'PropertyNotSetInConstructor', ], 'nullableTypedPropertyUseBeforeInitialisedWithDocblock' => [ 'code' => 'foo; } }', 'error_message' => 'UninitializedProperty', ], 'badStaticPropertyDefault' => [ 'code' => ' */ public static $test = ["string-key" => 1]; }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'addNullToMixedAfterNullablePropertyFetch' => [ 'code' => 'foo); }', 'error_message' => 'PossiblyNullArgument', ], 'catchBadArrayStaticProperty' => [ 'code' => ' */ public array $map = []; /** * @param string $class */ public function get(string $class) : void { $this->map[$class] = 5; } }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'preventArrayPushOnArrayValue' => [ 'code' => 'prop, "bad"); } }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'overriddenConstructorCalledMethod' => [ 'code' => 'init(); } public function init(): void { $this->prop = "zxc"; } } class ChildClass extends ParentClass { public function init(): void {} }', 'error_message' => 'PropertyNotSetInConstructor', ], 'propertyWithSameNameUndefined' => [ 'code' => 'id; } }', 'error_message' => 'UndefinedPropertyFetch', ], 'missingPropertyTypeWithDocblock' => [ 'code' => ' 'MissingPropertyType', ], 'promotedPrivateProperty' => [ 'code' => 'foo;', 'error_message' => 'InaccessibleProperty', ], 'overwritePropertyType' => [ 'code' => ' 'MismatchingDocblockPropertyType', ], 'possiblyNullOnFunctionCallNotCoalesced' => [ 'code' => 'id; } class C { public int $id = 1; }', 'error_message' => 'PossiblyNullPropertyFetch', ], 'noCrashWhenCallingMagicSet' => [ 'code' => '__set("foo");', 'error_message' => 'TooFewArguments', ], 'noCrashWhenCallingMagicGet' => [ 'code' => '__get();', 'error_message' => 'TooFewArguments', ], 'staticReadOfNonStaticProperty' => [ 'code' => ' 'UndefinedPropertyFetch', ], 'staticWriteToNonStaticProperty' => [ 'code' => ' 'UndefinedPropertyAssignment', ], 'nonStaticReadOfStaticProperty' => [ 'code' => 'prop; ', 'error_message' => 'UndefinedPropertyFetch', ], 'nonStaticWriteToStaticProperty' => [ 'code' => 'prop = 42; ', 'error_message' => 'UndefinedPropertyAssignment', ], 'setPropertiesOfSimpleXMLElement1' => [ 'code' => '"); $a->b = "c"; ', 'error_message' => 'UndefinedPropertyAssignment', ], 'setPropertiesOfSimpleXMLElement2' => [ 'code' => '"); if (isset($a->b)) { $a->b = "c"; } ', 'error_message' => 'UndefinedPropertyAssignment', ], ]; } }