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
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 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()
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/
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.
@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.