mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
parent
f82a55d836
commit
1ab6345bac
@ -1214,50 +1214,6 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
return $this->codebase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method_id
|
||||
* @param array<int, PhpParser\Node\Arg> $args
|
||||
*
|
||||
* @return array<int, FunctionLikeParameter>
|
||||
*/
|
||||
public static function getMethodParamsById(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
$method_id,
|
||||
array $args
|
||||
) {
|
||||
$fq_class_name = strpos($method_id, '::') !== false ? explode('::', $method_id)[0] : null;
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($fq_class_name) {
|
||||
$fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name);
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
|
||||
|
||||
if ($class_storage->user_defined || $class_storage->stubbed) {
|
||||
$method_params = $codebase->methods->getMethodParams($method_id, $statements_analyzer, $args);
|
||||
|
||||
return $method_params;
|
||||
}
|
||||
}
|
||||
|
||||
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
||||
|
||||
if (CallMap::inCallMap($declaring_method_id ?: $method_id)) {
|
||||
$function_param_options = CallMap::getParamsFromCallMap($declaring_method_id ?: $method_id);
|
||||
|
||||
if ($function_param_options === null) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Not expecting $function_param_options to be null for ' . $method_id
|
||||
);
|
||||
}
|
||||
|
||||
return self::getMatchingParamsFromCallMapOptions($codebase, $function_param_options, $args);
|
||||
}
|
||||
|
||||
return $codebase->methods->getMethodParams($method_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method_id
|
||||
* @param array<int, PhpParser\Node\Arg> $args
|
||||
@ -1283,7 +1239,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
*
|
||||
* @return array<int, FunctionLikeParameter>
|
||||
*/
|
||||
protected static function getMatchingParamsFromCallMapOptions(
|
||||
public static function getMatchingParamsFromCallMapOptions(
|
||||
Codebase $codebase,
|
||||
array $function_param_options,
|
||||
array $args
|
||||
|
@ -232,7 +232,7 @@ class CallAnalyzer
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$method_params = $method_id
|
||||
? FunctionLikeAnalyzer::getMethodParamsById($statements_analyzer, $method_id, $args)
|
||||
? $codebase->methods->getMethodParams($method_id, $statements_analyzer, $args, $context)
|
||||
: null;
|
||||
|
||||
if (self::checkFunctionArguments(
|
||||
@ -295,15 +295,6 @@ class CallAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (!$class_storage->user_defined) {
|
||||
// check again after we've processed args
|
||||
$method_params = FunctionLikeAnalyzer::getMethodParamsById(
|
||||
$statements_analyzer,
|
||||
$method_id,
|
||||
$args
|
||||
);
|
||||
}
|
||||
|
||||
if (self::checkFunctionLikeArgumentsMatch(
|
||||
$statements_analyzer,
|
||||
$args,
|
||||
|
@ -4,6 +4,8 @@ namespace Psalm\Internal\Codebase;
|
||||
use PhpParser;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Provider\{
|
||||
ClassLikeStorageProvider,
|
||||
FileReferenceProvider,
|
||||
@ -221,12 +223,16 @@ class Methods
|
||||
|
||||
/**
|
||||
* @param string $method_id
|
||||
* @param array<PhpParser\Node\Arg> $args
|
||||
* @param array<int, PhpParser\Node\Arg> $args
|
||||
*
|
||||
* @return array<int, FunctionLikeParameter>
|
||||
*/
|
||||
public function getMethodParams($method_id, StatementsSource $source = null, array $args = null)
|
||||
{
|
||||
public function getMethodParams(
|
||||
$method_id,
|
||||
StatementsSource $source = null,
|
||||
array $args = null,
|
||||
Context $context = null
|
||||
) : array {
|
||||
list($fq_class_name, $method_name) = explode('::', $method_id);
|
||||
|
||||
if ($this->params_provider->has($fq_class_name)) {
|
||||
@ -234,7 +240,8 @@ class Methods
|
||||
$fq_class_name,
|
||||
$method_name,
|
||||
$args,
|
||||
$source
|
||||
$source,
|
||||
$context
|
||||
);
|
||||
|
||||
if ($method_params !== null) {
|
||||
@ -242,7 +249,46 @@ class Methods
|
||||
}
|
||||
}
|
||||
|
||||
if ($declaring_method_id = $this->getDeclaringMethodId($method_id)) {
|
||||
$declaring_method_id = $this->getDeclaringMethodId($method_id);
|
||||
|
||||
// functions
|
||||
if (CallMap::inCallMap($declaring_method_id ?: $method_id)) {
|
||||
$declaring_fq_class_name = explode('::', $declaring_method_id ?: $method_id)[0];
|
||||
|
||||
$class_storage = $this->classlike_storage_provider->get($declaring_fq_class_name);
|
||||
|
||||
if (!$class_storage->stubbed) {
|
||||
$function_param_options = CallMap::getParamsFromCallMap($declaring_method_id ?: $method_id);
|
||||
|
||||
if ($function_param_options === null) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Not expecting $function_param_options to be null for ' . $declaring_method_id
|
||||
);
|
||||
}
|
||||
|
||||
if (!$source || $args === null || count($function_param_options) === 1) {
|
||||
return $function_param_options[0];
|
||||
}
|
||||
|
||||
if ($context && $source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) {
|
||||
foreach ($args as $arg) {
|
||||
\Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(
|
||||
$source,
|
||||
$arg->value,
|
||||
$context
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return FunctionLikeAnalyzer::getMatchingParamsFromCallMapOptions(
|
||||
$source->getCodebase(),
|
||||
$function_param_options,
|
||||
$args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($declaring_method_id) {
|
||||
$storage = $this->getStorage($declaring_method_id);
|
||||
|
||||
$params = $storage->params;
|
||||
|
@ -28,6 +28,8 @@ class MethodParamsProvider
|
||||
public function __construct()
|
||||
{
|
||||
self::$handlers = [];
|
||||
|
||||
$this->registerClass(ReturnTypeProvider\PdoStatementSetFetchMode::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Internal\Provider\ReturnTypeProvider;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
|
||||
class PdoStatementSetFetchMode implements \Psalm\Plugin\Hook\MethodParamsProviderInterface
|
||||
{
|
||||
public static function getClassLikeNames() : array
|
||||
{
|
||||
return ['PDOStatement'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
* @return ?array<int, \Psalm\Storage\FunctionLikeParameter>
|
||||
*/
|
||||
public static function getMethodParams(
|
||||
string $fq_classlike_name,
|
||||
string $method_name_lowercase,
|
||||
array $call_args = null,
|
||||
StatementsSource $statements_source = null,
|
||||
Context $context = null,
|
||||
CodeLocation $code_location = null
|
||||
) {
|
||||
if ($method_name_lowercase === 'setfetchmode') {
|
||||
if (!$context
|
||||
|| !$call_args
|
||||
|| !$statements_source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer
|
||||
|| \Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(
|
||||
$statements_source,
|
||||
$call_args[0]->value,
|
||||
$context
|
||||
) === false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($call_args[0]->value->inferredType)
|
||||
&& $call_args[0]->value->inferredType->isSingleIntLiteral()
|
||||
) {
|
||||
$params = [
|
||||
new \Psalm\Storage\FunctionLikeParameter(
|
||||
'mode',
|
||||
false,
|
||||
Type::getInt(),
|
||||
null,
|
||||
null,
|
||||
false
|
||||
)
|
||||
];
|
||||
|
||||
$value = $call_args[0]->value->inferredType->getSingleIntLiteral()->value;
|
||||
|
||||
switch ($value) {
|
||||
case \PDO::FETCH_COLUMN:
|
||||
$params[] = new \Psalm\Storage\FunctionLikeParameter(
|
||||
'colno',
|
||||
false,
|
||||
Type::getInt(),
|
||||
null,
|
||||
null,
|
||||
false
|
||||
);
|
||||
break;
|
||||
|
||||
case \PDO::FETCH_CLASS:
|
||||
$params[] = new \Psalm\Storage\FunctionLikeParameter(
|
||||
'classname',
|
||||
false,
|
||||
Type::getClassString(),
|
||||
null,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
$params[] = new \Psalm\Storage\FunctionLikeParameter(
|
||||
'ctorargs',
|
||||
false,
|
||||
Type::getArray(),
|
||||
null,
|
||||
null,
|
||||
true
|
||||
);
|
||||
break;
|
||||
|
||||
case \PDO::FETCH_INTO:
|
||||
$params[] = new \Psalm\Storage\FunctionLikeParameter(
|
||||
'object',
|
||||
false,
|
||||
Type::getObject(),
|
||||
null,
|
||||
null,
|
||||
false
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -291,6 +291,31 @@ class MethodCallTest extends TestCase
|
||||
|
||||
Foo::$current->bar();',
|
||||
],
|
||||
'pdoStatementSetFetchMode' => [
|
||||
'<?php
|
||||
class A {
|
||||
/** @var ?string */
|
||||
public $a;
|
||||
}
|
||||
|
||||
$db = new PDO("sqlite::memory:");
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$stmt = $db->prepare("select \"a\" as a");
|
||||
$stmt->setFetchMode(PDO::FETCH_CLASS, A::class);
|
||||
$stmt->execute();
|
||||
/** @psalm-suppress MixedAssignment */
|
||||
$a = $stmt->fetch();'
|
||||
],
|
||||
'datePeriodConstructor' => [
|
||||
'<?php
|
||||
function foo(DateTime $d1, DateTime $d2) : void {
|
||||
new DatePeriod(
|
||||
$d1,
|
||||
DateInterval::createFromDateString("1 month"),
|
||||
$d2
|
||||
);
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user