Skip to content

Commit

Permalink
Fix vimeo#302 - add a way to seal objects with magic properties
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Nov 17, 2017
1 parent 440db3b commit a083069
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/Psalm/Checker/CommentChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ public static function extractClassLikeDocblockInfo($comment, $line_number)
$info->deprecated = true;
}

if (isset($comments['specials']['psalm-seal-properties'])) {
$info->sealed_properties = true;
}

if (isset($comments['specials']['psalm-suppress'])) {
/** @var string $suppress_entry */
foreach ($comments['specials']['psalm-suppress'] as $suppress_entry) {
Expand Down
13 changes: 10 additions & 3 deletions src/Psalm/Checker/Statements/Expression/AssignmentChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -661,9 +661,9 @@ public static function analyzePropertyAssignment(
if ($lhs_var_id !== '$this' &&
MethodChecker::methodExists($project_checker, $lhs_type_part . '::__set')
) {
if ($var_id) {
$class_storage = $project_checker->classlike_storage_provider->get((string)$lhs_type_part);
$class_storage = $project_checker->classlike_storage_provider->get((string)$lhs_type_part);

if ($var_id) {
if (isset($class_storage->pseudo_property_set_types['$' . $prop_name])) {
$class_property_types[] =
clone $class_storage->pseudo_property_set_types['$' . $prop_name];
Expand All @@ -674,7 +674,14 @@ public static function analyzePropertyAssignment(

$context->vars_in_scope[$var_id] = Type::getMixed();
}
continue;

/*
* If we have an explicit list of all allowed magic properties on the class, and we're
* not in that list, fall through
*/
if (!$var_id || !$class_storage->sealed_properties) {
continue;
}
}

$has_regular_setter = true;
Expand Down
8 changes: 7 additions & 1 deletion src/Psalm/Checker/Statements/Expression/FetchChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,13 @@ public static function analyzePropertyFetch(
$stmt->inferredType = Type::getMixed();
}

continue;
/*
* If we have an explicit list of all allowed magic properties on the class, and we're
* not in that list, fall through
*/
if (!$class_storage->sealed_properties) {
continue;
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/Psalm/ClassLikeDocblockComment.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class ClassLikeDocblockComment
*/
public $properties = [];

/**
* @var bool
*/
public $sealed_properties = false;

/**
* @var array<int, string>
*/
Expand Down
5 changes: 5 additions & 0 deletions src/Psalm/Storage/ClassLikeStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ class ClassLikeStorage
*/
public $deprecated = false;

/**
* @var bool
*/
public $sealed_properties = false;

/**
* @var array<int, string>
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Psalm/Visitor/DependencyFinderVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ public function enterNode(PhpParser\Node $node)

$storage->deprecated = $docblock_info->deprecated;

$storage->sealed_properties = $docblock_info->sealed_properties;

$storage->suppressed_issues = $docblock_info->suppressed_issues;
}
}
Expand Down
47 changes: 46 additions & 1 deletion tests/AnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ public function __set($name, $value) : void {
}
$a = new A();
$a->foo = "hello";',
$a->foo = "hello";
$a->bar = "hello"; // not a property',
],
'ignoreNullableReturn' => [
'<?php
Expand Down Expand Up @@ -421,6 +422,50 @@ public function __set(string $name, $value) : void {
$a->foo = 5;',
'error_message' => 'InvalidPropertyAssignment',
],
'propertySealedDocblockUndefinedPropertyAssignment' => [
'<?php
/**
* @property string $foo
* @psalm-seal-properties
*/
class A {
public function __get(string $name) : ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value) : void {
}
}
$a = new A();
$a->bar = 5;',
'error_message' => 'UndefinedPropertyAssignment',
],
'propertySealedDocblockUndefinedPropertyFetch' => [
'<?php
/**
* @property string $foo
* @psalm-seal-properties
*/
class A {
public function __get(string $name) : ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value) : void {
}
}
$a = new A();
echo $a->bar;',
'error_message' => 'UndefinedPropertyFetch',
],
'noStringParamType' => [
'<?php
function fooFoo($a) : void {
Expand Down

0 comments on commit a083069

Please sign in to comment.