1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Allow class constant as offset key (#5943)

* allow class-constant as an array offset when the type match

* remove object with __toString as a possible offset as it's not valid
This commit is contained in:
orklah 2021-06-18 00:15:57 +02:00 committed by GitHub
parent 872f1c232c
commit 79478a60b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 146 additions and 23 deletions

View File

@ -14,6 +14,7 @@ use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Issue\EmptyArrayAccess;
use Psalm\Issue\InvalidArrayAccess;
use Psalm\Issue\InvalidArrayAssignment;
@ -42,6 +43,7 @@ use Psalm\Node\VirtualIdentifier;
use Psalm\Node\VirtualName;
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TClassStringMap;
@ -450,7 +452,6 @@ class ArrayFetchAnalyzer
$non_array_types = [];
$has_valid_expected_offset = false;
$has_valid_absolute_offset = false;
$expected_offset_types = [];
$key_values = [];
@ -519,28 +520,11 @@ class ArrayFetchAnalyzer
}
if ($array_type->isArray()) {
foreach ($offset_type->getAtomicTypes() as $atomic_offset_type) {
if ($atomic_offset_type instanceof Type\Atomic\TFalse &&
$offset_type->ignore_falsable_issues === true
) {
//do nothing
} elseif ($atomic_offset_type instanceof Type\Atomic\TNull &&
$offset_type->ignore_nullable_issues === true
) {
//do nothing
} elseif ($atomic_offset_type instanceof Type\Atomic\TString ||
$atomic_offset_type instanceof Type\Atomic\TInt ||
$atomic_offset_type instanceof Type\Atomic\TArrayKey ||
$atomic_offset_type instanceof Type\Atomic\TMixed ||
$atomic_offset_type instanceof Type\Atomic\TTemplateParam ||
(
$atomic_offset_type instanceof Type\Atomic\TObjectWithProperties
&& isset($atomic_offset_type->methods['__toString'])
)
) {
$has_valid_absolute_offset = true;
}
}
$has_valid_absolute_offset = self::checkArrayOffsetType(
$offset_type,
$offset_type->getAtomicTypes(),
$codebase
);
if ($has_valid_absolute_offset === false) {
//we didn't find a single type that could be valid
@ -2069,4 +2053,74 @@ class ArrayFetchAnalyzer
}
}
}
/**
* @param Atomic[] $offset_types
*/
private static function checkArrayOffsetType(
Type\Union $offset_type,
array $offset_types,
\Psalm\Codebase $codebase
): bool {
$has_valid_absolute_offset = false;
foreach ($offset_types as $atomic_offset_type) {
if ($atomic_offset_type instanceof Type\Atomic\TClassConstant) {
$expanded = TypeExpander::expandAtomic(
$codebase,
$atomic_offset_type,
$atomic_offset_type->fq_classlike_name,
$atomic_offset_type->fq_classlike_name,
null,
true,
true
);
if ($expanded instanceof Atomic) {
$has_valid_absolute_offset = self::checkArrayOffsetType(
$offset_type,
[$expanded],
$codebase
);
} else {
$has_valid_absolute_offset = self::checkArrayOffsetType(
$offset_type,
$expanded,
$codebase
);
}
if ($has_valid_absolute_offset) {
break;
}
}
if ($atomic_offset_type instanceof Type\Atomic\TFalse &&
$offset_type->ignore_falsable_issues === true
) {
//do nothing
} elseif ($atomic_offset_type instanceof Type\Atomic\TNull &&
$offset_type->ignore_nullable_issues === true
) {
//do nothing
} elseif ($atomic_offset_type instanceof Type\Atomic\TString ||
$atomic_offset_type instanceof Type\Atomic\TInt ||
$atomic_offset_type instanceof Type\Atomic\TArrayKey ||
$atomic_offset_type instanceof Type\Atomic\TMixed
) {
$has_valid_absolute_offset = true;
break;
} elseif ($atomic_offset_type instanceof Type\Atomic\TTemplateParam) {
$has_valid_absolute_offset = self::checkArrayOffsetType(
$offset_type,
$atomic_offset_type->as->getAtomicTypes(),
$codebase
);
if ($has_valid_absolute_offset) {
break;
}
}
}
return $has_valid_absolute_offset;
}
}

View File

@ -3,7 +3,9 @@
namespace Psalm\Internal\Type\Comparator;
use Psalm\Codebase;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TMixed;
@ -67,6 +69,26 @@ class UnionTypeComparator
continue;
}
if ($input_type_part instanceof Type\Atomic\TClassConstant) {
$expanded = TypeExpander::expandAtomic(
$codebase,
$input_type_part,
$input_type_part->fq_classlike_name,
$input_type_part->fq_classlike_name,
null,
true,
true
);
if ($expanded instanceof Atomic) {
$input_atomic_types[] = $expanded;
} else {
$input_atomic_types = array_merge($expanded, $input_atomic_types);
}
continue;
}
$type_match_found = false;
$scalar_type_match_found = false;
$all_to_string_cast = true;

View File

@ -1579,6 +1579,37 @@ class ArrayAssignmentTest extends TestCase
return (array)$a;
}',
],
'ClassConstantAsKey' => [
'<?php
/**
* @property Foo::C_* $aprop
*/
class Foo {
public const C_ONE = 1;
public const C_TWO = 2;
public function __get(string $prop) {
if ($prop === "aprop")
return self::C_ONE;
throw new \RuntimeException("Unsupported property: $prop");
}
/** @return array<Foo::C_*, string> */
public static function getNames(): array {
return [
self::C_ONE => "One",
self::C_TWO => "Two",
];
}
public function getThisName(): string {
$names = self::getNames();
$aprop = $this->aprop;
return $names[$aprop];
}
}',
],
];
}
@ -1991,6 +2022,22 @@ class ArrayAssignmentTest extends TestCase
}',
'error_message' => 'InvalidArrayOffset'
],
'TemplateAsKey' => [
'<?php
class Foo {
/**
* @psalm-template T of array
* @param T $offset
* @param array<array, string> $weird_array
*/
public function getThisName($offset, $weird_array): string {
return $weird_array[$offset];
}
}',
'error_message' => 'InvalidArrayOffset'
],
];
}
}