1
0
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:
Pete Walker 2022-01-21 17:31:48 +00:00
parent a3bdf1ba8c
commit 299eca4daa
No known key found for this signature in database
GPG Key ID: 2B13EAD6AA88F7F5
5 changed files with 214 additions and 9 deletions

View File

@ -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;

View File

@ -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,

View File

@ -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);
}

View File

@ -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
View 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'
],
];
}
}