1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Add support for namespaced templates

This commit is contained in:
Matthew Brown 2017-02-10 00:14:44 -05:00
parent 4a82c0a09a
commit 8ffd45407c
5 changed files with 227 additions and 51 deletions

View File

@ -942,7 +942,13 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
if ($comment && $config->use_docblock_types) {
try {
$type_in_comment = CommentChecker::getTypeFromComment((string) $comment, null, $this);
$type_in_comment = CommentChecker::getTypeFromComment(
(string)$comment,
null,
$this,
null,
$storage->template_types
);
} catch (DocblockParseException $e) {
if (IssueBuffer::accepts(
new InvalidDocblock(

View File

@ -19,6 +19,7 @@ class CommentChecker
* @param Context|null $context
* @param StatementsSource $source
* @param string $var_id
* @param array<string, string>|null $template_types
* @return Type\Union|null
* @throws DocblockParseException If there was a problem parsing the docblock.
* @psalm-suppress MixedArrayAccess
@ -27,7 +28,8 @@ class CommentChecker
$comment,
Context $context = null,
StatementsSource $source,
$var_id = null
$var_id = null,
array $template_types = null
) {
$type_in_comments_var_id = null;
@ -47,7 +49,7 @@ class CommentChecker
$line_parts[0],
$source->getFQCLN(),
$source,
[]
$template_types
);
// support PHPStorm-style docblocks like

View File

@ -309,8 +309,10 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
}
foreach ($storage->params as $offset => $function_param) {
$param_type = clone $function_param->type;
$param_type = ExpressionChecker::fleshOutTypes(
clone $function_param->type,
$param_type,
[],
$context->self,
$this->getMethodId()
@ -347,10 +349,10 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
}
if ($template_types) {
$substituded_type = clone $param_type;
$substituted_type = clone $param_type;
$generic_types = [];
$substituded_type->replaceTemplateTypes($template_types, $generic_types, null);
$substituded_type->check($this->source, $function_param->code_location, $this->suppressed_issues);
$substituted_type->replaceTemplateTypes($template_types, $generic_types, null);
$substituted_type->check($this->source, $function_param->code_location, $this->suppressed_issues);
} else {
$param_type->check($this->source, $function_param->code_location, $this->suppressed_issues);
}
@ -615,9 +617,6 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$config = Config::getInstance();
$docblock_info = null;
$doc_comment = $function->getDocComment();
if ($parser_return_type = $function->getReturnType()) {
$suffix = '';
@ -646,12 +645,13 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
);
}
$docblock_info = null;
$doc_comment = $function->getDocComment();
if (!$doc_comment) {
return $storage;
}
$docblock_info = null;
try {
$docblock_info = CommentChecker::extractFunctionDocblockInfo(
(string)$doc_comment,
@ -680,49 +680,45 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$storage->variadic = true;
}
if ($docblock_info->template_types) {
$storage->template_types = [];
foreach ($docblock_info->template_types as $template_type) {
if (count($template_type) === 3) {
$as_type_string = ClassLikeChecker::getFQCLNFromString($template_type[2], $source);
$storage->template_types[$template_type[0]] = $as_type_string;
} else {
$storage->template_types[$template_type[0]] = 'mixed';
}
}
}
if ($docblock_info->template_typeofs) {
$storage->template_typeof_params = [];
foreach ($docblock_info->template_typeofs as $template_typeof) {
foreach ($storage->params as $i => $param) {
if ($param->name === $template_typeof['param_name']) {
$storage->template_typeof_params[$i] = $template_typeof['template_type'];
break;
}
}
}
}
$storage->suppressed_issues = $docblock_info->suppress;
if (!$config->use_docblock_types) {
return $storage;
}
$template_types = $class_storage && $class_storage->template_types ? $class_storage->template_types : null;
if ($docblock_info) {
if ($docblock_info->template_types) {
$storage->template_types = [];
foreach ($docblock_info->template_types as $template_type) {
if (count($template_type) === 3) {
$as_type_string = ClassLikeChecker::getFQCLNFromString($template_type[2], $source);
$storage->template_types[$template_type[0]] = $as_type_string;
} else {
$storage->template_types[$template_type[0]] = 'mixed';
}
}
$template_types = array_merge($template_types ?: [], $storage->template_types);
}
if ($docblock_info->template_typeofs) {
$storage->template_typeof_params = [];
foreach ($docblock_info->template_typeofs as $template_typeof) {
foreach ($storage->params as $i => $param) {
if ($param->name === $template_typeof['param_name']) {
$storage->template_typeof_params[$i] = $template_typeof['template_type'];
break;
}
}
}
}
}
if ($docblock_info->return_type) {
$template_types = null;
if ($storage->template_types) {
$template_types = $storage->template_types;
}
if ($class_storage && $class_storage->template_types) {
$template_types = array_merge($class_storage->template_types, $template_types ?: []);
}
$storage->has_template_return_type =
$template_types !== null &&
count(array_intersect(Type::tokenize($docblock_info->return_type), array_keys($template_types))) > 0;
@ -768,6 +764,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
self::improveParamsFromDocblock(
$storage,
$docblock_info->params,
$template_types,
$source,
$source->getFQCLN(),
new CodeLocation($source, $function, true)
@ -1135,6 +1132,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
/**
* @param array<int, array{type:string,name:string,line_number:int}> $docblock_params
* @param FunctionLikeStorage $storage
* @param array<string, string>|null $template_types
* @param StatementsSource $source
* @param string|null $fq_class_name
* @param CodeLocation $code_location
@ -1143,6 +1141,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
protected static function improveParamsFromDocblock(
FunctionLikeStorage $storage,
array $docblock_params,
$template_types,
StatementsSource $source,
$fq_class_name,
CodeLocation $code_location
@ -1190,7 +1189,8 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
self::fixUpLocalType(
$docblock_param['type'],
null,
$source
$source,
$template_types
)
);

View File

@ -283,8 +283,9 @@ class FetchChecker
$declaring_property_class = ClassLikeChecker::getDeclaringClassForProperty($property_id);
$property_storage =
ClassLikeChecker::$storage[strtolower((string)$declaring_property_class)]->properties[$stmt->name];
$declaring_class_storage = ClassLikeChecker::$storage[strtolower((string)$declaring_property_class)];
$property_storage = $declaring_class_storage->properties[$stmt->name];
$class_property_type = $property_storage->type;
@ -302,6 +303,38 @@ class FetchChecker
$class_property_type = Type::getMixed();
} else {
$class_property_type = clone $class_property_type;
if ($lhs_type_part instanceof TGenericObject) {
$class_storage = ClassLikeChecker::$storage[strtolower($lhs_type_part->value)];
if ($class_storage->template_types) {
$class_template_params = [];
/** @var array<int, string> */
$reversed_class_template_types = array_reverse(array_keys($class_storage->template_types));
$provided_type_param_count = count($lhs_type_part->type_params);
foreach ($reversed_class_template_types as $i => $type_name) {
if (isset($lhs_type_part->type_params[$provided_type_param_count - 1 - $i])) {
$class_template_params[$type_name] =
(string)$lhs_type_part->type_params[$provided_type_param_count - 1 - $i];
} else {
$class_template_params[$type_name] = 'mixed';
}
}
$type_tokens = Type::tokenize((string)$class_property_type);
foreach ($type_tokens as &$type_token) {
if (isset($class_template_params[$type_token])) {
$type_token = $class_template_params[$type_token];
}
}
$class_property_type = Type::parseString(implode('', $type_tokens));
}
}
}
if (isset($stmt->inferredType)) {

View File

@ -144,6 +144,141 @@ class TemplateTest extends PHPUnit_Framework_TestCase
$this->assertEquals('A', (string) $context->vars_in_scope['$afoo_bar']);
}
/**
* @return void
*/
public function testPhanTuple()
{
$stmts = self::$parser->parse('<?php
namespace Phan\Library;
/**
* An abstract tuple.
*/
abstract class Tuple
{
const ARITY = 0;
/**
* @return int
* The arity of this tuple
*/
public function arity() : int
{
return (int)static::ARITY;
}
/**
* @return array
* An array of all elements in this tuple.
*/
abstract public function toArray() : array;
}
/**
* A tuple of 1 element.
*
* @template T0
* The type of element zero
*/
class Tuple1 extends Tuple
{
/** @var int */
const ARITY = 1;
/** @var T0 */
public $_0;
/**
* @param T0 $_0
* The 0th element
*/
public function __construct($_0) {
$this->_0 = $_0;
}
/**
* @return int
* The arity of this tuple
*/
public function arity() : int
{
return (int)static::ARITY;
}
/**
* @return array
* An array of all elements in this tuple.
*/
public function toArray() : array
{
return [
$this->_0,
];
}
}
/**
* A tuple of 2 elements.
*
* @template T0
* The type of element zero
*
* @template T1
* The type of element one
*
* @inherits Tuple1<T0>
*/
class Tuple2 extends Tuple1
{
/** @var int */
const ARITY = 2;
/** @var T1 */
public $_1;
/**
* @param T0 $_0
* The 0th element
*
* @param T1 $_1
* The 1st element
*/
public function __construct($_0, $_1) {
parent::__construct($_0);
$this->_1 = $_1;
}
/**
* @return array
* An array of all elements in this tuple.
*/
public function toArray() : array
{
return [
$this->_0,
$this->_1,
];
}
}
$a = new Tuple2("cool", 5);
/** @return void */
function takes_int(int $i) {}
/** @return void */
function takes_string(string $s) {}
takes_string($a->_0);
takes_int($a->_1);
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @return void
*/