0.7.0
Added
@psalm-return,@psalm-param, and@psalm-vartag support. Psalm-prefixed docblock tags are now recognized alongside their PHPStan equivalents for return types, parameter types, variable types, conditional return types, template parameter bindings, and semantic token highlighting.- Refactoring code actions. Extract function, extract method, extract variable, extract constant, inline variable, promote constructor parameter, generate constructor (traditional and promoted), generate getter/setter, and generate property hooks (PHP 8.4+). Deferred computation ensures the lightbulb menu appears instantly; edit generation only runs when the user picks an action.
- PHPStan quickfixes. Automated fixes for a wide range of PHPStan diagnostics: update or remove mismatched
@return/@param/@vartags, remove unused return type union members, fix unsafenew static()(add@phpstan-consistent-constructor,finalclass, orfinalconstructor), add or remove#[Override], add#[\ReturnTypeWillChange], fix void return mismatches, add inferred iterable return types, remove unreachable statements, remove always-trueassert()calls, fix overriding member visibility, fix vendor-prefixed class names, and simplify ternary expressions to??or?->. All quickfixes eagerly clear their diagnostic on apply. fixCLI subcommand.phpantom_lsp fixapplies automated code fixes across a project. Specify rules with--rule(multiple allowed) or omit to run all preferred fixers.--dry-runreports what would change without writing files. The first shipped rule,unused_import, removes unusedusestatements project-wide, collapsing blank lines left behind by removals. Supports path filtering and single-file mode.- Keyword completions. Context-aware PHP keyword suggestions filtered by scope (e.g.
returnonly inside functions,breakonly inside loops, member keywords inside class bodies, enum backing types afterenum Name:). Contributed by @ryangjchandler in #43. - Attribute completion. Typing inside
#[…]offers only classes decorated with#[\Attribute], filtered by the target declaration kind. - Eloquent model enhancements. Timestamp properties (
created_at,updated_at) are automatically typed asCarbonwith support for$timestamps = falseand custom column constants. Legacy$datesarrays produce typed virtual properties.$appendsentries produce virtual properties.where{PropertyName}()dynamic methods are synthesized from all known columns (including@propertyannotations) on both the model and the Builder.whereHas/whereDoesntHaveclosure parameters resolve toBuilder<RelatedModel>by traversing relationship methods, with dot-notation chain support.Conditionable::when()/unless()chains preserve type information. - Type-guard narrowing.
is_array(),is_string(),is_int(),is_float(),is_bool(),is_object(),is_numeric(), andis_callable()narrow union types insideif/else/elseifbodies and after guard clauses, preserving generic element types through narrowing. - Array value type tracking. Arrays built incrementally with variable keys inside loops now carry element types through
foreachiteration, bracket access, and null-coalescing. Foreach over generic arrays with non-class element types (array shapes, scalars) now preserves the full element type. - Inherited docblock type propagation. When a child class overrides a method without providing its own
@returnor@paramdocblock, the ancestor's richer types flow through automatically. Applies to return types, parameter types (matched by position), property type hints, and descriptions. - Bidirectional template inference from closures. Templates appearing in callable parameter signatures are now inferred from both the closure's return type and its parameter types. Positional matching is supported, and return-type bindings take priority when the same template appears in both positions.
- Drupal project support. Drupal projects are detected via
composer.json. Drupal-specific directories and PHP extensions (.module,.install,.theme,.profile,.inc,.engine) are recognized and indexed. Contributed by @syntlyx in #52. - Completion and signature help for
new self,new static, andnew parent. Constructor parameter snippets and signature help inside the parentheses. Contributed by @RemcoSmitsDev in #51. - Hover on parameter variables at their definition site. Hovering on a function or method parameter now shows its resolved type, using the
@paramdocblock type when it is richer than the native hint. Contributed by @RemcoSmitsDev in #68. - Array element type extraction from property generics. Bracket access on properties annotated with generic array or collection types (e.g.
$this->cache[$key]->) now resolves the element type correctly through nested chains, string-literal keys, and method chains after the bracket. @phpstan-assert-if-true $thisnarrowing. Instance methods annotated with@phpstan-assert-if-trueor@phpstan-assert-if-falsetargeting$thisnow narrow the receiver variable in the corresponding branch. Contributed by @syntlyx in #52.- Namespace completion from file path. When creating a new PHP file, typing
namespacesuggests the correct namespace inferred from the file's location and the project's PSR-4 autoload mappings. The most specific mapping is preselected so you can accept it with a single keypress. When multiple PSR-4 roots match the same directory, all candidates appear ranked by specificity (longest match first). - Standalone
@vardocblock for untyped closure parameters. When a closure parameter lacks a type hint and no assignment follows, a@varblock above the usage is now picked up as the variable's type. --stdioCLI flag. Accepted (and ignored) for compatibility with LSP client wrappers that pass--stdioby default.--tcpCLI flag.phpantom_lsp --tcp 9257starts the server listening on a TCP port instead of stdin/stdout. Useful for debugging or connecting from IDE plugins that prefer a network transport over spawning a child process. Accepts a full address (127.0.0.1:9257) or just a port number. The server accepts one connection and exits when the client disconnects.- Method-level template parameters resolve inside method bodies.
@template T of Builderwith@param T $querynow resolves$queryto the template bound inside the method body, providing completions from the bound class. - Undefined variable diagnostic. Variable reads that have no prior definition (assignment, parameter, foreach binding, catch variable,
global,static,use()clause, or destructuring) in the same scope are flagged as errors. Writes must appear before the read in source order, catching use-before-assign bugs, while assignments inside branches (if/else, switch, try/catch) still count to avoid false positives. Suppressed for superglobals,isset()/empty()guards,compact()references,extract()calls, variable variables ($$),@error suppression, and@varannotations. Static property accesses (self::$prop,static::$prop,parent::$prop) are excluded. Variables passed to by-reference parameters are recognized as definitions: 40+ built-in PHP functions are covered (regex, cURL, OpenSSL, sockets, DNS, etc.), and user-defined functions, static methods, and constructors with&$paramparameters are detected automatically from their signatures. Scoping is tracked through arbitrary nesting of closures, arrow functions, and catch blocks. Top-level code outside functions is skipped. - By-reference parameter type inference for method, static, and constructor calls. When a variable is passed to a by-reference parameter with a type hint (e.g.
function foo(Baz &$bar)), the variable acquires that type after the call. Previously this only worked for standalone function calls. Now it also works for$this->method(), static method calls, and constructor calls.
Changed
- Fewer false-positive diagnostics. Variable resolution now produces the same result across completions, hover, and diagnostics, eliminating cases where diagnostics disagreed about a variable's type.
@phpstan-ignoreis never the preferred quickfix. The "Ignore PHPStan error" code action is explicitly non-preferred, so editor keyboard shortcuts no longer accidentally apply it when another fix is available.- Generate PHPDoc infers
@returnfrom the function body. Typing/**above a function that returnsarraynow produces a specific element type (e.g.@return list<string>) instead of@return array<mixed>. - Faster startup. Stub loading during initialization is significantly faster.
- More accurate generics resolution. Type substitution and resolution for complex nested generic types is more correct, particularly for unions, intersections, array shapes, and deeply nested generic arguments.
- More accurate type predicates.
NULL,Null, and case variants ofnullare now handled consistently throughout type checking, matching PHP's case-insensitive treatment of type keywords. - Go-to-definition at declaration sites returns the symbol's own location. Class, member, and variable declaration names now return their own location instead of nothing, so editors that detect "definition == cursor" can automatically fall back to Find References. Contributed by @lucasacoutinho in #76.
Fixed
- Completion no longer triggers on the
<?phpopen tag. Typing<?phpand pressing enter no longer applies a spurious function suggestion likephp_ini_loaded_file(). - Case-insensitive
parenthandling in chained static calls.resolve_lhs_to_classnow handlesparent::method(...)in chained callable expressions and uses case-insensitive matching forself/staticin the same context. - Intersection types preserved through resolution. Variables and parameters with intersection types (e.g.
Countable&Serializable) now display correctly in hover, extract-function parameter hints, and generated docblocks. Previously intersection types were flattened to unions (Countable|Serializable). - Return types now carry class info through the resolution pipeline. Method and function return types that name a class (e.g.
Collection<User>) now populate the resolved class info eagerly, so downstream consumers (hover, narrowing, completion) no longer need a second resolution pass. - Generic parameters preserved on resolved types. Catch clause variables, pass-by-reference parameters, closure parameters, and constructor calls now thread the original type hint (including generic parameters) through the resolution pipeline instead of discarding it.
- Type-guard narrowing no longer drops class info on unions. Narrowing a union like
Foobar|string|intwithis_string()/is_int()in elseif chains now correctly preserves class info for the remaining class member. - False-positive undefined-variable diagnostic on static property access.
self::$prop,static::$prop, andClassName::$propno longer trigger an undefined variable warning. Dynamic forms (self::$$prop,self::${expr}) still correctly flag undefined variables used in the expression. Contributed by @lucasacoutinho in #75. - Case-insensitive
self,static, andparentresolution.SELF::method(),Static::create(),PARENT::foo(), and other non-lowercase spellings now resolve correctly. Previously only the exact lowercase forms were recognized. - Property type resolution in call arguments. When a method argument is
$this->propand the property has a generic, nullable, or union type, the full type structure is now preserved. Previously only the base class name was extracted, discarding generics and union components. - Update docblock enrichment comparison. The "Update docblock" code action now uses structural type comparison instead of string equality when deciding whether a
@paramtype needs enrichment. Types that are semantically equivalent but formatted differently (e.g.\App\UservsApp\User) no longer trigger spurious updates. Body-based@returnenrichment now correctly detects when an existing@returntag already has type structure, instead of always proposing a replacement. @phpstan-assertand@psalm-asserttags with generic types. Assertions like@phpstan-assert Collection<int, User> $paramnow parse the full generic type instead of truncating at the first space inside angle brackets.parent::method()resolution in inline arguments. Passingparent::method()as an argument to a function now resolves the return type correctly, matching the existing handling forself::andstatic::.- Laravel Eloquent Builder and Collection type resolution. Generic and nullable types on Eloquent models (e.g.
Collection<int, User>,?User) now resolve correctly when used for Builder scope injection, custom collection swapping, and relationship chain inference. Previously these types were stringified with their generic parameters or nullable prefix, causing lookups to fail silently. - Docblock generation no longer panics on lines with multibyte characters. Files containing non-ASCII characters (e.g. accented letters) could cause the
/**docblock trigger to crash or produce misaligned edits due to a mismatch between UTF-16 column offsets and byte offsets. - Conditional return types showing
mixedin hover. When a method with a conditional return type (e.g.@phpstan-return ($type is class-string<T> ? T : mixed)) resolved to a concrete class, hover still displayed the method's declared return type (mixed) instead of the resolved class. Affects methods like Symfony'sSerializerInterface::deserialize(). - Method-level
@throwstypes now resolve short names to FQN. Exception types in@throwstags on class methods are now fully qualified using the file'suseimports, matching the behaviour already in place for standalone functions. Cross-file throws propagation and the "Update docblock" code action produce correct results when the exception class is imported via ausestatement. - Missing diagnostics and import actions in files without a namespace. When a namespaced class (e.g.
Carbon\Carbon) had already been parsed, using its short name (Carbon) in a file without anamespacedeclaration incorrectly resolved against the namespaced class. This suppressed both the "class not found" diagnostic and the "Import" code action. Bare-name lookups now only match classes that are themselves in the global namespace. - Find-references false positives for global classes. Searching for references to a global-scope class (e.g.
Helperwith no namespace) could include references to unrelated namespaced classes with the same short name (e.g.App\Helper). Short-name fallback matching now only applies when the resolved name is unqualified. - Fluent chains only flag the first broken link. In a chain where the first method does not exist, only that method is flagged instead of every subsequent call receiving its own warning.
- Null narrowing from
!== nullchecks. Null-initialized variables guarded by$var !== null,!is_null(), or bare truthy checks now havenullnarrowed away inside the then-body and in subsequent&&operands. Works in chained conditions, ternary expressions, and return statements. - Variables assigned inside
if/whileconditions now resolve in the body.if ($admin = AdminUser::first())andwhile ($row = nextRow())register the assignment so the variable has a type inside the branch or loop body. - Loop-body assignments not visible inside the same loop iteration. When a variable is initialized as
nulland reassigned later in a loop body, the assigned type is now visible at every point inside the loop. Combined with null narrowing, variables correctly resolve to the assigned class type. @vardocblock annotations no longer leak across class and method boundaries. A@varannotation for a same-named variable in a different class no longer bleeds into the current scope.- Inline
@varcast no longer overrides the variable type on the RHS of the same assignment./** @var array<string, mixed> */ $data = $data->toArray()no longer resolves the RHS$datausing the cast type. - Foreach over union types containing arrays now resolves the element type. A parameter typed
User|array<User>iterated withforeachnow correctly yieldsUseras the loop variable type. Previously the element type extraction did not look inside union members, producing no completions. @paramdocblock overrides ignored when the native type hint resolves. When a parameter has both a native type hint and a more specific@paramoverride, the docblock type now takes effect. Contributed by @calebdw in #55.- Variable reassignment inside
try/catch/finallyblocks now tracked. Subsequent accesses within the same block resolve against the reassigned type instead of the original. - Self-referential variable reassignments in nested loops no longer produce false "type could not be resolved" diagnostics. Recursive resolution that hits the depth limit no longer poisons the cache for later lookups.
instanceofnarrowing with unresolvable target class. When the target class cannot be loaded, the variable's type is treated as unknown instead of keeping the un-narrowed type, eliminating false positives for members on the narrowed subclass.stdClassandobjecttypes no longer produce false-positive diagnostics. Variables typed asobjectorstdClassnow permit arbitrary property access.is_object()correctly narrowsmixedtoobjectand compound&&conditions propagate the narrowing.- Docblock type refinement no longer matches class names containing type keywords. A class named
PointOfInterestwould incorrectly be treated as anintrefinement because the refinement check used substring matching. Refinement compatibility now uses structural type predicates. class-string<T>static method dispatch. Calling static methods on aclass-string<Foo>variable now resolves return types correctly, includingstaticsubstitution to the bound class.self/static/$thisin cross-file method return types now resolve correctly. When a method on a cross-file class returns a type referencingself(e.g.@return HasMany<self, $this>), the owning class was looked up by short name through the consuming file's import table, which failed when the consuming file did not import that class. The owning class is now looked up by its fully-qualified name.in_arrayguard clause no longer wipes out variable type. When the haystack's element type matches the variable's type, the narrowing system no longer excludes the type entirely.- Method chains through
__callno longer lose the return type. When__callreturns$this,static, orself, the chain type is preserved through dynamic method calls. - Scope methods on Eloquent Builder no longer produce false-positive diagnostics. Bare
Builderreturn types on scope methods are automatically wrapped asBuilder<ConcreteModel>to preserve the chain. - Scope methods missing from completion on relationship results. Scope methods from related models now appear in completions, not just hover.
- Closure and variable hover now preserves generic arguments. Closure parameters inferred from callable signatures, variables assigned from chained methods returning
static/$this/self, and hovering on the$sign of a variable at its assignment site all now show the correct generic type. - Callable parameter inference preserves generic arguments from the receiver. A closure typed as
fn(Builder $q)inside aBuilder<Product>chain now infers$qasBuilder<Product>, so model-specific scope methods resolve correctly. @seetags in floating docblocks now support go-to-definition. Docblock comments not directly attached to a class, function, or statement (e.g. inline/** @see SupervisorOptions::$balanceCooldown */inside array literals or after expressions) are now parsed for symbol references. Previously these were silently ignored, particularly in files without a namespace.- Nullable
staticreturn types on inherited methods. Methods returning?staticorstatic|nullnow correctly resolve to the calling subclass across files. - Template binding with nested generics. Parameter types like
Wrapper<Collection<T>, V>no longer break during template binding. - Single generic argument on collections bound to the wrong template parameter.
Collection<User>now binds to the value parameter instead of the key parameter when key-like template parameters precede value parameters. - Nullable return types losing
|nullafter template substitution.@return TValue|nullnow preserves|nullthrough substitution, so calls like::first()correctly show the nullable type. @mixinreferencing a template parameter now resolves. A class with@template Tand@mixin Tnow pulls in methods from the concrete type passed via generic arguments.@propertyand@methodtags losing nullable types. Tags like@property int|null $foono longer have|nullstripped.- Callable types inside unions displayed ambiguously.
(Closure(int): string)|Foois now parenthesized correctly in hover and completions. - Hover and go-to-definition on attributes. Attributes on properties, class constants, parameters, and enum cases are now navigable.
- Function-level
@templatewith generic wrapper parameters. Template substitution at call sites now correctly handlesarray,iterable, andlistas wrapper names. - Closure parameter inference from function-level
@templatebindings. Functions likearray_anyandarray_allnow infer concrete types for untyped closure arguments from the array parameter's element type. - Property chain arguments in template substitution. Expressions like
$this->itemspassed to templated functions now resolve their type for template binding. - Variadic parameter element type lost in
foreach. Iterating over a variadic parameter now resolves the loop variable to the element type. - Anonymous class variables now resolve their type.
$model = new class extends Foo { ... }followed by$model->method()now resolves through the anonymous class's inherited members. - Namespaced functions imported via
use functionno longer flagged as unknown. Functions defined in one file and imported viause functionin another now resolve correctly. parent::method()return type resolution in variable analysis. Callingparent::method()and assigning the result now correctly resolves the parent method's return type.- Closure parameter inference inside
switchcases andifconditions. Closure parameters that should be inferred from the enclosing callable context now resolve correctly when the closure appears inside a switch case or if-condition. - Generic arguments propagated through transitive
@extendschains. When a class extends a parent that itself extends a generic grandparent, generic arguments now flow through the full chain. - Stack overflow when a foreach value variable shadows the iterator receiver. Patterns like
foreach ($category->getBranch() as $category)no longer cause infinite recursion. - PHPStan
*wildcard in generic type arguments. Type strings likeRelation<TRelatedModel, *, *>now parse correctly. - Types with
covariantorcontravariantvariance annotations in generic args now parse correctly. Annotations likeBelongsTo<Category, covariant $this>no longer cause the entire type to become unresolvable. - Diagnostics now work for vendor files open in the editor. Projects using
--prefer-sourceor monorepo setups no longer have diagnostics suppressed in vendor files. - PHPStan diagnostics no longer hidden by unrelated native diagnostics on the same line. Deduplication now only suppresses a full-line diagnostic when the precise diagnostic on the same line reports a related issue.
- Nullable boolean properties now use
isprefix for getters. Properties typed?boolor?booleannow generateisFoo()instead ofgetFoo()when using the "Generate getter" code action. - Aliased namespace imports used in attributes no longer flagged as unused.
use Symfony\Component\Validator\Constraints as Assert;with#[Assert\Uuid(...)]no longer produces a false "Unused import" diagnostic. DB::select()return type.DB::select()and related methods now returnarray<int, stdClass>instead of barearray, andDB::selectOne()returns?stdClass.- Redis
Connectionmethod resolution. Redis commands onIlluminate\Redis\Connections\Connectionnow resolve through the phpredis stubs. - Array shape tracking from keyed assignments inside conditional branches. Shape types built incrementally with variable keys inside loops with if/else branching are now preserved through foreach iteration.
- Deprecated class in
implementsrenders with strikethrough. Deprecated classes referenced inimplementsclauses are correctly tagged. - Interleaved array access and property chains no longer produce false positives. Expressions like
$results[$i]->activities[$id]->extraswhere array subscript and property access alternate were incorrectly parsed, causing the intermediate property chain to be dropped. This led to "Property not found on class" false positives when the element type was resolved but the subsequent property lookup was skipped. - FQN
\assert()now narrows types. Writing\assert($var instanceof Foo)with a leading backslash was not recognized as an instanceof narrowing, causing false-positive "property not found" diagnostics after the assertion. - Generic template substitution producing invalid types. When a template parameter was the base of a generic type (e.g.
T<int>whereTmaps toCollection<string>), the substitution produced malformed types likeCollection<string><int>. The replacement's base name is now used correctly, yieldingCollection<int>.
New Contributors
- @ryangjchandler made their first contribution in #43
- @daronspence made their first contribution in #47
- @RemcoSmitsDev made their first contribution in #51
- @syntlyx made their first contribution in #52
- @mattsches made their first contribution in #61
- @markkimsal made their first contribution in #67
- @lucasacoutinho made their first contribution in #75
Full Changelog: 0.6.0...0.7.0