From 350df114f309a83fe9ec2102fdfcbd8867ed71d2 Mon Sep 17 00:00:00 2001 From: orklah Date: Sun, 23 May 2021 20:43:30 +0200 Subject: [PATCH] Transform bad offsets (#5817) * Transform bad offset access * fix build --- .../Statements/Expression/ArrayAnalyzer.php | 2 + .../Expression/Fetch/ArrayFetchAnalyzer.php | 44 +++++++++++++++++++ tests/ArrayAccessTest.php | 15 +++++++ 3 files changed, 61 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php index d59462f4e7c..d5a95a8f4ac 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php @@ -186,6 +186,8 @@ public static function analyze( } elseif ($atomic_key_type instanceof Type\Atomic\TBool) { $good_types[] = new Type\Atomic\TLiteralInt(0); $good_types[] = new Type\Atomic\TLiteralInt(1); + } elseif ($atomic_key_type instanceof Type\Atomic\TLiteralFloat) { + $good_types[] = new Type\Atomic\TLiteralInt((int) $atomic_key_type->value); } elseif ($atomic_key_type instanceof Type\Atomic\TFloat) { $good_types[] = new Type\Atomic\TInt; } else { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 92d81fc46ab..02da490e8c3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -11,6 +11,7 @@ use Psalm\Internal\Type\TemplateInferredTypeReplacer; use Psalm\CodeLocation; use Psalm\Context; +use Psalm\Internal\Type\TypeCombiner; use Psalm\Issue\EmptyArrayAccess; use Psalm\Issue\InvalidArrayAccess; use Psalm\Issue\InvalidArrayAssignment; @@ -426,6 +427,10 @@ public static function taintArrayFetch( } } + /** + * @psalm-suppress ComplexMethod to be refactored. + * Good type/bad type behaviour could be mutualised with ArrayAnalyzer + */ public static function getArrayAccessTypeGivenOffset( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\ArrayDimFetch $stmt, @@ -806,6 +811,45 @@ public static function getArrayAccessTypeGivenOffset( } } } else { + $good_types = []; + $bad_types = []; + foreach ($offset_type->getAtomicTypes() as $atomic_key_type) { + if (!$atomic_key_type instanceof Type\Atomic\TString + && !$atomic_key_type instanceof Type\Atomic\TInt + && !$atomic_key_type instanceof Type\Atomic\TArrayKey + && !$atomic_key_type instanceof Type\Atomic\TMixed + && !$atomic_key_type instanceof Type\Atomic\TTemplateParam + && !( + $atomic_key_type instanceof Type\Atomic\TObjectWithProperties + && isset($atomic_key_type->methods['__toString']) + ) + ) { + $bad_types[] = $atomic_key_type; + + if ($atomic_key_type instanceof Type\Atomic\TFalse) { + $good_types[] = new Type\Atomic\TLiteralInt(0); + } elseif ($atomic_key_type instanceof Type\Atomic\TTrue) { + $good_types[] = new Type\Atomic\TLiteralInt(1); + } elseif ($atomic_key_type instanceof Type\Atomic\TBool) { + $good_types[] = new Type\Atomic\TLiteralInt(0); + $good_types[] = new Type\Atomic\TLiteralInt(1); + } elseif ($atomic_key_type instanceof Type\Atomic\TLiteralFloat) { + $good_types[] = new Type\Atomic\TLiteralInt((int)$atomic_key_type->value); + } elseif ($atomic_key_type instanceof Type\Atomic\TFloat) { + $good_types[] = new Type\Atomic\TInt; + } else { + $good_types[] = new Type\Atomic\TArrayKey; + } + } + } + + if ($bad_types && $good_types) { + $offset_type->substitute( + TypeCombiner::combine($bad_types, $codebase), + TypeCombiner::combine($good_types, $codebase) + ); + } + if (IssueBuffer::accepts( new InvalidArrayOffset( 'Cannot access value on variable ' . $array_var_id . ' ' . $used_offset diff --git a/tests/ArrayAccessTest.php b/tests/ArrayAccessTest.php index 445adc34c17..4fdc6375f64 100644 --- a/tests/ArrayAccessTest.php +++ b/tests/ArrayAccessTest.php @@ -1002,6 +1002,21 @@ function foo(?array $arr, string $s) : void { echo $arr[$s]["c"]; }' ], + 'TransformBadOffsetToGoodOnes' => [ + ' 5]; + + $_arr2 = []; + /** @psalm-suppress InvalidArrayOffset */ + $_arr2[$index] = 5;', + [ + '$_arr1===' => 'non-empty-array<1, 5>', + '$_arr2===' => 'non-empty-array<1, 5>', + ] + ], ]; }