Skip to content
Permalink
Browse files

Allow callables to be understood as pure

Fixes #2112
  • Loading branch information...
muglug committed Sep 9, 2019
1 parent 3c7fd94 commit a6baa2d04cf2587803226e55ac8872c8a9320556
@@ -619,7 +619,7 @@ public static function analyze(
|| $codebase->find_unused_variables
|| !$config->remember_property_assignments_after_call)
) {
$must_use = false;
$must_use = true;
$callmap_function_pure = $function_id && $in_call_map
? $codebase->functions->isCallMapFunctionPure($codebase, $function_id, $stmt->args, $must_use)
@@ -645,7 +645,7 @@ public static function analyze(
$context->removeAllObjectVars();
}
} elseif ($function_id
&& (($function_storage && $function_storage->pure)
&& (($function_storage && $function_storage->pure && $must_use)
|| ($callmap_function_pure === true && $must_use))
&& $codebase->find_unused_variables
&& !$context->inside_conditional
@@ -436,6 +436,9 @@ public static function analyze(
$use_context = new Context($context->self);
$use_context->collect_references = $codebase->collect_references;
$use_context->mutation_free = $context->mutation_free;
$use_context->external_mutation_free = $context->external_mutation_free;
$use_context->pure = $context->pure;
if (!$statements_analyzer->isStatic()) {
if ($context->collect_mutations &&
@@ -1543,7 +1543,8 @@ public static function getCallableFromAtomic(
return new TCallable(
'callable',
$function_storage->params,
$function_storage->return_type
$function_storage->return_type,
$function_storage->pure
);
} catch (\Exception $e) {
if (CallMap::inCallMap($input_type_part->value)) {
@@ -1561,11 +1562,22 @@ public static function getCallableFromAtomic(
}
}
return \Psalm\Internal\Codebase\CallMap::getCallableFromCallMapById(
$matching_callable = \Psalm\Internal\Codebase\CallMap::getCallableFromCallMapById(
$codebase,
$input_type_part->value,
$args
);
$must_use = false;
$matching_callable->is_pure = $codebase->functions->isCallMapFunctionPure(
$codebase,
$input_type_part->value,
null,
$must_use
);
return $matching_callable;
}
}
} elseif ($input_type_part instanceof ObjectLike) {
@@ -262,13 +262,13 @@ public static function isVariadic(Codebase $codebase, $function_id, $file_path)
}
/**
* @param array<int, \PhpParser\Node\Arg> $args
* @param ?array<int, \PhpParser\Node\Arg> $args
*/
public function isCallMapFunctionPure(
Codebase $codebase,
string $function_id,
array $args,
bool &$must_use
?array $args,
bool &$must_use = true
) : bool {
$impure_functions = [
// file io
@@ -358,18 +358,27 @@ public function isCallMapFunctionPure(
$function_callable = \Psalm\Internal\Codebase\CallMap::getCallableFromCallMapById(
$codebase,
$function_id,
$args
$args ?: []
);
if (!$function_callable->params || !$args) {
if (!$function_callable->params || ($args !== null && \count($args) === 0)) {
return false;
}
$must_use = true;
foreach ($function_callable->params as $i => $param) {
if ($param->type && $param->type->hasCallableType() && isset($args[$i])) {
return false;
foreach ($param->type->getTypes() as $possible_callable) {
$possible_callable = \Psalm\Internal\Analyzer\TypeAnalyzer::getCallableFromAtomic(
$codebase,
$possible_callable
);
if ($possible_callable && !$possible_callable->is_pure) {
return false;
}
}
}
if ($param->by_ref && isset($args[$i])) {
@@ -24,18 +24,28 @@ trait CallableTrait
*/
public $return_type;
/**
* @var bool
*/
public $is_pure;
/**
* Constructs a new instance of a generic type
*
* @param string $value
* @param array<int, FunctionLikeParameter> $params
* @param Union $return_type
*/
public function __construct($value = 'callable', array $params = null, Union $return_type = null)
{
public function __construct(
$value = 'callable',
array $params = null,
Union $return_type = null,
bool $is_pure = false
) {
$this->value = $value;
$this->params = $params;
$this->return_type = $return_type;
$this->is_pure = $is_pure;
}
public function __clone()
@@ -38,6 +38,7 @@
unset($args[$psalm_proxy]);
}
/** @psalm-suppress UnusedFunctionCall */
array_map(
/**
* @param string $arg
@@ -27,6 +27,7 @@
// get options from command line
$options = getopt(implode('', $valid_short_options), $valid_long_options);
/** @psalm-suppress UnusedFunctionCall */
array_map(
/**
* @param string $arg
@@ -89,6 +89,7 @@
exit;
}
/** @psalm-suppress UnusedFunctionCall */
array_map(
/**
* @param string $arg
@@ -29,6 +29,7 @@
// get options from command line
$options = getopt(implode('', $valid_short_options), $valid_long_options);
/** @psalm-suppress UnusedFunctionCall */
array_map(
/**
* @param string $arg
@@ -155,6 +155,44 @@ public function withBar(string $bar): self {
}
}'
],
'allowArrayMapCallable' => [
'<?php
/**
* @psalm-immutable
*/
class Address
{
private $line1;
private $line2;
private $city;
public function __construct(
string $line1,
?string $line2,
string $city
) {
$this->line1 = $line1;
$this->line2 = $line2;
$this->city = $city;
}
public function __toString()
{
$parts = [
$this->line1,
$this->line2 ?? "",
$this->city,
];
// Remove empty parts
$parts = \array_map("trim", $parts);
$parts = \array_filter($parts, "strlen");
$parts = \array_map(function(string $s) { return $s;}, $parts);
return \implode(", ", $parts);
}
}'
],
];
}
@@ -145,6 +145,16 @@ function toDateTime(?DateTime $dateTime) : DateTime {
return $dateTime;
}'
],
'allowArrayMapClosure' => [
'<?php
/**
* @psalm-pure
* @param string[] $arr
*/
function foo(array $arr) : array {
return \array_map(function(string $s) { return $s;}, $arr);
}'
],
];
}
@@ -276,6 +286,17 @@ function addCumulative(int $left) : int {
}',
'error_message' => 'ImpureStaticVariable',
],
'preventImpureArrayMapClosure' => [
'<?php
/**
* @psalm-pure
* @param string[] $arr
*/
function foo(array $arr) : array {
return \array_map(function(string $s) { return $s . rand(0, 1);}, $arr);
}',
'error_message' => 'ImpureFunctionCall',
],
];
}
}
@@ -606,6 +606,14 @@ function makesACounter(int $i) : Counter {
return $c;
}',
],
'usedUsort' => [
'<?php
/** @param string[] $arr */
function foo(array $arr) : array {
usort($arr, "strnatcasecmp");
return $arr;
}'
],
];
}

0 comments on commit a6baa2d

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