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:
parent
754a4d9950
commit
df890fbfb0
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 = [];
|
||||
}
|
||||
|
@ -120,6 +120,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $properties = [];
|
||||
|
||||
/**
|
||||
* @var array<string, Type\Union>
|
||||
*/
|
||||
public $pseudo_instance_properties = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user