From 603714518b3894a57b464920b906a13332f23c02 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Mon, 31 Jan 2022 14:37:42 +0200 Subject: [PATCH 01/20] Revert "PHP 8.1: Report missing typehints in overridden native methods" --- config.xsd | 1 - docs/running_psalm/error_levels.md | 1 - docs/running_psalm/issues.md | 1 - .../MethodSignatureMustProvideReturnType.md | 17 --------- .../Internal/Analyzer/MethodComparator.php | 35 +------------------ .../MethodSignatureMustProvideReturnType.php | 9 ----- tests/DocumentationTest.php | 4 --- tests/MethodSignatureTest.php | 31 ---------------- 8 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md delete mode 100644 src/Psalm/Issue/MethodSignatureMustProvideReturnType.php diff --git a/config.xsd b/config.xsd index a4ba114c2b5..3c8257bc1df 100644 --- a/config.xsd +++ b/config.xsd @@ -331,7 +331,6 @@ - diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md index c58cca026ff..3babb92488a 100644 --- a/docs/running_psalm/error_levels.md +++ b/docs/running_psalm/error_levels.md @@ -56,7 +56,6 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [InvalidThrow](issues/InvalidThrow.md) - [LoopInvalidation](issues/LoopInvalidation.md) - [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md) - - [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md) - [MissingDependency](issues/MissingDependency.md) - [MissingFile](issues/MissingFile.md) - [MissingImmutableAnnotation](issues/MissingImmutableAnnotation.md) diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md index 50385f60e31..c471c86f4cb 100644 --- a/docs/running_psalm/issues.md +++ b/docs/running_psalm/issues.md @@ -99,7 +99,6 @@ - [LoopInvalidation](issues/LoopInvalidation.md) - [MethodSignatureMismatch](issues/MethodSignatureMismatch.md) - [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md) - - [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md) - [MismatchingDocblockParamType](issues/MismatchingDocblockParamType.md) - [MismatchingDocblockPropertyType](issues/MismatchingDocblockPropertyType.md) - [MismatchingDocblockReturnType](issues/MismatchingDocblockReturnType.md) diff --git a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md deleted file mode 100644 index 80aa53c8ac2..00000000000 --- a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md +++ /dev/null @@ -1,17 +0,0 @@ -# MethodSignatureMustProvideReturnType - -In PHP 8.1+, [most non-final internal methods now require overriding methods to declare a compatible return type, otherwise a deprecated notice is emitted during inheritance validation](https://www.php.net/manual/en/migration81.incompatible.php#migration81.incompatible.core.type-compatibility-internal). - -This issue is emitted when a method overriding a native method is defined without a return type. - -**Only if** the return type cannot be declared to keep support for PHP 7, a `#[ReturnTypeWillChange]` attribute can be added to silence the PHP deprecation notice and Psalm issue. - -```php - 'A']; - } -} -``` diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index dd0b6ee6497..16ff2d9d98b 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -21,14 +21,12 @@ use Psalm\Issue\ImplementedReturnTypeMismatch; use Psalm\Issue\LessSpecificImplementedReturnType; use Psalm\Issue\MethodSignatureMismatch; -use Psalm\Issue\MethodSignatureMustProvideReturnType; use Psalm\Issue\MissingImmutableAnnotation; use Psalm\Issue\MoreSpecificImplementedParamType; use Psalm\Issue\OverriddenMethodAccess; use Psalm\Issue\ParamNameMismatch; use Psalm\Issue\TraitMethodSignatureMismatch; use Psalm\IssueBuffer; -use Psalm\Storage\AttributeStorage; use Psalm\Storage\ClassLikeStorage; use Psalm\Storage\FunctionLikeParameter; use Psalm\Storage\MethodStorage; @@ -37,7 +35,6 @@ use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Union; -use function array_filter; use function in_array; use function strpos; use function strtolower; @@ -116,29 +113,6 @@ public static function compare( ); } - if (!$guide_classlike_storage->user_defined - && $implementer_classlike_storage->user_defined - && $codebase->analysis_php_version_id >= 80100 - && ($guide_method_storage->return_type - || $guide_method_storage->signature_return_type - ) - && !$implementer_method_storage->signature_return_type - && !array_filter( - $implementer_method_storage->attributes, - function (AttributeStorage $s) { - return $s->fq_class_name === 'ReturnTypeWillChange'; - } - ) - ) { - IssueBuffer::maybeAdd( - new MethodSignatureMustProvideReturnType( - 'Method ' . $cased_implementer_method_id . ' must have a return type signature!', - $implementer_method_storage->location ?: $code_location - ), - $suppressed_issues + $implementer_classlike_storage->suppressed_issues - ); - } - if ($guide_method_storage->return_type && $implementer_method_storage->return_type && !$implementer_method_storage->inherited_return_type @@ -888,14 +862,7 @@ private static function compareMethodSignatureReturnTypes( $implementer_signature_return_type, $guide_signature_return_type ) - : (!$implementer_signature_return_type - && $guide_signature_return_type->isMixed() - ? false - : UnionTypeComparator::isContainedByInPhp( - $implementer_signature_return_type, - $guide_signature_return_type - ) - ); + : UnionTypeComparator::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type); if (!$is_contained_by) { if ($codebase->php_major_version >= 8 diff --git a/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php b/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php deleted file mode 100644 index ab05f06c0d4..00000000000 --- a/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php +++ /dev/null @@ -1,9 +0,0 @@ -markTestSkipped(); } - if (strpos($error_message, 'MethodSignatureMustProvideReturnType') !== false) { - $php_version = '8.1'; - } - $this->project_analyzer->setPhpVersion($php_version, 'tests'); if ($check_references) { diff --git a/tests/MethodSignatureTest.php b/tests/MethodSignatureTest.php index 87c3bc71dbf..11f217b172f 100644 --- a/tests/MethodSignatureTest.php +++ b/tests/MethodSignatureTest.php @@ -1569,37 +1569,6 @@ public function bar(string ...$_args): void {} ', 'error_message' => 'MethodSignatureMismatch', ], - 'noMixedTypehintInDescendant' => [ - ' 'MethodSignatureMismatch', - [], - false, - '8.0' - ], - 'noTypehintInNativeDescendant' => [ - ' 'MethodSignatureMustProvideReturnType', - [], - false, - '8.1' - ], ]; } } From 3ae067795dc54a8eca6f391836d8fa334c9d6470 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Mon, 31 Jan 2022 15:11:06 +0200 Subject: [PATCH 02/20] Update baseline --- psalm-baseline.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index dcd0433d3dd..ab3669360b9 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $comment_block->tags['variablesfrom'][0] @@ -21,6 +21,9 @@ $matches[0] $symbol_parts[1] + + $analysis_php_version_id + From fc281672ea74b52f225839ebff23ece110f0b8ae Mon Sep 17 00:00:00 2001 From: orklah Date: Mon, 31 Jan 2022 20:52:25 +0100 Subject: [PATCH 03/20] fix wrong detection of purity --- .../Expression/Call/FunctionCallAnalyzer.php | 25 +++++++++++++------ .../PureAnnotationAdditionTest.php | 13 ++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 9b6bf31b1ea..77996abb786 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -644,14 +644,23 @@ private static function getAnalyzeNamedExpression( } if ($var_type_part instanceof TClosure || $var_type_part instanceof TCallable) { - 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', - new CodeLocation($statements_analyzer->getSource(), $stmt) - ), - $statements_analyzer->getSuppressedIssues() - ); + if (!$var_type_part->is_pure) { + if ($context->pure || $context->mutation_free) { + IssueBuffer::maybeAdd( + new ImpureFunctionCall( + 'Cannot call an impure function from a mutation-free context', + new CodeLocation($statements_analyzer->getSource(), $stmt) + ), + $statements_analyzer->getSuppressedIssues() + ); + } + + if (!$function_call_info->function_storage) { + $function_call_info->function_storage = new FunctionStorage(); + } + + $function_call_info->function_storage->pure = false; + $function_call_info->function_storage->mutation_free = false; } $function_call_info->function_params = $var_type_part->params; diff --git a/tests/FileManipulation/PureAnnotationAdditionTest.php b/tests/FileManipulation/PureAnnotationAdditionTest.php index ec193460dfa..6938b1294b6 100644 --- a/tests/FileManipulation/PureAnnotationAdditionTest.php +++ b/tests/FileManipulation/PureAnnotationAdditionTest.php @@ -211,6 +211,19 @@ public function bar(string $s) : string { ['MissingPureAnnotation'], true, ], + 'dontAddPureIfCallableNotPure' => [ + ' Date: Mon, 31 Jan 2022 20:55:53 +0100 Subject: [PATCH 04/20] fix --- .../Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 77996abb786..fae2ef0feae 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -39,6 +39,7 @@ use Psalm\Plugin\EventHandler\Event\AfterEveryFunctionCallAnalysisEvent; use Psalm\Storage\Assertion; use Psalm\Storage\FunctionLikeParameter; +use Psalm\Storage\FunctionStorage; use Psalm\Type; use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; From 121a80161634103e278ca1507f8c736742491c56 Mon Sep 17 00:00:00 2001 From: adrew Date: Mon, 31 Jan 2022 21:23:29 +0300 Subject: [PATCH 05/20] Fix object constant inference --- .../Expression/SimpleTypeInferer.php | 15 ++++++- .../Internal/PhpVisitor/ReflectorVisitor.php | 6 ++- tests/ConstantTest.php | 41 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php index 5a1bb9975ed..a22a858465f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php @@ -55,7 +55,8 @@ public static function infer( Aliases $aliases, FileSource $file_source = null, ?array $existing_class_constants = null, - ?string $fq_classlike_name = null + ?string $fq_classlike_name = null, + bool $const_inference = false ): ?Union { if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { @@ -431,6 +432,18 @@ public static function infer( } } + if ($const_inference && $stmt instanceof PhpParser\Node\Expr\New_) { + $resolved_class_name = $stmt->class->getAttribute('resolvedName'); + + if (!is_string($resolved_class_name)) { + return null; + } + + return new Union([ + new Type\Atomic\TNamedObject($resolved_class_name) + ]); + } + return null; } diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index e99259cfca0..95a6e983a55 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -287,7 +287,11 @@ public function enterNode(PhpParser\Node $node): ?int $this->codebase, new NodeDataProvider(), $const->value, - $this->aliases + $this->aliases, + null, + null, + null, + true ) ?? Type::getMixed(); $fq_const_name = Type::getFQCLNFromString($const->name->name, $this->aliases); diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php index 22250503f13..d1a1af3ea43 100644 --- a/tests/ConstantTest.php +++ b/tests/ConstantTest.php @@ -2,14 +2,55 @@ namespace Psalm\Tests; +use Psalm\Context; use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; +use function getcwd; + +use const DIRECTORY_SEPARATOR; + class ConstantTest extends TestCase { use InvalidCodeAnalysisTestTrait; use ValidCodeAnalysisTestTrait; + public function testUseObjectConstant(): void + { + $file1 = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file1.php'; + $file2 = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file2.php'; + + $this->addFile( + $file1, + 'addFile( + $file2, + 'analyzeFile($file1, new Context()); + $this->analyzeFile($file2, new Context()); + } + /** * @return iterable,error_levels?:string[], php_version?: string}> */ From e284b91b82d00200878508cfbfd92dbc506e5cda Mon Sep 17 00:00:00 2001 From: adrew Date: Mon, 31 Jan 2022 22:41:44 +0300 Subject: [PATCH 06/20] Remove redundant flag from SimpleTypeInferer --- .../Analyzer/Statements/Expression/SimpleTypeInferer.php | 5 ++--- src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php index a22a858465f..6462c6d1cd4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php @@ -55,8 +55,7 @@ public static function infer( Aliases $aliases, FileSource $file_source = null, ?array $existing_class_constants = null, - ?string $fq_classlike_name = null, - bool $const_inference = false + ?string $fq_classlike_name = null ): ?Union { if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) { if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { @@ -432,7 +431,7 @@ public static function infer( } } - if ($const_inference && $stmt instanceof PhpParser\Node\Expr\New_) { + if ($stmt instanceof PhpParser\Node\Expr\New_) { $resolved_class_name = $stmt->class->getAttribute('resolvedName'); if (!is_string($resolved_class_name)) { diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index 95a6e983a55..e99259cfca0 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -287,11 +287,7 @@ public function enterNode(PhpParser\Node $node): ?int $this->codebase, new NodeDataProvider(), $const->value, - $this->aliases, - null, - null, - null, - true + $this->aliases ) ?? Type::getMixed(); $fq_const_name = Type::getFQCLNFromString($const->name->name, $this->aliases); From a598efb4abd3efaadc433a2f4328372fa2840800 Mon Sep 17 00:00:00 2001 From: orklah Date: Mon, 31 Jan 2022 21:36:01 +0100 Subject: [PATCH 07/20] Handle first class callable on unknown functions --- .../Statements/Expression/Call/FunctionCallAnalyzer.php | 9 +++++---- tests/ClosureTest.php | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 9b6bf31b1ea..fdc287cb1f6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -484,6 +484,8 @@ private static function handleNamedFunction( $is_maybe_root_function = !$function_name instanceof PhpParser\Node\Name\FullyQualified && count($function_name->parts) === 1; + $args = $stmt->isFirstClassCallable() ? [] : $stmt->getArgs(); + if (!$function_call_info->in_call_map) { $predefined_functions = $codebase->config->getPredefinedFunctions(); $is_predefined = isset($predefined_functions[strtolower($original_function_id)]) @@ -495,11 +497,10 @@ private static function handleNamedFunction( $function_call_info->function_id, $code_location, $is_maybe_root_function - ) === false - ) { - if (ArgumentsAnalyzer::analyze( + ) === false) { + if ($args && ArgumentsAnalyzer::analyze( $statements_analyzer, - $stmt->getArgs(), + $args, null, null, true, diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index a4558a08801..41f10b9231a 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -798,6 +798,10 @@ public static function __callStatic(string $name, array $args): mixed { 'error_levels' => [], '8.1' ], + 'unknownFirstClassCallable' => [ + ' Date: Mon, 31 Jan 2022 21:51:31 +0100 Subject: [PATCH 08/20] fix test --- tests/ClosureTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index 41f10b9231a..e5f6c9551b1 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -800,6 +800,7 @@ public static function __callStatic(string $name, array $args): mixed { ], 'unknownFirstClassCallable' => [ ' Date: Mon, 31 Jan 2022 23:38:15 +0200 Subject: [PATCH 09/20] Reinstate MethodSignatureMustProvideReturnType It's never emitted, but is there for BC. --- config.xsd | 1 + docs/running_psalm/error_levels.md | 1 + docs/running_psalm/issues.md | 1 + .../MethodSignatureMustProvideReturnType.md | 16 ++++++++++++++++ .../MethodSignatureMustProvideReturnType.php | 9 +++++++++ tests/DocumentationTest.php | 4 ++++ 6 files changed, 32 insertions(+) create mode 100644 docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md create mode 100644 src/Psalm/Issue/MethodSignatureMustProvideReturnType.php diff --git a/config.xsd b/config.xsd index 3c8257bc1df..a4ba114c2b5 100644 --- a/config.xsd +++ b/config.xsd @@ -331,6 +331,7 @@ + diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md index 3babb92488a..c58cca026ff 100644 --- a/docs/running_psalm/error_levels.md +++ b/docs/running_psalm/error_levels.md @@ -56,6 +56,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [InvalidThrow](issues/InvalidThrow.md) - [LoopInvalidation](issues/LoopInvalidation.md) - [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md) + - [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md) - [MissingDependency](issues/MissingDependency.md) - [MissingFile](issues/MissingFile.md) - [MissingImmutableAnnotation](issues/MissingImmutableAnnotation.md) diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md index c471c86f4cb..50385f60e31 100644 --- a/docs/running_psalm/issues.md +++ b/docs/running_psalm/issues.md @@ -99,6 +99,7 @@ - [LoopInvalidation](issues/LoopInvalidation.md) - [MethodSignatureMismatch](issues/MethodSignatureMismatch.md) - [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md) + - [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md) - [MismatchingDocblockParamType](issues/MismatchingDocblockParamType.md) - [MismatchingDocblockPropertyType](issues/MismatchingDocblockPropertyType.md) - [MismatchingDocblockReturnType](issues/MismatchingDocblockReturnType.md) diff --git a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md new file mode 100644 index 00000000000..0c85db90d36 --- /dev/null +++ b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md @@ -0,0 +1,16 @@ +# MethodSignatureMustProvideReturnType + +In PHP 8.1+, [most non-final internal methods now require overriding methods to declare a compatible return type, otherwise a deprecated notice is emitted during inheritance validation](https://www.php.net/manual/en/migration81.incompatible.php#migration81.incompatible.core.type-compatibility-internal). + +This issue is emitted when a method overriding a native method is defined without a return type. + +**Only if** the return type cannot be declared to keep support for PHP 7, a `#[ReturnTypeWillChange]` attribute can be added to silence the PHP deprecation notice and Psalm issue. + +```php + 'A']; + } +} +``` diff --git a/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php b/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php new file mode 100644 index 00000000000..ab05f06c0d4 --- /dev/null +++ b/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php @@ -0,0 +1,9 @@ + Date: Tue, 1 Feb 2022 00:06:25 +0200 Subject: [PATCH 10/20] Suppress UnusedClass --- psalm-baseline.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index ab3669360b9..3b9bb725ee3 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -662,6 +662,11 @@ array_keys($template_type_map[$template_param_name])[0] + + + MethodSignatureMustProvideReturnType + + VirtualClass From fabcda16b48d92db3bcbd4960b8408031f8d47b6 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 1 Feb 2022 16:42:12 +0100 Subject: [PATCH 11/20] Ensure `trait_exists()` always returns `bool` Fixes #7478 As discussed in the upstream issue, `trait_exists()` always returns `bool`: while it can return `null` when the arguments passed to it do not match (either no arguments, or 3 or more arguments), we do not support that scenario, as that already doesn't respect the type signature of this function. We cut to the point: always make it `bool`, which is the scenario that works under healthy operational conditions. Ref: https://github.com/Roave/BetterReflection/pull/983#discussion_r790908170 Ref: https://psalm.dev/r/c41a43805d Ref: https://github.com/vimeo/psalm/issues/7478#issuecomment-1020330351 Ref: https://github.com/vimeo/psalm/issues/7478#issuecomment-1020337712 Ref: https://3v4l.org/XpHmh --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_historical.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 7c061338960..5c8237d3831 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -14855,7 +14855,7 @@ 'trader_wclprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], 'trader_willr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], 'trader_wma' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trait_exists' => ['?bool', 'trait'=>'string', 'autoload='=>'bool'], +'trait_exists' => ['bool', 'trait'=>'string', 'autoload='=>'bool'], 'Transliterator::create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'], 'Transliterator::createFromRules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'], 'Transliterator::createInverse' => ['Transliterator'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 2e81f3d3334..8ca16b883dc 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -15941,7 +15941,7 @@ 'trader_wclprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], 'trader_willr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], 'trader_wma' => ['array', 'real'=>'array', 'timePeriod='=>'int'], - 'trait_exists' => ['?bool', 'trait'=>'string', 'autoload='=>'bool'], + 'trait_exists' => ['bool', 'trait'=>'string', 'autoload='=>'bool'], 'transliterator_create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'], 'transliterator_create_from_rules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'], 'transliterator_create_inverse' => ['Transliterator', 'transliterator'=>'Transliterator'], From 1c2ffc81c2e6544381fe07969271d9624da6ef26 Mon Sep 17 00:00:00 2001 From: orklah Date: Tue, 1 Feb 2022 21:46:05 +0100 Subject: [PATCH 12/20] tweaks --- .../Internal/Analyzer/Statements/Expression/CastAnalyzer.php | 1 + src/Psalm/Internal/Codebase/ConstantTypeResolver.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 4f80b23e0a7..35400570ab9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -230,6 +230,7 @@ public static function analyze( if ($type instanceof Scalar) { $keyed_array = new TKeyedArray([new Union([$type])]); $keyed_array->is_list = true; + $keyed_array->sealed = true; $permissible_atomic_types[] = $keyed_array; } elseif ($type instanceof TNull) { $permissible_atomic_types[] = new TArray([Type::getEmpty(), Type::getEmpty()]); diff --git a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php index c27cc523f7c..c73e67a3d35 100644 --- a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php +++ b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php @@ -137,7 +137,9 @@ public static function resolve( } if ($left instanceof TKeyedArray && $right instanceof TKeyedArray) { - return new TKeyedArray($left->properties + $right->properties); + $keyed_array = new TKeyedArray($left->properties + $right->properties); + $keyed_array->sealed = true; + return $keyed_array; } return new TMixed; From 3c3e692e7e1f2cd97b2aca94123d1643cfab34e6 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 2 Feb 2022 13:51:45 +0100 Subject: [PATCH 13/20] AtomicStaticCallAnalyzer: clear tmp var from context (fix #7556) --- .../StaticMethod/AtomicStaticCallAnalyzer.php | 16 ++++++++----- tests/MagicMethodAnnotationTest.php | 23 ++++++++++++++++++ tests/MixinAnnotationTest.php | 24 +++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index 4c57dfaf9c1..4e85c2c0f70 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -481,14 +481,15 @@ private static function handleNamedCall( $class_storage->final ); - $context->vars_in_scope['$tmp_mixin_var'] = $new_lhs_type; + $mixin_context = clone $context; + $mixin_context->vars_in_scope['$__tmp_mixin_var__'] = $new_lhs_type; return self::forwardCallToInstanceMethod( $statements_analyzer, $stmt, $stmt_name, - $context, - 'tmp_mixin_var', + $mixin_context, + '__tmp_mixin_var__', true ); } @@ -694,18 +695,21 @@ function (PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem { // with nonexistent method, we try to forward to instance method call for resolve pseudo method. // Use parent type as static type for the method call - $context->vars_in_scope['$tmp_parent_var'] = new Union([$lhs_type_part]); + $tmp_context = clone $context; + $tmp_context->vars_in_scope['$__tmp_parent_var__'] = new Union([$lhs_type_part]); if (self::forwardCallToInstanceMethod( $statements_analyzer, $stmt, $stmt_name, - $context, - 'tmp_parent_var' + $tmp_context, + '__tmp_parent_var__' ) === false) { return false; } + unset($tmp_context); + // Resolve actual static return type according to caller (i.e. $this) static type if (isset($context->vars_in_scope['$this']) && $method_call_type = $statements_analyzer->node_data->getType($stmt) diff --git a/tests/MagicMethodAnnotationTest.php b/tests/MagicMethodAnnotationTest.php index 46f8e0db842..edde62ee191 100644 --- a/tests/MagicMethodAnnotationTest.php +++ b/tests/MagicMethodAnnotationTest.php @@ -1119,6 +1119,29 @@ class B extends A {} class C {}', 'error_message' => 'InvalidDocblock', ], + 'magicParentCallShouldNotPolluteContext' => [ + ' 'UndefinedVariable', + ] ]; } diff --git a/tests/MixinAnnotationTest.php b/tests/MixinAnnotationTest.php index 37d04f70404..12755d3309a 100644 --- a/tests/MixinAnnotationTest.php +++ b/tests/MixinAnnotationTest.php @@ -687,6 +687,30 @@ function test() : FooGrandChild { }', 'error_message' => 'LessSpecificReturnStatement' ], + 'mixinStaticCallShouldNotPolluteContext' => [ + ' + */ + class Bar + { + public function baz(): self + { + self::foobar(); + return $__tmp_mixin_var__; + } + }', + 'error_message' => 'UndefinedVariable' + ], ]; } } From 582624a932b90310e54af3e9c40b3c6ad6d2e062 Mon Sep 17 00:00:00 2001 From: phptest2 <98947593+phptest2@users.noreply.github.com> Date: Thu, 3 Feb 2022 17:58:12 +0100 Subject: [PATCH 14/20] improving error message for Could not resolve config path $directory_path is false, so better use $prospective_directory_path in the error message. --- src/Psalm/Config/FileFilter.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php index 1443594c7a7..76f53d6be2e 100644 --- a/src/Psalm/Config/FileFilter.php +++ b/src/Psalm/Config/FileFilter.php @@ -170,8 +170,7 @@ public static function loadFromArray( } throw new ConfigException( - 'Could not resolve config path to ' . $base_dir - . DIRECTORY_SEPARATOR . $directory_path + 'Could not resolve config path to ' . $prospective_directory_path ); } From bcbfbed07224bdcf22f5e9f8738a021d892f9f5a Mon Sep 17 00:00:00 2001 From: Tomasz Kusy Date: Fri, 4 Feb 2022 19:39:39 +0100 Subject: [PATCH 15/20] Resolve __DIR__ / __FILE__ when const/variable is used for include --- .../Statements/Expression/MagicConstAnalyzer.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php index 4432087d9b8..1849735a4a6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php @@ -84,10 +84,16 @@ public static function analyze( } else { $statements_analyzer->node_data->setType($stmt, new Union([new TCallableString])); } - } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File - || $stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir - ) { - $statements_analyzer->node_data->setType($stmt, new Union([new TNonEmptyString()])); + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir) { + $statements_analyzer->node_data->setType( + $stmt, + Type::getString(dirname($statements_analyzer->getSource()->getFilePath())) + ); + } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File) { + $statements_analyzer->node_data->setType( + $stmt, + Type::getString($statements_analyzer->getSource()->getFilePath()) + ); } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Trait_) { if ($statements_analyzer->getSource() instanceof TraitAnalyzer) { $statements_analyzer->node_data->setType($stmt, new Union([new TNonEmptyString()])); From b73f2c96d822e066650edc0a21f58d717862ae6f Mon Sep 17 00:00:00 2001 From: Tomasz Kusy Date: Fri, 4 Feb 2022 19:43:55 +0100 Subject: [PATCH 16/20] Resolve __DIR__ / __FILE__ when const/variable is used for include CS fix --- .../Analyzer/Statements/Expression/MagicConstAnalyzer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php index 1849735a4a6..ccaebe7ceda 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php @@ -87,7 +87,7 @@ public static function analyze( } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir) { $statements_analyzer->node_data->setType( $stmt, - Type::getString(dirname($statements_analyzer->getSource()->getFilePath())) + Type::getString(\dirname($statements_analyzer->getSource()->getFilePath())) ); } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File) { $statements_analyzer->node_data->setType( From 8da45aa7d8287baa23b29c2589539109b100ed14 Mon Sep 17 00:00:00 2001 From: Tomasz Kusy Date: Fri, 4 Feb 2022 20:26:06 +0100 Subject: [PATCH 17/20] Resolve __DIR__ / __FILE__ when const/variable is used for include CS fix fix --- .../Analyzer/Statements/Expression/MagicConstAnalyzer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php index ccaebe7ceda..fc8ce4eba0e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php @@ -15,6 +15,7 @@ use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Union; +use function dirname; class MagicConstAnalyzer { @@ -87,7 +88,7 @@ public static function analyze( } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir) { $statements_analyzer->node_data->setType( $stmt, - Type::getString(\dirname($statements_analyzer->getSource()->getFilePath())) + Type::getString(dirname($statements_analyzer->getSource()->getFilePath())) ); } elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File) { $statements_analyzer->node_data->setType( From 124aa22fe9bff661a284f03a7acb1667e25ccc5b Mon Sep 17 00:00:00 2001 From: Tomasz Kusy Date: Fri, 4 Feb 2022 20:27:45 +0100 Subject: [PATCH 18/20] Resolve __DIR__ / __FILE__ when const/variable is used for include CS fix fix fix :) --- .../Analyzer/Statements/Expression/MagicConstAnalyzer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php index fc8ce4eba0e..481a5afa0d1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php @@ -15,6 +15,7 @@ use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Union; + use function dirname; class MagicConstAnalyzer From 21e6371ce25c42ea19dcd6f0e1ce16a5e8ae82c7 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Wed, 9 Feb 2022 15:35:40 +0200 Subject: [PATCH 19/20] Strip colours from success message Fixes vimeo/psalm#7619 --- src/Psalm/IssueBuffer.php | 20 +++++++++++++++----- tests/IssueBufferTest.php | 4 +++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index 9de67613558..5c666574ffe 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -691,7 +691,7 @@ function (IssueData $d1, IssueData $d2): int { : $error_count . ' errors' ) . ' found' . "\n"; } else { - self::printSuccessMessage(); + self::printSuccessMessage($project_analyzer); } $show_info = $project_analyzer->stdout_report_options->show_info; @@ -782,8 +782,12 @@ function (IssueData $d1, IssueData $d2): int { } } - public static function printSuccessMessage(): void + public static function printSuccessMessage(ProjectAnalyzer $project_analyzer): void { + if (!$project_analyzer->stdout_report_options) { + throw new UnexpectedValueException('Cannot print success message without stdout report options'); + } + // this message will be printed $message = "No errors found!"; @@ -808,9 +812,15 @@ public static function printSuccessMessage(): void // text style, 1 = bold $style = "1"; - echo "\e[{$background};{$style}m{$paddingTop}\e[0m" . "\n"; - echo "\e[{$background};{$foreground};{$style}m{$messageWithPadding}\e[0m" . "\n"; - echo "\e[{$background};{$style}m{$paddingBottom}\e[0m" . "\n"; + if ($project_analyzer->stdout_report_options->use_color) { + echo "\e[{$background};{$style}m{$paddingTop}\e[0m" . "\n"; + echo "\e[{$background};{$foreground};{$style}m{$messageWithPadding}\e[0m" . "\n"; + echo "\e[{$background};{$style}m{$paddingBottom}\e[0m" . "\n"; + } else { + echo "\n"; + echo "$messageWithPadding\n"; + echo "\n"; + } } /** diff --git a/tests/IssueBufferTest.php b/tests/IssueBufferTest.php index a1f8714771b..7da31f16a64 100644 --- a/tests/IssueBufferTest.php +++ b/tests/IssueBufferTest.php @@ -113,8 +113,10 @@ public function testFinishDoesNotCorruptInternalState(): void public function testPrintSuccessMessageWorks(): void { + $project_analyzer = $this->createMock(ProjectAnalyzer::class); + $project_analyzer->stdout_report_options = new ReportOptions; ob_start(); - IssueBuffer::printSuccessMessage(); + IssueBuffer::printSuccessMessage($project_analyzer); $output = ob_get_clean(); $this->assertStringContainsString('No errors found!', $output); From 0702a0b3e71a70ae09b72e59a8eeb5623242ec7c Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 9 Feb 2022 19:32:17 +0100 Subject: [PATCH 20/20] add ReflectionIntersectionType stub --- stubs/Php81.phpstub | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/stubs/Php81.phpstub b/stubs/Php81.phpstub index 97dd281e1d2..d3398388de4 100644 --- a/stubs/Php81.phpstub +++ b/stubs/Php81.phpstub @@ -56,6 +56,13 @@ namespace { */ public function getBackingValue(): int|string; } + + class ReflectionIntersectionType extends ReflectionType { + /** + * @return non-empty-list + */ + public function getTypes() {} + } } namespace FTP {