From e371685c3b29f6983e97b81e7d2e9dd2e0c1f78f Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Mon, 16 Nov 2020 16:31:25 -0500 Subject: [PATCH] Allow PHP major version to determine substr return type --- .../Expression/Call/FunctionCallAnalyzer.php | 4 ++++ .../Call/Method/MethodCallReturnTypeFetcher.php | 7 +++++++ .../ExistingAtomicStaticCallAnalyzer.php | 7 +++++++ .../Reflector/FunctionLikeDocblockScanner.php | 15 +++++++++++++++ src/Psalm/Internal/Type/TypeTokenizer.php | 2 +- stubs/CoreGenericFunctions.phpstub | 2 +- 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 8f7556d20..b44422ab8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -951,6 +951,10 @@ class FunctionCallAnalyzer extends CallAnalyzer $template_result->upper_bounds[$template_name] = [ 'fn-' . $function_id => [Type::getInt(false, count($stmt->args)), 0] ]; + } elseif ($template_name === 'TPhpMajorVersion') { + $template_result->upper_bounds[$template_name] = [ + 'fn-' . $function_id => [Type::getInt(false, $codebase->php_major_version), 0] + ]; } else { $template_result->upper_bounds[$template_name] = [ 'fn-' . $function_id => [Type::getEmpty(), 0] diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php index aa928c416..0e27f215e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php @@ -324,6 +324,13 @@ class MethodCallReturnTypeFetcher 0 ] ]; + } elseif ($template_type->param_name === 'TPhpMajorVersion') { + $template_result->upper_bounds[$template_type->param_name] = [ + 'fn-' . strtolower((string) $method_id) => [ + Type::getInt(false, $codebase->php_major_version), + 0 + ] + ]; } else { $template_result->upper_bounds[$template_type->param_name] = [ ($template_type->defining_class) => [Type::getEmpty(), 0] diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 921a40d63..7240f280c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -250,6 +250,13 @@ class ExistingAtomicStaticCallAnalyzer 0 ] ]; + } elseif ($template_type->param_name === 'TPhpMajorVersion') { + $template_result->upper_bounds[$template_type->param_name] = [ + 'fn-' . strtolower((string) $method_id) => [ + Type::getInt(false, $codebase->php_major_version), + 0 + ] + ]; } else { $template_result->upper_bounds[$template_type->param_name] = [ ($template_type->defining_class) => [Type::getEmpty(), 0] diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index aab944227..d76c168cc 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -714,6 +714,21 @@ class FunctionLikeDocblockScanner $fixed_type_tokens[$i][0] = $template_name; } + + if ($token_body === 'PHP_MAJOR_VERSION') { + $template_name = 'TPhpMajorVersion'; + + $storage->template_types[$template_name] = [ + $template_function_id => [ + Type::getInt() + ], + ]; + + $function_template_types[$template_name] + = $storage->template_types[$template_name]; + + $fixed_type_tokens[$i][0] = $template_name; + } } $storage->return_type = TypeParser::parseTokens( diff --git a/src/Psalm/Internal/Type/TypeTokenizer.php b/src/Psalm/Internal/Type/TypeTokenizer.php index 8d6ee17d0..62757c109 100644 --- a/src/Psalm/Internal/Type/TypeTokenizer.php +++ b/src/Psalm/Internal/Type/TypeTokenizer.php @@ -447,7 +447,7 @@ class TypeTokenizer continue; } - if ($string_type_token[0] === 'func_num_args()') { + if ($string_type_token[0] === 'func_num_args()' || $string_type_token[0] === 'PHP_MAJOR_VERSION') { continue; } diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index ca5e13ee0..2647d738a 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -725,7 +725,7 @@ function preg_match_all($pattern, $subject, &$matches = [], int $flags = 1, int /** * @psalm-pure * - * @return string|false + * @return (PHP_MAJOR_VERSION is 5|7 ? string|false : string) * @psalm-ignore-falsable-return * * @psalm-flow ($string) -> return