Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7471 from klimick/function-dynamic-storage-provider
Function dynamic storage provider
- Loading branch information
Showing
14 changed files
with
644 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
src/Psalm/Internal/Provider/DynamicFunctionStorageProvider.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psalm\Internal\Provider; | ||
|
||
use Closure; | ||
use PhpParser; | ||
use Psalm\CodeLocation; | ||
use Psalm\Context; | ||
use Psalm\Internal\Analyzer\StatementsAnalyzer; | ||
use Psalm\Plugin\ArgTypeInferer; | ||
use Psalm\Plugin\DynamicFunctionStorage; | ||
use Psalm\Plugin\DynamicTemplateProvider; | ||
use Psalm\Plugin\EventHandler\DynamicFunctionStorageProviderInterface; | ||
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent; | ||
use Psalm\Storage\FunctionStorage; | ||
|
||
use function strtolower; | ||
|
||
/** | ||
* For each function call analysis will be created individual FunctionStorage in plugin hook. | ||
* If it is created be aware, it shadows the FunctionStorage Psalm may generate during the scanning phase. | ||
* | ||
* @internal | ||
*/ | ||
final class DynamicFunctionStorageProvider | ||
{ | ||
/** @var array<lowercase-string, array<Closure(DynamicFunctionStorageProviderEvent): ?DynamicFunctionStorage>> */ | ||
private static $handlers = []; | ||
|
||
/** @var array<lowercase-string, ?FunctionStorage> */ | ||
private static $dynamic_storages = []; | ||
|
||
/** | ||
* @param class-string<DynamicFunctionStorageProviderInterface> $class | ||
*/ | ||
public function registerClass(string $class): void | ||
{ | ||
$callable = Closure::fromCallable([$class, 'getFunctionStorage']); | ||
|
||
foreach ($class::getFunctionIds() as $function_id) { | ||
$this->registerClosure($function_id, $callable); | ||
} | ||
} | ||
|
||
/** | ||
* @param Closure(DynamicFunctionStorageProviderEvent): ?DynamicFunctionStorage $c | ||
*/ | ||
public function registerClosure(string $fq_function_name, Closure $c): void | ||
{ | ||
self::$handlers[strtolower($fq_function_name)][] = $c; | ||
} | ||
|
||
public function has(string $fq_function_name): bool | ||
{ | ||
return isset(self::$handlers[strtolower($fq_function_name)]); | ||
} | ||
|
||
public function getFunctionStorage( | ||
PhpParser\Node\Expr\FuncCall $stmt, | ||
StatementsAnalyzer $statements_analyzer, | ||
string $function_id, | ||
Context $context, | ||
CodeLocation $code_location | ||
): ?FunctionStorage { | ||
if ($stmt->isFirstClassCallable()) { | ||
return null; | ||
} | ||
|
||
$dynamic_storage_id = strtolower($statements_analyzer->getFilePath()) | ||
. ':' . $stmt->getLine() | ||
. ':' . (int)$stmt->getAttribute('startFilePos') | ||
. ':dynamic-storage' | ||
. ':-:' . strtolower($function_id); | ||
|
||
if (isset(self::$dynamic_storages[$dynamic_storage_id])) { | ||
return self::$dynamic_storages[$dynamic_storage_id]; | ||
} | ||
|
||
foreach (self::$handlers[strtolower($function_id)] ?? [] as $class_handler) { | ||
$event = new DynamicFunctionStorageProviderEvent( | ||
new ArgTypeInferer($context, $statements_analyzer), | ||
new DynamicTemplateProvider('fn-' . strtolower($function_id)), | ||
$statements_analyzer, | ||
$function_id, | ||
$stmt, | ||
$context, | ||
$code_location, | ||
); | ||
|
||
$result = $class_handler($event); | ||
|
||
return self::$dynamic_storages[$dynamic_storage_id] = $result | ||
? $result->toFunctionStorage($function_id) | ||
: null; | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psalm\Plugin; | ||
|
||
use PhpParser; | ||
use Psalm\Context; | ||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; | ||
use Psalm\Internal\Analyzer\StatementsAnalyzer; | ||
use Psalm\Type; | ||
use Psalm\Type\Union; | ||
|
||
final class ArgTypeInferer | ||
{ | ||
private Context $context; | ||
private StatementsAnalyzer $statements_analyzer; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public function __construct(Context $context, StatementsAnalyzer $statements_analyzer) | ||
{ | ||
$this->context = $context; | ||
$this->statements_analyzer = $statements_analyzer; | ||
} | ||
|
||
/** | ||
* @return false|Union | ||
*/ | ||
public function infer(PhpParser\Node\Arg $arg) | ||
{ | ||
$already_inferred_type = $this->statements_analyzer->node_data->getType($arg->value); | ||
|
||
if ($already_inferred_type) { | ||
return $already_inferred_type; | ||
} | ||
|
||
if (ExpressionAnalyzer::analyze($this->statements_analyzer, $arg->value, $this->context) === false) { | ||
return false; | ||
} | ||
|
||
return $this->statements_analyzer->node_data->getType($arg->value) ?? Type::getMixed(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psalm\Plugin; | ||
|
||
use Psalm\Storage\FunctionLikeParameter; | ||
use Psalm\Storage\FunctionStorage; | ||
use Psalm\Type\Atomic\TTemplateParam; | ||
use Psalm\Type\Union; | ||
|
||
final class DynamicFunctionStorage | ||
{ | ||
/** | ||
* Required param list for a function. | ||
* | ||
* @var list<FunctionLikeParameter> | ||
*/ | ||
public array $params = []; | ||
|
||
/** | ||
* A function return type. Maybe null. | ||
* That means we can infer it in {@see FunctionReturnTypeProviderInterface} hook. | ||
*/ | ||
public ?Union $return_type = null; | ||
|
||
/** | ||
* A function can have template args or return type. | ||
* Plugin hook must fill all used templates here. | ||
* | ||
* @var list<TTemplateParam> | ||
*/ | ||
public array $templates = []; | ||
|
||
/** | ||
* Determines if a function can be called with named arguments. | ||
*/ | ||
public bool $allow_named_arg_calls = true; | ||
|
||
/** | ||
* Function purity. | ||
* If function is pure then plugin hook should set it to true. | ||
*/ | ||
public bool $pure = false; | ||
|
||
/** | ||
* Determines if a function can be called with a various number of arguments. | ||
*/ | ||
public bool $variadic = false; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public function toFunctionStorage(string $function_cased_name): FunctionStorage | ||
{ | ||
$storage = new FunctionStorage(); | ||
$storage->cased_name = $function_cased_name; | ||
$storage->setParams($this->params); | ||
$storage->return_type = $this->return_type; | ||
$storage->allow_named_arg_calls = $this->allow_named_arg_calls; | ||
$storage->pure = $this->pure; | ||
$storage->variadic = $this->variadic; | ||
|
||
if (!empty($this->templates)) { | ||
$storage->template_types = []; | ||
|
||
foreach ($this->templates as $template) { | ||
$storage->template_types[$template->param_name] = [ | ||
$template->defining_class => $template->as | ||
]; | ||
} | ||
} | ||
|
||
return $storage; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psalm\Plugin; | ||
|
||
use Psalm\Type; | ||
use Psalm\Type\Atomic\TTemplateParam; | ||
use Psalm\Type\Union; | ||
|
||
final class DynamicTemplateProvider | ||
{ | ||
private string $defining_class; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public function __construct(string $defining_class) | ||
{ | ||
$this->defining_class = $defining_class; | ||
} | ||
|
||
/** | ||
* If {@see DynamicFunctionStorage} requires template params this method can create it. | ||
*/ | ||
public function createTemplate(string $param_name, Union $as = null): TTemplateParam | ||
{ | ||
return new TTemplateParam($param_name, $as ?? Type::getMixed(), $this->defining_class); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/Psalm/Plugin/EventHandler/DynamicFunctionStorageProviderInterface.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psalm\Plugin\EventHandler; | ||
|
||
use Psalm\Plugin\DynamicFunctionStorage; | ||
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent; | ||
|
||
interface DynamicFunctionStorageProviderInterface | ||
{ | ||
/** | ||
* @return array<lowercase-string> | ||
*/ | ||
public static function getFunctionIds(): array; | ||
|
||
public static function getFunctionStorage(DynamicFunctionStorageProviderEvent $event): ?DynamicFunctionStorage; | ||
} |
Oops, something went wrong.