From 36c2eeed16796f190c1078b9f64a3d53b917f263 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 20 Oct 2023 11:18:56 +0200 Subject: [PATCH] Partially fix #9698 --- .../Statements/Block/ForeachAnalyzer.php | 11 +++++++++++ .../Fetch/InstancePropertyFetchAnalyzer.php | 3 +++ .../Reflector/FunctionLikeDocblockScanner.php | 16 ++++++++++------ src/Psalm/Internal/Type/TypeExpander.php | 1 + src/Psalm/Type.php | 8 ++++++++ src/Psalm/Type/MutableUnion.php | 11 +++++++++++ src/Psalm/Type/Reconciler.php | 4 ++++ src/Psalm/Type/Union.php | 8 ++++++++ 8 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index 263f445af7e..dd5598fe3f8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -919,6 +919,17 @@ public static function handleIterable( 'key', ); + if ($iterator_value_type->ignore_nullable_issues_foreach) { + $iterator_value_type = $iterator_value_type->setProperties([ + 'ignore_nullable_issues' => true + ]); + } + if ($iterator_key_type->ignore_nullable_issues_foreach) { + $iterator_key_type = $iterator_key_type->setProperties([ + 'ignore_nullable_issues' => true + ]); + } + if ($iterator_value_type && !$iterator_value_type->isMixed()) { $value_type = Type::combineUnionTypes($value_type, $iterator_value_type); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php index 528acd0609d..2b5fd7d1882 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php @@ -259,6 +259,9 @@ public static function analyze( if ($stmt_var_type->ignore_nullable_issues) { $stmt_type->ignore_nullable_issues = true; } + if ($stmt_var_type->ignore_nullable_issues_foreach) { + $stmt_type->ignore_nullable_issues_foreach = true; + } $stmt_type = $stmt_type->freeze(); $statements_analyzer->node_data->setType($stmt, $stmt_type); } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index 5514169c401..1db75024f63 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -3,6 +3,7 @@ namespace Psalm\Internal\PhpVisitor\Reflector; use AssertionError; +use Iterator; use PhpParser; use Psalm\Aliases; use Psalm\CodeLocation; @@ -1040,15 +1041,18 @@ private static function handleReturn( ); } - // we make sure we only add ignore flag for internal stubs if the config is set to true if ($docblock_info->ignore_nullable_return && $storage->return_type - && ($codebase->config->ignore_internal_nullable_issues - || !in_array($file_storage->file_path, $codebase->config->internal_stubs) - ) ) { - /** @psalm-suppress InaccessibleProperty We just created this type */ - $storage->return_type->ignore_nullable_issues = true; + // we make sure we only add ignore flag for internal stubs if the config is set to true + if ($codebase->config->ignore_internal_nullable_issues + || !in_array($file_storage->file_path, $codebase->config->internal_stubs) + ) { + /** @psalm-suppress InaccessibleProperty We just created this type */ + $storage->return_type->ignore_nullable_issues = true; + } elseif ($storage instanceof MethodStorage && $storage->defining_fqcln === Iterator::class) { + $storage->return_type->ignore_nullable_issues_foreach = true; + } } // we make sure we only add ignore flag for internal stubs if the config is set to true diff --git a/src/Psalm/Internal/Type/TypeExpander.php b/src/Psalm/Internal/Type/TypeExpander.php index 56f558038d5..7c849c2507b 100644 --- a/src/Psalm/Internal/Type/TypeExpander.php +++ b/src/Psalm/Internal/Type/TypeExpander.php @@ -95,6 +95,7 @@ public static function expandUnion( ); $fleshed_out_type->from_docblock = $return_type->from_docblock; + $fleshed_out_type->ignore_nullable_issues_foreach = $return_type->ignore_nullable_issues_foreach; $fleshed_out_type->ignore_nullable_issues = $return_type->ignore_nullable_issues; $fleshed_out_type->ignore_falsable_issues = $return_type->ignore_falsable_issues; $fleshed_out_type->possibly_undefined = $return_type->possibly_undefined; diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index f1acd2f3a5f..481dc085fd9 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -665,6 +665,10 @@ public static function combineUnionTypes( $combined_type->ignore_nullable_issues = true; } + if ($type_1->ignore_nullable_issues_foreach || $type_2->ignore_nullable_issues_foreach) { + $combined_type->ignore_nullable_issues_foreach = true; + } + if ($type_1->ignore_falsable_issues || $type_2->ignore_falsable_issues) { $combined_type->ignore_falsable_issues = true; } @@ -814,6 +818,10 @@ public static function intersectUnionTypes( $combined_type->ignore_nullable_issues = true; } + if ($type_1->ignore_nullable_issues_foreach && $type_2->ignore_nullable_issues_foreach) { + $combined_type->ignore_nullable_issues_foreach = true; + } + if ($type_1->ignore_falsable_issues && $type_2->ignore_falsable_issues) { $combined_type->ignore_falsable_issues = true; } diff --git a/src/Psalm/Type/MutableUnion.php b/src/Psalm/Type/MutableUnion.php index d22059efcab..7154cc58047 100644 --- a/src/Psalm/Type/MutableUnion.php +++ b/src/Psalm/Type/MutableUnion.php @@ -96,6 +96,13 @@ final class MutableUnion implements TypeNode */ public $failed_reconciliation = false; + /** + * Whether or not to ignore issues with possibly-null values when using foreach + * + * @var bool + */ + public $ignore_nullable_issues_foreach = false; + /** * Whether or not to ignore issues with possibly-null values * @@ -398,6 +405,10 @@ public function substitute($old_type, $new_type = null): self $this->ignore_nullable_issues = true; } + if ($new_type && $new_type->ignore_nullable_issues_foreach) { + $this->ignore_nullable_issues_foreach = true; + } + if ($new_type && $new_type->ignore_falsable_issues) { $this->ignore_falsable_issues = true; } diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index f10a66408fe..74399d2b5ee 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -738,6 +738,10 @@ private static function getValueForKey( /** @psalm-suppress InaccessibleProperty We just created this type */ $new_base_type_candidate->ignore_nullable_issues = true; } + if ($existing_keys[$base_key]->ignore_nullable_issues_foreach) { + /** @psalm-suppress InaccessibleProperty We just created this type */ + $new_base_type_candidate->ignore_nullable_issues_foreach = true; + } } elseif ($existing_key_type_part instanceof TClassStringMap) { return Type::getMixed(); } elseif ($existing_key_type_part instanceof TNever diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 90a891917b1..23c52e5d21c 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -116,6 +116,14 @@ final class Union implements TypeNode */ public $ignore_nullable_issues = false; + + /** + * Whether or not to ignore issues with possibly-null values when using foreach + * + * @var bool + */ + public $ignore_nullable_issues_foreach = false; + /** * Whether or not to ignore issues with possibly-false values *