>|null * @psalm-suppress MoreSpecificReturnType * @psalm-suppress LessSpecificReturnStatement */ public static function collect( Codebase $codebase, ClassLikeStorage $class_storage, ClassLikeStorage $static_class_storage, ?string $method_name = null, ?Atomic $lhs_type_part = null, bool $self_call = false ): ?array { $static_fq_class_name = $static_class_storage->name; $non_trait_class_storage = $class_storage->is_trait ? $static_class_storage : $class_storage; $template_types = $class_storage->template_types; $candidate_class_storages = [$class_storage]; if ($static_class_storage->template_extended_params && $method_name && !empty($non_trait_class_storage->overridden_method_ids[$method_name]) && isset($class_storage->methods[$method_name]) && (!isset($non_trait_class_storage->methods[$method_name]->return_type) || $class_storage->methods[$method_name]->inherited_return_type) ) { foreach ($non_trait_class_storage->overridden_method_ids[$method_name] as $overridden_method_id) { $overridden_storage = $codebase->methods->getStorage($overridden_method_id); if (!$overridden_storage->return_type) { continue; } if ($overridden_storage->return_type->isNull()) { continue; } $fq_overridden_class = $overridden_method_id->fq_class_name; $overridden_class_storage = $codebase->classlike_storage_provider->get($fq_overridden_class); $overridden_template_types = $overridden_class_storage->template_types; if (!$template_types) { $template_types = $overridden_template_types; } elseif ($overridden_template_types) { foreach ($overridden_template_types as $template_name => $template_map) { if (isset($template_types[$template_name])) { $template_types[$template_name] = array_merge( $template_types[$template_name], $template_map ); } else { $template_types[$template_name] = $template_map; } } } $candidate_class_storages[] = $overridden_class_storage; } } if (!$template_types) { return null; } $class_template_params = []; $e = $static_class_storage->template_extended_params; if ($lhs_type_part instanceof TGenericObject) { if ($class_storage === $static_class_storage && $class_storage->template_types) { $i = 0; foreach ($class_storage->template_types as $type_name => $_) { if (isset($lhs_type_part->type_params[$i])) { $class_template_params[$type_name][$class_storage->name] = $lhs_type_part->type_params[$i]; } $i++; } } foreach ($template_types as $type_name => $_) { if (isset($class_template_params[$type_name])) { continue; } if ($class_storage !== $static_class_storage && isset($e[$class_storage->name][$type_name]) ) { $input_type_extends = $e[$class_storage->name][$type_name]; $output_type_extends = self::resolveTemplateParam( $input_type_extends, $static_class_storage, $lhs_type_part ); if (!$self_call || $static_fq_class_name !== $class_storage->name) { $class_template_params[$type_name][$class_storage->name] = $output_type_extends ?? Type::getMixed(); } } if ((!$self_call || $static_fq_class_name !== $class_storage->name) && !isset($class_template_params[$type_name]) ) { $class_template_params[$type_name] = [$class_storage->name => Type::getMixed()]; } } } foreach ($template_types as $type_name => $type_map) { foreach ($type_map as $type) { foreach ($candidate_class_storages as $candidate_class_storage) { if ($candidate_class_storage !== $static_class_storage && isset($e[$candidate_class_storage->name][$type_name]) && !isset($class_template_params[$type_name][$candidate_class_storage->name]) ) { $class_template_params[$type_name][$candidate_class_storage->name] = new Union( self::expandType( $codebase, $e[$candidate_class_storage->name][$type_name], $e, $static_class_storage->name, $static_class_storage->template_types ) ); } } if (!$self_call) { if (!isset($class_template_params[$type_name])) { $class_template_params[$type_name][$class_storage->name] = $type; } } } } return $class_template_params; } public static function resolveTemplateParam( Union $input_type_extends, ClassLikeStorage $static_class_storage, TGenericObject $lhs_type_part ): ?Union { $output_type_extends = null; foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) { if ($type_extends_atomic instanceof TTemplateParam) { if (isset( $static_class_storage ->template_types [$type_extends_atomic->param_name] [$type_extends_atomic->defining_class] ) ) { $mapped_offset = array_search( $type_extends_atomic->param_name, array_keys($static_class_storage->template_types), true ); if ($mapped_offset !== false && isset($lhs_type_part->type_params[$mapped_offset]) ) { $output_type_extends = Type::combineUnionTypes( $lhs_type_part->type_params[$mapped_offset], $output_type_extends ); } } elseif (isset( $static_class_storage ->template_extended_params [$type_extends_atomic->defining_class] [$type_extends_atomic->param_name] )) { $nested_output_type = self::resolveTemplateParam( $static_class_storage ->template_extended_params [$type_extends_atomic->defining_class] [$type_extends_atomic->param_name], $static_class_storage, $lhs_type_part ); if ($nested_output_type !== null) { $output_type_extends = Type::combineUnionTypes( $nested_output_type, $output_type_extends ); } } } else { $output_type_extends = Type::combineUnionTypes( new Union([$type_extends_atomic]), $output_type_extends ); } } return $output_type_extends; } /** * @param array> $e * @return non-empty-list */ private static function expandType( Codebase $codebase, Union $input_type_extends, array $e, string $static_fq_class_name, ?array $static_template_types ): array { $output_type_extends = []; foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) { if ($type_extends_atomic instanceof TTemplateParam && ($static_fq_class_name !== $type_extends_atomic->defining_class || !isset($static_template_types[$type_extends_atomic->param_name])) && isset($e[$type_extends_atomic->defining_class][$type_extends_atomic->param_name]) ) { $output_type_extends = array_merge( $output_type_extends, self::expandType( $codebase, $e[$type_extends_atomic->defining_class][$type_extends_atomic->param_name], $e, $static_fq_class_name, $static_template_types ) ); } elseif ($type_extends_atomic instanceof TClassConstant) { $expanded = TypeExpander::expandAtomic( $codebase, $type_extends_atomic, $type_extends_atomic->fq_classlike_name, $type_extends_atomic->fq_classlike_name, null, true, true ); if ($expanded instanceof Atomic) { $output_type_extends[] = $expanded; } else { foreach ($expanded as $type) { $output_type_extends[] = $type; } } } else { $output_type_extends[] = $type_extends_atomic; } } return $output_type_extends; } }