classlike_storage_provider = $classlike_storage_provider; $this->file_storage_provider = $file_storage_provider; $this->classlikes = $classlikes; $this->debug_output = $debug_output; $this->config = $config; } /** * @return void */ public function populateCodebase(\Psalm\Codebase $codebase) { if ($this->debug_output) { echo 'ClassLikeStorage is populating' . "\n"; } foreach ($this->classlike_storage_provider->getAll() as $class_storage) { if (!$class_storage->user_defined && !$class_storage->stubbed) { continue; } $this->populateClassLikeStorage($class_storage); } if ($this->debug_output) { echo 'ClassLikeStorage is populated' . "\n"; } if ($this->debug_output) { echo 'FileStorage is populating' . "\n"; } $all_file_storage = $this->file_storage_provider->getAll(); foreach ($all_file_storage as $file_storage) { $this->populateFileStorage($file_storage); } foreach ($this->classlike_storage_provider->getAll() as $class_storage) { if ($this->config->allow_phpstorm_generics) { foreach ($class_storage->properties as $property_storage) { if ($property_storage->type) { $this->convertPhpStormGenericToPsalmGeneric($property_storage->type, true); } } foreach ($class_storage->methods as $method_storage) { if ($method_storage->return_type) { $this->convertPhpStormGenericToPsalmGeneric($method_storage->return_type); } foreach ($method_storage->params as $param_storage) { if ($param_storage->type) { $this->convertPhpStormGenericToPsalmGeneric($param_storage->type); } } } } if ($class_storage->aliases) { foreach ($class_storage->public_class_constant_nodes as $const_name => $node) { $const_type = \Psalm\Checker\StatementsChecker::getSimpleType( $codebase, $node, $class_storage->aliases, null, null, $class_storage->name ); $class_storage->public_class_constants[$const_name] = $const_type ?: Type::getMixed(); } foreach ($class_storage->protected_class_constant_nodes as $const_name => $node) { $const_type = \Psalm\Checker\StatementsChecker::getSimpleType( $codebase, $node, $class_storage->aliases, null, null, $class_storage->name ); $class_storage->protected_class_constants[$const_name] = $const_type ?: Type::getMixed(); } foreach ($class_storage->private_class_constant_nodes as $const_name => $node) { $const_type = \Psalm\Checker\StatementsChecker::getSimpleType( $codebase, $node, $class_storage->aliases, null, null, $class_storage->name ); $class_storage->private_class_constants[$const_name] = $const_type ?: Type::getMixed(); } } } if ($this->config->allow_phpstorm_generics) { foreach ($all_file_storage as $file_storage) { foreach ($file_storage->functions as $function_storage) { if ($function_storage->return_type) { $this->convertPhpStormGenericToPsalmGeneric($function_storage->return_type); } foreach ($function_storage->params as $param_storage) { if ($param_storage->type) { $this->convertPhpStormGenericToPsalmGeneric($param_storage->type); } } } } } if ($this->debug_output) { echo 'FileStorage is populated' . "\n"; } } /** * @param ClassLikeStorage $storage * @param array $dependent_classlikes * * @return void */ private function populateClassLikeStorage(ClassLikeStorage $storage, array $dependent_classlikes = []) { if ($storage->populated) { return; } $fq_classlike_name_lc = strtolower($storage->name); if (isset($dependent_classlikes[$fq_classlike_name_lc])) { if ($storage->location && IssueBuffer::accepts( new CircularReference( 'Circular reference discovered when loading ' . $storage->name, $storage->location ) )) { // fall through } return; } $storage_provider = $this->classlike_storage_provider; $this->populateDataFromTraits($storage, $storage_provider, $dependent_classlikes); $dependent_classlikes[$fq_classlike_name_lc] = true; if ($storage->parent_classes) { $this->populateDataFromParentClass($storage, $storage_provider, $dependent_classlikes); } $this->populateInterfaceDataFromParentInterfaces($storage, $storage_provider, $dependent_classlikes); $this->populateDataFromImplementedInterfaces($storage, $storage_provider, $dependent_classlikes); if ($storage->location) { $file_path = $storage->location->file_path; foreach ($storage->parent_interfaces as $parent_interface_lc) { FileReferenceProvider::addFileInheritanceToClass($file_path, $parent_interface_lc); } foreach ($storage->parent_classes as $parent_class_lc) { FileReferenceProvider::addFileInheritanceToClass($file_path, $parent_class_lc); } foreach ($storage->class_implements as $implemented_interface) { FileReferenceProvider::addFileInheritanceToClass($file_path, strtolower($implemented_interface)); } foreach ($storage->used_traits as $used_trait_lc) { FileReferenceProvider::addFileInheritanceToClass($file_path, $used_trait_lc); } } if ($this->debug_output) { echo 'Have populated ' . $storage->name . "\n"; } $storage->populated = true; } /** * @return void */ private function populateDataFromTraits( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes ) { foreach ($storage->used_traits as $used_trait_lc => $_) { try { $trait_storage = $storage_provider->get($used_trait_lc); } catch (\InvalidArgumentException $e) { continue; } $this->populateClassLikeStorage($trait_storage, $dependent_classlikes); $this->inheritMethodsFromParent($storage, $trait_storage); $this->inheritPropertiesFromParent($storage, $trait_storage); } } /** * @return void */ private function populateDataFromParentClass( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes ) { $parent_storage_class = reset($storage->parent_classes); try { $parent_storage = $storage_provider->get($parent_storage_class); } catch (\InvalidArgumentException $e) { $storage->invalid_dependencies[] = $parent_storage_class; $parent_storage = null; } if ($parent_storage) { $this->populateClassLikeStorage($parent_storage, $dependent_classlikes); $storage->parent_classes = array_merge($storage->parent_classes, $parent_storage->parent_classes); $this->inheritMethodsFromParent($storage, $parent_storage); $this->inheritPropertiesFromParent($storage, $parent_storage); $storage->class_implements += $parent_storage->class_implements; $storage->invalid_dependencies = array_merge( $storage->invalid_dependencies, $parent_storage->invalid_dependencies ); $storage->public_class_constants += $parent_storage->public_class_constants; $storage->protected_class_constants += $parent_storage->protected_class_constants; $storage->pseudo_property_get_types += $parent_storage->pseudo_property_get_types; $storage->pseudo_property_set_types += $parent_storage->pseudo_property_set_types; } } /** * @return void */ private function populateInterfaceDataFromParentInterfaces( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes ) { $parent_interfaces = []; foreach ($storage->parent_interfaces as $parent_interface_lc => $_) { try { $parent_interface_storage = $storage_provider->get($parent_interface_lc); } catch (\InvalidArgumentException $e) { $storage->invalid_dependencies[] = $parent_interface_lc; continue; } $this->populateClassLikeStorage($parent_interface_storage, $dependent_classlikes); // copy over any constants $storage->public_class_constants = array_merge( $storage->public_class_constants, $parent_interface_storage->public_class_constants ); $storage->invalid_dependencies = array_merge( $storage->invalid_dependencies, $parent_interface_storage->invalid_dependencies ); $parent_interfaces = array_merge($parent_interfaces, $parent_interface_storage->parent_interfaces); $this->inheritMethodsFromParent($storage, $parent_interface_storage); } $storage->parent_interfaces = array_merge($parent_interfaces, $storage->parent_interfaces); } /** * @return void */ private function populateDataFromImplementedInterfaces( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes ) { $extra_interfaces = []; foreach ($storage->class_implements as $implemented_interface_lc => $_) { try { $implemented_interface_storage = $storage_provider->get($implemented_interface_lc); } catch (\InvalidArgumentException $e) { $storage->invalid_dependencies[] = $implemented_interface_lc; continue; } $this->populateClassLikeStorage($implemented_interface_storage, $dependent_classlikes); // copy over any constants $storage->public_class_constants = array_merge( $storage->public_class_constants, $implemented_interface_storage->public_class_constants ); $storage->invalid_dependencies = array_merge( $storage->invalid_dependencies, $implemented_interface_storage->invalid_dependencies ); $extra_interfaces = array_merge($extra_interfaces, $implemented_interface_storage->parent_interfaces); $storage->public_class_constants += $implemented_interface_storage->public_class_constants; } $storage->class_implements = array_merge($extra_interfaces, $storage->class_implements); $interface_method_implementers = []; foreach ($storage->class_implements as $implemented_interface) { try { $implemented_interface_storage = $storage_provider->get($implemented_interface); } catch (\InvalidArgumentException $e) { continue; } foreach ($implemented_interface_storage->methods as $method_name => $method) { if ($method->visibility === ClassLikeChecker::VISIBILITY_PUBLIC) { $mentioned_method_id = $implemented_interface . '::' . $method_name; $interface_method_implementers[$method_name][] = $mentioned_method_id; } } } foreach ($interface_method_implementers as $method_name => $interface_method_ids) { if (count($interface_method_ids) === 1) { $storage->overridden_method_ids[$method_name][] = $interface_method_ids[0]; } else { $storage->interface_method_ids[$method_name] = $interface_method_ids; } } } /** * @param FileStorage $storage * @param array $dependent_file_paths * * @return void */ private function populateFileStorage(FileStorage $storage, array $dependent_file_paths = []) { if ($storage->populated) { return; } $file_path_lc = strtolower($storage->file_path); if (isset($dependent_file_paths[$file_path_lc])) { return; } $dependent_file_paths[$file_path_lc] = true; $all_required_file_paths = $storage->required_file_paths; foreach ($storage->required_file_paths as $included_file_path => $_) { try { $included_file_storage = $this->file_storage_provider->get($included_file_path); } catch (\InvalidArgumentException $e) { continue; } $this->populateFileStorage($included_file_storage, $dependent_file_paths); $all_required_file_paths = $all_required_file_paths + $included_file_storage->required_file_paths; } foreach ($all_required_file_paths as $included_file_path => $_) { try { $included_file_storage = $this->file_storage_provider->get($included_file_path); } catch (\InvalidArgumentException $e) { continue; } $storage->declaring_function_ids = array_merge( $included_file_storage->declaring_function_ids, $storage->declaring_function_ids ); $storage->declaring_constants = array_merge( $included_file_storage->declaring_constants, $storage->declaring_constants ); } $storage->required_file_paths = $all_required_file_paths; foreach ($all_required_file_paths as $required_file_path) { try { $required_file_storage = $this->file_storage_provider->get($required_file_path); } catch (\InvalidArgumentException $e) { continue; } $required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path]; } foreach ($storage->required_classes as $required_classlike) { try { $classlike_storage = $this->classlike_storage_provider->get($required_classlike); } catch (\InvalidArgumentException $e) { continue; } if (!$classlike_storage->location) { continue; } try { $required_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path); } catch (\InvalidArgumentException $e) { continue; } $required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path]; } $storage->populated = true; } /** * @param Type\Union $candidate * @param bool $is_property * * @return void */ private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, $is_property = false) { $atomic_types = $candidate->getTypes(); if (isset($atomic_types['array']) && count($atomic_types) > 1) { $iterator_name = null; $generic_params = null; foreach ($atomic_types as $type) { if ($type instanceof Type\Atomic\TNamedObject && (!$type->from_docblock || $is_property) && ( strtolower($type->value) === 'traversable' || $this->classlikes->interfaceExtends( $type->value, 'Traversable' ) || $this->classlikes->classImplements( $type->value, 'Traversable' ) ) ) { $iterator_name = $type->value; } elseif ($type instanceof Type\Atomic\TArray) { $generic_params = $type->type_params; } } if ($iterator_name && $generic_params) { $generic_iterator = new Type\Atomic\TGenericObject($iterator_name, $generic_params); $candidate->removeType('array'); $candidate->addType($generic_iterator); } } } /** * @param ClassLikeStorage $storage * @param ClassLikeStorage $parent_storage * * @return void */ protected function inheritMethodsFromParent(ClassLikeStorage $storage, ClassLikeStorage $parent_storage) { $fq_class_name = $storage->name; // register where they appear (can never be in a trait) foreach ($parent_storage->appearing_method_ids as $method_name => $appearing_method_id) { $aliased_method_names = [$method_name]; if ($parent_storage->is_trait && $storage->trait_alias_map && isset($storage->trait_alias_map[$method_name]) ) { $aliased_method_names[] = $storage->trait_alias_map[$method_name]; } foreach ($aliased_method_names as $aliased_method_name) { if (isset($storage->appearing_method_ids[$aliased_method_name])) { continue; } $implemented_method_id = $fq_class_name . '::' . $aliased_method_name; $storage->appearing_method_ids[$aliased_method_name] = $parent_storage->is_trait ? $implemented_method_id : $appearing_method_id; } } // register where they're declared foreach ($parent_storage->inheritable_method_ids as $method_name => $declaring_method_id) { if (!$parent_storage->is_trait) { $storage->overridden_method_ids[$method_name][] = $declaring_method_id; } $aliased_method_names = [$method_name]; if ($parent_storage->is_trait && $storage->trait_alias_map && isset($storage->trait_alias_map[$method_name]) ) { $aliased_method_names[] = $storage->trait_alias_map[$method_name]; } foreach ($aliased_method_names as $aliased_method_name) { if (isset($storage->declaring_method_ids[$aliased_method_name])) { list($implementing_fq_class_name, $implementing_method_name) = explode( '::', $storage->declaring_method_ids[$aliased_method_name] ); $implementing_class_storage = $this->classlike_storage_provider->get($implementing_fq_class_name); if (!$implementing_class_storage->methods[$implementing_method_name]->abstract) { continue; } } $storage->declaring_method_ids[$aliased_method_name] = $declaring_method_id; $storage->inheritable_method_ids[$aliased_method_name] = $declaring_method_id; } } foreach ($storage->methods as $method_name => $_) { if (isset($storage->overridden_method_ids[$method_name])) { foreach ($storage->overridden_method_ids[$method_name] as $declaring_method_id) { list($declaring_class, $declaring_method_name) = explode('::', $declaring_method_id); $declaring_class_storage = $this->classlike_storage_provider->get($declaring_class); // tell the declaring class it's overridden downstream $declaring_class_storage->methods[strtolower($declaring_method_name)]->overridden_downstream = true; } } } } /** * @param ClassLikeStorage $storage * @param ClassLikeStorage $parent_storage * * @return void */ private function inheritPropertiesFromParent(ClassLikeStorage $storage, ClassLikeStorage $parent_storage) { // register where they appear (can never be in a trait) foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) { if (isset($storage->appearing_property_ids[$property_name])) { continue; } if (!$parent_storage->is_trait && isset($parent_storage->properties[$property_name]) && $parent_storage->properties[$property_name]->visibility === ClassLikeChecker::VISIBILITY_PRIVATE ) { continue; } $implemented_property_id = $storage->name . '::$' . $property_name; $storage->appearing_property_ids[$property_name] = $parent_storage->is_trait ? $implemented_property_id : $appearing_property_id; } // register where they're declared foreach ($parent_storage->declaring_property_ids as $property_name => $declaring_property_id) { if (isset($storage->declaring_property_ids[$property_name])) { continue; } if (!$parent_storage->is_trait && isset($parent_storage->properties[$property_name]) && $parent_storage->properties[$property_name]->visibility === ClassLikeChecker::VISIBILITY_PRIVATE ) { continue; } $storage->declaring_property_ids[$property_name] = $declaring_property_id; } // register where they're declared foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) { if (!$parent_storage->is_trait && isset($parent_storage->properties[$property_name]) && $parent_storage->properties[$property_name]->visibility === ClassLikeChecker::VISIBILITY_PRIVATE ) { continue; } if (!$parent_storage->is_trait) { $storage->overridden_property_ids[$property_name][] = $inheritable_property_id; } $storage->inheritable_property_ids[$property_name] = $inheritable_property_id; } } }