1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Support PHP 8 union types

This commit is contained in:
Brown 2020-08-30 13:16:37 -04:00
parent 2f82f312b2
commit f34e54ec41
2 changed files with 90 additions and 95 deletions

View File

@ -2259,38 +2259,9 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$parser_return_type = $stmt->getReturnType();
if ($parser_return_type) {
$suffix = '';
$original_type = $parser_return_type;
if ($parser_return_type instanceof PhpParser\Node\NullableType) {
$suffix = '|null';
$parser_return_type = $parser_return_type->type;
}
if ($parser_return_type instanceof PhpParser\Node\Identifier) {
$return_type_string = $parser_return_type->name . $suffix;
} elseif ($parser_return_type instanceof PhpParser\Node\UnionType) {
// for now unsupported
$return_type_string = 'mixed';
} else {
$return_type_fq_classlike_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
$parser_return_type,
$this->aliases
);
if ($class_storage && !$class_storage->is_trait && $return_type_fq_classlike_name === 'self') {
$return_type_fq_classlike_name = $class_storage->name;
}
$return_type_string = $return_type_fq_classlike_name . $suffix;
}
$storage->return_type = Type::parseString(
$return_type_string,
[$this->php_major_version, $this->php_minor_version]
);
$storage->return_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
$storage->return_type = $this->getTypeFromTypeHint($parser_return_type);
$storage->return_type_location = new CodeLocation(
$this->file_scanner,
@ -3178,48 +3149,13 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$param_typehint = $param->type;
if ($param_typehint instanceof PhpParser\Node\NullableType) {
$is_nullable = true;
$param_typehint = $param_typehint->type;
}
if ($param_typehint) {
if ($param_typehint instanceof PhpParser\Node\Identifier) {
$param_type_string = $param_typehint->name;
} elseif ($param_typehint instanceof PhpParser\Node\Name\FullyQualified) {
$param_type_string = (string)$param_typehint;
$param_type = $this->getTypeFromTypeHint($param_typehint);
$this->codebase->scanner->queueClassLikeForScanning($param_type_string);
$this->file_storage->referenced_classlikes[strtolower($param_type_string)] = $param_type_string;
} elseif ($param_typehint instanceof PhpParser\Node\UnionType) {
// not yet supported
$param_type_string = 'mixed';
if ($is_nullable) {
$param_type->addType(new Type\Atomic\TNull);
} else {
if ($this->classlike_storages
&& strtolower($param_typehint->parts[0]) === 'self'
&& !end($this->classlike_storages)->is_trait
) {
$param_type_string = $this->fq_classlike_names[count($this->fq_classlike_names) - 1];
} else {
$param_type_string = ClassLikeAnalyzer::getFQCLNFromNameObject($param_typehint, $this->aliases);
}
if (!in_array(strtolower($param_type_string), ['self', 'static', 'parent'], true)) {
$this->codebase->scanner->queueClassLikeForScanning($param_type_string);
$this->file_storage->referenced_classlikes[strtolower($param_type_string)] = $param_type_string;
}
}
if ($param_type_string) {
$param_type = Type::parseString(
$param_type_string,
[$this->php_major_version, $this->php_minor_version],
[]
);
if ($is_nullable) {
$param_type->addType(new Type\Atomic\TNull);
}
$is_nullable = $param_type->isNullable();
}
}
@ -3268,6 +3204,74 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
);
}
/**
* @param PhpParser\Node\Identifier|PhpParser\Node\Name|PhpParser\Node\NullableType|PhpParser\Node\UnionType $hint
*/
private function getTypeFromTypeHint(PhpParser\NodeAbstract $hint) : Type\Union
{
if ($hint instanceof PhpParser\Node\UnionType) {
$type = null;
if (!$hint->types) {
throw new \UnexpectedValueException('bad');
}
foreach ($hint->types as $atomic_typehint) {
$resolved_type = $this->getTypeFromTypeHint($atomic_typehint);
if (!$type) {
$type = $resolved_type;
} else {
$type = Type::combineUnionTypes($resolved_type, $type);
}
}
return $type;
}
$is_nullable = false;
if ($hint instanceof PhpParser\Node\NullableType) {
$is_nullable = true;
$hint = $hint->type;
}
if ($hint instanceof PhpParser\Node\Identifier) {
$type_string = $hint->name;
} elseif ($hint instanceof PhpParser\Node\Name\FullyQualified) {
$type_string = (string)$hint;
$this->codebase->scanner->queueClassLikeForScanning($type_string);
$this->file_storage->referenced_classlikes[strtolower($type_string)] = $type_string;
} else {
if ($this->classlike_storages
&& strtolower($hint->parts[0]) === 'self'
&& !end($this->classlike_storages)->is_trait
) {
$type_string = $this->fq_classlike_names[count($this->fq_classlike_names) - 1];
} else {
$type_string = ClassLikeAnalyzer::getFQCLNFromNameObject($hint, $this->aliases);
}
if (!in_array(strtolower($type_string), ['self', 'static', 'parent'], true)) {
$this->codebase->scanner->queueClassLikeForScanning($type_string);
$this->file_storage->referenced_classlikes[strtolower($type_string)] = $type_string;
}
}
$type = Type::parseString(
$type_string,
[$this->php_major_version, $this->php_minor_version],
[]
);
if ($is_nullable) {
$type->addType(new Type\Atomic\TNull);
}
return $type;
}
/**
* @param FunctionLikeStorage $storage
* @param array<int, array{type:string,name:string,line_number:int,start:int,end:int}> $docblock_params
@ -3548,34 +3552,9 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$signature_type_location = null;
if ($stmt->type) {
$suffix = '';
$parser_property_type = $stmt->type;
if ($parser_property_type instanceof PhpParser\Node\NullableType) {
$suffix = '|null';
$parser_property_type = $parser_property_type->type;
}
if ($parser_property_type instanceof PhpParser\Node\Identifier) {
$property_type_string = $parser_property_type->name . $suffix;
} elseif ($parser_property_type instanceof PhpParser\Node\UnionType) {
// not yet supported
$property_type_string = 'mixed';
} else {
$property_type_fq_classlike_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
$parser_property_type,
$this->aliases
);
$property_type_string = $property_type_fq_classlike_name . $suffix;
}
$signature_type = Type::parseString(
$property_type_string,
[$this->php_major_version, $this->php_minor_version]
);
$signature_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
$signature_type = $this->getTypeFromTypeHint($stmt->type);
$signature_type_location = new CodeLocation(
$this->file_scanner,

View File

@ -2059,6 +2059,22 @@ class PropertyTypeTest extends TestCase
}
}'
],
'unionPropertyType' => [
'<?php
class A {
public string|int $i;
public function __construct() {
$this->i = 5;
$this->i = "hello";
}
}
$a = new A();
if ($a->i === 3) {}
if ($a->i === "foo") {}'
],
];
}