Skip to content

Commit

Permalink
Merge branch '5.x' into update-master
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan committed Feb 15, 2024
2 parents 639bed0 + 5f2e9d1 commit 029f1ae
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodCallReturnTypeFetcher;
use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodVisibilityAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
Expand All @@ -23,6 +24,7 @@
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TemplateStandinTypeReplacer;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Issue\AbstractInstantiation;
use Psalm\Issue\DeprecatedClass;
use Psalm\Issue\ImpureMethodCall;
Expand Down Expand Up @@ -60,6 +62,7 @@

use function array_map;
use function array_values;
use function count;
use function in_array;
use function md5;
use function preg_match;
Expand Down Expand Up @@ -431,6 +434,8 @@ private static function analyzeNamedConstructor(

$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);

$method_storage = null;

if ($declaring_method_id) {
$method_storage = $codebase->methods->getStorage($declaring_method_id);

Expand Down Expand Up @@ -502,6 +507,7 @@ private static function analyzeNamedConstructor(
}

$generic_param_types = null;
$self_out_candidate = null;

if ($storage->template_types) {
foreach ($storage->template_types as $template_name => $base_type) {
Expand Down Expand Up @@ -539,9 +545,49 @@ private static function analyzeNamedConstructor(
'had_template' => true,
]);
}

if ($method_storage && $method_storage->self_out_type) {
$self_out_candidate = $method_storage->self_out_type;

if ($template_result->lower_bounds) {
$self_out_candidate = TypeExpander::expandUnion(
$codebase,
$self_out_candidate,
$fq_class_name,
null,
$storage->parent_class,
true,
false,
false,
true,
);
}

$self_out_candidate = MethodCallReturnTypeFetcher::replaceTemplateTypes(
$self_out_candidate,
$template_result,
$method_id,
count($stmt->getArgs()),
$codebase,
);

$self_out_candidate = TypeExpander::expandUnion(
$codebase,
$self_out_candidate,
$fq_class_name,
$fq_class_name,
$storage->parent_class,
true,
false,
false,
true,
);
$statements_analyzer->node_data->setType($stmt, $self_out_candidate);
}
}

if ($generic_param_types) {
// XXX: what if we need both?
if ($generic_param_types && !$self_out_candidate) {
$result_atomic_type = new TGenericObject(
$fq_class_name,
$generic_param_types,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Psalm\Issue\InvalidStringClass;
use Psalm\Issue\MixedMethodCall;
use Psalm\Issue\UndefinedClass;
use Psalm\Issue\UndefinedMethod;
use Psalm\IssueBuffer;
use Psalm\Node\Expr\VirtualArray;
use Psalm\Node\Expr\VirtualMethodCall;
Expand Down Expand Up @@ -492,6 +493,7 @@ private static function handleNamedCall(
$method_name_lc,
);


if ($stmt->isFirstClassCallable()) {
if ($found_method_and_class_storage) {
[ $method_storage ] = $found_method_and_class_storage;
Expand All @@ -517,8 +519,31 @@ private static function handleNamedCall(
$codebase->getMethodReturnType($method_id, $fq_class_name),
$codebase->methods->getStorage($declaring_method_id)->pure,
)]);
} elseif ($codebase->methodExists(
$call_static_method_id = new MethodIdentifier($method_id->fq_class_name, '__callstatic'),
new CodeLocation($statements_analyzer, $stmt),
null,
null,
false,
)) {
$return_type_candidate = new Union([new TClosure(
'Closure',
null,
$codebase->getMethodReturnType($call_static_method_id, $fq_class_name),
$codebase->methods->getStorage($call_static_method_id)->pure,
)]);
} else {
// FIXME: perhaps Psalm should complain about nonexisting method here, or throw a logic exception?
if (IssueBuffer::accepts(
new UndefinedMethod(
'Method ' . $method_id . ' does not exist',
new CodeLocation($statements_analyzer, $stmt),
(string) $method_id,
),
$statements_analyzer->getSuppressedIssues(),
)) {
return false;
}

$return_type_candidate = Type::getClosure();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
use function assert;
use function count;
use function implode;
use function ltrim;
use function preg_match;
use function preg_split;
use function strtolower;
Expand Down Expand Up @@ -1925,7 +1926,7 @@ private static function getTypeAliasesFromCommentLines(
continue;
}

if ($var_line_parts[0] === ' ') {
while (isset($var_line_parts[0]) && $var_line_parts[0] === ' ') {
array_shift($var_line_parts);
}

Expand All @@ -1941,11 +1942,12 @@ private static function getTypeAliasesFromCommentLines(
continue;
}

if ($var_line_parts[0] === ' ') {
while (isset($var_line_parts[0]) && $var_line_parts[0] === ' ') {
array_shift($var_line_parts);
}

$type_string = implode('', $var_line_parts);
$type_string = ltrim($type_string, "* \n\r");
try {
$type_string = CommentAnalyzer::splitDocLine($type_string)[0];
} catch (DocblockParseException $e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,13 @@ public static function parse(

if (count($param_parts) >= 2) {
$info->taint_sink_params[] = ['name' => $param_parts[1], 'taint' => $param_parts[0]];
} else {
IssueBuffer::maybeAdd(
new InvalidDocblock(
'@psalm-taint-sink expects 2 arguments',
$code_location,
),
);
}
}
}
Expand Down Expand Up @@ -283,6 +290,13 @@ public static function parse(

if ($param_parts[0]) {
$info->taint_source_types[] = $param_parts[0];
} else {
IssueBuffer::maybeAdd(
new InvalidDocblock(
'@psalm-taint-source expects 1 argument',
$code_location,
),
);
}
}
} elseif (isset($parsed_docblock->tags['return-taint'])) {
Expand Down
10 changes: 10 additions & 0 deletions stubs/extensions/random.phpstub
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,20 @@ namespace Random
*/
public function getBytes(int $length): string {}

/**
* @template TValue
* @param array<TValue> $array
* @return list<TValue>
*/
public function shuffleArray(array $array): array {}

public function shuffleBytes(string $bytes): string {}

/**
* @template TKey as array-key
* @param array<TKey, mixed> $array
* @return list<TKey>
*/
public function pickArrayKeys(array $array, int $num): array {}

public function __serialize(): array {}
Expand Down
22 changes: 22 additions & 0 deletions tests/AnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,28 @@ function example(string $_x) : void {}',
*/
class A {}',
],
'multipeLineGenericArray2' => [
'code' => '<?php
/**
* @psalm-type TRelAlternate =
* list<
* array{
* href: string,
* lang: string
* }
* >
*/
class A {
/** @return TRelAlternate */
public function ret(): array { return []; }
}
$_ = (new A)->ret();
',
'assertions' => [
'$_===' => 'list<array{href: string, lang: string}>',
],
],
'builtInClassInAShape' => [
'code' => '<?php
/**
Expand Down
16 changes: 16 additions & 0 deletions tests/MethodCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,22 @@ public function foo(callable $_a = "strlen"): void {}
PHP,
'error_message' => 'TooManyArguments',
],
'firstClassCallableWithUnknownStaticMethod' => [
'code' => <<<'PHP'
<?php
class A {}
$_a = A::foo(...);
PHP,
'error_message' => 'UndefinedMethod',
],
'firstClassCallableWithUnknownInstanceMethod' => [
'code' => <<<'PHP'
<?php
class A {}
$_a = (new A)->foo(...);
PHP,
'error_message' => 'UndefinedMethod',
],
];
}
}
2 changes: 1 addition & 1 deletion tests/Template/ConditionalReturnTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ private function getBody() : string {
* @template TSource as self::SOURCE_*
* @param TSource $source
* @return (TSource is "BODY" ? object|list : array)
* @psalm-taint-source
* @psalm-taint-source input
*/
public function getParams(
string $source = self::SOURCE_GET
Expand Down
20 changes: 20 additions & 0 deletions tests/ThisOutTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,26 @@ public function getData(): array { return $this->data; }
'$data3===' => 'list<2|3>',
],
],
'provideDefaultTypeToTypeArguments' => [
'code' => <<<'PHP'
<?php
/** @template T of 'idle'|'running' */
class App {
/** @psalm-this-out self<'idle'> */
public function __construct() {}
/**
* @psalm-if-this-is self<'idle'>
* @psalm-this-out self<'running'>
*/
public function start(): void {}
}
$app = new App();
PHP,
'assertions' => [
'$app===' => "App<'idle'>",
],
],
];
}
}
16 changes: 16 additions & 0 deletions tests/TypeAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,22 @@ public function bar(array $foo): void {}
}
PHP,
],
'typeWithMultipleSpaces' => [
'code' => <<<'PHP'
<?php
/**
* @psalm-type Foo = string
* @psalm-type Bar int
*/
class A {
/**
* @psalm-param Foo $foo
* @psalm-param Bar $bar
*/
public function bar(string $foo, int $bar): void {}
}
PHP,
],
];
}

Expand Down

0 comments on commit 029f1ae

Please sign in to comment.