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

Invalidate signature types when use changes

This commit is contained in:
Matt Brown 2020-10-22 18:07:27 -04:00 committed by Daniil Gentili
parent b6dce59e9e
commit 67859ed19b
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
9 changed files with 132 additions and 21 deletions

View File

@ -720,7 +720,10 @@ class ReturnTypeAnalyzer
$storage->return_type_location,
$storage->suppressed_issues,
[],
false
false,
false,
false,
$context->calling_method_id
);
return null;

View File

@ -3390,13 +3390,15 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements FileSour
$hint = $hint->type;
}
if ($hint instanceof PhpParser\Node\Identifier) {
$type_string = $hint->name;
} elseif ($hint instanceof PhpParser\Node\Name\FullyQualified) {
$type_string = (string)$hint;
$type_string = null;
$this->codebase->scanner->queueClassLikeForScanning($type_string);
$this->file_storage->referenced_classlikes[strtolower($type_string)] = $type_string;
if ($hint instanceof PhpParser\Node\Identifier) {
$fq_type_string = $hint->name;
} elseif ($hint instanceof PhpParser\Node\Name\FullyQualified) {
$fq_type_string = (string)$hint;
$this->codebase->scanner->queueClassLikeForScanning($fq_type_string);
$this->file_storage->referenced_classlikes[strtolower($fq_type_string)] = $fq_type_string;
} else {
$lower_hint = strtolower($hint->parts[0]);
@ -3404,27 +3406,32 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements FileSour
&& ($lower_hint === 'self' || $lower_hint === 'static')
&& !end($this->classlike_storages)->is_trait
) {
$type_string = $this->fq_classlike_names[count($this->fq_classlike_names) - 1];
$fq_type_string = $this->fq_classlike_names[count($this->fq_classlike_names) - 1];
if ($lower_hint === 'static') {
$type_string .= '&static';
$fq_type_string .= '&static';
}
} else {
$type_string = ClassLikeAnalyzer::getFQCLNFromNameObject($hint, $this->aliases);
}
$type_string = implode('\\', $hint->parts);
$fq_type_string = ClassLikeAnalyzer::getFQCLNFromNameObject($hint, $this->aliases);
if (!in_array($lower_hint, ['self', 'static', 'parent'], true)) {
$this->codebase->scanner->queueClassLikeForScanning($type_string);
$this->file_storage->referenced_classlikes[strtolower($type_string)] = $type_string;
$this->codebase->scanner->queueClassLikeForScanning($fq_type_string);
$this->file_storage->referenced_classlikes[strtolower($fq_type_string)] = $fq_type_string;
}
}
$type = Type::parseString(
$type_string,
$fq_type_string,
[$this->php_major_version, $this->php_minor_version],
[]
);
if ($type_string) {
$atomic_types = $type->getAtomicTypes();
$atomic_type = reset($atomic_types);
$atomic_type->text = $type_string;
}
if ($is_nullable) {
$type->addType(new Type\Atomic\TNull);
}

View File

@ -21,6 +21,11 @@ class Value extends \Psalm\Internal\Type\ParseTree
*/
public $offset_end;
/**
* @var ?string
*/
public $text;
/**
* @param \Psalm\Internal\Type\ParseTree|null $parent
*/
@ -28,11 +33,13 @@ class Value extends \Psalm\Internal\Type\ParseTree
string $value,
int $offset_start,
int $offset_end,
?string $text,
\Psalm\Internal\Type\ParseTree $parent = null
) {
$this->offset_start = $offset_start;
$this->offset_end = $offset_end;
$this->value = $value;
$this->parent = $parent;
$this->text = $text === $value ? null : $text;
}
}

View File

@ -30,7 +30,7 @@ class ParseTreeCreator
private $t = 0;
/**
* @param list<array{0: string, 1: int}> $type_tokens
* @param list<array{0: string, 1: int, 2?: string}> $type_tokens
*/
public function __construct(array $type_tokens)
{
@ -715,7 +715,7 @@ class ParseTreeCreator
}
}
/** @param array{0: string, 1: int} $type_token */
/** @param array{0: string, 1: int, 2?: string} $type_token */
private function handleValue(array $type_token) : void
{
$new_parent = !$this->current_leaf instanceof ParseTree\Root ? $this->current_leaf : null;
@ -793,6 +793,7 @@ class ParseTreeCreator
$type_token[0] . '::' . $nexter_token[0],
$type_token[1],
$type_token[1] + 2 + strlen($nexter_token[0]),
$type_token[2] ?? null,
$new_parent
);
@ -809,6 +810,7 @@ class ParseTreeCreator
$type_token[0],
$type_token[1],
$type_token[1] + strlen($type_token[0]),
$type_token[2] ?? null,
$new_parent
);
break;

View File

@ -52,7 +52,7 @@ class TypeParser
/**
* Parses a string type representation
*
* @param list<array{0: string, 1: int}> $type_tokens
* @param list<array{0: string, 1: int, 2?: string}> $type_tokens
* @param array{int,int}|null $php_version
* @param array<string, array<string, array{Union}>> $template_type_map
* @param array<string, TypeAlias> $type_aliases
@ -81,6 +81,7 @@ class TypeParser
$atomic = Atomic::create($only_token[0], $php_version, $template_type_map, $type_aliases);
$atomic->offset_start = 0;
$atomic->offset_end = strlen($only_token[0]);
$atomic->text = isset($only_token[2]) && $only_token[2] !== $only_token[0] ? $only_token[2] : null;
return new Union([$atomic]);
}
@ -186,7 +187,7 @@ class TypeParser
$traversable = new TGenericObject('Traversable', $generic_params);
$array_acccess = new TGenericObject('ArrayAccess', $generic_params);
$countable = new TNamedObject('Countable');
$traversable->extra_types[$array_acccess->getKey()] = $array_acccess;
$traversable->extra_types[$countable->getKey()] = $countable;
@ -935,6 +936,7 @@ class TypeParser
$atomic_type->offset_start = $parse_tree->offset_start;
$atomic_type->offset_end = $parse_tree->offset_end;
$atomic_type->text = $parse_tree->text;
return $atomic_type;
}

View File

@ -328,7 +328,7 @@ class TypeTokenizer
* @param array<string, mixed>|null $template_type_map
* @param array<string, TypeAlias>|null $type_aliases
*
* @return list<array{0: string, 1: int}>
* @return list<array{0: string, 1: int, 2?: string}>
*/
public static function getFullyQualifiedTokens(
string $string_type,
@ -448,6 +448,8 @@ class TypeTokenizer
continue;
}
$type_tokens[$i][2] = $string_type_token[0];
if (isset($type_aliases[$string_type_token[0]])) {
$type_alias = $type_aliases[$string_type_token[0]];
@ -472,7 +474,7 @@ class TypeTokenizer
}
}
/** @var list<array{0: string, 1: int}> */
/** @var list<array{0: string, 1: int, 2?: string}> */
return $type_tokens;
}

View File

@ -149,6 +149,15 @@ class TypeChecker extends NodeVisitor
);
}
if ($this->calling_method_id
&& $atomic->text !== null
) {
$codebase->file_reference_provider->addMethodReferenceToClassMember(
$this->calling_method_id,
'use:' . $atomic->text . ':' . \md5($this->source->getFilePath())
);
}
if (!isset($this->phantom_classes[\strtolower($atomic->value)]) &&
ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$this->source,

View File

@ -82,6 +82,11 @@ abstract class Atomic implements TypeNode
*/
public $offset_end;
/**
* @var ?string
*/
public $text;
/**
* @param array{int,int}|null $php_version
* @param array<string, array<string, array{Union}>> $template_type_map

View File

@ -788,6 +788,80 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
],
'error_positions' => [[197], []],
],
'changeUseShouldInvalidateBadNew' => [
[
[
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo {
use Baz\B;
class A {
public function foo() : void {
new B();
}
}
}
namespace Bar {
class B {}
}',
],
[
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo {
use Bar\B;
class A {
public function foo() : void {
new B();
}
}
}
namespace Bar {
class B {}
}',
],
],
'error_positions' => [[247], []],
],
'changeUseShouldInvalidateBadReturn' => [
[
[
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo {
use Baz\B;
class A {
public function foo() : ?B {
return null;
}
}
}
namespace Bar {
class B {}
}',
],
[
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo {
use Bar\B;
class A {
public function foo() : ?B {
return null;
}
}
}
namespace Bar {
class B {}
}',
],
],
'error_positions' => [[196], []],
],
'fixMissingProperty' => [
[
[