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

Fix #826 allow better template replacements

This commit is contained in:
Matt Brown 2018-06-19 13:19:34 -04:00
parent 89f78befa7
commit 11240eb193
15 changed files with 106 additions and 77 deletions

View File

@ -18,7 +18,7 @@ class CommentChecker
/**
* @param string $comment
* @param Aliases $aliases
* @param array<string, string>|null $template_types
* @param array<string, string>|null $template_type_names
* @param int|null $var_line_number
* @param int|null $came_from_line_number what line number in $source that $comment came from
*
@ -31,7 +31,7 @@ class CommentChecker
$comment,
FileSource $source,
Aliases $aliases,
array $template_types = null,
array $template_type_names = null,
$var_line_number = null,
$came_from_line_number = null
) {
@ -74,7 +74,7 @@ class CommentChecker
$var_type_tokens = Type::fixUpLocalType(
$line_parts[0],
$aliases,
$template_types
$template_type_names
);
} catch (TypeParseTreeException $e) {
throw new DocblockParseException($line_parts[0] . ' is not a valid type');
@ -94,7 +94,7 @@ class CommentChecker
}
try {
$defined_type = Type::parseTokens($var_type_tokens, false, $template_types ?: []);
$defined_type = Type::parseTokens($var_type_tokens, false, $template_type_names ?: []);
} catch (TypeParseTreeException $e) {
if (is_int($came_from_line_number)) {
throw new DocblockParseException(
@ -277,9 +277,12 @@ class CommentChecker
$template_type = preg_split('/[\s]+/', $template_line);
if (count($template_type) > 2 && in_array(strtolower($template_type[1]), ['as', 'super'], true)) {
$info->template_types[] = [$template_type[0], strtolower($template_type[1]), $template_type[2]];
$info->template_type_names[] = [
$template_type[0],
strtolower($template_type[1]), $template_type[2]
];
} else {
$info->template_types[] = [$template_type[0]];
$info->template_type_names[] = [$template_type[0]];
}
}
}
@ -371,9 +374,12 @@ class CommentChecker
$template_type = preg_split('/[\s]+/', $template_line);
if (count($template_type) > 2 && in_array(strtolower($template_type[1]), ['as', 'super'], true)) {
$info->template_types[] = [$template_type[0], strtolower($template_type[1]), $template_type[2]];
$info->template_type_names[] = [
$template_type[0],
strtolower($template_type[1]), $template_type[2]
];
} else {
$info->template_types[] = [$template_type[0]];
$info->template_type_names[] = [$template_type[0]];
}
}
}

View File

@ -661,19 +661,7 @@ class CallChecker
}
}
if ($generic_params) {
$existing_generic_params_to_strings = array_map(
/**
* @return string
*/
function (Type\Union $type) {
return (string) $type;
},
$generic_params
);
} else {
$existing_generic_params_to_strings = [];
}
$existing_generic_params_to_strings = $generic_params ?: [];
foreach ($args as $argument_offset => $arg) {
$function_param = count($function_params) > $argument_offset

View File

@ -159,7 +159,7 @@ class Reflection
);
if ($class_name_lower === 'generator') {
$storage->template_types = ['TKey' => 'mixed', 'TValue' => 'mixed'];
$storage->template_types = ['TKey' => Type::getMixed(), 'TValue' => Type::getMixed()];
}
$interfaces = $reflected_class->getInterfaces();

View File

@ -13,7 +13,7 @@ class ClassLikeDocblockComment
/**
* @var array<int, array<int, string>>
*/
public $template_types = [];
public $template_type_names = [];
/**
* @var array<int, string>

View File

@ -57,7 +57,7 @@ class FunctionDocblockComment
/**
* @var array<int, array<int, string>>
*/
public $template_types = [];
public $template_type_names = [];
/**
* @var array<int, array{template_type: string, param_name: string, line_number?: int}>

View File

@ -201,7 +201,7 @@ class ClassLikeStorage
public $overridden_property_ids = [];
/**
* @var array<string, string>|null
* @var array<string, Type\Union>|null
*/
public $template_types;

View File

@ -87,7 +87,7 @@ class FunctionLikeStorage
public $global_types = [];
/**
* @var array<string, string>|null
* @var array<string, Type\Union>|null
*/
public $template_types;

View File

@ -74,13 +74,13 @@ abstract class Type
*
* @param string $type_string
* @param bool $php_compatible
* @param array<string, string> $template_types
* @param array<string, string> $template_type_names
*
* @return Union
*/
public static function parseString($type_string, $php_compatible = false, array $template_types = [])
public static function parseString($type_string, $php_compatible = false, array $template_type_names = [])
{
return self::parseTokens(self::tokenize($type_string), $php_compatible, $template_types);
return self::parseTokens(self::tokenize($type_string), $php_compatible, $template_type_names);
}
/**
@ -88,11 +88,11 @@ abstract class Type
*
* @param array<int, string> $type_tokens
* @param bool $php_compatible
* @param array<string, string> $template_types
* @param array<string, string> $template_type_names
*
* @return Union
*/
public static function parseTokens(array $type_tokens, $php_compatible = false, array $template_types = [])
public static function parseTokens(array $type_tokens, $php_compatible = false, array $template_type_names = [])
{
if (count($type_tokens) === 1) {
$only_token = $type_tokens[0];
@ -104,12 +104,12 @@ abstract class Type
$only_token = self::fixScalarTerms($only_token, $php_compatible);
return new Union([Atomic::create($only_token, $php_compatible, $template_types)]);
return new Union([Atomic::create($only_token, $php_compatible, $template_type_names)]);
}
try {
$parse_tree = ParseTree::createFromTokens($type_tokens);
$parsed_type = self::getTypeFromTree($parse_tree, $php_compatible, $template_types);
$parsed_type = self::getTypeFromTree($parse_tree, $php_compatible, $template_type_names);
} catch (TypeParseTreeException $e) {
throw $e;
}
@ -168,11 +168,11 @@ abstract class Type
/**
* @param ParseTree $parse_tree
* @param bool $php_compatible
* @param array<string, string> $template_types
* @param array<string, string> $template_type_names
*
* @return Atomic|TArray|TGenericObject|ObjectLike|Union
*/
private static function getTypeFromTree(ParseTree $parse_tree, $php_compatible, array $template_types)
private static function getTypeFromTree(ParseTree $parse_tree, $php_compatible, array $template_type_names)
{
if ($parse_tree instanceof ParseTree\GenericTree) {
$generic_type = $parse_tree->value;
@ -181,8 +181,8 @@ abstract class Type
/**
* @return Union
*/
function (ParseTree $child_tree) use ($template_types) {
$tree_type = self::getTypeFromTree($child_tree, false, $template_types);
function (ParseTree $child_tree) use ($template_type_names) {
$tree_type = self::getTypeFromTree($child_tree, false, $template_type_names);
return $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
},
@ -215,10 +215,10 @@ abstract class Type
foreach ($parse_tree->children as $child_tree) {
if ($child_tree instanceof ParseTree\NullableTree) {
$atomic_type = self::getTypeFromTree($child_tree->children[0], false, $template_types);
$atomic_type = self::getTypeFromTree($child_tree->children[0], false, $template_type_names);
$has_null = true;
} else {
$atomic_type = self::getTypeFromTree($child_tree, false, $template_types);
$atomic_type = self::getTypeFromTree($child_tree, false, $template_type_names);
}
if (!$atomic_type instanceof Atomic) {
@ -242,8 +242,8 @@ abstract class Type
/**
* @return Atomic
*/
function (ParseTree $child_tree) use ($template_types) {
$atomic_type = self::getTypeFromTree($child_tree, false, $template_types);
function (ParseTree $child_tree) use ($template_type_names) {
$atomic_type = self::getTypeFromTree($child_tree, false, $template_type_names);
if (!$atomic_type instanceof Atomic) {
throw new TypeParseTreeException(
@ -277,11 +277,11 @@ abstract class Type
foreach ($parse_tree->children as $i => $property_branch) {
if (!$property_branch instanceof ParseTree\ObjectLikePropertyTree) {
$property_type = self::getTypeFromTree($property_branch, false, $template_types);
$property_type = self::getTypeFromTree($property_branch, false, $template_type_names);
$property_maybe_undefined = false;
$property_key = (string)$i;
} elseif (count($property_branch->children) === 1) {
$property_type = self::getTypeFromTree($property_branch->children[0], false, $template_types);
$property_type = self::getTypeFromTree($property_branch->children[0], false, $template_type_names);
$property_maybe_undefined = $property_branch->possibly_undefined;
$property_key = $property_branch->value;
} else {
@ -313,7 +313,7 @@ abstract class Type
}
if ($parse_tree instanceof ParseTree\CallableWithReturnTypeTree) {
$callable_type = self::getTypeFromTree($parse_tree->children[0], false, $template_types);
$callable_type = self::getTypeFromTree($parse_tree->children[0], false, $template_type_names);
if (!$callable_type instanceof TCallable && !$callable_type instanceof Type\Atomic\Fn) {
throw new \InvalidArgumentException('Parsing callable tree node should return TCallable');
@ -323,7 +323,7 @@ abstract class Type
throw new TypeParseTreeException('Invalid return type');
}
$return_type = self::getTypeFromTree($parse_tree->children[1], false, $template_types);
$return_type = self::getTypeFromTree($parse_tree->children[1], false, $template_type_names);
$callable_type->return_type = $return_type instanceof Union ? $return_type : new Union([$return_type]);
@ -335,16 +335,16 @@ abstract class Type
/**
* @return FunctionLikeParameter
*/
function (ParseTree $child_tree) use ($template_types) {
function (ParseTree $child_tree) use ($template_type_names) {
$is_variadic = false;
$is_optional = false;
if ($child_tree instanceof ParseTree\CallableParamTree) {
$tree_type = self::getTypeFromTree($child_tree->children[0], false, $template_types);
$tree_type = self::getTypeFromTree($child_tree->children[0], false, $template_type_names);
$is_variadic = $child_tree->variadic;
$is_optional = $child_tree->has_default;
} else {
$tree_type = self::getTypeFromTree($child_tree, false, $template_types);
$tree_type = self::getTypeFromTree($child_tree, false, $template_type_names);
}
$tree_type = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]);
@ -371,11 +371,11 @@ abstract class Type
}
if ($parse_tree instanceof ParseTree\EncapsulationTree) {
return self::getTypeFromTree($parse_tree->children[0], false, $template_types);
return self::getTypeFromTree($parse_tree->children[0], false, $template_type_names);
}
if ($parse_tree instanceof ParseTree\NullableTree) {
$atomic_type = self::getTypeFromTree($parse_tree->children[0], false, $template_types);
$atomic_type = self::getTypeFromTree($parse_tree->children[0], false, $template_type_names);
if (!$atomic_type instanceof Atomic) {
throw new \UnexpectedValueException(
@ -412,7 +412,7 @@ abstract class Type
$atomic_type = self::fixScalarTerms($parse_tree->value, $php_compatible);
return Atomic::create($atomic_type, $php_compatible, $template_types);
return Atomic::create($atomic_type, $php_compatible, $template_type_names);
}
/**
@ -570,14 +570,14 @@ abstract class Type
/**
* @param string $string_type
* @param Aliases $aliases
* @param array<string, string>|null $template_types
* @param array<string, string>|null $template_type_names
*
* @return array<int, string>
*/
public static function fixUpLocalType(
$string_type,
Aliases $aliases,
array $template_types = null
array $template_type_names = null
) {
$type_tokens = self::tokenize($string_type);
@ -614,7 +614,7 @@ abstract class Type
continue;
}
if (isset($template_types[$string_type_token])) {
if (isset($template_type_names[$string_type_token])) {
continue;
}

View File

@ -53,11 +53,11 @@ abstract class Atomic
/**
* @param string $value
* @param bool $php_compatible
* @param array<string, string> $template_types
* @param array<string, string> $template_type_names
*
* @return Atomic
*/
public static function create($value, $php_compatible = false, array $template_types = [])
public static function create($value, $php_compatible = false, array $template_type_names = [])
{
switch ($value) {
case 'int':
@ -126,7 +126,7 @@ abstract class Atomic
throw new \Psalm\Exception\TypeParseTreeException('First character of type cannot be numeric');
}
if (isset($template_types[$value])) {
if (isset($template_type_names[$value])) {
return new TGenericParam($value);
}
@ -358,7 +358,7 @@ abstract class Atomic
}
/**
* @param array<string, string> $template_types
* @param array<string, Type\Union> $template_types
* @param array<string, Type\Union> $generic_params
* @param Type\Atomic|null $input_type
*

View File

@ -133,7 +133,7 @@ trait CallableTrait
}
/**
* @param array<string, string> $template_types
* @param array<string, Union> $template_types
* @param array<string, Union> $generic_params
* @param Atomic|null $input_type
*

View File

@ -132,7 +132,7 @@ trait GenericTrait
}
/**
* @param array<string, string> $template_types
* @param array<string, Union> $template_types
* @param array<string, Union> $generic_params
* @param Atomic|null $input_type
*

View File

@ -727,9 +727,9 @@ class Union
}
/**
* @param array<string, string> $template_types
* @param array<string, Type\Union> $generic_params
* @param Type\Union|null $input_type
* @param array<string, Union> $template_types
* @param array<string, Union> $generic_params
* @param Type\Union|null $input_type
*
* @return void
*/
@ -743,9 +743,10 @@ class Union
foreach ($this->types as $key => $atomic_type) {
if (isset($template_types[$key])) {
if ($template_types[$key] !== $key) {
if ($template_types[$key]->getId() !== $key) {
$keys_to_unset[] = $key;
$this->types[$template_types[$key]] = Atomic::create($template_types[$key]);
$first_atomic_type = array_values($template_types[$key]->getTypes())[0];
$this->types[$first_atomic_type->getKey()] = clone $first_atomic_type;
if ($input_type) {
$generic_params[$key] = clone $input_type;

View File

@ -225,18 +225,19 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
}
if ($docblock_info) {
if ($docblock_info->template_types) {
if ($docblock_info->template_type_names) {
$storage->template_types = [];
foreach ($docblock_info->template_types as $template_type) {
foreach ($docblock_info->template_type_names as $template_type) {
if (count($template_type) === 3) {
$as_type_string = Type::getFQCLNFromString(
$template_type[2],
$this->aliases
$storage->template_types[$template_type[0]] = Type::parseTokens(
Type::fixUpLocalType(
$template_type[2],
$this->aliases
)
);
$storage->template_types[$template_type[0]] = $as_type_string;
} else {
$storage->template_types[$template_type[0]] = 'mixed';
$storage->template_types[$template_type[0]] = Type::getMixed();
}
}
@ -1029,15 +1030,19 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$template_types = $class_storage && $class_storage->template_types ? $class_storage->template_types : null;
if ($docblock_info->template_types) {
if ($docblock_info->template_type_names) {
$storage->template_types = [];
foreach ($docblock_info->template_types as $template_type) {
foreach ($docblock_info->template_type_names as $template_type) {
if (count($template_type) === 3) {
$as_type_string = Type::getFQCLNFromString($template_type[2], $this->aliases);
$storage->template_types[$template_type[0]] = $as_type_string;
$storage->template_types[$template_type[0]] = Type::parseTokens(
Type::fixUpLocalType(
$template_type[2],
$this->aliases
)
);
} else {
$storage->template_types[$template_type[0]] = 'mixed';
$storage->template_types[$template_type[0]] = Type::getMixed();
}
}

View File

@ -138,6 +138,7 @@ class BinaryOperationTest extends TestCase
$b += 1;',
'assertions' => [
'$a' => 'float',
'$b' => 'float',
],
],
];

View File

@ -656,6 +656,34 @@ class TemplateTest extends TestCase
class A {}
class B {}'
],
'collectionOfClosure' => [
'<?php
/**
* @template TKey
* @template TValue
*/
class Collection {
/**
* @param Closure(TValue):bool $p
* @return Collection<TKey,TValue>
*/
public function filter(Closure $p);
}
class I {}
/** @var Collection<mixed,Collection<mixed,I>> $c */
$c = new Collection;
$c->filter(
/** @param Collection<mixed,I> $elt */
function(Collection $elt): bool { return (bool) rand(0,1); }
);
$c->filter(
/** @param Collection<mixed,I> $elt */
function(Collection $elt): bool { return true; }
);',
],
];
}