diff --git a/src/Psalm/Context.php b/src/Psalm/Context.php index fd90976fd8a..3b9470d6f64 100644 --- a/src/Psalm/Context.php +++ b/src/Psalm/Context.php @@ -353,11 +353,13 @@ class Context /** * @var bool + * Set by @psalm-immutable */ public $mutation_free = false; /** * @var bool + * Set by @psalm-external-mutation-free */ public $external_mutation_free = false; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 2dc7f5a3793..becd4b5ea11 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -643,7 +643,7 @@ private static function getAnalyzeNamedExpression( } if ($var_type_part instanceof TClosure || $var_type_part instanceof TCallable) { - if (!$var_type_part->is_pure && $context->pure) { + if (!$var_type_part->is_pure && ($context->pure || $context->mutation_free)) { IssueBuffer::maybeAdd( new ImpureFunctionCall( 'Cannot call an impure function from a mutation-free context', diff --git a/tests/PureAnnotationTest.php b/tests/PureAnnotationTest.php index b9264f20ebe..988459f9167 100644 --- a/tests/PureAnnotationTest.php +++ b/tests/PureAnnotationTest.php @@ -876,6 +876,38 @@ function testImpure(): void ', 'error_message' => 'ImpureMethodCall', ], + 'impureCallableInImmutableContext' => [ + 'fold( + function (): void {} + ); + } + } + + new Whatever(); + ', + 'error_message' => 'ImpureFunctionCall', + ], ]; } } diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index ff2f03cfec1..eca908c2bdf 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -2991,7 +2991,7 @@ public function toString(): string /** * @psalm-template TResult - * @psalm-param callable(self::READ_UNCOMMITTED): TResult $readUncommitted + * @psalm-param pure-callable(self::READ_UNCOMMITTED): TResult $readUncommitted * @psalm-return TResult */ public function resolve(callable $readUncommitted) {