diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php index 9d5ed21ca..765535b82 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php @@ -4,6 +4,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression\Call\Method; use PhpParser; use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; +use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector; use Psalm\Codebase; use Psalm\CodeLocation; use Psalm\Context; @@ -145,16 +146,83 @@ class MethodCallPurityAnalyzer && isset($context->vars_in_scope[$mutation_var_id]) && !isset($class_storage->declaring_property_ids[$name]); - $current_context = $context; - - do { - $current_context->remove($mutation_var_id); - } while ($current_context = $current_context->parent_context); - if ($this_property_didnt_exist) { $context->vars_in_scope[$mutation_var_id] = Type::getMixed(); + } else { + $context->vars_in_scope[$mutation_var_id] = self::getExpandedPropertyType( + $codebase, + $class_storage->name, + $name, + $class_storage + ) ?: Type::getMixed(); } } } } + + private static function getExpandedPropertyType( + Codebase $codebase, + string $fq_class_name, + string $property_name, + \Psalm\Storage\ClassLikeStorage $storage + ) : ?Type\Union { + $property_class_name = $codebase->properties->getDeclaringClassForProperty( + $fq_class_name . '::$' . $property_name, + true + ); + + if ($property_class_name === null) { + return null; + } + + $property_class_storage = $codebase->classlike_storage_provider->get($property_class_name); + + $property_storage = $property_class_storage->properties[$property_name]; + + if (!$property_storage->type) { + return null; + } + + $property_type = clone $property_storage->type; + + $fleshed_out_type = !$property_type->isMixed() + ? \Psalm\Internal\Type\TypeExpander::expandUnion( + $codebase, + $property_type, + $fq_class_name, + $fq_class_name, + $storage->parent_class, + true, + false, + $storage->final + ) + : $property_type; + + $class_template_params = ClassTemplateParamCollector::collect( + $codebase, + $property_class_storage, + $storage, + null, + new Type\Atomic\TNamedObject($fq_class_name), + true + ); + + $template_result = new \Psalm\Internal\Type\TemplateResult( + $class_template_params ?: [], + [] + ); + + if ($class_template_params) { + $fleshed_out_type = \Psalm\Internal\Type\TemplateStandinTypeReplacer::replace( + $fleshed_out_type, + $template_result, + $codebase, + null, + null, + null + ); + } + + return $fleshed_out_type; + } }