Describe the bug
session_set_save_handler() has two prototypes. One requires only 2 arguments. Using that prototype (with correct argument types) intelephense incorrectly reports that Expected 6 arguments. Found 2.
To Reproduce
session_set_save_handler($handlerObject, false)
Expected behavior
No error to be reported.
https://github.com/JetBrains/phpstorm-stubs/blob/master/session/session.php#L220-L282
Good catch, Side effect of fixing that could be support for "Method Overloading".
For contrast number_format cannot be called with 3 parameters, but this isn't even mentioned in stubs
https://github.com/JetBrains/phpstorm-stubs/blob/master/standard/standard_3.php#L693-L708
The callmap used by psalm is one of the most up to date in my experience https://raw.githubusercontent.com/vimeo/psalm/master/src/Psalm/Internal/CallMap.php
You may be right, it's done using reflection in contrary to user-created phpstorm-stubs, but also don't need to be commented (phpdoc). Intelephense uses phpstorm-stubs because of comments.
it's done using reflection
Maybe to begin with but it's now maintained by hand, I have personally submitted manual updates to that file.
If I'm not mistaken phpstorm stubs started from php.net docs years ago. User changes are unavoidable in both, but scale is a bit different.
Yeah, psalm doesn't need to show docblock descriptions like intelephense, it only needs the type info.
I've generated list of functions w/ multiple variants, using file from @ADmad comment. I found 113 functions with 234 variants.
abs ( int $number ) : int {}
abs ( float $number ) : float {}
abs ( numeric $number ) : numeric {}
apc_add ( string $key, mixed $var, [ int $ttl ] ) : bool {}
apc_add ( array $values, [ $unused, int $ttl ] ) : array {}
apc_exists ( string $keys ) : bool {}
apc_exists ( string[] $keys ) : array {}
apc_fetch ( string $key, [ bool &$w_success ] ) : mixed|false {}
apc_fetch ( string[] $key, [ bool &$w_success ] ) : array|false {}
apc_store ( string $key, $var, [ int $ttl ] ) : bool {}
apc_store ( array $values, [ $unused, int $ttl ] ) : array {}
apcu_add ( string $key, $var, [ int $ttl ] ) : bool {}
apcu_add ( array<string,mixed> $values, [ $unused, int $ttl ] ) : array<string,int> {}
apcu_exists ( string $keys ) : bool {}
apcu_exists ( string[] $keys ) : array {}
apcu_fetch ( string $key, [ bool &$w_success ] ) : mixed|false {}
apcu_fetch ( string[] $key, [ bool &$w_success ] ) : array|false {}
apcu_store ( string $key, [ $var, int $ttl ] ) : bool {}
apcu_store ( array $values, [ $unused, int $ttl ] ) : array {}
array_diff_uassoc ( array $arr1, array $arr2, callable(mixed,mixed):int $data_comp_func ) : array {}
array_diff_uassoc ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
array_diff_ukey ( array $arr1, array $arr2, callable(mixed,mixed):int $key_comp_func ) : array {}
array_diff_ukey ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
array_intersect_uassoc ( array $arr1, array $arr2, callable(mixed,mixed):int $key_compare_func ) : array {}
array_intersect_uassoc ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, array|callable(mixed,mixed):int ...$rest ) : array {}
array_intersect_ukey ( array $arr1, array $arr2, callable(mixed,mixed):int $key_compare_func ) : array {}
array_intersect_ukey ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, array|callable(mixed,mixed):int ...$rest ) : array {}
array_rand ( array $input, int $num_req ) : int|string|array<int,int>|array<int,string> {}
array_rand ( array $input ) : int|string {}
array_udiff ( array $arr1, array $arr2, callable(mixed,mixed):int $data_comp_func ) : array {}
array_udiff ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
array_udiff_assoc ( array $arr1, array $arr2, callable(mixed,mixed):int $key_comp_func ) : array {}
array_udiff_assoc ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
array_udiff_uassoc ( array $arr1, array $arr2, callable(mixed,mixed):int $data_comp_func, callable(mixed,mixed):int $key_comp_func ) : array {}
array_udiff_uassoc ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, array|callable(mixed,mixed):int $arg5, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
array_uintersect ( array $arr1, array $arr2, callable(mixed,mixed):int $data_compare_func ) : array {}
array_uintersect ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
array_uintersect_assoc ( array $arr1, array $arr2, callable(mixed,mixed):int $data_compare_func ) : array {}
array_uintersect_assoc ( array $arr1, array $arr2, array $arr3, array|callable $arg4, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
array_uintersect_uassoc ( array $arr1, array $arr2, callable(mixed,mixed):int $data_compare_func, callable(mixed,mixed):int $key_compare_func ) : array {}
array_uintersect_uassoc ( array $arr1, array $arr2, array $arr3, array|callable(mixed,mixed):int $arg4, array|callable(mixed,mixed):int $arg5, [ array|callable(mixed,mixed):int ...$rest ] ) : array {}
DatePeriod::__construct ( DateTimeInterface $start, DateInterval $interval, int $recur, [ int $options ] ) : void {}
DatePeriod::__construct ( DateTimeInterface $start, DateInterval $interval, DateTimeInterface $end, [ int $options ] ) : void {}
DatePeriod::__construct ( string $iso, [ int $options ] ) : void {}
dba_fetch ( string $key, int $skip, resource $handle ) : string|false {}
dba_fetch ( string $key, resource $handle ) : string|false {}
fscanf ( resource $stream, string $format ) : array {}
fscanf ( resource $stream, string $format, [ string|int|float &...$w_vars ] ) : int {}
getenv ( string $varname, [ bool $local_only ] ) : string|false {}
getenv ( ) : array<string,string> {}
gettimeofday ( ) : array {}
gettimeofday ( [ true $get_as_float ] ) : float {}
hrtime ( [ false $get_as_number ] ) : array{0:int,1:int}|false {}
hrtime ( [ true $get_as_number ] ) : int|float|false {}
ibase_blob_echo ( $link_identifier, string $blob_id ) : bool {}
ibase_blob_echo ( string $blob_id ) : bool {}
ibase_blob_info ( resource $link_identifier, string $blob_id ) : array {}
ibase_blob_info ( string $blob_id ) : array {}
ibase_blob_open ( resource $link_identifier, string $blob_id ) : resource {}
ibase_blob_open ( string $blob_id ) : resource {}
ibase_set_event_handler ( $link_identifier, callable $callback, [ string $event, ...$args ] ) : resource {}
ibase_set_event_handler ( callable $callback, string $event, ...$args ) : resource {}
ibase_wait_event ( $link_identifier, [ string $event, ...$args ] ) : string {}
ibase_wait_event ( string $event, ...$args ) : string {}
implode ( string $glue, array $pieces ) : string {}
implode ( array $pieces ) : string {}
intlcal_set ( IntlCalendar $cal, int $field, int $value ) : bool {}
intlcal_set ( IntlCalendar $cal, int $year, int $month, [ int $dayOfMonth, int $hour, int $minute, int $second ] ) : bool {}
IntlCalendar::set ( int $field, int $value ) : bool {}
IntlCalendar::set ( int $year, int $month, [ int $dayOfMonth, int $hour, int $minute, int $second ] ) : bool {}
IntlGregorianCalendar::set ( int $field, int $value ) : bool {}
IntlGregorianCalendar::set ( int $year, int $month, [ int $dayOfMonth, int $hour, int $minute, int $second ] ) : bool {}
join ( string $glue, array $pieces ) : string {}
join ( array $pieces ) : string {}
levenshtein ( string $str1, string $str2 ) : int {}
levenshtein ( string $str1, string $str2, int $cost_ins, int $cost_rep, int $cost_del ) : int {}
max ( array $arg1 ) : mixed {}
max ( $arg1, $arg2, [ ...$args ] ) : mixed {}
maxdb_stmt::bind_param ( $stmt, string $types, &...$rw_var ) : bool {}
maxdb_stmt::bind_param ( $stmt, string $types, array &$rw_var ) : bool {}
min ( array $arg1 ) : mixed {}
min ( $arg1, $arg2, [ ...$args ] ) : mixed {}
MongoCollection::aggregate ( array $op, [ array $op, array ...$args ] ) : array {}
MongoCollection::aggregate ( array $pipeline, [ array $options ] ) : array {}
mt_rand ( int $min, int $max ) : int {}
mt_rand ( ) : int {}
newrelic_notice_error ( string $message, [ Exception|Throwable $exception ] ) : void {}
newrelic_notice_error ( string $unused_1, string $message, string $unused_2, int $unused_3, [ $unused_4 ] ) : void {}
number_format ( float|int $number, [ int $num_decimal_places ] ) : string {}
number_format ( float|int $number, int $num_decimal_places, string $dec_separator, string $thousands_separator ) : string {}
PDO::query ( string $sql ) : PDOStatement|false {}
PDO::query ( string $sql, int $fetch_column, int $colno ) : PDOStatement|false {}
PDO::query ( string $sql, int $fetch_class, string $classname, array $ctorargs ) : PDOStatement|false {}
PDO::query ( string $sql, int $fetch_into, object $object ) : PDOStatement|false {}
PDOStatement::setFetchMode ( int $mode ) : bool {}
PDOStatement::setFetchMode ( int $fetch_column, int $colno ) : bool {}
PDOStatement::setFetchMode ( int $fetch_class, string $classname, array $ctorargs ) : bool {}
PDOStatement::setFetchMode ( int $fetch_into, object $object ) : bool {}
pg_escape_bytea ( resource $connection, string $data ) : string {}
pg_escape_bytea ( string $data ) : string {}
pg_escape_identifier ( resource $connection, string $data ) : string {}
pg_escape_identifier ( string $data ) : string {}
pg_escape_literal ( resource $connection, string $data ) : string {}
pg_escape_literal ( string $data ) : string {}
pg_escape_string ( resource $connection, string $data ) : string {}
pg_escape_string ( string $data ) : string {}
pg_execute ( resource $connection, string $stmtname, array $params ) : resource|false {}
pg_execute ( string $stmtname, array $params ) : resource|false {}
pg_fetch_object ( resource $result, [ ?int $row, int $result_type ] ) : object {}
pg_fetch_object ( resource $result, [ ?int $row, string $class_name, array $ctor_params ] ) : object {}
pg_fetch_result ( resource $result, string|int $field_name ) : string {}
pg_fetch_result ( resource $result, ?int $row, string|int $field_name ) : string {}
pg_field_is_null ( resource $result, string|int $field_name_or_number ) : int {}
pg_field_is_null ( resource $result, int $row, string|int $field_name_or_number ) : int {}
pg_field_prtlen ( resource $result, $field_name_or_number ) : int|false {}
pg_field_prtlen ( resource $result, int $row, string|int $field_name_or_number ) : int {}
pg_lo_export ( resource $connection, int $oid, string $filename ) : bool {}
pg_lo_export ( int $oid, string $pathname ) : bool {}
pg_lo_import ( resource $connection, string $pathname, $oid ) : int {}
pg_lo_import ( string $pathname, $oid ) : int {}
pg_parameter_status ( resource $connection, string $param_name ) : string|false {}
pg_parameter_status ( string $param_name ) : string|false {}
pg_prepare ( resource $connection, string $stmtname, string $query ) : resource|false {}
pg_prepare ( string $stmtname, string $query ) : resource|false {}
pg_put_line ( resource $connection, string $data ) : bool {}
pg_put_line ( string $data ) : bool {}
pg_query ( resource $connection, string $query ) : resource|false {}
pg_query ( string $query ) : resource|false {}
pg_query_params ( resource $connection, string $query, array $params ) : resource|false {}
pg_query_params ( string $query, array $params ) : resource|false {}
pg_set_client_encoding ( resource $connection, string $encoding ) : int {}
pg_set_client_encoding ( string $encoding ) : int {}
pg_set_error_verbosity ( resource $connection, int $verbosity ) : int {}
pg_set_error_verbosity ( int $verbosity ) : int {}
pg_tty ( [ resource $connection ] ) : string {}
pg_tty ( ) : string {}
pg_untrace ( [ resource $connection ] ) : bool {}
pg_untrace ( ) : bool {}
preg_match ( string $pattern, string $subject, [ string[] &$w_subpatterns, 0| $flags, int $offset ] ) : int|false {}
preg_match ( string $pattern, string $subject, [ array &$w_subpatterns, int $flags, int $offset ] ) : int|false {}
preg_replace_callback ( string|array $regex, callable(array<int, string>):string $callback, string $subject, [ int $limit, int &$w_count ] ) : string|null {}
preg_replace_callback ( string|array $regex, callable(array<int, string>):string $callback, string[] $subject, [ int $limit, int &$w_count ] ) : string[]|null {}
preg_split ( string $pattern, string $subject, ?int $limit, [ null $flags ] ) : array<int,string>|false {}
preg_split ( string $pattern, string $subject, [ ?int $limit, int $flags ] ) : array<int,string>|array[]|false {}
print_r ( mixed $var ) : string {}
print_r ( mixed $var, [ bool $return ] ) : true {}
rand ( int $min, int $max ) : int {}
rand ( ) : int {}
Redis::del ( string $key, string ...$args ) : int {}
Redis::del ( string[] $key ) : int {}
Redis::delete ( string $key, string ...$args ) : int {}
Redis::delete ( string[] $key ) : int {}
Redis::exists ( string $key ) : int {}
Redis::exists ( string[] $key ) : int {}
Redis::set ( string $key, string $value, [ array $options ] ) : bool {}
Redis::set ( string $key, string $value, [ int $timeout ] ) : bool {}
Redis::slave ( string $host, int $port ) : bool {}
Redis::slave ( string $host, int $port ) : bool {}
Redis::unlink ( string $key, string ...$args ) : int {}
Redis::unlink ( string[] $key ) : int {}
RedisArray::delete ( string $key, string ...$args ) : bool {}
RedisArray::delete ( string[] $key ) : bool {}
ReflectionMethod::__construct ( string|object $class, string $name ) : void {}
ReflectionMethod::__construct ( string $class_method ) : void {}
ReflectionProperty::setValue ( object $object, $value ) : void {}
ReflectionProperty::setValue ( $value ) : void {}
runkit_function_add ( string $funcname, string $arglist, string $code, [ ?string $doccomment ] ) : bool {}
runkit_function_add ( string $funcname, Closure $closure, [ ?string $doccomment ] ) : bool {}
runkit_function_redefine ( string $funcname, string $arglist, string $code, [ ?string $doccomment ] ) : bool {}
runkit_function_redefine ( string $funcname, Closure $closure, [ ?string $doccomment ] ) : bool {}
runkit_method_add ( string $classname, string $methodname, string $args, string $code, [ int $flags, ?string $doccomment ] ) : bool {}
runkit_method_add ( string $classname, string $methodname, Closure $closure, [ int $flags, ?string $doccomment ] ) : bool {}
runkit_method_redefine ( string $classname, string $methodname, string $args, string $code, [ int $flags, ?string $doccomment ] ) : bool {}
runkit_method_redefine ( string $classname, string $methodname, Closure $closure, [ int $flags, ?string $doccomment ] ) : bool {}
session_set_cookie_params ( int $lifetime, [ string $path, ?string $domain, bool $secure, bool $httponly ] ) : bool {}
session_set_cookie_params ( array{lifetime?:int,path?:string,domain?:?string,secure?:bool,httponly?:bool} $options ) : bool {}
session_set_save_handler ( callable(string,string):bool $open, callable():bool $close, callable(string):string $read, callable(string,string):bool $write, callable(string):bool $destroy, callable(string):bool $gc, [ callable():string $create_sid, callable(string):bool $validate_sid, callable(string):bool $update_timestamp ] ) : bool {}
session_set_save_handler ( SessionHandlerInterface $sessionhandler, [ bool $register_shutdown ] ) : bool {}
setcookie ( string $name, [ string $value, int $expires, string $path, string $domain, bool $secure, bool $httponly ] ) : bool {}
setcookie ( string $name, [ string $value, array $options ] ) : bool {}
setlocale ( int $category, string|0|null $locale, [ string ...$args ] ) : string|false {}
setlocale ( int $category, ?array $locale ) : string|false {}
SimpleXMLElement::asXML ( string $filename ) : bool {}
SimpleXMLElement::asXML ( ) : string|false {}
stream_context_set_option ( $context, string $wrappername, string $optionname, $value ) : bool {}
stream_context_set_option ( $context, array $options ) : bool {}
strtok ( string $str, string $token ) : string|false {}
strtok ( string $token ) : string|false {}
strtr ( string $str, string $from, string $to ) : string {}
strtr ( string $str, array $replace_pairs ) : string {}
SWFShape::addFill ( int $red, int $green, int $blue, [ int $alpha, swfbitmap $bitmap, int $flags, swfgradient $gradient ] ) : SWFFill {}
SWFShape::addFill ( SWFBitmap $bitmap, [ int $flags ] ) : SWFFill {}
SWFShape::addFill ( SWFGradient $gradient, [ int $flags ] ) : SWFFill {}
uopz_backup ( string $class, string $function ) : void {}
uopz_backup ( string $function ) : void {}
uopz_copy ( string $class, string $function ) : Closure {}
uopz_copy ( string $function ) : Closure {}
uopz_delete ( string $class, string $function ) : void {}
uopz_delete ( string $function ) : void {}
uopz_flags ( string $class, string $function, int $flags ) : int {}
uopz_flags ( string $function, int $flags ) : int {}
uopz_function ( string $class, string $function, Closure $handler, [ int $modifiers ] ) : void {}
uopz_function ( string $function, Closure $handler, [ int $modifiers ] ) : void {}
uopz_get_hook ( string $class, string $function ) : ?Closure {}
uopz_get_hook ( string $function ) : ?Closure {}
uopz_redefine ( string $class, string $constant, mixed $value ) : bool {}
uopz_redefine ( string $constant, mixed $value ) : bool {}
uopz_rename ( string $class, string $function, string $rename ) : void {}
uopz_rename ( string $function, string $rename ) : void {}
uopz_restore ( string $class, string $function ) : void {}
uopz_restore ( string $function ) : void {}
uopz_set_hook ( string $class, string $function, Closure $hook ) : bool {}
uopz_set_hook ( string $function, Closure $hook ) : bool {}
uopz_set_return ( string $function, mixed $value, [ string $class, bool $execute ] ) : bool {}
uopz_set_return ( string $function, mixed $value, [ bool $execute ] ) : bool {}
uopz_undefine ( string $class, string $constant ) : bool {}
uopz_undefine ( string $constant ) : bool {}
uopz_unset_hook ( string $class, string $function ) : bool {}
uopz_unset_hook ( string $function ) : bool {}
uopz_unset_return ( string $function, [ string $class ] ) : bool {}
uopz_unset_return ( string $function ) : bool {}
version_compare ( string $ver1, string $ver2, '\x3c'|'lt'|'\x3c='|'le'|'\x3e'|'gt'|'\x3e='|'ge'|'=='|'='|'eq'|'!='|'\x3c\x3e'|'ne' $oper ) : bool {}
version_compare ( string $ver1, string $ver2, [ $oper ] ) : int {}
wincache_ucache_add ( string $key, mixed $value, [ int $ttl ] ) : bool {}
wincache_ucache_add ( array $values, [ $unused, int $ttl ] ) : bool {}
wincache_ucache_set ( $key, $value, [ int $ttl ] ) : bool {}
wincache_ucache_set ( array $values, [ $unused, int $ttl ] ) : bool {}
XSLTProcessor::setParameter ( string $namespace, string $name, string $value ) : bool {}
XSLTProcessor::setParameter ( string $namespace, array $options ) : bool {}
Yaf_Controller_Abstract::forward ( string $action, [ array $parameters ] ) : void {}
Yaf_Controller_Abstract::forward ( string $controller, string $action, [ array $parameters ] ) : void {}
Yaf_Controller_Abstract::forward ( string $module, string $controller, string $action, [ array $parameters ] ) : void {}
ZMQSocket::send ( array $message, [ int $mode ] ) : ZMQSocket {}
ZMQSocket::send ( string $message, [ int $mode ] ) : ZMQSocket {}
// Functions: 113 / Variants: 234
fixed in 1.3.3
Not fixed properly...

@KaduAmaral What error you have? What type are $sql and $params? Are you using 1.3.3 version?
Same error of issue #859, Invalid number of arguments. In case my $sql is a string, and my $params is an array or null.
My version is 1.3.3.

After last updates it's not throwing errors even for this strtr('', '');, maybe it needs reindexing?
I've tried uninstall/reinstall, but is still showing the message. How can I reindex?
When any php file is open: Command palette (Ctrl+Shift+P) -> Intelephense: Index workspace.
I can confirm that error is no longer generated with usage like session_set_save_handler($handlerObject, false) but now it doesn't show error even if I have usage like session_set_save_handler('', '').
@ADmad This is because #788 you can complain show your support in #830
@KapitanOczywisty I don't see how that relates to my issue. My call to session_set_save_handler() doesn't involve any use of arrays.
tl;dr there were cases when types were incompatible when they wouldn't irl and author made type checking more relaxed. It's not related to function overloading itself.
When any php file is open:
Command palette (Ctrl+Shift+P) -> Intelephense: Index workspace.
After do this the message disappear. Thank you!

Your plugin is amazing, if you need help let me know.
@ADmad I opened #869 as there appears to be another issue here when multiple function signatures are found
@bmewburn Thanks
Most helpful comment
When any php file is open:
Command palette (Ctrl+Shift+P) -> Intelephense: Index workspace.