2018-01-14 18:09:40 +01:00
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
|
|
|
use PhpParser;
|
2020-05-19 18:56:23 +02:00
|
|
|
use Psalm\Aliases;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
2020-05-19 18:56:23 +02:00
|
|
|
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2020-05-19 18:56:23 +02:00
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\SimpleTypeInferer;
|
2018-07-06 19:35:36 +02:00
|
|
|
use Psalm\Codebase;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
|
|
|
use Psalm\Issue\UndefinedConstant;
|
|
|
|
use Psalm\IssueBuffer;
|
|
|
|
use Psalm\Type;
|
2019-11-29 15:30:01 +01:00
|
|
|
use function array_key_exists;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function implode;
|
|
|
|
use function strtolower;
|
2020-05-19 18:56:23 +02:00
|
|
|
use function explode;
|
|
|
|
use function array_pop;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
class ConstFetchAnalyzer
|
2018-01-14 18:09:40 +01:00
|
|
|
{
|
|
|
|
/**
|
2018-11-11 18:01:14 +01:00
|
|
|
* @param StatementsAnalyzer $statements_analyzer
|
2018-01-14 18:09:40 +01:00
|
|
|
* @param PhpParser\Node\Expr\ConstFetch $stmt
|
|
|
|
* @param Context $context
|
|
|
|
*
|
2018-02-08 02:26:26 +01:00
|
|
|
* @return void
|
2018-01-14 18:09:40 +01:00
|
|
|
*/
|
|
|
|
public static function analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
PhpParser\Node\Expr\ConstFetch $stmt,
|
|
|
|
Context $context
|
|
|
|
) {
|
|
|
|
$const_name = implode('\\', $stmt->name->parts);
|
2019-03-03 22:43:24 +01:00
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
switch (strtolower($const_name)) {
|
|
|
|
case 'null':
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getNull());
|
2018-01-14 18:09:40 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'false':
|
|
|
|
// false is a subtype of bool
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getFalse());
|
2018-01-14 18:09:40 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'true':
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getTrue());
|
2018-01-14 18:09:40 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'stdin':
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getResource());
|
2018-01-14 18:09:40 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2020-05-19 18:56:23 +02:00
|
|
|
$const_type = self::getConstType(
|
|
|
|
$statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
$const_name,
|
|
|
|
$stmt->name instanceof PhpParser\Node\Name\FullyQualified,
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($const_type) {
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setType($stmt, clone $const_type);
|
2018-01-14 18:09:40 +01:00
|
|
|
} elseif ($context->check_consts) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UndefinedConstant(
|
|
|
|
'Const ' . $const_name . ' is not defined',
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
2018-01-14 18:09:40 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-01-14 18:09:40 +01:00
|
|
|
)) {
|
2018-02-08 02:26:26 +01:00
|
|
|
// fall through
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-06 19:35:36 +02:00
|
|
|
/**
|
|
|
|
* @param Codebase $codebase
|
2018-07-06 20:14:24 +02:00
|
|
|
* @param ?string $fq_const_name
|
2018-07-06 19:35:36 +02:00
|
|
|
* @param string $const_name
|
|
|
|
*
|
|
|
|
* @return Type\Union|null
|
|
|
|
*/
|
|
|
|
public static function getGlobalConstType(
|
|
|
|
Codebase $codebase,
|
|
|
|
$fq_const_name,
|
|
|
|
$const_name
|
|
|
|
) {
|
2018-07-10 23:40:34 +02:00
|
|
|
if ($const_name === 'STDERR'
|
|
|
|
|| $const_name === 'STDOUT'
|
|
|
|
|| $const_name === 'STDIN'
|
|
|
|
) {
|
|
|
|
return Type::getResource();
|
|
|
|
}
|
|
|
|
|
2020-01-17 15:52:43 +01:00
|
|
|
if ($fq_const_name) {
|
|
|
|
$stubbed_const_type = $codebase->getStubbedConstantType(
|
|
|
|
$fq_const_name
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($stubbed_const_type) {
|
|
|
|
return $stubbed_const_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$stubbed_const_type = $codebase->getStubbedConstantType(
|
|
|
|
$const_name
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($stubbed_const_type) {
|
|
|
|
return $stubbed_const_type;
|
|
|
|
}
|
|
|
|
|
2018-07-06 19:35:36 +02:00
|
|
|
$predefined_constants = $codebase->config->getPredefinedConstants();
|
|
|
|
|
2019-12-08 06:49:34 +01:00
|
|
|
if (($fq_const_name && array_key_exists($fq_const_name, $predefined_constants))
|
2019-11-29 16:12:46 +01:00
|
|
|
|| array_key_exists($const_name, $predefined_constants)
|
2019-01-20 15:52:26 +01:00
|
|
|
) {
|
|
|
|
switch ($const_name) {
|
2018-07-06 19:35:36 +02:00
|
|
|
case 'PHP_VERSION':
|
|
|
|
case 'DIRECTORY_SEPARATOR':
|
|
|
|
case 'PATH_SEPARATOR':
|
|
|
|
case 'PEAR_EXTENSION_DIR':
|
|
|
|
case 'PEAR_INSTALL_DIR':
|
|
|
|
case 'PHP_BINARY':
|
|
|
|
case 'PHP_BINDIR':
|
|
|
|
case 'PHP_CONFIG_FILE_PATH':
|
|
|
|
case 'PHP_CONFIG_FILE_SCAN_DIR':
|
|
|
|
case 'PHP_DATADIR':
|
|
|
|
case 'PHP_EOL':
|
|
|
|
case 'PHP_EXTENSION_DIR':
|
|
|
|
case 'PHP_EXTRA_VERSION':
|
|
|
|
case 'PHP_LIBDIR':
|
|
|
|
case 'PHP_LOCALSTATEDIR':
|
|
|
|
case 'PHP_MANDIR':
|
|
|
|
case 'PHP_OS':
|
|
|
|
case 'PHP_OS_FAMILY':
|
|
|
|
case 'PHP_PREFIX':
|
|
|
|
case 'PHP_SAPI':
|
|
|
|
case 'PHP_SYSCONFDIR':
|
|
|
|
return Type::getString();
|
|
|
|
|
|
|
|
case 'PHP_MAJOR_VERSION':
|
|
|
|
case 'PHP_MINOR_VERSION':
|
|
|
|
case 'PHP_RELEASE_VERSION':
|
|
|
|
case 'PHP_DEBUG':
|
|
|
|
case 'PHP_FLOAT_DIG':
|
|
|
|
case 'PHP_INT_MAX':
|
|
|
|
case 'PHP_INT_MIN':
|
|
|
|
case 'PHP_INT_SIZE':
|
|
|
|
case 'PHP_MAXPATHLEN':
|
|
|
|
case 'PHP_VERSION_ID':
|
|
|
|
case 'PHP_ZTS':
|
|
|
|
return Type::getInt();
|
|
|
|
|
|
|
|
case 'PHP_FLOAT_EPSILON':
|
|
|
|
case 'PHP_FLOAT_MAX':
|
|
|
|
case 'PHP_FLOAT_MIN':
|
|
|
|
return Type::getFloat();
|
|
|
|
}
|
|
|
|
|
2019-11-29 16:12:46 +01:00
|
|
|
if ($fq_const_name && array_key_exists($fq_const_name, $predefined_constants)) {
|
2019-01-20 15:52:26 +01:00
|
|
|
return ClassLikeAnalyzer::getTypeFromValue($predefined_constants[$fq_const_name]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ClassLikeAnalyzer::getTypeFromValue($predefined_constants[$const_name]);
|
2018-07-06 19:35:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
2020-05-19 18:56:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $const_name
|
|
|
|
* @param bool $is_fully_qualified
|
|
|
|
* @param Context $context
|
|
|
|
*
|
|
|
|
* @return Type\Union|null
|
|
|
|
*/
|
|
|
|
public static function getConstType(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
string $const_name,
|
|
|
|
bool $is_fully_qualified,
|
|
|
|
?Context $context
|
|
|
|
) {
|
|
|
|
$aliased_constants = $statements_analyzer->getAliases()->constants;
|
|
|
|
|
|
|
|
if (isset($aliased_constants[$const_name])) {
|
|
|
|
$fq_const_name = $aliased_constants[$const_name];
|
|
|
|
} elseif ($is_fully_qualified) {
|
|
|
|
$fq_const_name = $const_name;
|
|
|
|
} else {
|
|
|
|
$fq_const_name = Type::getFQCLNFromString($const_name, $statements_analyzer->getAliases());
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($fq_const_name) {
|
|
|
|
$const_name_parts = explode('\\', $fq_const_name);
|
|
|
|
$const_name = array_pop($const_name_parts);
|
|
|
|
$namespace_name = implode('\\', $const_name_parts);
|
|
|
|
$namespace_constants = NamespaceAnalyzer::getConstantsForNamespace(
|
|
|
|
$namespace_name,
|
|
|
|
\ReflectionProperty::IS_PUBLIC
|
|
|
|
);
|
|
|
|
|
|
|
|
if (isset($namespace_constants[$const_name])) {
|
|
|
|
return $namespace_constants[$const_name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($context && $context->hasVariable($fq_const_name, $statements_analyzer)) {
|
|
|
|
return $context->vars_in_scope[$fq_const_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
$file_path = $statements_analyzer->getRootFilePath();
|
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
|
|
|
$file_storage_provider = $codebase->file_storage_provider;
|
|
|
|
|
|
|
|
$file_storage = $file_storage_provider->get($file_path);
|
|
|
|
|
|
|
|
if (isset($file_storage->declaring_constants[$const_name])) {
|
|
|
|
$constant_file_path = $file_storage->declaring_constants[$const_name];
|
|
|
|
|
|
|
|
return $file_storage_provider->get($constant_file_path)->constants[$const_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($file_storage->declaring_constants[$fq_const_name])) {
|
|
|
|
$constant_file_path = $file_storage->declaring_constants[$fq_const_name];
|
|
|
|
|
|
|
|
return $file_storage_provider->get($constant_file_path)->constants[$fq_const_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return ConstFetchAnalyzer::getGlobalConstType($codebase, $fq_const_name, $const_name)
|
|
|
|
?? ConstFetchAnalyzer::getGlobalConstType($codebase, $const_name, $const_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $const_name
|
|
|
|
* @param Type\Union $const_type
|
|
|
|
* @param Context $context
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function setConstType(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
string $const_name,
|
|
|
|
Type\Union $const_type,
|
|
|
|
Context $context
|
|
|
|
) {
|
|
|
|
$context->vars_in_scope[$const_name] = $const_type;
|
|
|
|
$context->constants[$const_name] = $const_type;
|
|
|
|
|
|
|
|
$source = $statements_analyzer->getSource();
|
|
|
|
|
|
|
|
if ($source instanceof NamespaceAnalyzer) {
|
|
|
|
$source->setConstType($const_name, $const_type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getConstName(
|
|
|
|
PhpParser\Node\Expr $first_arg_value,
|
|
|
|
\Psalm\Internal\Provider\NodeDataProvider $type_provider,
|
|
|
|
Codebase $codebase,
|
|
|
|
Aliases $aliases
|
|
|
|
) : ?string {
|
|
|
|
$const_name = null;
|
|
|
|
|
|
|
|
if ($first_arg_value instanceof PhpParser\Node\Scalar\String_) {
|
|
|
|
$const_name = $first_arg_value->value;
|
|
|
|
} elseif ($first_arg_type = $type_provider->getType($first_arg_value)) {
|
|
|
|
if ($first_arg_type->isSingleStringLiteral()) {
|
|
|
|
$const_name = $first_arg_type->getSingleStringLiteral()->value;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$simple_type = SimpleTypeInferer::infer($codebase, $type_provider, $first_arg_value, $aliases);
|
|
|
|
|
|
|
|
if ($simple_type && $simple_type->isSingleStringLiteral()) {
|
|
|
|
$const_name = $simple_type->getSingleStringLiteral()->value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $const_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param PhpParser\Node\Stmt\Const_ $stmt
|
|
|
|
* @param Context $context
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function analyzeConstAssignment(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Stmt\Const_ $stmt,
|
|
|
|
Context $context
|
|
|
|
) {
|
|
|
|
foreach ($stmt->consts as $const) {
|
|
|
|
ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context);
|
|
|
|
|
|
|
|
self::setConstType(
|
|
|
|
$statements_analyzer,
|
|
|
|
$const->name->name,
|
|
|
|
$statements_analyzer->node_data->getType($const->value) ?: Type::getMixed(),
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|