mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Allow ability to memoize empty method calls based on config
This commit is contained in:
parent
a0984cdaf1
commit
da6209276f
@ -36,6 +36,7 @@
|
||||
<xs:attribute name="allowCoercionFromStringToClassConst" type="xs:string" />
|
||||
<xs:attribute name="allowStringToStandInForClass" type="xs:string" />
|
||||
<xs:attribute name="usePhpDocMethodsWithoutMagicCall" type="xs:string" />
|
||||
<xs:attribute name="memoizeMethodCallResults" type="xs:string" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ProjectFilesType">
|
||||
|
@ -83,6 +83,27 @@ class AssertionFinder
|
||||
return $if_types;
|
||||
}
|
||||
|
||||
if ($conditional instanceof PhpParser\Node\Expr\MethodCall
|
||||
&& $conditional->name instanceof PhpParser\Node\Identifier
|
||||
&& !$conditional->args
|
||||
) {
|
||||
$config = \Psalm\Config::getInstance();
|
||||
|
||||
if ($config->memoize_method_calls) {
|
||||
$lhs_var_name = ExpressionChecker::getArrayVarId(
|
||||
$conditional->var,
|
||||
$this_class_name,
|
||||
$source
|
||||
);
|
||||
|
||||
if ($lhs_var_name) {
|
||||
$method_var_id = $lhs_var_name . '->' . strtolower($conditional->name->name) . '()';
|
||||
$if_types[$method_var_id] = '!falsy';
|
||||
return $if_types;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) {
|
||||
$if_types_to_negate = self::getAssertions(
|
||||
$conditional->expr,
|
||||
|
@ -582,6 +582,18 @@ class MethodCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
|
||||
$context->getPhantomClasses()
|
||||
);
|
||||
}
|
||||
|
||||
if (!$stmt->args && $var_id) {
|
||||
if ($config->memoize_method_calls) {
|
||||
$method_var_id = $var_id . '->' . $method_name_lc . '()';
|
||||
|
||||
if (isset($context->vars_in_scope[$method_var_id])) {
|
||||
$return_type_candidate = clone $context->vars_in_scope[$method_var_id];
|
||||
} else {
|
||||
$context->vars_in_scope[$method_var_id] = $return_type_candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$returns_by_ref =
|
||||
$returns_by_ref
|
||||
@ -614,8 +626,10 @@ class MethodCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
|
||||
$method_id,
|
||||
$appearing_method_id,
|
||||
$declaring_method_id,
|
||||
$var_id,
|
||||
$stmt->args,
|
||||
$code_location,
|
||||
$context,
|
||||
$file_manipulations,
|
||||
$return_type_candidate
|
||||
);
|
||||
|
@ -394,8 +394,10 @@ class StaticCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
|
||||
$method_id,
|
||||
$appearing_method_id,
|
||||
$declaring_method_id,
|
||||
null,
|
||||
$stmt->args,
|
||||
$code_location,
|
||||
$context,
|
||||
$file_manipulations,
|
||||
$return_type_candidate
|
||||
);
|
||||
|
@ -203,6 +203,11 @@ class Config
|
||||
*/
|
||||
public $use_phpdoc_methods_without_call = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $memoize_method_calls = false;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
@ -499,6 +504,11 @@ class Config
|
||||
$config->use_phpdoc_methods_without_call = $attribute_text === 'true' || $attribute_text === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml['memoizeMethodCallResults'])) {
|
||||
$attribute_text = (string) $config_xml['memoizeMethodCallResults'];
|
||||
$config->memoize_method_calls = $attribute_text === 'true' || $attribute_text === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml->projectFiles)) {
|
||||
$config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, $base_dir, true);
|
||||
}
|
||||
|
@ -86,7 +86,9 @@ abstract class Plugin
|
||||
|
||||
/**
|
||||
* @param string $method_id - the method id being checked
|
||||
* @param string $appearing_method_id - the method id of the class that the method appears in
|
||||
* @param string $declaring_method_id - the method id of the class or trait that declares the method
|
||||
* @param string|null $var_id - a reference to the LHS of the variable
|
||||
* @param PhpParser\Node\Arg[] $args
|
||||
* @param FileManipulation[] $file_replacements
|
||||
*
|
||||
@ -95,9 +97,12 @@ abstract class Plugin
|
||||
public static function afterMethodCallCheck(
|
||||
StatementsSource $statements_source,
|
||||
$method_id,
|
||||
$appearing_method_id,
|
||||
$declaring_method_id,
|
||||
$var_id,
|
||||
array $args,
|
||||
CodeLocation $code_location,
|
||||
Context $context,
|
||||
array &$file_replacements = [],
|
||||
Union &$return_type_candidate = null
|
||||
) {
|
||||
|
@ -732,6 +732,47 @@ class ConfigTest extends TestCase
|
||||
$this->analyzeFile($file_path, new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testMethodCallMemoize()
|
||||
{
|
||||
$this->project_checker = $this->getProjectCheckerWithConfig(
|
||||
TestConfig::loadFromXML(
|
||||
dirname(__DIR__),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm memoizeMethodCallResults="true">
|
||||
<projectFiles>
|
||||
<directory name="src" />
|
||||
</projectFiles>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$file_path = getcwd() . '/src/somefile.php';
|
||||
|
||||
$this->addFile(
|
||||
$file_path,
|
||||
'<?php
|
||||
class A {
|
||||
function getFoo() : ?Foo {
|
||||
return rand(0, 1) ? new Foo : null;
|
||||
}
|
||||
}
|
||||
class Foo {
|
||||
public function bar() : void {}
|
||||
};
|
||||
|
||||
$a = new A();
|
||||
|
||||
if ($a->getFoo()) {
|
||||
$a->getFoo()->bar();
|
||||
}'
|
||||
);
|
||||
|
||||
$this->analyzeFile($file_path, new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user