1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #155 - support @property declaration for classes with magic getters & setters

This commit is contained in:
Matt Brown 2017-05-04 18:35:05 -04:00
parent 754a4d9950
commit df890fbfb0
7 changed files with 125 additions and 6 deletions

View File

@ -335,6 +335,19 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
}
}
}
if ($docblock_info->properties) {
foreach ($docblock_info->properties as $property) {
$pseudo_property_type = Type::parseString(
FunctionLikeChecker::fixUpLocalType(
$property['type'],
$this
)
);
$storage->pseudo_instance_properties[$property['name']] = $pseudo_property_type;
}
}
}
}

View File

@ -261,6 +261,48 @@ class CommentChecker
}
}
if (isset($comments['specials']['deprecated'])) {
$info->deprecated = true;
}
if (isset($comments['specials']['property'])) {
/** @var string $property */
foreach ($comments['specials']['property'] as $line_number => $property) {
try {
$line_parts = self::splitDocLine($property);
} catch (DocblockParseException $e) {
throw $e;
}
if (count($line_parts) === 1 && $line_parts[0][0] === '$') {
array_unshift($line_parts, 'mixed');
}
if (count($line_parts) > 1) {
if (preg_match('/^' . self::TYPE_REGEX . '$/', $line_parts[0])
&& !preg_match('/\[[^\]]+\]/', $line_parts[0])
&& preg_match('/^(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1])
&& !strpos($line_parts[0], '::')
&& $line_parts[0][0] !== '{'
) {
if ($line_parts[1][0] === '&') {
$line_parts[1] = substr($line_parts[1], 1);
}
$line_parts[1] = preg_replace('/,$/', '', $line_parts[1]);
$info->properties[] = [
'name' => $line_parts[1],
'type' => $line_parts[0],
'line_number' => $line_number
];
}
} else {
throw new DocblockParseException('Badly-formatted @param');
}
}
}
return $info;
}

View File

@ -608,6 +608,14 @@ class AssignmentChecker
if ($lhs_var_id !== '$this' && MethodChecker::methodExists($lhs_type_part . '::__set', $file_checker)) {
if ($var_id) {
$class_storage = ClassLikeChecker::$storage[strtolower((string)$lhs_type_part)];
if (isset($class_storage->pseudo_instance_properties['$' . $prop_name])) {
$class_property_types[] = clone $class_storage->pseudo_instance_properties['$' . $prop_name];
$has_regular_setter = true;
continue;
}
$context->vars_in_scope[$var_id] = Type::getMixed();
}
continue;

View File

@ -240,7 +240,14 @@ class FetchChecker
if ($stmt_var_id !== '$this' &&
MethodChecker::methodExists($lhs_type_part->value . '::__get', $file_checker)
) {
$stmt->inferredType = Type::getMixed();
$class_storage = ClassLikeChecker::$storage[strtolower((string)$lhs_type_part)];
if (isset($class_storage->pseudo_instance_properties['$' . $stmt->name])) {
$stmt->inferredType = clone $class_storage->pseudo_instance_properties['$' . $stmt->name];
} else {
$stmt->inferredType = Type::getMixed();
}
continue;
}

View File

@ -13,4 +13,9 @@ class ClassLikeDocblockComment
* @var array<int, array<int, string>>
*/
public $template_types = [];
/**
* @var array<int, array{name:string, type:string}>
*/
public $properties = [];
}

View File

@ -120,6 +120,11 @@ class ClassLikeStorage
*/
public $properties = [];
/**
* @var array<string, Type\Union>
*/
public $pseudo_instance_properties = [];
/**
* @var array<string, string>
*/

View File

@ -50,14 +50,14 @@ class AnnotationTest extends TestCase
function fooFoo() : string {
return "boop";
}
/**
* @return array<int, string>
*/
function foo2() : array {
return ["hello"];
}
/**
* @return array<int, string>
*/
@ -112,7 +112,7 @@ class AnnotationTest extends TestCase
'goodDocblockInNamespace' => [
'<?php
namespace Foo;
class A {
/**
* @param \Foo\A $a
@ -121,7 +121,26 @@ class AnnotationTest extends TestCase
public function g(A $a, $b) : void {
}
}'
]
],
'propertyDocblock' => [
'<?php
/**
* @property string $foo
*/
class A {
public function __get($name) : ?string {
if ($name === "foo") {
return "hello";
}
}
public function __set($name, $value) : void {
}
}
$a = new A();
$a->foo = "hello";'
],
];
}
@ -190,7 +209,27 @@ class AnnotationTest extends TestCase
function fooFoo() : void {
}',
'error_message' => 'InvalidDocblock'
]
],
'propertyDocblockInvalidAssignment' => [
'<?php
/**
* @property string $foo
*/
class A {
public function __get($name) : ?string {
if ($name === "foo") {
return "hello";
}
}
public function __set($name, $value) : void {
}
}
$a = new A();
$a->foo = 5;',
'error_message' => 'InvalidPropertyAssignment',
],
];
}
}