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

Add new annotation: @psalm-self-out (#3650)

* Add new config: sealAllMethods

* Add some more tests

* Fix codesniffer issue with preg_quote

* Fix missing method in test

* New tag @self-out (WIP)

* Add self_out_type to method storage

* Add some notes

* More work on self-out (WIP)

* More work on self-out (WIP)

* Use psalm-self-out instead of self-out

* Remove extra file

* Cleanup

* Wrap around try-catch - how to check if a method has/should have storage?

* New method hasStorage()

* Fix indentation

* Fix some errors

* Fix indentation

* Cast storage type to type

* Add proper use-statement in method storage

* Correct test class name

* Allow self_out to be null

* method_id can be string (why, when?)

Co-authored-by: Olle <noemail>
This commit is contained in:
Olle Härstedt 2020-07-02 00:10:24 +02:00 committed by GitHub
parent b0a3de47e8
commit d8e8ce428e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 120 additions and 1 deletions

View File

@ -35,7 +35,7 @@ class DocComment
'mutation-free', 'external-mutation-free', 'immutable', 'readonly',
'allow-private-mutation', 'readonly-allow-private-mutation',
'yield', 'trace', 'import-type', 'flow', 'taint-specialize', 'taint-escape',
'taint-unescape'
'taint-unescape', 'self-out'
];
/**

View File

@ -467,6 +467,21 @@ class CommentAnalyzer
}
}
if (isset($parsed_docblock->tags['psalm-self-out'])) {
foreach ($parsed_docblock->tags['psalm-self-out'] as $offset => $param) {
$line_parts = self::splitDocLine($param);
if (count($line_parts) > 0) {
$line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0]));
$info->self_out = [
'type' => str_replace("\n", '', $line_parts[0]),
'line_number' => $comment->getLine() + substr_count($comment_text, "\n", 0, $offset),
];
}
}
}
if (isset($parsed_docblock->tags['psalm-flow'])) {
foreach ($parsed_docblock->tags['psalm-flow'] as $param) {
$info->flows[] = trim($param);

View File

@ -5,6 +5,7 @@ use PhpParser;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\MethodIdentifier;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\InvalidMethodCall;
@ -404,6 +405,23 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$context->vars_in_scope[$lhs_var_id] = $class_type;
}
if ($lhs_var_id) {
// TODO: Always defined? Always correct?
$method_id = $result->existent_method_ids[0];
if ($method_id instanceof MethodIdentifier) {
// TODO: When should a method have a storage?
if ($codebase->methods->hasStorage($method_id)) {
$storage = $codebase->methods->getStorage($method_id);
if ($storage->self_out_type) {
$self_out_type = $storage->self_out_type;
$context->vars_in_scope[$lhs_var_id] = $self_out_type;
}
}
} else {
// TODO: When is method_id a string?
}
}
return true;
}
}

View File

@ -1057,4 +1057,24 @@ class Methods
return $class_storage->methods[$method_name];
}
/**
* @return bool
*/
public function hasStorage(MethodIdentifier $method_id)
{
try {
$class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
} catch (\InvalidArgumentException $e) {
return false;
}
$method_name = $method_id->method_name;
if (!isset($class_storage->methods[$method_name])) {
return false;
}
return true;
}
}

View File

@ -2503,6 +2503,22 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
}
}
if ($docblock_info->self_out
&& $storage instanceof MethodStorage) {
$out_type = TypeParser::parseTokens(
TypeTokenizer::getFullyQualifiedTokens(
$docblock_info->self_out['type'],
$this->aliases,
$this->function_template_types + $class_template_types,
$this->type_aliases
),
null,
$this->function_template_types + $class_template_types,
$this->type_aliases
);
$storage->self_out_type = $out_type;
}
foreach ($docblock_info->taint_sink_params as $taint_sink_param) {
$param_name = substr($taint_sink_param['name'], 1);

View File

@ -41,6 +41,11 @@ class FunctionDocblockComment
*/
public $params_out = [];
/**
* @var array{type:string, line_number: int}|null
*/
public $self_out;
/**
* @var array<int, array{name:string, type:string, line_number: int}>
*/

View File

@ -1,6 +1,8 @@
<?php
namespace Psalm\Storage;
use Psalm\Type;
class MethodStorage extends FunctionLikeStorage
{
/**
@ -72,4 +74,9 @@ class MethodStorage extends FunctionLikeStorage
* @var ?string
*/
public $plain_getter = null;
/**
* @var Type\Union|null
*/
public $self_out_type = null;
}

38
tests/SelfOutTest.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace Psalm\Tests;
class SelfOutTest extends TestCase
{
use Traits\ValidCodeAnalysisTestTrait;
/**
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
*/
public function providerValidCodeParse()
{
return [
'changeInterface' => [
'<?php
interface Foo {
/**
* @return void
*/
public function far() {
}
}
class Bar {
/**
* @psalm-self-out Foo
* @return void
*/
public function baz() {
}
}
$bar = new Bar();
$bar->baz();
$bar->far();
'
]
];
}
}