mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Improve formatting of intersection error messages
Also reduce false positives on intersections of templates
This commit is contained in:
parent
f981c61990
commit
8db4bc7691
@ -597,7 +597,25 @@ class TypeAnalyzer
|
||||
if ($intersection_container_type instanceof TTemplateParam
|
||||
&& $intersection_input_type instanceof TTemplateParam
|
||||
) {
|
||||
if ($intersection_container_type->param_name !== $intersection_input_type->param_name) {
|
||||
if ($intersection_container_type->param_name !== $intersection_input_type->param_name
|
||||
|| ((string)$intersection_container_type->defining_class
|
||||
!== (string)$intersection_input_type->defining_class
|
||||
&& \substr($intersection_input_type->defining_class, 0, 3) !== 'fn-'
|
||||
&& \substr($intersection_container_type->defining_class, 0, 3) !== 'fn-')
|
||||
) {
|
||||
if (\substr($intersection_input_type->defining_class, 0, 3) !== 'fn-') {
|
||||
$input_class_storage = $codebase->classlike_storage_provider->get(
|
||||
$intersection_input_type->defining_class
|
||||
);
|
||||
|
||||
if (isset($input_class_storage->template_type_extends
|
||||
[$intersection_container_type->defining_class]
|
||||
[$intersection_container_type->param_name])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -668,28 +686,12 @@ class TypeAnalyzer
|
||||
?TypeComparisonResult $atomic_comparison_result = null
|
||||
) : bool {
|
||||
if ($container_type_part instanceof TTemplateParam && $input_type_part instanceof TTemplateParam) {
|
||||
if ($container_type_part->param_name !== $input_type_part->param_name
|
||||
|| ((string)$container_type_part->defining_class !== (string)$input_type_part->defining_class
|
||||
&& \substr($input_type_part->defining_class, 0, 3) !== 'fn-'
|
||||
&& \substr($container_type_part->defining_class, 0, 3) !== 'fn-')
|
||||
) {
|
||||
if (\substr($input_type_part->defining_class, 0, 3) !== 'fn-') {
|
||||
$input_class_storage = $codebase->classlike_storage_provider->get(
|
||||
$input_type_part->defining_class
|
||||
);
|
||||
|
||||
if (isset($input_class_storage->template_type_extends
|
||||
[$container_type_part->defining_class]
|
||||
[$container_type_part->param_name])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return self::isObjectContainedByObject(
|
||||
$codebase,
|
||||
$container_type_part,
|
||||
$input_type_part,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TMixed
|
||||
|
@ -778,7 +778,7 @@ abstract class Atomic
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->__toString();
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ trait CallableTrait
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
$param_string = '';
|
||||
$return_type_string = '';
|
||||
|
@ -45,7 +45,7 @@ trait GenericTrait
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
$s = '';
|
||||
foreach ($this->type_params as $type_param) {
|
||||
@ -55,7 +55,15 @@ trait GenericTrait
|
||||
$extra_types = '';
|
||||
|
||||
if ($this instanceof TNamedObject && $this->extra_types) {
|
||||
$extra_types = '&' . implode('&', $this->extra_types);
|
||||
$extra_types = '&' . implode(
|
||||
'&',
|
||||
array_map(
|
||||
function ($type) {
|
||||
return $type->getId(true);
|
||||
},
|
||||
$this->extra_types
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types;
|
||||
|
@ -30,7 +30,7 @@ class GetClassT extends TString
|
||||
$this->as_type = $as_type;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'class-string';
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ class ObjectLike extends \Psalm\Type\Atomic
|
||||
return static::KEY . '{' . implode(', ', $union_type_parts) . '}';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
$union_type_parts = array_map(
|
||||
/**
|
||||
|
@ -11,7 +11,7 @@ class TCallableString extends TString
|
||||
return 'callable-string';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class TClassString extends TString
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class TClassStringMap extends \Psalm\Type\Atomic
|
||||
. '>';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
/** @psalm-suppress MixedOperand */
|
||||
return static::KEY
|
||||
|
@ -22,7 +22,7 @@ class TClosedResource extends \Psalm\Type\Atomic
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'closed-resource';
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class TEmptyMixed extends TMixed
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'empty-mixed';
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class TEmptyNumeric extends TNumeric
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'empty-numeric';
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class TEmptyScalar extends TScalar
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'empty-scalar';
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class THtmlEscapedString extends TString
|
||||
return 'html-escaped-string';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class TIterable extends Atomic
|
||||
return 'iterable';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
$s = '';
|
||||
foreach ($this->type_params as $type_param) {
|
||||
|
@ -43,7 +43,7 @@ class TKeyOfClassConstant extends Scalar
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class TList extends \Psalm\Type\Atomic
|
||||
return static::KEY . '<' . $this->type_param . '>';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
/** @psalm-suppress MixedOperand */
|
||||
return static::KEY . '<' . $this->type_param->getId() . '>';
|
||||
|
@ -61,7 +61,7 @@ class TLiteralClassString extends TLiteralString
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->value . '::class';
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class TLiteralFloat extends TFloat
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'float(' . $this->value . ')';
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class TLiteralInt extends TInt
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'int(' . $this->value . ')';
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class TLiteralString extends TString
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
$no_newline_value = preg_replace("/\n/m", '\n', $this->value);
|
||||
if (strlen($this->value) > 80) {
|
||||
|
@ -8,6 +8,7 @@ use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic;
|
||||
use function substr;
|
||||
use function array_map;
|
||||
|
||||
class TNamedObject extends Atomic
|
||||
{
|
||||
@ -47,9 +48,21 @@ class TNamedObject extends Atomic
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
if ($this->extra_types) {
|
||||
return $this->value . '&' . implode(
|
||||
'&',
|
||||
array_map(
|
||||
function ($type) {
|
||||
return $type->getId(true);
|
||||
},
|
||||
$this->extra_types
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,7 @@ class TNonEmptyMixed extends TMixed
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'non-empty-mixed';
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class TNonEmptyScalar extends TScalar
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'non-empty-scalar';
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class TNonEmptyString extends TString
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'non-empty-string';
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class TNumericString extends TString
|
||||
return 'numeric-string';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ class TObjectWithProperties extends TObject
|
||||
. '}' . $extra_types;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
$extra_types = '';
|
||||
|
||||
|
@ -41,7 +41,7 @@ class TScalarClassConstant extends Scalar
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class TTemplateIndexedAccess extends \Psalm\Type\Atomic
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class TTemplateKeyOf extends TArrayKey
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Union;
|
||||
use Psalm\Storage\MethodStorage;
|
||||
use function array_map;
|
||||
|
||||
class TTemplateParam extends \Psalm\Type\Atomic
|
||||
{
|
||||
@ -63,16 +64,18 @@ class TTemplateParam extends \Psalm\Type\Atomic
|
||||
return $this->as->getId();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
if ($this->extra_types) {
|
||||
return '(' . $this->param_name . ' as ' . $this->as->getId()
|
||||
. ')&' . implode('&', $this->extra_types);
|
||||
return '(' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as->getId()
|
||||
. ')&' . implode('&', array_map(function ($type) {
|
||||
return $type->getId(true);
|
||||
}, $this->extra_types));
|
||||
}
|
||||
|
||||
return $this->param_name
|
||||
return ($nested ? '(' : '') . $this->param_name
|
||||
. ':' . $this->defining_class
|
||||
. ' as ' . $this->as->getId();
|
||||
. ' as ' . $this->as->getId() . ($nested ? ')' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,7 +54,7 @@ class TTemplateParamClass extends TClassString
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return 'class-string<' . $this->param_name . ' as ' . $this->as . '>';
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class TTraitString extends TString
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class TValueOfClassConstant extends \Psalm\Type\Atomic
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = false)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
@ -214,7 +214,9 @@ class Union
|
||||
$this->literal_string_types[$key] = $type;
|
||||
} elseif ($type instanceof TLiteralFloat) {
|
||||
$this->literal_float_types[$key] = $type;
|
||||
} elseif ($type instanceof Type\Atomic\TClassString && $type->as_type) {
|
||||
} elseif ($type instanceof Type\Atomic\TClassString
|
||||
&& ($type->as_type || $type instanceof Type\Atomic\TTemplateParamClass)
|
||||
) {
|
||||
$this->typed_class_strings[$key] = $type;
|
||||
}
|
||||
|
||||
@ -262,7 +264,7 @@ class Union
|
||||
unset($this->literal_string_types[$key], $this->types[$key]);
|
||||
}
|
||||
if (!$type instanceof Type\Atomic\TClassString
|
||||
|| !$type->as_type
|
||||
|| (!$type->as_type && !$type instanceof Type\Atomic\TTemplateParamClass)
|
||||
) {
|
||||
foreach ($this->typed_class_strings as $key => $_) {
|
||||
unset($this->typed_class_strings[$key], $this->types[$key]);
|
||||
@ -297,7 +299,9 @@ class Union
|
||||
$this->literal_string_types[$key] = $type;
|
||||
} elseif ($type instanceof TLiteralFloat) {
|
||||
$this->literal_float_types[$key] = $type;
|
||||
} elseif ($type instanceof Type\Atomic\TClassString && $type->as_type) {
|
||||
} elseif ($type instanceof Type\Atomic\TClassString
|
||||
&& ($type->as_type || $type instanceof Type\Atomic\TTemplateParamClass)
|
||||
) {
|
||||
$this->typed_class_strings[$key] = $type;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class TSqlSelectString extends \Psalm\Type\Atomic\TLiteralString
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(bool $nested = true)
|
||||
{
|
||||
return 'sql-select-string(' . $this->value . ')';
|
||||
}
|
||||
|
@ -2385,7 +2385,7 @@ class ClassTemplateTest extends TestCase
|
||||
type($closure);
|
||||
}
|
||||
}',
|
||||
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, callable(State):(T as mixed)&Foo provided',
|
||||
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:20:34 - Argument 1 of type expects string, callable(State):(T:AlmostFooMap as mixed)&Foo provided',
|
||||
],
|
||||
'templateWithNoReturn' => [
|
||||
'<?php
|
||||
|
@ -215,7 +215,7 @@ class FunctionClassStringTemplateTest extends TestCase
|
||||
$d->faa();
|
||||
}',
|
||||
],
|
||||
'templateArrayIntersection' => [
|
||||
'templateFilterArrayWithIntersection' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T as object
|
||||
@ -244,6 +244,34 @@ class FunctionClassStringTemplateTest extends TestCase
|
||||
'$y' => 'array<array-key, A&B>',
|
||||
],
|
||||
],
|
||||
'templateFilterWithIntersection' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T as object
|
||||
* @template S as object
|
||||
* @param T $item
|
||||
* @param class-string<S> $type
|
||||
* @return T&S
|
||||
*/
|
||||
function filter($item, string $type) {
|
||||
if (is_a($item, $type)) {
|
||||
return $item;
|
||||
};
|
||||
|
||||
throw new \UnexpectedValueException("bad");
|
||||
}
|
||||
|
||||
interface A {}
|
||||
interface B {}
|
||||
|
||||
/** @var A */
|
||||
$x = null;
|
||||
|
||||
$y = filter($x, B::class);',
|
||||
[
|
||||
'$y' => 'A&B',
|
||||
],
|
||||
],
|
||||
'unionTOrClassStringTPassedClassString' => [
|
||||
'<?php
|
||||
/**
|
||||
|
@ -918,6 +918,18 @@ class FunctionTemplateTest extends TestCase
|
||||
return array_map("from_other", $a);
|
||||
}'
|
||||
],
|
||||
'templateFlipIntersection' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T as object
|
||||
* @template S as object
|
||||
* @param S&T $item
|
||||
* @return T&S
|
||||
*/
|
||||
function filter(object $item) {
|
||||
return $item;
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user