1
0
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:
Matthew Brown 2020-02-02 12:25:24 -05:00
parent f981c61990
commit 8db4bc7691
39 changed files with 138 additions and 68 deletions

View File

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

View File

@ -778,7 +778,7 @@ abstract class Atomic
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return $this->__toString();
}

View File

@ -162,7 +162,7 @@ trait CallableTrait
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
$param_string = '';
$return_type_string = '';

View File

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

View File

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

View File

@ -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(
/**

View File

@ -11,7 +11,7 @@ class TCallableString extends TString
return 'callable-string';
}
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -42,7 +42,7 @@ class TClassString extends TString
return $this->getKey();
}
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

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

View File

@ -22,7 +22,7 @@ class TClosedResource extends \Psalm\Type\Atomic
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'closed-resource';
}

View File

@ -6,7 +6,7 @@ class TEmptyMixed extends TMixed
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'empty-mixed';
}

View File

@ -6,7 +6,7 @@ class TEmptyNumeric extends TNumeric
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'empty-numeric';
}

View File

@ -6,7 +6,7 @@ class TEmptyScalar extends TScalar
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'empty-scalar';
}

View File

@ -11,7 +11,7 @@ class THtmlEscapedString extends TString
return 'html-escaped-string';
}
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -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) {

View File

@ -43,7 +43,7 @@ class TKeyOfClassConstant extends Scalar
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -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() . '>';

View File

@ -61,7 +61,7 @@ class TLiteralClassString extends TLiteralString
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return $this->value . '::class';
}

View File

@ -25,7 +25,7 @@ class TLiteralFloat extends TFloat
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'float(' . $this->value . ')';
}

View File

@ -25,7 +25,7 @@ class TLiteralInt extends TInt
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'int(' . $this->value . ')';
}

View File

@ -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) {

View File

@ -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;
}
/**

View File

@ -6,7 +6,7 @@ class TNonEmptyMixed extends TMixed
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'non-empty-mixed';
}

View File

@ -6,7 +6,7 @@ class TNonEmptyScalar extends TScalar
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'non-empty-scalar';
}

View File

@ -9,7 +9,7 @@ class TNonEmptyString extends TString
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return 'non-empty-string';
}

View File

@ -11,7 +11,7 @@ class TNumericString extends TString
return 'numeric-string';
}
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -82,7 +82,7 @@ class TObjectWithProperties extends TObject
. '}' . $extra_types;
}
public function getId()
public function getId(bool $nested = false)
{
$extra_types = '';

View File

@ -41,7 +41,7 @@ class TScalarClassConstant extends Scalar
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -47,7 +47,7 @@ class TTemplateIndexedAccess extends \Psalm\Type\Atomic
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -40,7 +40,7 @@ class TTemplateKeyOf extends TArrayKey
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -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 ? ')' : '');
}
/**

View File

@ -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 . '>';
}

View File

@ -19,7 +19,7 @@ class TTraitString extends TString
return $this->getKey();
}
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

@ -38,7 +38,7 @@ class TValueOfClassConstant extends \Psalm\Type\Atomic
/**
* @return string
*/
public function getId()
public function getId(bool $nested = false)
{
return $this->getKey();
}

View File

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

View File

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

View File

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

View File

@ -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
/**

View File

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