1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Merge branch 'master' into fix-shepherd-without-custom-domain

This commit is contained in:
Alies Lapatsin 2023-01-22 13:03:40 +01:00 committed by GitHub
commit 73f90229bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 165 additions and 70 deletions

View File

@ -62,7 +62,9 @@ use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union; use Psalm\Type\Union;
use function array_filter;
use function array_keys; use function array_keys;
use function array_map;
use function array_search; use function array_search;
use function count; use function count;
use function in_array; use function in_array;
@ -70,6 +72,8 @@ use function is_int;
use function is_string; use function is_string;
use function strtolower; use function strtolower;
use const ARRAY_FILTER_USE_KEY;
/** /**
* @internal * @internal
*/ */
@ -198,7 +202,7 @@ class AtomicPropertyFetchAnalyzer
]), ]),
); );
} elseif ($prop_name === 'value' && $class_storage->enum_type !== null && $class_storage->enum_cases) { } elseif ($prop_name === 'value' && $class_storage->enum_type !== null && $class_storage->enum_cases) {
self::handleEnumValue($statements_analyzer, $stmt, $class_storage); self::handleEnumValue($statements_analyzer, $stmt, $stmt_var_type, $class_storage);
} elseif ($prop_name === 'name') { } elseif ($prop_name === 'name') {
self::handleEnumName($statements_analyzer, $stmt, $lhs_type_part); self::handleEnumName($statements_analyzer, $stmt, $lhs_type_part);
} else { } else {
@ -941,11 +945,31 @@ class AtomicPropertyFetchAnalyzer
private static function handleEnumValue( private static function handleEnumValue(
StatementsAnalyzer $statements_analyzer, StatementsAnalyzer $statements_analyzer,
PropertyFetch $stmt, PropertyFetch $stmt,
Union $stmt_var_type,
ClassLikeStorage $class_storage ClassLikeStorage $class_storage
): void { ): void {
$relevant_enum_cases = array_filter(
$stmt_var_type->getAtomicTypes(),
static fn(Atomic $type): bool => $type instanceof TEnumCase,
);
$relevant_enum_case_names = array_map(
static fn(TEnumCase $enumCase): string => $enumCase->case_name,
$relevant_enum_cases,
);
$enum_cases = $class_storage->enum_cases;
if (!empty($relevant_enum_case_names)) {
// If we have a known subset of enum cases, include only those
$enum_cases = array_filter(
$enum_cases,
static fn(string $key) => in_array($key, $relevant_enum_case_names, true),
ARRAY_FILTER_USE_KEY,
);
}
$case_values = []; $case_values = [];
foreach ($class_storage->enum_cases as $enum_case) { foreach ($enum_cases as $enum_case) {
if (is_string($enum_case->value)) { if (is_string($enum_case->value)) {
$case_values[] = new TLiteralString($enum_case->value); $case_values[] = new TLiteralString($enum_case->value);
} elseif (is_int($enum_case->value)) { } elseif (is_int($enum_case->value)) {
@ -956,7 +980,6 @@ class AtomicPropertyFetchAnalyzer
} }
} }
// todo: this is suboptimal when we reference enum directly, e.g. Status::Open->value
/** @psalm-suppress ArgumentTypeCoercion */ /** @psalm-suppress ArgumentTypeCoercion */
$statements_analyzer->node_data->setType( $statements_analyzer->node_data->setType(
$stmt, $stmt,

View File

@ -36,17 +36,19 @@ class DocCommentTest extends BaseTestCase
], ],
); );
$expectedDoc = '/** $expectedDoc = <<<'PHP'
* some desc /**
* * some desc
* @param string $bli *
* @param int $bla * @param string $bli
* * @param int $bla
* @throws \Exception *
* * @throws \Exception
* @return bool *
*/ * @return bool
'; */
PHP;
$this->assertSame($expectedDoc, $docComment->render('')); $this->assertSame($expectedDoc, $docComment->render(''));
} }
@ -74,15 +76,17 @@ class DocCommentTest extends BaseTestCase
], ],
); );
$expectedDoc = '/** $expectedDoc = <<<'PHP'
* some desc /**
* * some desc
* @param string $bli *
* @param int $bla * @param string $bli
* @throws \Exception * @param int $bla
* @return bool * @throws \Exception
*/ * @return bool
'; */
PHP;
$this->assertSame($expectedDoc, $docComment->render('')); $this->assertSame($expectedDoc, $docComment->render(''));
} }
@ -110,17 +114,19 @@ class DocCommentTest extends BaseTestCase
], ],
); );
$expectedDoc = '/** $expectedDoc = <<<'PHP'
* some desc /**
* * some desc
* @param string $bli *
* @param int $bla * @param string $bli
* * @param int $bla
* @throws \Exception *
* * @throws \Exception
* @return bool *
*/ * @return bool
'; */
PHP;
$this->assertSame($expectedDoc, $docComment->render('')); $this->assertSame($expectedDoc, $docComment->render(''));
} }
@ -129,17 +135,19 @@ class DocCommentTest extends BaseTestCase
{ {
ParsedDocblock::addNewLineBetweenAnnotations(true); ParsedDocblock::addNewLineBetweenAnnotations(true);
$expectedDoc = '/** $expectedDoc = <<<'PHP'
* some desc /**
* * some desc
* @param string $bli *
* @param int $bla * @param string $bli
* * @param int $bla
* @throws \Exception *
* * @throws \Exception
* @return bool *
*/ * @return bool
'; */
PHP;
$docComment = DocComment::parsePreservingLength( $docComment = DocComment::parsePreservingLength(
new Doc($expectedDoc), new Doc($expectedDoc),
); );
@ -151,17 +159,21 @@ class DocCommentTest extends BaseTestCase
{ {
ParsedDocblock::addNewLineBetweenAnnotations(true); ParsedDocblock::addNewLineBetweenAnnotations(true);
$expectedDoc = '/** $expectedDoc = <<<'PHP'
* some desc /**
* * some desc
* @param string $bli *
* @param int $bla * @param string $bli
* * @param int $bla
* @throws \Exception *
* * @throws \Exception
* @return bool *
*/ * @return bool
'; */
PHP
. " ";
$docComment = DocComment::parsePreservingLength( $docComment = DocComment::parsePreservingLength(
new Doc($expectedDoc), new Doc($expectedDoc),
); );
@ -173,19 +185,21 @@ class DocCommentTest extends BaseTestCase
{ {
ParsedDocblock::addNewLineBetweenAnnotations(true); ParsedDocblock::addNewLineBetweenAnnotations(true);
$expectedDoc = '/** $expectedDoc = <<<'PHP'
* some self-referential desc with " * @return bool /**
* " as part of it. * some self-referential desc with " * @return bool
* * " as part of it.
* @param string $bli *
* @param string $bli_this_suffix_is_kept * @param string $bli
* @param int $bla * @param string $bli_this_suffix_is_kept
* * @param int $bla
* @throws \Exception *
* * @throws \Exception
* @return bool *
*/ * @return bool
'; */
PHP;
$docComment = DocComment::parsePreservingLength( $docComment = DocComment::parsePreservingLength(
new Doc($expectedDoc), new Doc($expectedDoc),
); );

View File

@ -94,12 +94,70 @@ class EnumTest extends TestCase
$z = Mask::Two->value; $z = Mask::Two->value;
', ',
'assertions' => [ 'assertions' => [
// xxx: we should be able to do better when we reference a case explicitly, like above '$z===' => '2',
],
'ignored_issues' => [],
'php_version' => '8.1',
],
'EnumCaseValue #8568' => [
'code' => '<?php
enum Mask: int {
case One = 1 << 0;
case Two = 1 << 1;
}
/** @return Mask */
function a() {
return Mask::One;
}
$z = a()->value;
',
'assertions' => [
'$z===' => '1|2', '$z===' => '1|2',
], ],
'ignored_issues' => [], 'ignored_issues' => [],
'php_version' => '8.1', 'php_version' => '8.1',
], ],
'EnumUnionAsCaseValue #8568' => [
'code' => '<?php
enum Mask: int {
case One = 1 << 0;
case Two = 1 << 1;
case Four = 1 << 2;
}
/** @return Mask::One|Mask::Two */
function a() {
return Mask::One;
}
$z = a()->value;
',
'assertions' => [
'$z===' => '1|2',
],
'ignored_issues' => [],
'php_version' => '8.1',
],
'matchCaseOnEnumValue #8812' => [
'code' => '<?php
enum SomeType: string
{
case FOO = "FOO";
case BAR = "BAR";
}
function getSomething(string $moduleString): int
{
return match ($moduleString) {
SomeType::FOO->value => 1,
SomeType::BAR->value => 2,
};
}
',
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.1',
],
'namePropertyFromOutside' => [ 'namePropertyFromOutside' => [
'code' => '<?php 'code' => '<?php
enum Status enum Status