mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #952 - improve checks for string array offsets
This commit is contained in:
parent
bf79169a1d
commit
699a34fc9d
@ -192,6 +192,7 @@ class MethodCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
|
||||
case Type\Atomic\TArray::class:
|
||||
case Type\Atomic\ObjectLike::class:
|
||||
case Type\Atomic\TString::class:
|
||||
case Type\Atomic\TSingleLetter::class:
|
||||
case Type\Atomic\TLiteralString::class:
|
||||
case Type\Atomic\TLiteralClassString::class:
|
||||
case Type\Atomic\TNumericString::class:
|
||||
|
@ -38,6 +38,7 @@ use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TSingleLetter;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
|
||||
class ArrayFetchChecker
|
||||
@ -576,23 +577,37 @@ class ArrayFetchChecker
|
||||
}
|
||||
}
|
||||
|
||||
if ($type instanceof TSingleLetter) {
|
||||
$valid_offset_type = Type::getInt(false, 0);
|
||||
} elseif ($type instanceof TLiteralString) {
|
||||
$valid_offsets = [];
|
||||
|
||||
for ($i = 0, $l = strlen($type->value); $i < $l; $i++) {
|
||||
$valid_offsets[] = new TLiteralInt($i);
|
||||
}
|
||||
|
||||
$valid_offset_type = new Type\Union($valid_offsets);
|
||||
} else {
|
||||
$valid_offset_type = Type::getInt();
|
||||
}
|
||||
|
||||
if (!TypeChecker::isContainedBy(
|
||||
$project_checker->codebase,
|
||||
$offset_type,
|
||||
Type::getInt(),
|
||||
$valid_offset_type,
|
||||
true
|
||||
)) {
|
||||
$expected_offset_types[] = 'int';
|
||||
$expected_offset_types[] = $valid_offset_type->getId();
|
||||
} else {
|
||||
$has_valid_offset = true;
|
||||
}
|
||||
|
||||
if (!$array_access_type) {
|
||||
$array_access_type = Type::getString();
|
||||
$array_access_type = Type::getSingleLetter();
|
||||
} else {
|
||||
$array_access_type = Type::combineUnionTypes(
|
||||
$array_access_type,
|
||||
Type::getString()
|
||||
Type::getSingleLetter()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ use Psalm\Type\Atomic\TNumericString;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TScalar;
|
||||
use Psalm\Type\Atomic\TSingleLetter;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TTrue;
|
||||
|
||||
@ -604,7 +605,10 @@ class TypeChecker
|
||||
return true;
|
||||
}
|
||||
|
||||
if (get_class($container_type_part) === TString::class && $input_type_part instanceof TLiteralString) {
|
||||
if ((get_class($container_type_part) === TString::class
|
||||
|| get_class($container_type_part) === TSingleLetter::class)
|
||||
&& $input_type_part instanceof TLiteralString
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -622,7 +626,9 @@ class TypeChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_class($input_type_part) === TString::class && $container_type_part instanceof TLiteralString) {
|
||||
if ((get_class($input_type_part) === TString::class || get_class($container_type_part) === TSingleLetter::class)
|
||||
&& $container_type_part instanceof TLiteralString
|
||||
) {
|
||||
$type_coerced = true;
|
||||
$type_coerced_from_scalar = true;
|
||||
|
||||
@ -651,6 +657,7 @@ class TypeChecker
|
||||
|
||||
if (($input_type_part instanceof TClassString || $input_type_part instanceof TLiteralClassString)
|
||||
&& (get_class($container_type_part) === TString::class
|
||||
|| get_class($container_type_part) === TSingleLetter::class
|
||||
|| get_class($container_type_part) === Type\Atomic\GetClassT::class)
|
||||
) {
|
||||
return true;
|
||||
|
@ -24,6 +24,7 @@ use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TSingleLetter;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TTrue;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
@ -742,6 +743,16 @@ abstract class Type
|
||||
return new Union([$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type\Union
|
||||
*/
|
||||
public static function getSingleLetter()
|
||||
{
|
||||
$type = new TSingleLetter;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class_type
|
||||
*
|
||||
|
6
src/Psalm/Type/Atomic/TSingleLetter.php
Normal file
6
src/Psalm/Type/Atomic/TSingleLetter.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TSingleLetter extends TString
|
||||
{
|
||||
}
|
@ -373,7 +373,16 @@ class TypeCombination
|
||||
}
|
||||
} else {
|
||||
$combination->strings = null;
|
||||
$combination->value_types[$type_key] = $type;
|
||||
|
||||
if (!isset($combination->value_types['string'])) {
|
||||
$combination->value_types[$type_key] = $type;
|
||||
} elseif (get_class($combination->value_types['string']) !== TString::class) {
|
||||
if (get_class($type) === TString::class) {
|
||||
$combination->value_types[$type_key] = $type;
|
||||
} elseif (get_class($combination->value_types['string']) !== get_class($type)) {
|
||||
$combination->value_types[$type_key] = new TString();
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($type instanceof TInt) {
|
||||
if ($type instanceof TLiteralInt) {
|
||||
|
@ -243,6 +243,13 @@ class ArrayAccessTest extends TestCase
|
||||
echo $x["a"];',
|
||||
'error_message' => 'InvalidArrayOffset',
|
||||
],
|
||||
'noImpossibleStringAccess' => [
|
||||
'<?php
|
||||
function foo(string $s) : void {
|
||||
echo $s[0][1];
|
||||
}',
|
||||
'error_message' => 'InvalidArrayOffset',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user