Docblock Annotations

Every docblock tag Psalm 7 reads, with the spellings it accepts. Verified against Psalm 7.0.0-beta24.

The types these tags carry: Psalm Type Syntax. Purity tags: Purity and Capabilities. Taint tags: Taint Analysis Stubs.

Ground truth:

  • vendor/vimeo/psalm/src/Psalm/DocComment.php: PSALM_ANNOTATIONS, the only suffixes allowed after @psalm-. Anything else is InvalidDocblock: Unrecognised annotation @psalm-...
  • vendor/vimeo/psalm/src/Psalm/Internal/Scanner/DocblockParser.php: how bare, @phpstan- and @psalm- spellings merge
  • .../Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php, ClassLikeDocblockParser.php, .../Internal/Analyzer/CommentAnalyzer.php, StatementsAnalyzer.php: per-target readers
  • Upstream prose: Supported annotations

Prefixes

  • Unprefixed and @phpstan- tags are not validated: an unknown one is ignored. An unknown @psalm- tag is an error.
  • @param, @var, @param-out, @template, @extends and friends merge all three spellings. @return does not: @psalm-return beats @phpstan-return beats @return. Templates resolve per name with the same order.
  • Use the @psalm- form when the type is beyond plain PHPDoc and the file is also read by IDEs or PHPStan. PHPStan ignores @psalm-* entirely (a plain @return is needed where both engines must agree). Rector strips narrower plain @var / @return but leaves @psalm- tags alone.
  • Not accepted: @psalm-mixin, @psalm-throws, @psalm-deprecated, @phpstan-method, @phpstan-property, bare @assert, bare @self-out, bare @immutable / @mutation-free / @impure.

Types on declarations

Tag Spellings Target Notes
@param Type $name bare, psalm-, phpstan- parameter Type ...$name for variadics, Type &$name for by-ref
@return Type bare, psalm-, phpstan- function-like Precedence instead of merge, see above
@var Type [$name] bare, psalm-, phpstan- property, constant, statement Inline @var narrows a variable
@psalm-ignore-var also bare @ignore-var statement Drops the @var of the same docblock (for IDE-only hints)
@param-out Type $name bare, psalm-, phpstan- by-ref parameter Type after the call returns
@param-closure-this Type $name bare, psalm-, phpstan- closure parameter Type of $this inside the passed closure (beta23)
@property Type $name bare, psalm- class Magic property. Also @property-read, @property-write. Needs a real __get / __set (or usePhpDocPropertiesWithoutMagicCall)
@method [static] Ret name(Type $p = default) bare, psalm- class Magic method. Purity in parameter types allowed (Closure[_]). Needs a real __call / __callStatic in the hierarchy (or usePhpDocMethodsWithoutMagicCall), else UndefinedMethod
@mixin Foo bare only class, trait Forward unknown members to Foo. A child @mixin replaces the parent’s
@psalm-type Name = Type psalm-, phpstan- class, file Local type alias, also a capability set alias
@psalm-import-type Name from Foo [as Local] psalm-, phpstan- class  

Generics and templates

Tag Spellings Target Notes
@template T [of\|as Bound] [= Default] bare, psalm-, phpstan- function-like, class Invariant. super Bound sets a lower bound. See defaults
@template-covariant T [of Bound] [= Default] bare, psalm-, phpstan- class  
@extends Parent<A> bare, psalm-, phpstan-, @template-extends, @inherits class, interface  
@implements Iface<A> bare, psalm-, phpstan-, @template-implements class  
@use Trait<A> bare, psalm-, phpstan-, @template-use docblock above use Trait;  
@psalm-consistent-templates   class Children keep the template list, so new static is safe (UnsafeGenericInstantiation)
@psalm-purity-template P   function-like, class Template over capability sets (purity)

No @template-contravariant exists.

Template defaults

@template T of int|null = int|null (Psalm 7.0) lets a class reference leave its trailing templates out.

  • Where a default applies. A class type read from a docblock with fewer arguments than templates (@extends Factory<User>, @param BelongsToMany<Part, Shop>, @use Trait<A>, @implements Iface<A>) takes the defaults for the missing trailing arguments. Omitting a template without a default is MissingTemplateParam: the message says expecting at least N when some template of the class has a default, and plain expecting N when none has (N is the count of templates without one in the first case, the total in the second).
  • The default must sit inside the bound, else InvalidDocblock. It may name an earlier template (B = list<A>). Defaults are trailing: write them after the templates without one.
  • A bare class name reads the bounds, not the defaults. @param Factory $f is Factory<Model, int|null>, while @param Factory<User> $f fills TCount from its default. A conditional return keyed on that template therefore resolves both branches for the bare form.
  • Plugin-built types are not filled. A TGenericObject created in a handler or provider keeps exactly the arguments it was given: emit every slot (see ModelRelationReturnTypeHandler, which emits all four BelongsToMany slots).
  • A method template default binds a template nothing infers. @template T = string with @return T and no argument to infer T gives string (without the default: never). In a union the default shows up (int|TDef with TDef = mixed gives mixed), so = never contributes nothing and Laravel’s TReturnDefault = never is unobservable. Laravel’s TMinResult = mixed is unchanged because the template sits in a conditional branch that the callback argument already infers, or that is not selected.
  • A default value on a parameter wins over the template default when the argument is omitted: new Holder() binds T from $x = null, not from = int.

Assertions and narrowing

Tag Spellings Target Notes
@psalm-assert [!]Type $x psalm-, phpstan- function-like After a normal return. Target may be $x->prop or $x->method()
@psalm-assert-if-true [!]Type $x psalm-, phpstan- function-like When the call returns true
@psalm-assert-if-false [!]Type $x psalm-, phpstan- function-like When the call returns false
@psalm-this-out Type psalm-, phpstan-, @psalm-self-out, @phpstan-self-out method Type of $this after the call (fluent builders). self-out is the legacy spelling
@psalm-if-this-is Type   method Callable only when $this matches

Negations (!null, !false) assert the opposite.

Purity and mutability

Full model: Purity and Capabilities.

Tag Target
@psalm-capabilities a\|b function-like, class
@psalm-pure (functions also bare @pure, @phpstan-pure) function-like, class
@psalm-impure function-like
@psalm-mutation-free, @psalm-external-mutation-free function-like, class. Legacy, undocumented upstream, still parsed
@psalm-immutable, @psalm-mutable class
@psalm-purity-template, @psalm-purity-from-template function-like, class

Properties

Tag Target Notes
@psalm-readonly, @readonly property Writable only inside the declaring class’s constructor
@psalm-allow-private-mutation property With readonly: also writable from the declaring class’s other methods
@psalm-readonly-allow-private-mutation property Both in one tag

Magic members and sealing

Tag Spellings Target Notes
@psalm-seal-properties, @psalm-no-seal-properties also bare @seal-properties class Only declared @property names pass __get/__set
@psalm-seal-methods, @psalm-no-seal-methods also bare @seal-methods class Only declared @method names pass __call
@psalm-override-property-visibility   class Let @property override real property visibility
@psalm-override-method-visibility   class Let @method override real method visibility

Defaults depend on sealAllProperties / sealAllMethods in psalm.xml, but only for user-defined classes (ClassLikeStorage::hasSealedMethods() / hasSealedProperties()). A stub class docblock replaces the reflected class’s seal flag, so a stub that re-declares a class Laravel seals through @method / @property tags must restate @psalm-seal-methods / @psalm-seal-properties, otherwise the class resolves to unsealed. A class sealed only by the config default (a Macroable class without tags) gets no tag: list it in RedeclaredClassSealHandler, which copies sealAllMethods onto it.

Class contracts

Tag Target Notes
@psalm-consistent-constructor class Children keep the constructor signature, so new static(...) is safe (UnsafeInstantiation)
@psalm-require-extends Foo trait, abstract class Users must extend Foo
@psalm-require-implements Foo trait, interface Users must implement Foo
@psalm-inheritors A\|B class, interface Closed set of allowed subtypes
@final class Treated as final without the keyword
@inheritDoc function-like Inherit the parent docblock (also matched inside description text)

Visibility, API, deprecation

Tag Target Notes
@psalm-internal Ns\Prefix class, function-like, property Use restricted to the namespace. Argument required
@internal class, function-like, property Unscoped internal
@psalm-api, @api class, function-like Public API: exempt from unused-code detection
@deprecated class, function-like, property, constant  
@no-named-arguments function-like Named-argument calls are an error; lets variadics infer list<T>

Suppression and debugging

Tag Target Notes
@psalm-suppress Issue[, Issue] any declaration, statement Plugin rule: fix at the source first (hard rule 6)
@psalm-trace $a, $b statement Emits a Trace issue with the inferred types
@psalm-check-type $x = Type statement Inferred type must be contained by Type; $x? = ... also checks possibly-undefined
@psalm-check-type-exact $x = Type statement Inferred type must equal Type. The workhorse of tests/Type/
@psalm-ignore-nullable-return, @psalm-ignore-falsable-return function-like Callers skip null / false checks on the return
@psalm-ignore-variable-method, @psalm-ignore-variable-property statement Unused-code detection ignores $obj->$name() / $obj->$name

Stubs and special cases

Tag Target Notes
@psalm-stub-override stubbed class, function-like Error if the real codebase has no such symbol. Catches stale stubs; does not change merge order
@psalm-variadic function-like Accepts any number of arguments without ... (func_get_args() bodies)
@psalm-scope-this Foo statement holding a closure $this inside is Foo (closures bound at runtime)
@psalm-yield Type class, interface What a yield $thisObject sends back (promise libraries); usually a template
@since X.Y stubbed class, function-like In stub files only: PHP version that introduced the symbol

Taint analysis

@psalm-taint-source, @psalm-taint-sink, @psalm-taint-escape (plain and conditional), @psalm-taint-unescape, @psalm-taint-specialize, @psalm-flow, @psalm-assert-untainted. Semantics, the escape plus flow pairing rule, and the kind list: Taint Analysis Stubs. Psalm also reads bare param-taint / return-taint (a MediaWiki compatibility shim); do not use them.

Attributes Psalm reads

Attribute Equivalent
#[\Deprecated], #[Psalm\Deprecated], #[JetBrains\PhpStorm\Deprecated] @deprecated
#[\Override] Checked: must override something
#[\NoDiscard] Callers must use the return value
#[\ReturnTypeWillChange] Skips return covariance against a builtin parent
#[Psalm\Pure], #[JetBrains\PhpStorm\Pure] @psalm-pure
#[Psalm\Immutable], #[JetBrains\PhpStorm\Immutable] @psalm-immutable
#[Psalm\ExternalMutationFree] @psalm-external-mutation-free
#[Psalm\Readonly] @psalm-readonly
#[Psalm\Internal] @psalm-internal (namespace of the declaring class)
#[JetBrains\PhpStorm\NoReturn] return type never

Psalm matches the Psalm\ and JetBrains\ attributes by name and ships no classes for them, so code using them needs its own class or gets UndefinedAttributeClass.


This site uses Just the Docs, a documentation theme for Jekyll.