Skip to content
Permalink
Browse files

Add working arrow-functions implementation

  • Loading branch information
muglug committed Sep 1, 2019
1 parent 90cb3f4 commit d1fbd1fa26563956b67b8e2f1afb356eec344f0b
@@ -11,7 +11,7 @@
],
"require": {
"php": "^7.1.3",
"nikic/php-parser": "4.2.*",
"nikic/php-parser": "^4.2.3||^4.3",
"openlss/lib-array2xml": "^1.0",
"ocramius/package-versions": "^1.2",
"composer/xdebug-handler": "^1.1",
@@ -1786,6 +1786,7 @@ public static function analyzeClassMethodReturnType(
FunctionLike\ReturnTypeAnalyzer::verifyReturnType(
$stmt,
$stmt->getStmts() ?: [],
$source,
$type_provider,
$method_analyzer,
@@ -1799,6 +1800,7 @@ public static function analyzeClassMethodReturnType(
FunctionLike\ReturnTypeAnalyzer::verifyReturnType(
$stmt,
$stmt->getStmts() ?: [],
$source,
$type_provider,
$method_analyzer,
@@ -1,12 +1,18 @@
<?php
namespace Psalm\Internal\Analyzer;
use PhpParser;
/**
* @internal
*/
class ClosureAnalyzer extends FunctionLikeAnalyzer
{
public function __construct(\PhpParser\Node\Expr\Closure $function, SourceAnalyzer $source)
/**
* @param PhpParser\Node\Expr\Closure|PhpParser\Node\Expr\ArrowFunction $function
* @param SourceAnalyzer $source [description]
*/
public function __construct(PhpParser\Node\FunctionLike $function, SourceAnalyzer $source)
{
$codebase = $source->getCodebase();
@@ -2,6 +2,7 @@
namespace Psalm\Internal\Analyzer\FunctionLike;
use PhpParser;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
@@ -47,7 +48,8 @@
class ReturnTypeAnalyzer
{
/**
* @param Closure|Function_|ClassMethod $function
* @param Closure|Function_|ClassMethod|ArrowFunction $function
* @param PhpParser\Node\Stmt[] $function_stmts
* @param Type\Union|null $return_type
* @param string $fq_class_name
* @param CodeLocation|null $return_type_location
@@ -57,6 +59,7 @@ class ReturnTypeAnalyzer
*/
public static function verifyReturnType(
FunctionLike $function,
array $function_stmts,
SourceAnalyzer $source,
\Psalm\Internal\Provider\NodeDataProvider $type_provider,
FunctionLikeAnalyzer $function_like_analyzer,
@@ -115,15 +118,12 @@ public static function verifyReturnType(
if (!$return_type_location) {
$return_type_location = new CodeLocation(
$function_like_analyzer,
$function instanceof Closure ? $function : $function->name
$function instanceof Closure || $function instanceof ArrowFunction ? $function : $function->name
);
}
$inferred_yield_types = [];
/** @var PhpParser\Node\Stmt[] */
$function_stmts = $function->getStmts();
$ignore_nullable_issues = false;
$ignore_falsable_issues = false;
@@ -269,7 +269,7 @@ public static function verifyReturnType(
}
if (!$return_type) {
if ($function instanceof Closure) {
if ($function instanceof Closure || $function instanceof ArrowFunction) {
if (!$closure_inside_call || $inferred_return_type->isMixed()) {
if ($codebase->alter_code
&& isset($project_analyzer->getIssuesToFix()['MissingClosureReturnType'])
@@ -532,6 +532,7 @@ public static function verifyReturnType(
) {
if ($function instanceof Function_
|| $function instanceof Closure
|| $function instanceof ArrowFunction
|| $function->isPrivate()
) {
$check_for_less_specific_type = true;
@@ -637,7 +638,7 @@ public static function verifyReturnType(
}
/**
* @param Closure|Function_|ClassMethod $function
* @param Closure|Function_|ClassMethod|ArrowFunction $function
*
* @return false|null
*/
@@ -718,7 +719,7 @@ public static function checkReturnType(
return false;
}
if ($function instanceof Closure) {
if ($function instanceof Closure || $function instanceof ArrowFunction) {
return;
}
@@ -792,7 +793,7 @@ public static function checkReturnType(
}
/**
* @param Closure|Function_|ClassMethod $function
* @param Closure|Function_|ClassMethod|ArrowFunction $function
* @param bool $docblock_only
*
* @return void
@@ -2,6 +2,7 @@
namespace Psalm\Internal\Analyzer;
use PhpParser;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
@@ -50,7 +51,7 @@
abstract class FunctionLikeAnalyzer extends SourceAnalyzer
{
/**
* @var Closure|Function_|ClassMethod
* @var Closure|Function_|ClassMethod|ArrowFunction
*/
protected $function;
@@ -100,7 +101,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
protected $storage;
/**
* @param Closure|Function_|ClassMethod $function
* @param Closure|Function_|ClassMethod|ArrowFunction $function
* @param SourceAnalyzer $source
*/
protected function __construct($function, SourceAnalyzer $source, FunctionLikeStorage $storage)
@@ -551,8 +552,11 @@ function (FunctionLikeParameter $p) {
);
}
if ($this->function instanceof Closure) {
if ($this->function instanceof Closure
|| $this->function instanceof ArrowFunction
) {
$this->verifyReturnType(
$function_stmts,
$statements_analyzer,
$storage->return_type,
$this->source->getFQCLN(),
@@ -568,7 +572,7 @@ function (FunctionLikeParameter $p) {
$closure_return_types = ReturnTypeCollector::getReturnTypes(
$codebase,
$type_provider,
$this->function->stmts,
$function_stmts,
$closure_yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues,
@@ -1243,13 +1247,15 @@ private function alterParams(
}
/**
* @param array<PhpParser\Node\Stmt> $function_stmts
* @param Type\Union|null $return_type
* @param string $fq_class_name
* @param CodeLocation|null $return_type_location
*
* @return false|null
*/
public function verifyReturnType(
array $function_stmts,
StatementsAnalyzer $statements_analyzer,
Type\Union $return_type = null,
$fq_class_name = null,
@@ -1258,6 +1264,7 @@ public function verifyReturnType(
) {
ReturnTypeAnalyzer::verifyReturnType(
$this->function,
$function_stmts,
$statements_analyzer,
$statements_analyzer->node_data,
$this,
@@ -437,10 +437,14 @@ public static function analyze(
}
} elseif ($stmt instanceof PhpParser\Node\Expr\Empty_) {
self::analyzeEmpty($statements_analyzer, $stmt, $context);
} elseif ($stmt instanceof PhpParser\Node\Expr\Closure) {
} elseif ($stmt instanceof PhpParser\Node\Expr\Closure
|| $stmt instanceof PhpParser\Node\Expr\ArrowFunction
) {
$closure_analyzer = new ClosureAnalyzer($stmt, $statements_analyzer);
if (self::analyzeClosureUses($statements_analyzer, $stmt, $context) === false) {
if ($stmt instanceof PhpParser\Node\Expr\Closure
&& self::analyzeClosureUses($statements_analyzer, $stmt, $context) === false
) {
return false;
}
@@ -479,29 +483,47 @@ public static function analyze(
$byref_uses = [];
foreach ($stmt->uses as $use) {
if (!is_string($use->var->name)) {
continue;
}
if ($stmt instanceof PhpParser\Node\Expr\Closure) {
foreach ($stmt->uses as $use) {
if (!is_string($use->var->name)) {
continue;
}
$use_var_id = '$' . $use->var->name;
$use_var_id = '$' . $use->var->name;
if ($use->byRef) {
$byref_uses[$use_var_id] = true;
}
if ($use->byRef) {
$byref_uses[$use_var_id] = true;
}
// insert the ref into the current context if passed by ref, as whatever we're passing
// the closure to could execute it straight away.
if (!$context->hasVariable($use_var_id, $statements_analyzer) && $use->byRef) {
$context->vars_in_scope[$use_var_id] = Type::getMixed();
// insert the ref into the current context if passed by ref, as whatever we're passing
// the closure to could execute it straight away.
if (!$context->hasVariable($use_var_id, $statements_analyzer) && $use->byRef) {
$context->vars_in_scope[$use_var_id] = Type::getMixed();
}
$use_context->vars_in_scope[$use_var_id] =
$context->hasVariable($use_var_id, $statements_analyzer) && !$use->byRef
? clone $context->vars_in_scope[$use_var_id]
: Type::getMixed();
$use_context->vars_possibly_in_scope[$use_var_id] = true;
}
} else {
$traverser = new PhpParser\NodeTraverser;
$short_closure_visitor = new \Psalm\Internal\Visitor\ShortClosureVisitor();
$use_context->vars_in_scope[$use_var_id] =
$context->hasVariable($use_var_id, $statements_analyzer) && !$use->byRef
? clone $context->vars_in_scope[$use_var_id]
: Type::getMixed();
$traverser->addVisitor($short_closure_visitor);
$traverser->traverse($stmt->getStmts());
$use_context->vars_possibly_in_scope[$use_var_id] = true;
foreach ($short_closure_visitor->getUsedVariables() as $use_var_id => $_) {
$use_context->vars_in_scope[$use_var_id] =
$context->hasVariable($use_var_id, $statements_analyzer)
? clone $context->vars_in_scope[$use_var_id]
: Type::getMixed();
$use_context->vars_possibly_in_scope[$use_var_id] = true;
}
}
$use_context->calling_method_id = $context->calling_method_id;
@@ -660,6 +660,7 @@ function ($line) {
$return_type_location = $function_storage->return_type_location;
$this->function_analyzers[$function_id]->verifyReturnType(
$stmt->getStmts(),
$this,
$return_type,
$this->getFQCLN(),
@@ -4,6 +4,7 @@
use function count;
use function ltrim;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
@@ -35,7 +36,7 @@ class FunctionDocblockManipulator
*/
private static $ordered_manipulators = [];
/** @var Closure|Function_|ClassMethod */
/** @var Closure|Function_|ClassMethod|ArrowFunction */
private $stmt;
/** @var int */
@@ -89,7 +90,7 @@ class FunctionDocblockManipulator
/**
* @param string $file_path
* @param string $function_id
* @param Closure|Function_|ClassMethod $stmt
* @param Closure|Function_|ClassMethod|ArrowFunction $stmt
*
* @return self
*/
@@ -113,7 +114,7 @@ public static function getForFunction(
/**
* @param string $file_path
* @param Closure|Function_|ClassMethod $stmt
* @param Closure|Function_|ClassMethod|ArrowFunction $stmt
*/
private function __construct($file_path, FunctionLike $stmt, ProjectAnalyzer $project_analyzer)
{
@@ -372,7 +372,7 @@ public static function parseStatements(
];
if (!self::$lexer) {
self::$lexer = new PhpParser\Lexer(['usedAttributes' => $attributes]);
self::$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => $attributes]);
}
if (!self::$parser) {
@@ -0,0 +1,36 @@
<?php
namespace Psalm\Internal\Visitor;
use PhpParser;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
/**
* @internal
*/
class ShortClosureVisitor extends PhpParser\NodeVisitorAbstract implements PhpParser\NodeVisitor
{
/**
* @var array<string, bool>
*/
protected $used_variables = [];
/**
* @param PhpParser\Node $node
*
* @return null|int
*/
public function enterNode(PhpParser\Node $node)
{
if ($node instanceof PhpParser\Node\Expr\Variable && \is_string($node->name)) {
$this->used_variables['$' . $node->name] = true;
};
}
/**
* @return array<string, bool>
*/
public function getUsedVariables()
{
return $this->used_variables;
}
}

0 comments on commit d1fbd1f

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