<commit_before><?php
namespace Psalm\Internal\Analyzer\FunctionLike;

use PhpParser;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\InterfaceAnalyzer;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Analyzer\ScopeAnalyzer;
use Psalm\Internal\Analyzer\SourceAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\TypeAnalyzer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\FileManipulation\FunctionDocblockManipulator;
use Psalm\Issue\InvalidFalsableReturnType;
use Psalm\Issue\InvalidNullableReturnType;
use Psalm\Issue\InvalidParent;
use Psalm\Issue\InvalidReturnType;
use Psalm\Issue\InvalidToString;
use Psalm\Issue\LessSpecificReturnType;
use Psalm\Issue\MismatchingDocblockReturnType;
use Psalm\Issue\MissingClosureReturnType;
use Psalm\Issue\MissingReturnType;
use Psalm\Issue\MixedInferredReturnType;
use Psalm\Issue\MixedReturnTypeCoercion;
use Psalm\Issue\MoreSpecificReturnType;
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
use Psalm\Storage\FunctionLikeStorage;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
use function strtolower;
use function substr;
use function count;
use function in_array;

/**
 * @internal
 */
class ReturnTypeAnalyzer
{
    /**
     * @param Closure|Function_|ClassMethod|ArrowFunction $function
     * @param PhpParser\Node\Stmt[] $function_stmts
     * @param string[]            $compatible_method_ids
     *
     * @return  false|null
     */
    public static function verifyReturnType(
        FunctionLike $function,
        array $function_stmts,
        SourceAnalyzer $source,
        \Psalm\Internal\Provider\NodeDataProvider $type_provider,
        FunctionLikeAnalyzer $function_like_analyzer,
        ?Type\Union $return_type = null,
        ?string $fq_class_name = null,
        ?CodeLocation $return_type_location = null,
        array $compatible_method_ids = [],
        bool $did_explicitly_return = false,
        bool $closure_inside_call = false
    ): ?bool {
        $suppressed_issues = $function_like_analyzer->getSuppressedIssues();
        $codebase = $source->getCodebase();
        $project_analyzer = $source->getProjectAnalyzer();

        $function_like_storage = null;

        if ($source instanceof StatementsAnalyzer) {
            $function_like_storage = $function_like_analyzer->getFunctionLikeStorage($source);
        } elseif ($source instanceof \Psalm\Internal\Analyzer\ClassAnalyzer
            || $source instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
        ) {
            $function_like_storage = $function_like_analyzer->getFunctionLikeStorage();
        }

        $cased_method_id = $function_like_analyzer->getCorrectlyCasedMethodId();

        if (!$function->getStmts() &&
            (
                $function instanceof ClassMethod &&
                ($source instanceof InterfaceAnalyzer || $function->isAbstract())
            )
        ) {
            if (!$return_type) {
                if (IssueBuffer::accepts(
                    new MissingReturnType(
                        'Method ' . $cased_method_id . ' does not have a return type',
                        new CodeLocation($function_like_analyzer, $function->name, null, true)
                    ),
                    $suppressed_issues
                )) {
                    // fall through
                }
            }

            return null;
        }

        $is_to_string = $function instanceof ClassMethod && strtolower($function->name->name) === '__tostring';

        if ($function instanceof ClassMethod
            && substr($function->name->name, 0, 2) === '__'
            && !$is_to_string
            && !$return_type
        ) {
            // do not check __construct, __set, __get, __call etc.
            return null;
        }

        if (!$return_type_location) {
            $return_type_location = new CodeLocation(
                $function_like_analyzer,
                $function instanceof Closure || $function instanceof ArrowFunction ? $function : $function->name
            );
        }

        $inferred_yield_types = [];

        $inferred_return_type_parts = ReturnTypeCollector::getReturnTypes(
            $codebase,
            $type_provider,
            $function_stmts,
            $inferred_yield_types,
            true
        );

        if (!$inferred_return_type_parts) {
            $did_explicitly_return = true;
        }

        if ((!$return_type || $return_type->from_docblock)
            && ScopeAnalyzer::getFinalControlActions(
                $function_stmts,
                $type_provider,
                $codebase->config->exit_functions
            ) !== [ScopeAnalyzer::ACTION_END]
            && !$inferred_yield_types
            && count($inferred_return_type_parts)
            && !$did_explicitly_return
        ) {
            // only add null if we have a return statement elsewhere and it wasn't void
            foreach ($inferred_return_type_parts as $inferred_return_type_part) {
                if (!$inferred_return_type_part->isVoid()) {
                    $atomic_null = new Type\Atomic\TNull();
                    $atomic_null->from_docblock = true;
                    $inferred_return_type_parts[] = new Type\Union([$atomic_null]);
                    break;
                }
            }
        }

        if ($return_type
            && (!$return_type->from_docblock
                || ($return_type->isNullable()
                    && !$return_type->hasTemplate()
                    && !$return_type->getAtomicTypes()['null']->from_docblock
                )
            )
            && !$return_type->isVoid()
            && !$inferred_yield_types
            && (!$function_like_storage || !$function_like_storage->has_yield)
            && ScopeAnalyzer::getFinalControlActions(
                $function_stmts,
                $type_provider,
                $codebase->config->exit_functions
            ) !== [ScopeAnalyzer::ACTION_END]
        ) {
            if (IssueBuffer::accepts(
                new InvalidReturnType(
                    'Not all code paths of ' . $cased_method_id . ' end in a return statement, return type '
                        . $return_type . ' expected',
                    $return_type_location
                ),
                $suppressed_issues<commit_msg>Add back suppression<commit_after><?php
namespace Psalm\Internal\Analyzer\FunctionLike;

use PhpParser;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\InterfaceAnalyzer;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Analyzer\ScopeAnalyzer;
use Psalm\Internal\Analyzer\SourceAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\TypeAnalyzer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\FileManipulation\FunctionDocblockManipulator;
use Psalm\Issue\InvalidFalsableReturnType;
use Psalm\Issue\InvalidNullableReturnType;
use Psalm\Issue\InvalidParent;
use Psalm\Issue\InvalidReturnType;
use Psalm\Issue\InvalidToString;
use Psalm\Issue\LessSpecificReturnType;
use Psalm\Issue\MismatchingDocblockReturnType;
use Psalm\Issue\MissingClosureReturnType;
use Psalm\Issue\MissingReturnType;
use Psalm\Issue\MixedInferredReturnType;
use Psalm\Issue\MixedReturnTypeCoercion;
use Psalm\Issue\MoreSpecificReturnType;
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
use Psalm\Storage\FunctionLikeStorage;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
use function strtolower;
use function substr;
use function count;
use function in_array;

/**
 * @internal
 */
class ReturnTypeAnalyzer
{
    /**
     * @param Closure|Function_|ClassMethod|ArrowFunction $function
     * @param PhpParser\Node\Stmt[] $function_stmts
     * @param string[]            $compatible_method_ids
     *
     * @return  false|null
     */
    public static function verifyReturnType(
        FunctionLike $function,
        array $function_stmts,
        SourceAnalyzer $source,
        \Psalm\Internal\Provider\NodeDataProvider $type_provider,
        FunctionLikeAnalyzer $function_like_analyzer,
        ?Type\Union $return_type = null,
        ?string $fq_class_name = null,
        ?CodeLocation $return_type_location = null,
        array $compatible_method_ids = [],
        bool $did_explicitly_return = false,
        bool $closure_inside_call = false
    ): ?bool {
        $suppressed_issues = $function_like_analyzer->getSuppressedIssues();
        $codebase = $source->getCodebase();
        $project_analyzer = $source->getProjectAnalyzer();

        $function_like_storage = null;

        if ($source instanceof StatementsAnalyzer) {
            $function_like_storage = $function_like_analyzer->getFunctionLikeStorage($source);
        } elseif ($source instanceof \Psalm\Internal\Analyzer\ClassAnalyzer
            || $source instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
        ) {
            $function_like_storage = $function_like_analyzer->getFunctionLikeStorage();
        }

        $cased_method_id = $function_like_analyzer->getCorrectlyCasedMethodId();

        if (!$function->getStmts() &&
            (
                $function instanceof ClassMethod &&
                ($source instanceof InterfaceAnalyzer || $function->isAbstract())
            )
        ) {
            if (!$return_type) {
                if (IssueBuffer::accepts(
                    new MissingReturnType(
                        'Method ' . $cased_method_id . ' does not have a return type',
                        new CodeLocation($function_like_analyzer, $function->name, null, true)
                    ),
                    $suppressed_issues
                )) {
                    // fall through
                }
            }

            return null;
        }

        $is_to_string = $function instanceof ClassMethod && strtolower($function->name->name) === '__tostring';

        if ($function instanceof ClassMethod
            && substr($function->name->name, 0, 2) === '__'
            && !$is_to_string
            && !$return_type
        ) {
            // do not check __construct, __set, __get, __call etc.
            return null;
        }

        if (!$return_type_location) {
            $return_type_location = new CodeLocation(
                $function_like_analyzer,
                $function instanceof Closure || $function instanceof ArrowFunction ? $function : $function->name
            );
        }

        $inferred_yield_types = [];

        $inferred_return_type_parts = ReturnTypeCollector::getReturnTypes(
            $codebase,
            $type_provider,
            $function_stmts,
            $inferred_yield_types,
            true
        );

        if (!$inferred_return_type_parts) {
            $did_explicitly_return = true;
        }

        if ((!$return_type || $return_type->from_docblock)
            && ScopeAnalyzer::getFinalControlActions(
                $function_stmts,
                $type_provider,
                $codebase->config->exit_functions
            ) !== [ScopeAnalyzer::ACTION_END]
            && !$inferred_yield_types
            && count($inferred_return_type_parts)
            && !$did_explicitly_return
        ) {
            // only add null if we have a return statement elsewhere and it wasn't void
            foreach ($inferred_return_type_parts as $inferred_return_type_part) {
                if (!$inferred_return_type_part->isVoid()) {
                    $atomic_null = new Type\Atomic\TNull();
                    $atomic_null->from_docblock = true;
                    $inferred_return_type_parts[] = new Type\Union([$atomic_null]);
                    break;
                }
            }
        }

        /** @psalm-suppress PossiblyUndefinedStringArrayOffset */
        if ($return_type
            && (!$return_type->from_docblock
                || ($return_type->isNullable()
                    && !$return_type->hasTemplate()
                    && !$return_type->getAtomicTypes()['null']->from_docblock
                )
            )
            && !$return_type->isVoid()
            && !$inferred_yield_types
            && (!$function_like_storage || !$function_like_storage->has_yield)
            && ScopeAnalyzer::getFinalControlActions(
                $function_stmts,
                $type_provider,
                $codebase->config->exit_functions
            ) !== [ScopeAnalyzer::ACTION_END]
        ) {
            if (IssueBuffer::accepts(
                new InvalidReturnType(
                    'Not all code paths of ' . $cased_method_id . ' end in a return statement, return type '
                        . $return_type . ' expected',
                    $return_type_location
                ),
                $suppressed_issues
{'fraction_non_alphanumeric': 0.09755553904450553, 'fraction_numerical': 0.00029719890036406866, 'mean_word_length': 2.2829268292682925, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7249019', 'n_tokens_mistral': 3872, 'n_tokens_neox': 3675, 'n_words': 635}