mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Merge branch 'vimeo:5.x' into 5.x
This commit is contained in:
commit
eea7c33309
@ -67,6 +67,8 @@ return [
|
|||||||
'socket_set_block' => true,
|
'socket_set_block' => true,
|
||||||
'socket_set_nonblock' => true,
|
'socket_set_nonblock' => true,
|
||||||
'socket_listen' => true,
|
'socket_listen' => true,
|
||||||
|
'stream_socket_shutdown' => true,
|
||||||
|
'socket_shutdown' => true,
|
||||||
// meta calls
|
// meta calls
|
||||||
'call_user_func' => true,
|
'call_user_func' => true,
|
||||||
'call_user_func_array' => true,
|
'call_user_func_array' => true,
|
||||||
@ -93,7 +95,6 @@ return [
|
|||||||
'mcrypt_generic_deinit' => true,
|
'mcrypt_generic_deinit' => true,
|
||||||
'mcrypt_module_close' => true,
|
'mcrypt_module_close' => true,
|
||||||
// internal optimisation
|
// internal optimisation
|
||||||
'opcache_compile_file' => true,
|
|
||||||
'clearstatcache' => true,
|
'clearstatcache' => true,
|
||||||
// process-related
|
// process-related
|
||||||
'pcntl_signal' => true,
|
'pcntl_signal' => true,
|
||||||
|
@ -1260,6 +1260,17 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($param_type->isNever()) {
|
||||||
|
IssueBuffer::maybeAdd(
|
||||||
|
new ReservedWord(
|
||||||
|
'Parameter cannot be never',
|
||||||
|
$function_param->type_location,
|
||||||
|
'never',
|
||||||
|
),
|
||||||
|
$this->suppressed_issues,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($param_type->check(
|
if ($param_type->check(
|
||||||
$this->source,
|
$this->source,
|
||||||
$function_param->type_location,
|
$function_param->type_location,
|
||||||
|
@ -313,6 +313,16 @@ class MethodComparator
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($guide_method_storage->returns_by_ref && !$implementer_method_storage->returns_by_ref) {
|
||||||
|
IssueBuffer::maybeAdd(
|
||||||
|
new MethodSignatureMismatch(
|
||||||
|
'Method ' . $cased_implementer_method_id . ' must return by-reference',
|
||||||
|
$code_location,
|
||||||
|
),
|
||||||
|
$suppressed_issues + $implementer_classlike_storage->suppressed_issues,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($guide_method_storage->external_mutation_free
|
if ($guide_method_storage->external_mutation_free
|
||||||
&& !$implementer_method_storage->external_mutation_free
|
&& !$implementer_method_storage->external_mutation_free
|
||||||
&& !$guide_method_storage->mutation_free_inferred
|
&& !$guide_method_storage->mutation_free_inferred
|
||||||
|
@ -678,7 +678,11 @@ class StatementsAnalyzer extends SourceAnalyzer
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$checked_type = $context->vars_in_scope[$checked_var_id];
|
$checked_type = $context->vars_in_scope[$checked_var_id];
|
||||||
$check_type = Type::parseString($check_type_string);
|
$fq_check_type_string = Type::getFQCLNFromString(
|
||||||
|
$check_type_string,
|
||||||
|
$statements_analyzer->getAliases(),
|
||||||
|
);
|
||||||
|
$check_type = Type::parseString($fq_check_type_string);
|
||||||
/** @psalm-suppress InaccessibleProperty We just created this type */
|
/** @psalm-suppress InaccessibleProperty We just created this type */
|
||||||
$check_type->possibly_undefined = $possibly_undefined;
|
$check_type->possibly_undefined = $possibly_undefined;
|
||||||
|
|
||||||
|
@ -122,6 +122,11 @@ class NegatedAssertionReconciler extends Reconciler
|
|||||||
$existing_var_type->removeType('array');
|
$existing_var_type->removeType('array');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($assertion instanceof IsNotType && $assertion_type instanceof TClassString) {
|
||||||
|
$existing_var_type->removeType(TClassString::class);
|
||||||
|
$existing_var_type->addType(new TString);
|
||||||
|
}
|
||||||
|
|
||||||
if (!$is_equality
|
if (!$is_equality
|
||||||
&& isset($existing_var_atomic_types['int'])
|
&& isset($existing_var_atomic_types['int'])
|
||||||
&& $existing_var_type->from_calculation
|
&& $existing_var_type->from_calculation
|
||||||
|
@ -235,6 +235,46 @@ class ParseTreeCreator
|
|||||||
$this->current_leaf = $new_parent_leaf;
|
$this->current_leaf = $new_parent_leaf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{0: string, 1: int, 2?: string} $current_token
|
||||||
|
*/
|
||||||
|
private function parseCallableParam(array $current_token, ParseTree $current_parent): void
|
||||||
|
{
|
||||||
|
$variadic = false;
|
||||||
|
$has_default = false;
|
||||||
|
|
||||||
|
if ($current_token[0] === '&') {
|
||||||
|
++$this->t;
|
||||||
|
$current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null;
|
||||||
|
} elseif ($current_token[0] === '...') {
|
||||||
|
$variadic = true;
|
||||||
|
|
||||||
|
++$this->t;
|
||||||
|
$current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null;
|
||||||
|
} elseif ($current_token[0] === '=') {
|
||||||
|
$has_default = true;
|
||||||
|
|
||||||
|
++$this->t;
|
||||||
|
$current_token = $this->t < $this->type_token_count ? $this->type_tokens[$this->t] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$current_token || $current_token[0][0] !== '$') {
|
||||||
|
throw new TypeParseTreeException('Unexpected token after space');
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_leaf = new CallableParamTree($current_parent);
|
||||||
|
$new_leaf->has_default = $has_default;
|
||||||
|
$new_leaf->variadic = $variadic;
|
||||||
|
|
||||||
|
if ($current_parent !== $this->current_leaf) {
|
||||||
|
$new_leaf->children = [$this->current_leaf];
|
||||||
|
array_pop($current_parent->children);
|
||||||
|
}
|
||||||
|
$current_parent->children[] = $new_leaf;
|
||||||
|
|
||||||
|
$this->current_leaf = $new_leaf;
|
||||||
|
}
|
||||||
|
|
||||||
private function handleLessThan(): void
|
private function handleLessThan(): void
|
||||||
{
|
{
|
||||||
if (!$this->current_leaf instanceof FieldEllipsis) {
|
if (!$this->current_leaf instanceof FieldEllipsis) {
|
||||||
@ -553,24 +593,27 @@ class ParseTreeCreator
|
|||||||
|
|
||||||
$current_parent = $this->current_leaf->parent;
|
$current_parent = $this->current_leaf->parent;
|
||||||
|
|
||||||
if ($current_parent instanceof CallableTree) {
|
//while ($current_parent && !$method_or_callable_parent) {
|
||||||
return;
|
while ($current_parent && !$current_parent instanceof MethodTree && !$current_parent instanceof CallableTree) {
|
||||||
}
|
|
||||||
|
|
||||||
while ($current_parent && !$current_parent instanceof MethodTree) {
|
|
||||||
$this->current_leaf = $current_parent;
|
$this->current_leaf = $current_parent;
|
||||||
$current_parent = $current_parent->parent;
|
$current_parent = $current_parent->parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
$next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null;
|
$next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null;
|
||||||
|
|
||||||
if (!$current_parent instanceof MethodTree || !$next_token) {
|
if (!($current_parent instanceof MethodTree || $current_parent instanceof CallableTree) || !$next_token) {
|
||||||
throw new TypeParseTreeException('Unexpected space');
|
throw new TypeParseTreeException('Unexpected space');
|
||||||
}
|
}
|
||||||
|
|
||||||
++$this->t;
|
|
||||||
|
|
||||||
$this->createMethodParam($next_token, $current_parent);
|
if ($current_parent instanceof MethodTree) {
|
||||||
|
++$this->t;
|
||||||
|
$this->createMethodParam($next_token, $current_parent);
|
||||||
|
}
|
||||||
|
if ($current_parent instanceof CallableTree) {
|
||||||
|
++$this->t;
|
||||||
|
$this->parseCallableParam($next_token, $current_parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleQuestionMark(): void
|
private function handleQuestionMark(): void
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Psalm\Issue;
|
namespace Psalm\Issue;
|
||||||
|
|
||||||
use function array_pop;
|
use function array_pop;
|
||||||
|
use function array_unique;
|
||||||
use function count;
|
use function count;
|
||||||
use function implode;
|
use function implode;
|
||||||
use function reset;
|
use function reset;
|
||||||
@ -15,6 +16,7 @@ final class InternalClass extends ClassIssue
|
|||||||
/** @param non-empty-list<non-empty-string> $words */
|
/** @param non-empty-list<non-empty-string> $words */
|
||||||
public static function listToPhrase(array $words): string
|
public static function listToPhrase(array $words): string
|
||||||
{
|
{
|
||||||
|
$words = array_unique($words);
|
||||||
if (count($words) === 1) {
|
if (count($words) === 1) {
|
||||||
return reset($words);
|
return reset($words);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,26 @@ class CheckTypeTest extends TestCase
|
|||||||
$foo = 1;
|
$foo = 1;
|
||||||
',
|
',
|
||||||
];
|
];
|
||||||
|
yield 'allowNamespace' => [
|
||||||
|
'code' => '<?php
|
||||||
|
|
||||||
|
namespace X;
|
||||||
|
|
||||||
|
final class A {}
|
||||||
|
|
||||||
|
$_a = new A();
|
||||||
|
/** @psalm-check-type-exact $_a = A */',
|
||||||
|
];
|
||||||
|
yield 'allowImport' => [
|
||||||
|
'code' => '<?php
|
||||||
|
|
||||||
|
namespace X;
|
||||||
|
|
||||||
|
use \stdClass;
|
||||||
|
|
||||||
|
$_a = new stdClass();
|
||||||
|
/** @psalm-check-type-exact $_a = \stdClass */',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function providerInvalidCodeParse(): iterable
|
public function providerInvalidCodeParse(): iterable
|
||||||
|
@ -611,6 +611,18 @@ class ClassLikeStringTest extends TestCase
|
|||||||
new \RuntimeException();
|
new \RuntimeException();
|
||||||
}',
|
}',
|
||||||
],
|
],
|
||||||
|
'convertToStringClassExistsNegated' => [
|
||||||
|
'code' => '<?php
|
||||||
|
/** @param class-string $className */
|
||||||
|
$className = stdClass::class;
|
||||||
|
if (class_exists($className)) {
|
||||||
|
throw new \RuntimeException($className);
|
||||||
|
}',
|
||||||
|
'assertions' => [
|
||||||
|
'$className===' => 'string',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
'createNewObjectFromGetClass' => [
|
'createNewObjectFromGetClass' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
/**
|
/**
|
||||||
|
@ -3031,6 +3031,17 @@ class FunctionCallTest extends TestCase
|
|||||||
}',
|
}',
|
||||||
'error_message' => 'InvalidScalarArgument',
|
'error_message' => 'InvalidScalarArgument',
|
||||||
],
|
],
|
||||||
|
'disallowNeverTypeForParam' => [
|
||||||
|
'code' => '<?php
|
||||||
|
function foo(never $_): void
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
',
|
||||||
|
'error_message' => 'ReservedWord',
|
||||||
|
'ignored_issues' => [],
|
||||||
|
'php_version' => '8.1',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,6 +929,34 @@ class MethodSignatureTest extends TestCase
|
|||||||
}
|
}
|
||||||
',
|
',
|
||||||
],
|
],
|
||||||
|
'allowByRefReturn' => [
|
||||||
|
'code' => '<?php
|
||||||
|
interface Foo {
|
||||||
|
public function &foo(): int;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar implements Foo {
|
||||||
|
private int $x = 0;
|
||||||
|
public function &foo(): int {
|
||||||
|
return $this->x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
',
|
||||||
|
],
|
||||||
|
'descendantAddsByRefReturn' => [
|
||||||
|
'code' => '<?php
|
||||||
|
interface Foo {
|
||||||
|
public function foo(): int;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar implements Foo {
|
||||||
|
private int $x = 0;
|
||||||
|
public function &foo(): int {
|
||||||
|
return $this->x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1586,6 +1614,20 @@ class MethodSignatureTest extends TestCase
|
|||||||
'ignored_issues' => [],
|
'ignored_issues' => [],
|
||||||
'php_version' => '8.1',
|
'php_version' => '8.1',
|
||||||
],
|
],
|
||||||
|
'absentByRefReturnInDescendant' => [
|
||||||
|
'code' => '<?php
|
||||||
|
interface Foo {
|
||||||
|
public function &foo(): int;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar implements Foo {
|
||||||
|
public function foo(): int {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
',
|
||||||
|
'error_message' => 'MethodSignatureMismatch',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -679,6 +679,68 @@ class TypeAnnotationTest extends TestCase
|
|||||||
'$output===' => 'callable():int',
|
'$output===' => 'callable():int',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'callableFormats' => [
|
||||||
|
'code' => '<?php
|
||||||
|
/**
|
||||||
|
* @psalm-type A callable(int, int): string
|
||||||
|
* @psalm-type B callable(int, int=): string
|
||||||
|
* @psalm-type C callable(int $a, string $b): void
|
||||||
|
* @psalm-type D callable(string $c): mixed
|
||||||
|
* @psalm-type E callable(string $c): mixed
|
||||||
|
* @psalm-type F callable(float...): (int|null)
|
||||||
|
* @psalm-type G callable(float ...$d): (int|null)
|
||||||
|
* @psalm-type H callable(array<int>): array<string>
|
||||||
|
* @psalm-type I callable(array<string, int> $e): array<int, string>
|
||||||
|
* @psalm-type J callable(array<int> ...): string
|
||||||
|
* @psalm-type K callable(array<int> ...$e): string
|
||||||
|
* @psalm-type L \Closure(int, int): string
|
||||||
|
*
|
||||||
|
* @method ma(): A
|
||||||
|
* @method mb(): B
|
||||||
|
* @method mc(): C
|
||||||
|
* @method md(): D
|
||||||
|
* @method me(): E
|
||||||
|
* @method mf(): F
|
||||||
|
* @method mg(): G
|
||||||
|
* @method mh(): H
|
||||||
|
* @method mi(): I
|
||||||
|
* @method mj(): J
|
||||||
|
* @method mk(): K
|
||||||
|
* @method ml(): L
|
||||||
|
*/
|
||||||
|
class Foo {
|
||||||
|
public function __call(string $method, array $params) { return 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
$foo = new \Foo();
|
||||||
|
$output_ma = $foo->ma();
|
||||||
|
$output_mb = $foo->mb();
|
||||||
|
$output_mc = $foo->mc();
|
||||||
|
$output_md = $foo->md();
|
||||||
|
$output_me = $foo->me();
|
||||||
|
$output_mf = $foo->mf();
|
||||||
|
$output_mg = $foo->mg();
|
||||||
|
$output_mh = $foo->mh();
|
||||||
|
$output_mi = $foo->mi();
|
||||||
|
$output_mj = $foo->mj();
|
||||||
|
$output_mk = $foo->mk();
|
||||||
|
$output_ml = $foo->ml();
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$output_ma===' => 'callable(int, int):string',
|
||||||
|
'$output_mb===' => 'callable(int, int=):string',
|
||||||
|
'$output_mc===' => 'callable(int, string):void',
|
||||||
|
'$output_md===' => 'callable(string):mixed',
|
||||||
|
'$output_me===' => 'callable(string):mixed',
|
||||||
|
'$output_mf===' => 'callable(float...):(int|null)',
|
||||||
|
'$output_mg===' => 'callable(float...):(int|null)',
|
||||||
|
'$output_mh===' => 'callable(array<array-key, int>):array<array-key, string>',
|
||||||
|
'$output_mi===' => 'callable(array<string, int>):array<int, string>',
|
||||||
|
'$output_mj===' => 'callable(array<array-key, int>...):string',
|
||||||
|
'$output_mk===' => 'callable(array<array-key, int>...):string',
|
||||||
|
'$output_ml===' => 'Closure(int, int):string',
|
||||||
|
],
|
||||||
|
],
|
||||||
'unionOfStringsContainingBraceChar' => [
|
'unionOfStringsContainingBraceChar' => [
|
||||||
'code' => '<?php
|
'code' => '<?php
|
||||||
/** @psalm-type T \'{\'|\'}\' */
|
/** @psalm-type T \'{\'|\'}\' */
|
||||||
|
Loading…
Reference in New Issue
Block a user