vscode-intelephense icon indicating copy to clipboard operation
vscode-intelephense copied to clipboard

Support Psalm callable types

Open jaikdean opened this issue 6 years ago • 9 comments

It would be really nice to support Psalm's callable type syntax.

https://psalm.dev/docs/docblock_type_syntax/#callable-types

jaikdean avatar Apr 15 '19 09:04 jaikdean

Would be great to specify expected callback's arguments/return types in @param, e.g. @param callable(int):string.

Current link: https://psalm.dev/docs/annotating_code/type_syntax/callable_types/

As alternative there is also php 7.4 arrow syntax to consider, but existing psalm one is still winner for me.

KapitanOczywisty avatar Aug 26 '19 08:08 KapitanOczywisty

As I understand it Psalm and Phan use:

callable(Type1, OptionalType2=, ...SpreadType3):ReturnType

with Closure also allowed in place of callable.

PHPStan seems to have something similar but allows parameter names? So it's more akin to an actual function declaration.

callable(Type1 $type1, OptionalType2 $optionalType2 = null, SpreadType3 ...$spreadType3):ReturnType

Given that there was already array<K, V> and Generator<T> for documenting types I don't understand why additional syntax was needed for Closure. I'd rather see:

Closure<P1, P2, P3, R>

Though it would mean yet another way of documenting callables.

bmewburn avatar Sep 12 '19 23:09 bmewburn

Another way to implement this could be via existing @method syntax to give the parameter a local type name.

/**
* @method Foo MyCallableType(int $a, string $b) description...
* @param MyCallableType $callable
*/
function acceptsCallable(callable $callable) { }

bmewburn avatar Jun 21 '20 22:06 bmewburn

Another way to implement this could be via existing @method syntax to give the parameter a local type name.

@method means something completely different already, If you want own implementation just add @typedef like in JS/TS.

KapitanOczywisty avatar Jun 28 '20 10:06 KapitanOczywisty

I would appreciate it if there is a way to document callable directly and it is also recognised in intelephense. currently I use the simple syntax (string):void to document it, of course I would have no problems with callable(string):void. Only intelephense shows a \<string> which is of course completely incorrect. In my view, it would be sufficient if it were only treated as callable at the end. Only the type hint (i.e. the preview of which value is in this variable) has a great use. But even if this syntax is valid and it is converted to a callable and displayed in the type hint and not string, it is more useful than displaying something completely wrong.

lublak avatar Jul 04 '21 20:07 lublak

@lublak In some cases I'm using interfaces and anonymous classes, It has some overhead, but helps with more complicated code.

class SomeData{
	public string $whatever;
}

interface CallMeBackInterface {
	function done(SomeData $data);
}

function callMeBack(CallMeBackInterface $callback){
  // ...
}

callMeBack(new class implements CallMeBackInterface {
	function done(SomeData $data)
	{
		$data->whatever; // string
	}
});

KapitanOczywisty avatar Jul 04 '21 21:07 KapitanOczywisty

@KapitanOczywisty good idea, but hey it would be better if intelephense support it wihtout an interface.

what I found out by chance:

/**
 * @var callable(string) $testParam
 */
function test($testParam):void {
  $testParam;
}

grafik

/**
 * @var callable(string) $testParam
 */
$testParam;

grafik

So its half supported.

lublak avatar Jul 05 '21 09:07 lublak

It's supported as far as ignoring some common formats.

However if you copy type from anonymous function as @param it is suggesting correct types :D

// BAD CODE // DO NOT USE

$a = fn():string => 'foo';

/**
 * @param \Closure&\#Function#82d34e74 $cb
 */
function a ($cb){
	$b = $cb();
	// $b is string
}

KapitanOczywisty avatar Jul 05 '21 10:07 KapitanOczywisty

Hi, I also would really appreciate this feature to work a little better. One method which works quite well "inside" the called function, but which doesn't help a lot for calling the function is this:

interface MyMethod {
  public function __invoke(int|float $arg1, int|float $arg2): int|float;
}

/**
 * @param MyMethod $handler
 */
function add_callback(Closure $handler): void {
   // Completion works well here
   $result = $handler(10, 20.5);
}

/**
 * Completion doesn't work well here
 */
add_callback(function( /* HERE IT WOULD BE NICE TO HAVE COMPLETION */ ) {});

I'm sure it is possible to avoid actually declaring that interface using @typedef or whatnot(?), but just put these interfaces in a PHP file which your application will never actually load. Intelephense will still use the interface.

frodeborli avatar Aug 17 '22 10:08 frodeborli

Following will be supported in 1.9

callable(string, ?Foo): Bar and \Closure(string, ?Foo): Bar.

Longer form also supported callable(string $string, ?Foo $foo = null): Bar .

Variadic params and default args are not supported in the short format callable(Type1, OptionalType2=, SpreadType3...):ReturnType

bmewburn avatar Dec 28 '22 00:12 bmewburn