diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 4cbd63dd9..a2fc862a6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -120,6 +120,23 @@ class ArgumentAnalyzer return; } + if ($function_param->expect_variable + && $arg_value_type->hasLiteralString() + && !$arg->value instanceof PhpParser\Node\Scalar + ) { + if (IssueBuffer::accepts( + new InvalidArgument( + 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id + . ' expects a non-literal value, ' . $arg_value_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $cased_method_id + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + if (self::checkFunctionLikeTypeMatches( $statements_analyzer, $codebase, diff --git a/src/Psalm/Internal/Codebase/InternalCallMapHandler.php b/src/Psalm/Internal/Codebase/InternalCallMapHandler.php index 2fda24192..38836ea77 100644 --- a/src/Psalm/Internal/Codebase/InternalCallMapHandler.php +++ b/src/Psalm/Internal/Codebase/InternalCallMapHandler.php @@ -307,6 +307,10 @@ class InternalCallMapHandler $function_param->out_type = $out_type; } + if ($arg_name === 'haystack') { + $function_param->expect_variable = true; + } + if (isset(self::$taint_sink_map[$call_map_key][$arg_offset])) { $function_param->sinks = self::$taint_sink_map[$call_map_key][$arg_offset]; } diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index df7e66451..a54d38e70 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -2089,6 +2089,13 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse $param_array = $this->getTranslatedFunctionParam($param, $stmt, $fake_method, $fq_classlike_name); + if ($param_array->name === 'haystack' + && (strpos($this->file_path, 'CoreGenericFunctions.phpstub') + || strpos($this->file_path, 'CoreGenericClasses.phpstub')) + ) { + $param_array->expect_variable = true; + } + if (isset($existing_params['$' . $param_array->name])) { $storage->docblock_issues[] = new DuplicateParam( 'Duplicate param $' . $param_array->name . ' in docblock for ' . $cased_function_id, diff --git a/src/Psalm/Storage/FunctionLikeParameter.php b/src/Psalm/Storage/FunctionLikeParameter.php index 727393f18..315f55f78 100644 --- a/src/Psalm/Storage/FunctionLikeParameter.php +++ b/src/Psalm/Storage/FunctionLikeParameter.php @@ -88,6 +88,11 @@ class FunctionLikeParameter */ public $type_inferred = false; + /** + * @var bool + */ + public $expect_variable = false; + /** * @param string $name * @param Type\Union|null $type diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php index 7d1a12ac3..729e49f42 100644 --- a/tests/ConstantTest.php +++ b/tests/ConstantTest.php @@ -412,8 +412,8 @@ class ConstantTest extends TestCase class A { private const STRING = "x"; - public static function bar() : bool { - return !defined("FOO") && strpos("x", self::STRING) === 0; + public static function bar(string $s) : bool { + return !defined("FOO") && strpos($s, self::STRING) === 0; } }' ],