Skip to content
Permalink
Browse files

Add support for @immutable

Fixes #1272
  • Loading branch information...
muglug committed Aug 30, 2019
1 parent 9bd7f21 commit b7b4baff8fee3277239f40413d3e9d8006c3a273
@@ -308,6 +308,16 @@ class Context
*/
public $pure = false;
/**
* @var bool
*/
public $mutation_free = false;
/**
* @var bool
*/
public $external_mutation_free = false;
/**
* @param string|null $self
*/
@@ -151,6 +151,7 @@ public static function parse($docblock, $line_number = null, $preserve_format =
'generator-return', 'ignore-falsable-return', 'variadic', 'pure',
'ignore-variable-method', 'ignore-variable-property', 'internal',
'taint-sink', 'taint-source', 'assert-untainted', 'scope-this',
'mutation-free', 'external-mutation-free',
],
true
)) {
@@ -273,6 +274,7 @@ public static function parsePreservingLength(\PhpParser\Comment\Doc $docblock)
'generator-return', 'ignore-falsable-return', 'variadic', 'pure',
'ignore-variable-method', 'ignore-variable-property', 'internal',
'taint-sink', 'taint-source', 'assert-untainted', 'scope-this',
'mutation-free', 'external-mutation-free',
],
true
)) {
@@ -855,6 +855,16 @@ public static function extractClassLikeDocblockInfo(
$info->sealed_methods = true;
}
if (isset($parsed_docblock['specials']['immutable'])
|| isset($parsed_docblock['specials']['psalm-mutation-free'])
) {
$info->mutation_free = true;
}
if (isset($parsed_docblock['specials']['psalm-external-mutation-free'])) {
$info->external_mutation_free = true;
}
if (isset($parsed_docblock['specials']['psalm-override-property-visibility'])) {
$info->override_property_visibility = true;
}
@@ -413,6 +413,14 @@ function (FunctionLikeParameter $p) {
$context->pure = true;
}
if ($storage->mutation_free && $cased_method_id && !strpos($cased_method_id, '__construct')) {
$context->mutation_free = true;
}
if ($storage instanceof MethodStorage && $storage->external_mutation_free) {
$context->external_mutation_free = true;
}
if ($storage->unused_docblock_params) {
foreach ($storage->unused_docblock_params as $param_name => $param_location) {
if (IssueBuffer::accepts(
@@ -568,10 +568,10 @@ public static function analyze(
$assign_value_type
);
} elseif ($assign_var instanceof PhpParser\Node\Expr\PropertyFetch) {
if ($context->pure) {
if ($context->mutation_free) {
if (IssueBuffer::accepts(
new ImpurePropertyAssignment(
'Cannot assign to a property from a pure context',
'Cannot assign to a property from a mutation-free context',
new CodeLocation($statements_analyzer, $assign_var)
),
$statements_analyzer->getSuppressedIssues()
@@ -619,7 +619,7 @@ public static function analyze(
);
}
if ($context->pure || $codebase->find_unused_variables) {
if ($context->mutation_free || $codebase->find_unused_variables) {
$callmap_function_pure = $function_id && $in_call_map
? $codebase->functions->isCallMapFunctionPure($codebase, $function_id, $stmt->args)
: null;
@@ -628,10 +628,10 @@ public static function analyze(
&& !$function_storage->pure)
|| ($callmap_function_pure === false)
) {
if ($context->pure) {
if ($context->mutation_free) {
if (IssueBuffer::accepts(
new ImpureFunctionCall(
'Cannot call an impure function from a pure context',
'Cannot call an impure function from a mutation-free context',
new CodeLocation($statements_analyzer, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
@@ -1207,6 +1207,16 @@ function (PhpParser\Node\Arg $arg) {
)) {
// fall through
}
} elseif ($context->mutation_free && !$method_storage->mutation_free) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an possibly-mutating method from a mutation-free context',
new CodeLocation($source, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
if ($method_storage->assertions) {
@@ -890,6 +890,16 @@ function (PhpParser\Node\Arg $arg) {
)) {
// fall through
}
} elseif ($context->mutation_free && !$method_storage->mutation_free) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an possibly-mutating method from a mutation-free context',
new CodeLocation($source, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
if ($method_storage->assertions) {
@@ -357,10 +357,10 @@ public static function analyze(
$var_id = self::getArrayVarId($stmt->var, null);
if ($var_id && $context->pure && strpos($var_id, '->')) {
if ($var_id && $context->mutation_free && strpos($var_id, '->')) {
if (IssueBuffer::accepts(
new ImpurePropertyAssignment(
'Cannot assign to a property from a pure context',
'Cannot assign to a property from a mutation-free context',
new CodeLocation($statements_analyzer, $stmt->var)
),
$statements_analyzer->getSuppressedIssues()
@@ -260,6 +260,19 @@ private function populateClassLikeStorage(ClassLikeStorage $storage, array $depe
}
}
if ($storage->mutation_free || $storage->external_mutation_free) {
foreach ($storage->methods as $method) {
$method->mutation_free = $storage->mutation_free;
$method->external_mutation_free = $storage->external_mutation_free;
}
if ($storage->mutation_free) {
foreach ($storage->properties as $property) {
$property->readonly = true;
}
}
}
if ($storage->internal
&& !$storage->is_interface
&& !$storage->is_trait
@@ -72,6 +72,16 @@ class ClassLikeDocblockComment
*/
public $override_method_visibility = false;
/**
* @var bool
*/
public $mutation_free = false;
/**
* @var bool
*/
public $external_mutation_free = false;
/**
* @var array<int, string>
*/
@@ -144,4 +144,14 @@ class FunctionDocblockComment
* @var bool
*/
public $inheritdoc = false;
/**
* @var bool
*/
public $mutation_free = false;
/**
* @var bool
*/
public $external_mutation_free = false;
}
@@ -1173,6 +1173,9 @@ private function registerClassLike(PhpParser\Node\Stmt\ClassLike $node)
$storage->sealed_properties = $docblock_info->sealed_properties;
$storage->sealed_methods = $docblock_info->sealed_methods;
$storage->mutation_free = $docblock_info->mutation_free;
$storage->external_mutation_free = $docblock_info->external_mutation_free;
$storage->override_property_visibility = $docblock_info->override_property_visibility;
$storage->override_method_visibility = $docblock_info->override_method_visibility;
@@ -1958,6 +1961,18 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m
return $storage;
}
if ($docblock_info->mutation_free) {
$storage->mutation_free = true;
if ($storage instanceof MethodStorage) {
$storage->external_mutation_free = true;
}
}
if ($storage instanceof MethodStorage && $docblock_info->external_mutation_free) {
$storage->external_mutation_free = true;
}
if ($docblock_info->deprecated) {
$storage->deprecated = true;
}
@@ -1976,6 +1991,10 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m
if ($docblock_info->pure) {
$storage->pure = true;
$storage->mutation_free = true;
if ($storage instanceof MethodStorage) {
$storage->external_mutation_free = true;
}
}
if ($docblock_info->ignore_nullable_return && $storage->return_type) {
@@ -216,6 +216,16 @@ class ClassLikeStorage
*/
public $is_interface = false;
/**
* @var bool
*/
public $external_mutation_free = false;
/**
* @var bool
*/
public $mutation_free = false;
/**
* @var array<string, MethodStorage>
*/
@@ -161,6 +161,11 @@ class FunctionLikeStorage
*/
public $has_yield = false;
/**
* @var bool
*/
public $mutation_free = false;
/**
* @var string|null
*/
@@ -52,4 +52,9 @@ class MethodStorage extends FunctionLikeStorage
* @var bool
*/
public $has_docblock_param_types = false;
/**
* @var bool
*/
public $external_mutation_free = false;
}

0 comments on commit b7b4baf

Please sign in to comment.
You can’t perform that action at this time.