1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Allow constants in array offsets to be reasoned about

This commit is contained in:
Matt Brown 2018-05-31 16:49:01 -04:00
parent ef992612d9
commit c31d963918
5 changed files with 75 additions and 14 deletions

View File

@ -416,16 +416,20 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
{ {
switch (gettype($value)) { switch (gettype($value)) {
case 'boolean': case 'boolean':
return Type::getBool(); if ($value) {
return Type::getTrue();
}
return Type::getFalse();
case 'integer': case 'integer':
return Type::getInt(); return Type::getInt(false, $value);
case 'double': case 'double':
return Type::getFloat(); return Type::getFloat($value);
case 'string': case 'string':
return Type::getString(); return Type::getString($value);
case 'array': case 'array':
return Type::getArray(); return Type::getArray();

View File

@ -54,6 +54,8 @@ class ArrayAssignmentChecker
* @param Context $context * @param Context $context
* *
* @return false|null * @return false|null
*
* @psalm-suppress UnusedVariable due to Psalm bug
*/ */
public static function updateArrayType( public static function updateArrayType(
StatementsChecker $statements_checker, StatementsChecker $statements_checker,
@ -131,13 +133,31 @@ class ArrayAssignmentChecker
return null; return null;
} }
if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_) { if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_
if (preg_match('/^(0|[1-9][0-9]*)$/', $child_stmt->dim->value)) { || ($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch
$var_id_additions[] = '[' . $child_stmt->dim->value . ']'; && $child_stmt->dim->inferredType->isSingleStringLiteral())
) {
if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_) {
$value = $child_stmt->dim->value;
} else {
$value = $child_stmt->dim->inferredType->getSingleStringLiteral();
} }
$var_id_additions[] = '[\'' . $child_stmt->dim->value . '\']';
} elseif ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber) { if (preg_match('/^(0|[1-9][0-9]*)$/', $value)) {
$var_id_additions[] = '[' . $child_stmt->dim->value . ']'; $var_id_additions[] = '[' . $value . ']';
}
$var_id_additions[] = '[\'' . $value . '\']';
} elseif ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber
|| ($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch
&& $child_stmt->dim->inferredType->isSingleIntLiteral())
) {
if ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber) {
$value = $child_stmt->dim->value;
} else {
$value = $child_stmt->dim->inferredType->getSingleIntLiteral();
}
$var_id_additions[] = '[' . $value . ']';
} elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\Variable } elseif ($child_stmt->dim instanceof PhpParser\Node\Expr\Variable
&& is_string($child_stmt->dim->name) && is_string($child_stmt->dim->name)
) { ) {
@ -199,10 +219,24 @@ class ArrayAssignmentChecker
throw new \InvalidArgumentException('Should never get here'); throw new \InvalidArgumentException('Should never get here');
} }
$is_single_string_literal = false;
if ($current_dim instanceof PhpParser\Node\Scalar\String_ if ($current_dim instanceof PhpParser\Node\Scalar\String_
|| $current_dim instanceof PhpParser\Node\Scalar\LNumber || $current_dim instanceof PhpParser\Node\Scalar\LNumber
|| ($current_dim instanceof PhpParser\Node\Expr\ConstFetch
&& isset($current_dim->inferredType)
&& (($is_single_string_literal = $current_dim->inferredType->isSingleStringLiteral())
|| $current_dim->inferredType->isSingleIntLiteral()))
) { ) {
$key_value = $current_dim->value; if ($current_dim instanceof PhpParser\Node\Scalar\String_
|| $current_dim instanceof PhpParser\Node\Scalar\LNumber
) {
$key_value = $current_dim->value;
} elseif ($is_single_string_literal) {
$key_value = $current_dim->inferredType->getSingleStringLiteral();
} else {
$key_value = $current_dim->inferredType->getSingleIntLiteral();
}
$has_matching_objectlike_property = false; $has_matching_objectlike_property = false;
@ -261,13 +295,26 @@ class ArrayAssignmentChecker
} }
$root_is_string = $root_type->isString(); $root_is_string = $root_type->isString();
$is_single_string_literal = false;
if (($current_dim instanceof PhpParser\Node\Scalar\String_ if (($current_dim instanceof PhpParser\Node\Scalar\String_
|| $current_dim instanceof PhpParser\Node\Scalar\LNumber) || $current_dim instanceof PhpParser\Node\Scalar\LNumber
|| ($current_dim instanceof PhpParser\Node\Expr\ConstFetch
&& isset($current_dim->inferredType)
&& (($is_single_string_literal = $current_dim->inferredType->isSingleStringLiteral())
|| $current_dim->inferredType->isSingleIntLiteral())))
&& ($current_dim instanceof PhpParser\Node\Scalar\String_ && ($current_dim instanceof PhpParser\Node\Scalar\String_
|| !$root_is_string) || !$root_is_string)
) { ) {
$key_value = $current_dim->value; if ($current_dim instanceof PhpParser\Node\Scalar\String_
|| $current_dim instanceof PhpParser\Node\Scalar\LNumber
) {
$key_value = $current_dim->value;
} elseif ($is_single_string_literal) {
$key_value = $current_dim->inferredType->getSingleStringLiteral();
} else {
$key_value = $current_dim->inferredType->getSingleIntLiteral();
}
$has_matching_objectlike_property = false; $has_matching_objectlike_property = false;

View File

@ -728,6 +728,8 @@ class ExpressionChecker
&& is_string($stmt->dim->name) && is_string($stmt->dim->name)
) { ) {
$offset = '$' . $stmt->dim->name; $offset = '$' . $stmt->dim->name;
} elseif ($stmt->dim instanceof PhpParser\Node\Expr\ConstFetch) {
$offset = implode('\\', $stmt->dim->name->parts);
} }
return $root_var_id && $offset !== null ? $root_var_id . '[' . $offset . ']' : null; return $root_var_id && $offset !== null ? $root_var_id . '[' . $offset . ']' : null;

View File

@ -1070,7 +1070,8 @@ class StatementsChecker extends SourceChecker implements StatementsSource
$predefined_constants = Config::getInstance()->getPredefinedConstants(); $predefined_constants = Config::getInstance()->getPredefinedConstants();
if (isset($predefined_constants[$fq_const_name ?: $const_name])) { if (isset($predefined_constants[$fq_const_name ?: $const_name])) {
return ClassLikeChecker::getTypeFromValue($predefined_constants[$fq_const_name ?: $const_name]); $type = ClassLikeChecker::getTypeFromValue($predefined_constants[$fq_const_name ?: $const_name]);
return $type;
} }
$stubbed_const_type = $project_checker->codebase->getStubbedConstantType( $stubbed_const_type = $project_checker->codebase->getStubbedConstantType(

View File

@ -925,6 +925,13 @@ class ArrayAssignmentTest extends TestCase
$arr = [1 => 0, 1, 2, 3]; $arr = [1 => 0, 1, 2, 3];
$arr = [1 => "one", 2 => "two", "three")', $arr = [1 => "one", 2 => "two", "three")',
], ],
'constArrayAssignment' => [
'<?php
const BAR = 2;
$arr = [1 => 2];
$arr[BAR] = [6];
$bar = $arr[BAR][0];',
],
]; ];
} }