Skip to content
Permalink
Browse files

Fix #1603 - prevent invalid covariant template classes from being passed

  • Loading branch information...
muglug committed May 6, 2019
1 parent 57a5852 commit 751253ddf030f402453bea2cad035c0db0ad3242
@@ -128,7 +128,7 @@ public static function parse($docblock, $line_number = null, $preserve_format =
$special_key,
[
'return', 'param', 'template', 'var', 'type',
'property', 'method',
'template-covariant', 'property', 'method',
'assert', 'assert-if-true', 'assert-if-false', 'suppress',
'ignore-nullable-return', 'override-property-visibility',
'override-method-visibility', 'seal-properties', 'seal-methods',
@@ -401,15 +401,52 @@ public static function extractFunctionDocblockInfo($comment, $line_number)
foreach ($all_templates as $template_line) {
$template_type = preg_split('/[\s]+/', $template_line);
if (count($template_type) > 2
&& in_array(strtolower($template_type[1]), ['as', 'super', 'of'], true)
$template_name = array_shift($template_type);
if (count($template_type) > 1
&& in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true)
) {
$template_modifier = strtolower(array_shift($template_type));
$info->templates[] = [
$template_type[0],
strtolower($template_type[1]), $template_type[2]
$template_name,
$template_modifier,
implode(' ', $template_type),
false
];
} else {
$info->templates[] = [$template_type[0]];
$info->templates[] = [$template_name, null, null, false];
}
}
}
if (isset($comments['specials']['template-covariant'])
|| isset($comments['specials']['psalm-template-covariant'])
) {
$all_templates =
(isset($comments['specials']['template-covariant'])
? $comments['specials']['template-covariant']
: [])
+ (isset($comments['specials']['psalm-template-covariant'])
? $comments['specials']['psalm-template-covariant']
: []);
foreach ($all_templates as $template_line) {
$template_type = preg_split('/[\s]+/', $template_line);
$template_name = array_shift($template_type);
if (count($template_type) > 1
&& in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true)
) {
$template_modifier = strtolower(array_shift($template_type));
$info->templates[] = [
$template_name,
$template_modifier,
implode(' ', $template_type),
true
];
} else {
$info->templates[] = [$template_name, null, null, true];
}
}
}
@@ -549,10 +586,43 @@ public static function extractClassLikeDocblockInfo(\PhpParser\Node $node, $comm
$info->templates[] = [
$template_name,
$template_modifier,
implode(' ', $template_type)
implode(' ', $template_type),
false
];
} else {
$info->templates[] = [$template_name, null, null, false];
}
}
}
if (isset($comments['specials']['template-covariant'])
|| isset($comments['specials']['psalm-template-covariant'])
) {
$all_templates =
(isset($comments['specials']['template-covariant'])
? $comments['specials']['template-covariant']
: [])
+ (isset($comments['specials']['psalm-template-covariant'])
? $comments['specials']['psalm-template-covariant']
: []);
foreach ($all_templates as $template_line) {
$template_type = preg_split('/[\s]+/', $template_line);
$template_name = array_shift($template_type);
if (count($template_type) > 1
&& in_array(strtolower($template_type[0]), ['as', 'super', 'of'], true)
) {
$template_modifier = strtolower(array_shift($template_type));
$info->templates[] = [
$template_name,
$template_modifier,
implode(' ', $template_type),
true
];
} else {
$info->templates[] = [$template_name];
$info->templates[] = [$template_name, null, null, true];
}
}
}
@@ -653,6 +653,11 @@ public static function compareMethods(
$template_types,
$codebase
);
$guide_method_storage_return_type->replaceTemplateTypesWithArgTypes(
$template_types,
$codebase
);
}
// treat void as null when comparing against docblock implementer
@@ -1010,6 +1010,7 @@ public static function fleshOutType(
$fleshed_out_type->possibly_undefined = $return_type->possibly_undefined;
$fleshed_out_type->by_ref = $return_type->by_ref;
$fleshed_out_type->initialized = $return_type->initialized;
$fleshed_out_type->had_template = $return_type->had_template;
return $fleshed_out_type;
}
@@ -1605,6 +1605,40 @@ private static function isMatchingTypeContainedBy(
$allow_interface_equality
)) {
$all_types_contain = false;
} elseif (!$input_type_part instanceof TIterable
&& !$container_param->had_template
&& !$input_param->had_template
&& !$container_param->hasTemplate()
&& !$input_param->hasTemplate()
&& !$input_param->hasLiteralValue()
) {
$input_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
if (!($input_storage->template_covariants[$i] ?? false)) {
// Make sure types are basically the same
if (!self::isContainedBy(
$codebase,
$container_param,
$input_param,
$container_param->ignore_nullable_issues,
$container_param->ignore_falsable_issues,
$has_scalar_match,
$type_coerced,
$type_coerced_from_mixed,
$to_string_cast,
$type_coerced_from_scalar,
$allow_interface_equality
) || $type_coerced
) {
if ($container_param->hasMixed() || $container_param->isArrayKey()) {
$type_coerced_from_mixed = true;
} else {
$all_types_contain = false;
}
$type_coerced = false;
}
}
}
}
}
@@ -905,7 +905,7 @@ private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, $is
{
$atomic_types = $candidate->getTypes();
if (isset($atomic_types['array']) && count($atomic_types) > 1) {
if (isset($atomic_types['array']) && count($atomic_types) > 1 && !isset($atomic_types['null'])) {
$iterator_name = null;
$generic_params = null;
@@ -947,6 +947,7 @@ private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, $is
}
$candidate->removeType('array');
$candidate->removeType($iterator_name);
$candidate->addType($generic_iterator);
}
}
@@ -160,7 +160,7 @@ public function didClose(TextDocumentIdentifier $textDocument)
*
* @param TextDocumentIdentifier $textDocument The text document
* @param Position $position The position inside the text document
* @psalm-return Promise<Location|Hover>
* @psalm-return Promise<Location>|Promise<Hover>
*/
public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise
{
@@ -244,7 +244,7 @@ public function hover(TextDocumentIdentifier $textDocument, Position $position):
*
* @param TextDocumentIdentifier The text document
* @param Position $position The position
* @psalm-return Promise<CompletionItem[]|CompletionList>
* @psalm-return Promise<array<empty, empty>>|Promise<CompletionList>
*/
public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise
{
@@ -21,7 +21,7 @@ class ClassLikeDocblockComment
public $internal = false;
/**
* @var array<int, array<int, string>>
* @var array<int, array{string, ?string, ?string, bool}>
*/
public $templates = [];
@@ -80,7 +80,7 @@ class FunctionDocblockComment
public $return_type_line_number;
/**
* @var array<int, array<int, string>>
* @var array<int, array{string, ?string, ?string, bool}>
*/
public $templates = [];
@@ -4,8 +4,8 @@
* Interface to detect if a class is traversable using &foreach;.
* @link http://php.net/manual/en/class.traversable.php
*
* @template TKey
* @template TValue
* @template-covariant TKey
* @template-covariant TValue
*/
interface Traversable {
}
@@ -14,8 +14,8 @@ interface Traversable {
* Interface to create an external Iterator.
* @link http://php.net/manual/en/class.iteratoraggregate.php
*
* @template TKey
* @template TValue
* @template-covariant TKey
* @template-covariant TValue
*
* @template-extends Traversable<TKey, TValue>
*/
@@ -36,8 +36,8 @@ public function getIterator();
* themselves internally.
* @link http://php.net/manual/en/class.iterator.php
*
* @template TKey
* @template TValue
* @template-covariant TKey
* @template-covariant TValue
*
* @template-extends Traversable<TKey, TValue>
*/
@@ -86,10 +86,10 @@ public function rewind();
}
/**
* @template TKey
* @template TValue
* @template TSend
* @template TReturn
* @template-covariant TKey
* @template-covariant TValue
* @template-covariant TSend
* @template-covariant TReturn
*
* @template-implements Traversable<TKey, TValue>
*/
@@ -452,8 +452,8 @@ public function getIteratorClass() { }
/**
* The Seekable iterator.
* @link https://php.net/manual/en/class.seekableiterator.php
* @template TKey
* @template TValue
* @template-covariant TKey
* @template-covariant TValue
* @template-extends Iterator<TKey, TValue>
*/
interface SeekableIterator extends Iterator {
@@ -725,7 +725,7 @@ public function getElementsByTagNameNS ($namespaceURI, $localName) {}
}
/**
* @template TNode as DOMNode
* @template-covariant TNode as DOMNode
* @template-implements Traversable<int, TNode>
*/
class DOMNodeList implements Traversable, Countable {
@@ -1155,7 +1155,7 @@ public function getHash($object) {}
}
/**
* @template T as object
* @template-covariant T as object
*
* @property-read class-string<T> $name
*/

0 comments on commit 751253d

Please sign in to comment.
You can’t perform that action at this time.