use_phpdoc_property_without_magic_or_parent = true; $this->addFile( 'somefile.php', 'hello;' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return iterable,error_levels?:string[]}> */ public function providerValidCodeParse(): iterable { return [ 'propertyDocblock' => [ 'foo = "hello";', ], 'propertyOfTypeClassDocblock' => [ 'foo = new PropertyType();', ], 'propertySealedDocblockDefinedPropertyFetch' => [ 'foo;', ], /** * With a magic setter and no annotations specifying properties or types, we can * set anything we want on any variable name. The magic setter is trusted to figure * it out. */ 'magicSetterUndefinedPropertyNoAnnotation' => [ '__set("foo", new stdClass()); } }', ], /** * With a magic getter and no annotations specifying properties or types, we can * get anything we want with any variable name. The magic getter is trusted to figure * it out. */ 'magicGetterUndefinedPropertyNoAnnotation' => [ '__get("foo"); } }', ], /** * The property $foo is defined as a string with the `@property` annotation. We * use the magic setter to set it to a string, so everything is cool. */ 'magicSetterValidAssignmentType' => [ '__set("foo", "value"); } }', ], 'propertyDocblockAssignmentToMixed' => [ '__set("foo", $b); }', 'assertions' => [], 'error_level' => ['MixedAssignment', 'MixedPropertyTypeCoercion'], ], 'namedPropertyByVariable' => [ '$var_name; } return null; } }', ], 'getPropertyExplicitCall' => [ '__get("test"); } }', ], 'inheritedGetPropertyExplicitCall' => [ '__get("test"); } }', ], 'undefinedThisPropertyFetchWithMagic' => [ 'name; } public function goodGet2(): void { echo $this->otherName; } } $a = new A(); echo $a->name; echo $a->otherName;', ], 'psalmUndefinedThisPropertyFetchWithMagic' => [ 'name; } public function goodGet2(): void { echo $this->otherName; } } $a = new A(); echo $a->name; echo $a->otherName;', ], 'directFetchForMagicProperty' => [ 'test; } }', ], 'magicPropertyFetchOnProtected' => [ 'foo = "bar"; echo $c->foo;', 'assertions' => [], 'error_level' => ['MixedArgument'], ], 'dontAssumeNonNullAfterPossibleMagicFetch' => [ 'foo; if ($c) {} }', 'assertions' => [], 'error_level' => ['PossiblyNullPropertyFetch'], ], 'accessInMagicGet' => [ 'other; case "other": return "foo"; } return "default"; } }', 'assertions' => [], 'error_level' => ['MixedReturnStatement', 'MixedInferredReturnType'], ], 'overrideInheritedProperty' => [ 'service = $service; } } /** @property ConcreteService $service */ class FooBar extends Foo { public function __construct(ConcreteService $concreteService) { parent::__construct($concreteService); } public function doSomething(): void { $this->service->getById(123); } }', ], 'magicInterfacePropertyRead' => [ 'foo; }', ], 'phanMagicInterfacePropertyRead' => [ 'foo; }', ], 'magicInterfacePropertyWrite' => [ 'foo = "hello"; }', ], 'psalmMagicInterfacePropertyWrite' => [ 'foo = "hello"; }', ], 'psalmPropertyDocblock' => [ 'foo = "hello";', ], 'overridePropertyAnnotations' => [ 'foo = "hello";', ], 'overrideWithReadWritePropertyAnnotations' => [ 'foo = []; $a = new A(); $a->takesString($a->foo);', ], 'removeAssertionsAfterCall' => [ 'a)) { $this->a = ["something"]; /** * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArgument */ echo $this->a[0]; } } }' ], 'magicPropertyDefinedOnTrait' => [ 'props[$field]; } /** * @param mixed $value */ public function __set(string $field, $value) : void { $this->props[$field] = $value; } } /** * @property mixed $email * @property mixed $password * @property mixed $last_login_at */ trait UserFields {} $record = new UserRecord(); $record->email; $record->password; $record->last_login_at = new DateTimeImmutable("now");' ], 'reconcileMagicProperties' => [ 'props["a"] = "hello"; $this->props["b"] = "goodbye"; } /** * @psalm-mutation-free */ public function __get(string $prop){ return $this->props[$prop] ?? null; } /** @param mixed $b */ public function __set(string $a, $b){ $this->props[$a] = $b; } public function bar(): string { if (is_null($this->a) || is_null($this->b)) { } else { return $this->b; } return "hello"; } }' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse(): iterable { return [ 'annotationWithoutGetter' => [ 'is_protected; } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'propertyDocblockInvalidAssignment' => [ 'foo = 5;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertyInvalidClassAssignment' => [ 'foo = new SomeOtherPropertyType();', 'error_message' => 'InvalidPropertyAssignmentValue - src' . DIRECTORY_SEPARATOR . 'somefile.php:29:31 - $a->foo with declared type' . ' \'Bar\PropertyType\' cannot', ], 'propertyWriteDocblockInvalidAssignment' => [ 'foo = 5;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'psalmPropertyWriteDocblockInvalidAssignment' => [ 'foo = 5;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertySealedDocblockUndefinedPropertyAssignment' => [ 'bar = 5;', 'error_message' => 'UndefinedMagicPropertyAssignment', ], 'propertySealedDocblockDefinedPropertyAssignment' => [ 'foo = 5;', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertyReadInvalidFetch' => [ 'foo);', 'error_message' => 'InvalidArgument', ], 'psalmPropertyReadInvalidFetch' => [ 'foo);', 'error_message' => 'InvalidArgument', ], 'propertySealedDocblockUndefinedPropertyFetch' => [ 'bar;', 'error_message' => 'UndefinedMagicPropertyFetch', ], /** * The property $foo is not defined on the object, but accessed with the magic setter. * This is an error because `@psalm-seal-properties` is specified on the class block. */ 'magicSetterUndefinedProperty' => [ '__set("foo", "value"); } }', 'error_message' => 'UndefinedThisPropertyAssignment', ], /** * The property $foo is not defined on the object, but accessed with the magic getter. * This is an error because `@psalm-seal-properties` is specified on the class block. */ 'magicGetterUndefinedProperty' => [ '__get("foo"); } }', 'error_message' => 'UndefinedThisPropertyFetch', ], /** * The property $foo is defined as a string with the `@property` annotation, but * the magic setter is used to set it to an object. */ 'magicSetterInvalidAssignmentType' => [ '__set("foo", new stdClass()); } }', 'error_message' => 'InvalidPropertyAssignmentValue', ], 'propertyDocblockAssignmentToMixed' => [ '__set("foo", $b); }', 'error_message' => 'MixedPropertyTypeCoercion', 'error_levels' => ['MixedAssignment'], ], 'magicInterfacePropertyWrongProperty' => [ 'bar; }', 'error_message' => 'UndefinedMagicPropertyFetch', ], 'psalmMagicInterfacePropertyWrongProperty' => [ 'bar; }', 'error_message' => 'UndefinedMagicPropertyFetch', ], 'magicInterfaceWrongPropertyWrite' => [ 'bar = "hello"; }', 'error_message' => 'UndefinedMagicPropertyAssignment', ], 'psalmMagicInterfaceWrongPropertyWrite' => [ 'bar = "hello"; }', 'error_message' => 'UndefinedMagicPropertyAssignment', ], ]; } }