psalm: Mechanisms for not 'trusting' docblock types

This spins out from #8005 and some slack conversations

In general Psalm treats docblock types as truth. This is great for an application because I can add @param array<Foo>, no longer feel like I have to validate my array, and any calling code that doesn’t obey the type definition is caught.

For libraries this is different - specific APIs are going to be accessed by code that may not be validated using Psalm (or other SA). This leaves the author with choices

  • Decide that callers who ignore the type are ‘wrong’ and any errors are part of ‘undefined behaviour’ and their own fault
  • Write redundant checking code that Psalm will complain is unnecessary
  • De-activate the param somehow (e.g. @var mixed inside the body of the function)
  • Lose your mind like @Ocramius and write a library to make everyone use psalm

IMO we should have a way to opt out of this behaviour either globally, per method, or using an internal/external mechanism:

Global opt-out

Any validated callers would still need to pass the right types, but when evaluating the code inside the function the docblock param would effectively be ignored

<psalm
  useDocblockParamTypesInFunctionBody="false"
/>
/** @param non-empty-array<Foo> $a */
function f(array $a): Foo
{
   return current($a); // MixedReturnStatement here
}

f([]); // still an InvalidArgument here

Per-functions opt-out

It may be we only want to do this for certain functions that we ‘know’ are going to be called from an untrusted source. In that case a flag in the docblock would make sense:

/** 
 * @param non-empty-array<Foo> $a 
 * @psalm-ignore-docblock-params
 */
function f(array $a): Foo
{
   return current($a); // MixedReturnStatement here
}

‘External’ opt-out

Many libraries have an existing concept of what is internal or external to them. We could have a setting that uses that existing labelling

<psalm
  useDocblockParamTypesInExternalFunctionBody="false"
/>
/** 
 * @param non-empty-array<Foo> $a 
 */
function f(array $a): Foo
{
   return current($a); // MixedReturnStatement
}

/** 
 * @param non-empty-array<Foo> $a 
 * @internal
 */
function g(array $a): Foo
{
   return current($a); // no error here because we assume all callers are validated
}

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 3
  • Comments: 19 (9 by maintainers)

Commits related to this issue

Most upvoted comments

Lose your mind like @Ocramius and write a library to make everyone use psalm

🤣

Thanks for writing this up as a clear feature request.