diff --git a/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php index 40216375c..ac228933e 100644 --- a/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php @@ -66,20 +66,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer if (!$call_args) { switch ($call_map_key) { - case 'getenv': - return new Type\Union([new Type\Atomic\TArray([Type::getArrayKey(), Type::getString()])]); - - case 'gettimeofday': - return new Type\Union([ - new Type\Atomic\TArray([ - Type::getString(), - Type::getInt() - ]) - ]); - - case 'microtime': - return Type::getString(); - case 'hrtime': return new Type\Union([ new Type\Atomic\ObjectLike([ @@ -111,13 +97,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer } } else { switch ($call_map_key) { - case 'pathinfo': - if (isset($call_args[1])) { - return Type::getString(); - } - - return Type::getArray(); - case 'count': if (($first_arg_type = $statements_analyzer->node_data->getType($call_args[0]->value))) { $atomic_types = $first_arg_type->getAtomicTypes(); @@ -190,42 +169,6 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer $int->from_calculation = true; return $int; - case 'getenv': - return new Type\Union([new Type\Atomic\TString, new Type\Atomic\TFalse]); - - case 'min': - case 'max': - if (isset($call_args[0])) { - $first_arg = $call_args[0]->value; - - if ($first_arg_type = $statements_analyzer->node_data->getType($first_arg)) { - if ($first_arg_type->hasArray()) { - /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ - $array_type = $first_arg_type->getAtomicTypes()['array']; - if ($array_type instanceof Type\Atomic\ObjectLike) { - return $array_type->getGenericValueType(); - } - - if ($array_type instanceof Type\Atomic\TArray) { - return clone $array_type->type_params[1]; - } - - if ($array_type instanceof Type\Atomic\TList) { - return clone $array_type->type_param; - } - } elseif ($first_arg_type->hasScalarType() - && isset($call_args[1]) - && ($second_arg = $call_args[1]->value) - && ($second_arg_type = $statements_analyzer->node_data->getType($second_arg)) - && $second_arg_type->hasScalarType() - ) { - return Type::combineUnionTypes($first_arg_type, $second_arg_type); - } - } - } - - break; - case 'get_parent_class': // this is unreliable, as it's hard to know exactly what's wanted - attempted this in // https://github.com/vimeo/psalm/commit/355ed831e1c69c96bbf9bf2654ef64786cbe9fd7 diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 0c3920bf4..4d35977de 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -1401,6 +1401,20 @@ class FunctionCallAnalyzer extends CallAnalyzer } } } + } elseif (($function_id === 'min' || $function_id === 'max') && count($stmt->args) === 1) { + $first_arg_type = $statements_analyzer->node_data->getType($first_arg->value); + + if ($first_arg_type->hasScalarType()) { + if (IssueBuffer::accepts( + new \Psalm\Issue\TooFewArguments( + $function_id . ' must have more than one argument unless an array is passed', + new CodeLocation($statements_analyzer, $function_name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } } } } diff --git a/src/Psalm/Internal/CallMap.php b/src/Psalm/Internal/CallMap.php index b39a960b2..c2773c3b8 100644 --- a/src/Psalm/Internal/CallMap.php +++ b/src/Psalm/Internal/CallMap.php @@ -3659,7 +3659,7 @@ return [ 'getservbyname' => ['int|false', 'service'=>'string', 'protocol'=>'string'], 'getservbyport' => ['string|false', 'port'=>'int', 'protocol'=>'string'], 'gettext' => ['string', 'msgid'=>'string'], -'gettimeofday' => ['array'], +'gettimeofday' => ['array'], 'gettimeofday\'1' => ['float', 'get_as_float='=>'true'], 'gettype' => ['string', 'var'=>'mixed'], 'glob' => ['list|false', 'pattern'=>'string', 'flags='=>'int'], diff --git a/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub b/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub index 23c1ae06a..8835ee68f 100644 --- a/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub +++ b/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub @@ -413,3 +413,39 @@ function date(string $format, int $timestamp = 0) {} * @return (func_num_args() is 2 ? list : int) */ function sscanf(string $str, string $format, &...$vars) {} + +/** + * @return ( + * func_num_args() is 1 + * ? array{dirname: string, basename: string, extension?: string, filename: string} + * : string + * ) + */ +function pathinfo(string $path, int $options = \PATHINFO_DIRNAME) {} + +/** + * @template T as scalar + * @template TFirstArg as array|T + * + * @param TFirstArg $arg_1 + * @param T $arg_2 + * + * @return T + */ +function min($arg_1, ...$arg_2) {} + +/** + * @template T as scalar + * @template TFirstArg as array|T + * + * @param TFirstArg $arg_1 + * @param T $arg_2 + * + * @return T + */ +function max($arg_1, ...$arg_2) {} + +/** + * @return (func_num_args() is 0 ? array : string|false) + */ +function getenv(string $varname = '', bool $local_only = false) {} diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index 29a5621d2..817b6ceab 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -274,7 +274,7 @@ class FunctionCallTest extends TestCase $a = getenv(); $b = getenv("some_key");', 'assertions' => [ - '$a' => 'array', + '$a' => 'array', '$b' => 'false|string', ], ],