<commit_before><?php
namespace Psalm\Internal\Codebase;

use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Config;
use Psalm\Issue\CircularReference;
use Psalm\IssueBuffer;
use Psalm\Internal\Provider\ClassLikeStorageProvider;
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\FileStorageProvider;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\FileStorage;
use Psalm\Type;

/**
 * @internal
 *
 * Populates file and class information so that analysis can work properly
 */
class Populator
{
    /**
     * @var ClassLikeStorageProvider
     */
    private $classlike_storage_provider;

    /**
     * @var FileStorageProvider
     */
    private $file_storage_provider;

    /**
     * @var bool
     */
    private $debug_output;

    /**
     * @var ClassLikes
     */
    private $classlikes;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var FileReferenceProvider
     */
    private $file_reference_provider;

    /**
     * @param bool $debug_output
     */
    public function __construct(
        Config $config,
        ClassLikeStorageProvider $classlike_storage_provider,
        FileStorageProvider $file_storage_provider,
        ClassLikes $classlikes,
        FileReferenceProvider $file_reference_provider,
        $debug_output
    ) {
        $this->classlike_storage_provider = $classlike_storage_provider;
        $this->file_storage_provider = $file_storage_provider;
        $this->classlikes = $classlikes;
        $this->debug_output = $debug_output;
        $this->config = $config;
        $this->file_reference_provider = $file_reference_provider;
    }

    /**
     * @return void
     */
    public function populateCodebase(\Psalm\Codebase $codebase)
    {
        if ($this->debug_output) {
            echo 'ClassLikeStorage is populating' . "\n";
        }

        foreach ($this->classlike_storage_provider->getNew() 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->getNew();

        foreach ($all_file_storage as $file_storage) {
            $this->populateFileStorage($file_storage);
        }

        foreach ($this->classlike_storage_provider->getNew() 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\Internal\Analyzer\StatementsAnalyzer::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\Internal\Analyzer\StatementsAnalyzer::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\Internal\Analyzer\StatementsAnalyzer::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";
        }

        $this->classlike_storage_provider->populated();
        $this->file_storage_provider->populated();
    }

    /**
     * @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);
        }

        if (!strpos($fq_classlike_name_lc, '\\')
            && !isset($storage->methods['__construct'])
            && isset($storage->methods[$fq_classlike_name_lc])
            && !$storage->is_interface
            && !$storage->is_trait
        ) {
            $storage->methods['__construct'] = $storage->methods[$fq_classlike_name_lc];
        }

        $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) {
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_interface_lc);
            }

            foreach ($storage->parent_classes as $parent_class_lc => $_) {
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_class_lc);
            }

            foreach ($storage->class_implements as $implemented_interface) {
                $this->file_reference_provider->addFileInheritanceToClass(
                    $file_path,
                    strtolower($implemented_interface)
                );
            }

            foreach ($storage->used_traits as $used_trait_lc => $_) {
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $used_trait_lc);
            }
        }

        if ($storage->internal
            && !$storage->is_interface
            && !$storage->is_trait
        ) {
            foreach ($storage->methods as $method) {
                $method->internal = true;
            }

            foreach ($storage->properties as $property) {
                $property->internal = true;
            }
        }

        foreach ($storage->methods as $method_name => $method_storage) {
            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);

                    $declaring_method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];

                    if ($declaring_method_storage->has_docblock_param_types
                        && !$method_storage->has_docblock_param_types
                        && !isset($storage->documenting_method_ids[$method_name])
                    ) {
                        $storage->documenting_method_ids[$method_name] = $declaring_method_id;
                    }

                    // tell the declaring class it's overridden downstream
                    $declaring_method_storage->overridden_downstream = true;
                    $declaring_method_storage->overridden_somewhere = true;

                    if (!$method_storage->throws
                        && $method_storage->inheritdoc
                        && $declaring_method_storage->throws
                    ) {
                        $method_storage->throws = $declaring_method_storage->throws;
                    }

                    if (count($storage->overridden_method_ids[$method_name]) === 1
                        && $method_storage->signature_return_type
                        && !$method_storage->signature_return_type->isVoid()
                        && $method_storage->return_type === $method_storage->signature_return_type
                    ) {
                        if (isset($declaring_class_storage->methods[$method_name])) {
                            $declaring_method_storage = $declaring_class_storage->methods[$method_name];

                            if ($declaring_method_storage->return_type
                                && $declaring_method_storage->signature_return_type
                                && $declaring_method_storage->return_type
                                    !== $declaring_method_storage->signature_return_type
                            ) {
                                $method_storage->return_type = $declaring_method_storage->return_type;
                            }
                        }
                    }
                }
            }
        }

        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 {
                $used_trait_lc = $this->classlikes->getUnAliasedName(
                    $used_trait_lc<commit_msg>Populate overridden method ids in separate method<commit_after><?php
namespace Psalm\Internal\Codebase;

use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Config;
use Psalm\Issue\CircularReference;
use Psalm\IssueBuffer;
use Psalm\Internal\Provider\ClassLikeStorageProvider;
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\FileStorageProvider;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\FileStorage;
use Psalm\Type;

/**
 * @internal
 *
 * Populates file and class information so that analysis can work properly
 */
class Populator
{
    /**
     * @var ClassLikeStorageProvider
     */
    private $classlike_storage_provider;

    /**
     * @var FileStorageProvider
     */
    private $file_storage_provider;

    /**
     * @var bool
     */
    private $debug_output;

    /**
     * @var ClassLikes
     */
    private $classlikes;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var FileReferenceProvider
     */
    private $file_reference_provider;

    /**
     * @param bool $debug_output
     */
    public function __construct(
        Config $config,
        ClassLikeStorageProvider $classlike_storage_provider,
        FileStorageProvider $file_storage_provider,
        ClassLikes $classlikes,
        FileReferenceProvider $file_reference_provider,
        $debug_output
    ) {
        $this->classlike_storage_provider = $classlike_storage_provider;
        $this->file_storage_provider = $file_storage_provider;
        $this->classlikes = $classlikes;
        $this->debug_output = $debug_output;
        $this->config = $config;
        $this->file_reference_provider = $file_reference_provider;
    }

    /**
     * @return void
     */
    public function populateCodebase(\Psalm\Codebase $codebase)
    {
        if ($this->debug_output) {
            echo 'ClassLikeStorage is populating' . "\n";
        }

        foreach ($this->classlike_storage_provider->getNew() 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->getNew();

        foreach ($all_file_storage as $file_storage) {
            $this->populateFileStorage($file_storage);
        }

        foreach ($this->classlike_storage_provider->getNew() 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\Internal\Analyzer\StatementsAnalyzer::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\Internal\Analyzer\StatementsAnalyzer::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\Internal\Analyzer\StatementsAnalyzer::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";
        }

        $this->classlike_storage_provider->populated();
        $this->file_storage_provider->populated();
    }

    /**
     * @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);
        }

        if (!strpos($fq_classlike_name_lc, '\\')
            && !isset($storage->methods['__construct'])
            && isset($storage->methods[$fq_classlike_name_lc])
            && !$storage->is_interface
            && !$storage->is_trait
        ) {
            $storage->methods['__construct'] = $storage->methods[$fq_classlike_name_lc];
        }

        $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) {
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_interface_lc);
            }

            foreach ($storage->parent_classes as $parent_class_lc => $_) {
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_class_lc);
            }

            foreach ($storage->class_implements as $implemented_interface) {
                $this->file_reference_provider->addFileInheritanceToClass(
                    $file_path,
                    strtolower($implemented_interface)
                );
            }

            foreach ($storage->used_traits as $used_trait_lc => $_) {
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $used_trait_lc);
            }
        }

        if ($storage->internal
            && !$storage->is_interface
            && !$storage->is_trait
        ) {
            foreach ($storage->methods as $method) {
                $method->internal = true;
            }

            foreach ($storage->properties as $property) {
                $property->internal = true;
            }
        }

        $this->populateOverriddenMethods($storage);

        if ($this->debug_output) {
            echo 'Have populated ' . $storage->name . "\n";
        }

        $storage->populated = true;
    }

    /** @return void */
    private function populateOverriddenMethods(
        ClassLikeStorage $storage
    ) {
        foreach ($storage->methods as $method_name => $method_storage) {
            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);

                    $declaring_method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];

                    if ($declaring_method_storage->has_docblock_param_types
                        && !$method_storage->has_docblock_param_types
                        && !isset($storage->documenting_method_ids[$method_name])
                    ) {
                        $storage->documenting_method_ids[$method_name] = $declaring_method_id;
                    }

                    // tell the declaring class it's overridden downstream
                    $declaring_method_storage->overridden_downstream = true;
                    $declaring_method_storage->overridden_somewhere = true;

                    if (!$method_storage->throws
                        && $method_storage->inheritdoc
                        && $declaring_method_storage->throws
                    ) {
                        $method_storage->throws = $declaring_method_storage->throws;
                    }

                    if (count($storage->overridden_method_ids[$method_name]) === 1
                        && $method_storage->signature_return_type
                        && !$method_storage->signature_return_type->isVoid()
                        && $method_storage->return_type === $method_storage->signature_return_type
                    ) {
                        if (isset($declaring_class_storage->methods[$method_name])) {
                            $declaring_method_storage = $declaring_class_storage->methods[$method_name];

                            if ($declaring_method_storage->return_type
                                && $declaring_method_storage->signature_return_type
                                && $declaring_method_storage->return_type
                                    !== $declaring_method_storage->signature_return_type
                            ) {
                                $method_storage->return_type = $declaring_method_storage->return_type;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * @return void
     */
    private function populateDataFromTraits(
        ClassLikeStorage $storage,
        ClassLikeStorageProvider $storage_provider,
        array $dependent_classlikes
    ) {
        foreach ($storage->used_traits as $used_trait_lc => $_) {
            try {
                $used_trait_lc = $this->classlikes->getUnAliasedName(
                    $used_trait_lc
{'fraction_non_alphanumeric': 0.10216979992753915, 'fraction_numerical': 8.05120566804879e-05, 'mean_word_length': 1.7155662439877568, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 292, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '357074', 'n_tokens_mistral': 6575, 'n_tokens_neox': 6207, 'n_words': 883}