diff --git a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php index d40c86b2c..a584d9f0f 100644 --- a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -1650,7 +1650,9 @@ final class ClassAnalyzer extends ClassLikeAnalyzer $allow_native_type = !$docblock_only && $codebase->analysis_php_version_id >= 7_04_00 && $codebase->allow_backwards_incompatible_changes - && !$inferred_type->hasCallableType() // PHP does not support callable properties + // PHP does not support callable properties, but does allow Closure properties + // hasCallableType() treats Closure as a callable, but getCallableTypes() does not + && $inferred_type->getCallableTypes() === [] ; $manipulator->setType( diff --git a/src/Psalm/Type/Atomic/TClosure.php b/src/Psalm/Type/Atomic/TClosure.php index 94ee9446d..07d6740cb 100644 --- a/src/Psalm/Type/Atomic/TClosure.php +++ b/src/Psalm/Type/Atomic/TClosure.php @@ -50,7 +50,8 @@ final class TClosure extends TNamedObject public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { - return false; + // it can, if it's just 'Closure' + return $this->params === null && $this->return_type === null && $this->is_pure === null; } /** diff --git a/tests/FileManipulation/MissingPropertyTypeTest.php b/tests/FileManipulation/MissingPropertyTypeTest.php index 5e0db2d6c..f32d8c256 100644 --- a/tests/FileManipulation/MissingPropertyTypeTest.php +++ b/tests/FileManipulation/MissingPropertyTypeTest.php @@ -330,6 +330,29 @@ class MissingPropertyTypeTest extends FileManipulationTestCase 'issues_to_fix' => ['MissingPropertyType'], 'safe_types' => true, ], + 'addClosurePropertyType' => [ + 'input' => <<<'PHP' + u = $u; + } + } + PHP, + 'output' => <<<'PHP' + u = $u; + } + } + PHP, + 'php_version' => '7.4', + 'issues_to_fix' => ['MissingPropertyType'], + 'safe_types' => true, + ], ]; } }