mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
fix: Add PHP version checks / more tests
This commit is contained in:
parent
a3bdf1ba8c
commit
299eca4daa
@ -1488,15 +1488,6 @@ class ClassLikeNodeScanner
|
||||
$parser_property_type = $stmt->type;
|
||||
/** @var Identifier|IntersectionType|Name|NullableType|UnionType $parser_property_type */
|
||||
|
||||
$signature_type = TypeHintResolver::resolve(
|
||||
$parser_property_type,
|
||||
$this->codebase,
|
||||
$this->file_storage,
|
||||
$this->storage,
|
||||
$this->aliases,
|
||||
$this->codebase->analysis_php_version_id
|
||||
);
|
||||
|
||||
$signature_type_location = new CodeLocation(
|
||||
$this->file_scanner,
|
||||
$parser_property_type,
|
||||
@ -1504,6 +1495,16 @@ class ClassLikeNodeScanner
|
||||
false,
|
||||
CodeLocation::FUNCTION_RETURN_TYPE
|
||||
);
|
||||
|
||||
$signature_type = TypeHintResolver::resolve(
|
||||
$parser_property_type,
|
||||
$signature_type_location,
|
||||
$this->codebase,
|
||||
$this->file_storage,
|
||||
$this->storage,
|
||||
$this->aliases,
|
||||
$this->codebase->analysis_php_version_id
|
||||
);
|
||||
}
|
||||
|
||||
$doc_var_group_type = $var_comment->type ?? null;
|
||||
|
@ -430,6 +430,10 @@ class FunctionLikeNodeScanner
|
||||
|
||||
$storage->return_type = TypeHintResolver::resolve(
|
||||
$original_type,
|
||||
new CodeLocation(
|
||||
$this->file_scanner,
|
||||
$original_type
|
||||
),
|
||||
$this->codebase,
|
||||
$this->file_storage,
|
||||
$this->classlike_storage,
|
||||
@ -825,6 +829,10 @@ class FunctionLikeNodeScanner
|
||||
|
||||
$param_type = TypeHintResolver::resolve(
|
||||
$param_typehint,
|
||||
new CodeLocation(
|
||||
$this->file_scanner,
|
||||
$param_typehint
|
||||
),
|
||||
$this->codebase,
|
||||
$this->file_storage,
|
||||
$this->classlike_storage,
|
||||
|
@ -10,7 +10,10 @@ use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\UnionType;
|
||||
use Psalm\Aliases;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Issue\ParseError;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\ClassLikeStorage;
|
||||
use Psalm\Storage\FileStorage;
|
||||
use Psalm\Type;
|
||||
@ -31,6 +34,7 @@ class TypeHintResolver
|
||||
*/
|
||||
public static function resolve(
|
||||
PhpParser\NodeAbstract $hint,
|
||||
CodeLocation $code_location,
|
||||
Codebase $codebase,
|
||||
FileStorage $file_storage,
|
||||
?ClassLikeStorage $classlike_storage,
|
||||
@ -44,9 +48,19 @@ class TypeHintResolver
|
||||
throw new UnexpectedValueException('bad');
|
||||
}
|
||||
|
||||
if ($analysis_php_version_id < 8_00_00) {
|
||||
IssueBuffer::add(
|
||||
new ParseError(
|
||||
'Union types are not supported in PHP < 8',
|
||||
$code_location
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($hint->types as $atomic_typehint) {
|
||||
$resolved_type = self::resolve(
|
||||
$atomic_typehint,
|
||||
$code_location,
|
||||
$codebase,
|
||||
$file_storage,
|
||||
$classlike_storage,
|
||||
@ -67,9 +81,20 @@ class TypeHintResolver
|
||||
throw new UnexpectedValueException('bad');
|
||||
}
|
||||
|
||||
if ($analysis_php_version_id < 8_10_00) {
|
||||
IssueBuffer::add(
|
||||
new ParseError(
|
||||
'Intersection types are not supported in PHP < 8.1',
|
||||
$code_location
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($hint->types as $atomic_typehint) {
|
||||
$resolved_type = self::resolve(
|
||||
$atomic_typehint,
|
||||
$code_location,
|
||||
$codebase,
|
||||
$file_storage,
|
||||
$classlike_storage,
|
||||
@ -77,6 +102,16 @@ class TypeHintResolver
|
||||
$analysis_php_version_id
|
||||
);
|
||||
|
||||
if ($resolved_type->hasScalarType()) {
|
||||
IssueBuffer::add(
|
||||
new ParseError(
|
||||
'Intersection types cannot contain scalar types',
|
||||
$code_location
|
||||
)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = Type::intersectUnionTypes($resolved_type, $type, $codebase);
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,48 @@ class NativeIntersectionsTest extends TestCase
|
||||
'ignored_issues' => [],
|
||||
'php_version' => '8.1'
|
||||
],
|
||||
'intersectionsNotAllowedWithUnions' => [
|
||||
'<?php
|
||||
interface A {
|
||||
}
|
||||
interface B {
|
||||
}
|
||||
interface C {
|
||||
}
|
||||
function foo (A&B|C $test): A&B|C {
|
||||
return $test;
|
||||
}',
|
||||
'error_message' => 'ParseError',
|
||||
[],
|
||||
false,
|
||||
'8.1'
|
||||
],
|
||||
'intersectionsNotAllowedWithNonClasses' => [
|
||||
'<?php
|
||||
interface A {
|
||||
}
|
||||
function foo (A&string $test): A&string {
|
||||
return $test;
|
||||
}',
|
||||
'error_message' => 'ParseError',
|
||||
[],
|
||||
false,
|
||||
'8.1'
|
||||
],
|
||||
'intersectionsNotAllowedInPHP80' => [
|
||||
'<?php
|
||||
interface A {
|
||||
}
|
||||
interface B {
|
||||
}
|
||||
function foo (A&B $test): A&B {
|
||||
return $test;
|
||||
}',
|
||||
'error_message' => 'ParseError',
|
||||
[],
|
||||
false,
|
||||
'8.0'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
119
tests/NativeUnionsTest.php
Normal file
119
tests/NativeUnionsTest.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Tests;
|
||||
|
||||
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
|
||||
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
|
||||
|
||||
class NativeUnionsTest extends TestCase
|
||||
{
|
||||
use InvalidCodeAnalysisTestTrait;
|
||||
use ValidCodeAnalysisTestTrait;
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[],php_version?:string}>
|
||||
*/
|
||||
public function providerValidCodeParse(): iterable
|
||||
{
|
||||
return [
|
||||
'nativeTypeUnionInConstructor' => [
|
||||
'<?php
|
||||
interface A {
|
||||
}
|
||||
interface B {
|
||||
}
|
||||
class Foo {
|
||||
public function __construct(private A|B $self) {}
|
||||
|
||||
public function self(): A|B
|
||||
{
|
||||
return $this->self;
|
||||
}
|
||||
}',
|
||||
'assertions' => [],
|
||||
'error_levels' => [],
|
||||
'php_version' => '8.0'
|
||||
],
|
||||
'nativeTypeUnionAsArgument' => [
|
||||
'<?php
|
||||
interface A {
|
||||
function foo(): void;
|
||||
}
|
||||
interface B {
|
||||
function foo(): void;
|
||||
}
|
||||
class C implements A {
|
||||
function foo(): void {
|
||||
}
|
||||
}
|
||||
function test(A|B $in): void {
|
||||
$in->foo();
|
||||
}
|
||||
test(new C());
|
||||
',
|
||||
'assertions' => [],
|
||||
'error_levels' => [],
|
||||
'php_version' => '8.0'
|
||||
],
|
||||
'unionAndNullableEquivalent' => [
|
||||
'<?php
|
||||
function test(string|null $in): ?string {
|
||||
return $in;
|
||||
}
|
||||
',
|
||||
'assertions' => [],
|
||||
'error_levels' => [],
|
||||
'php_version' => '8.0'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
|
||||
*/
|
||||
public function providerInvalidCodeParse(): iterable
|
||||
{
|
||||
return [
|
||||
'invalidNativeUnionArgument' => [
|
||||
'<?php
|
||||
function test(string|null $in): string|null {
|
||||
return $in;
|
||||
}
|
||||
test(2);
|
||||
',
|
||||
'error_message' => 'InvalidScalarArgument',
|
||||
[],
|
||||
false,
|
||||
'8.0'
|
||||
],
|
||||
'mismatchDocblockNativeUnionArgument' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param string|null $in
|
||||
*/
|
||||
function test(int|bool $in): bool {
|
||||
return !!$in;
|
||||
}
|
||||
',
|
||||
'error_message' => 'MismatchingDocblockParamType',
|
||||
[],
|
||||
false,
|
||||
'8.0'
|
||||
],
|
||||
'unionsNotAllowedInPHP74' => [
|
||||
'<?php
|
||||
interface A {
|
||||
}
|
||||
interface B {
|
||||
}
|
||||
function foo (A|B $test): A&B {
|
||||
return $test;
|
||||
}',
|
||||
'error_message' => 'ParseError',
|
||||
[],
|
||||
false,
|
||||
'7.4'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user