All notable changes to PHPantom will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Static methods complete on instance access. Member completion after
->now offers a class's static methods alongside its instance methods, since PHP lets you call a static method through an instance ($obj->make()). Static properties remain excluded, as they are only reachable via::. Contributed by @calebdw in #174. - Array-callable navigation. Method-name strings in array callables —
[Controller::class, 'method']and[$object, 'method']— now resolve like a real member reference. This makes go-to-definition, find-references, and rename work on Laravel controller actions such asRoute::get('/', [IndexPageController::class, 'indexPage']). - Array-callable method completion. Typing inside the method-name string of an array callable (
[Controller::class, '|']) now offers method name completions from the resolved class, including inherited and trait methods. Works withClass::classconstants,$this, and typed variables. (thanks @calebdw) - Convert arrow function to closure. A new
refactor.rewritecode action converts arrow functions to anonymous closures (fn($x) => $x * 2tofunction($x) { return $x * 2; }). Variables from the outer scope are automatically captured via ause()clause. Preservesstaticand return type hints. Contributed by @calebdw in #191. - Magic methods complete when implemented. Magic methods declared on a class (
__invoke,__toString,__call, and the rest) are now offered in member completion, so explicit calls like$x->__invoke()autocomplete and support go-to-definition. They are sorted below the regular methods so they never appear at the top of the list. - Staleness detection and auto-refresh. The class index, function index, and constant index now stay fresh automatically. When PHP files are created or deleted outside the editor (e.g.
git checkout, code generation), the indices update without a restart, and edits made outside the editor are reflected the next time the file is used. Whencomposer.jsonorcomposer.lockchanges (e.g. aftercomposer install), vendor packages are rescanned automatically. #[ArrayShape]attribute support. Functions and methods annotated with#[ArrayShape(["key" => "type", ...])](used by ~84 phpstorm-stubs entries) now produce array shape key completions, hover type info, and correct type resolution. Affects commonly used functions likeparse_url,stat,pathinfo,gc_status,getimagesize, andsession_get_cookie_params.- Convert to arrow function. A new
refactor.rewritecode action converts single-expression closures to arrow functions (function($x) { return $x * 2; }tofn($x) => $x * 2). The action is only offered when the conversion is safe: single return statement, no by-referenceusecaptures, novoid/neverreturn type, and PHP >= 7.4. - Convert switch to match. A new
refactor.rewritecode action convertsswitchstatements tomatchexpressions when all arms are single-expression returns or assignments to the same variable. Handles fall-through cases (merged with commas), trailingbreakremoval, andthrowarms. Requires PHP >= 8.0. - Extract interface. A new
refactor.extractcode action generates an interface from a concrete class. All public method signatures (excluding the constructor) are extracted into a new{ClassName}Interface.phpfile in the same directory, and the class is updated withimplements {ClassName}Interface. Class-level and method-level@templatetags are preserved when referenced by extracted methods. @templateon@methodtags. Virtual methods declared via@methodPHPDoc tags can now define their own template parameters using the<T of Bound>syntax (e.g.@method TVal get<TVal of mixed>(TVal $default)). Template inference at call sites works the same as for real methods.- Laravel custom Eloquent builder support. Models using the
#[UseEloquentBuilder]attribute now have their custom builder's methods forwarded as static methods on the model.query(),newQuery(), andnewModelQuery()return the custom builder type with correct generic model substitution. Contributed by @MingJen in #118. - Eloquent relation and column string completion. Typing inside string arguments to
with(),load(),whereHas(), and other Eloquent methods that accept relation names now offers relationship method names as completions, with dot-notation traversal for nested relations. Similarly,where(),orderBy(),select(),pluck(), and other column-accepting methods offer model column names (from$casts,$fillable,@propertytags, timestamps, etc.). model-property<T>pseudo-type recognition. The Larastanmodel-property<Model>type no longer triggers "unknown class" diagnostics. It is treated as a string subtype.compact()strings are linked to local variables. A string argument tocompact('user')is now treated as a reference to the matching local variable. Renaming the variable updates the string (and renaming from the string updates the variable and its other uses), find-references includes the string, and go-to-definition on the string jumps to the variable's assignment. Contributed by @calebdw in #159.
- Class lookups no longer fall back to a linear scan. The O(n) fallback scan over all parsed files was dead code since the O(1) hash index is always populated first. Removing it eliminates a potential micro-stutter during initial indexing.
- Diagnostics don't update after function signature changes. Editing a standalone function's parameter or return type in one file (e.g. changing
bar(null $x)tobar(string $x)) did not refresh diagnostics in other open files that call that function, so stale errors persisted until the editor was restarted. The server now tracks function signature changes (not just class signatures) and invalidates cross-file diagnostics when they differ. AtextDocument/didSavehandler was also added as a reliable diagnostic refresh point for editors like Neovim. Fixes #123. (contributed by @calebdw) - Literal type matching in argument diagnostics. String, integer, and float literal arguments now match PHPDoc literal-union parameter types. For example,
orderBy('id', 'desc')no longer produces a bogus error when the parameter is typed as'asc'|'desc'. Conversely, passing a provably wrong literal (e.g.'invalid'to'asc'|'desc', or'hello'tonumeric-string) is now correctly flagged. Fixes #180. Contributed by @calebdw in #191. - Type Hierarchy works in more clients. The
textDocument/prepareTypeHierarchycapability was registered without registration options, so some clients (notably Zed) did not reliably expose the Type Hierarchy action. The dynamic registration now carries properTypeHierarchyRegistrationOptionswith a PHP document selector, so those clients recognise that the feature applies to PHP files. Contributed by @sidux in #179. - Extract method generates correct code for more selections. A variable that the selection reads before it first assigns (for example a parameter the extracted code both consults and updates) is now passed in as an argument as well as returned, instead of being left undefined inside the new method. And an early
returnwhose value references a variable defined inside the selection is now kept inside the extracted method and propagated to the caller, instead of being copied to the call site where that variable does not exist. - The editor stays responsive during fast typing in large files. Editors send a burst of requests on every keystroke (completion, a documentation lookup for each suggestion, diagnostics, code lens, semantic highlighting, and more). The server processed only a few at a time, so during continuous typing the burst backed up until the server stopped answering anything at all, including the completion the user was waiting on, and it only recovered after a restart. Now the burst is processed concurrently and every expensive request runs off the main loop: diagnostics (which re-analyze the whole file on each edit) compute in the background instead of on the request that asked for them, so a diagnostic pull returns immediately and never blocks the threads that deliver completion and hover, and repeated whole-file requests (semantic highlighting, code lens, the document outline, folding, document links) are collapsed so a fast typist's superseded requests no longer pile up and monopolize the CPU. Completion and other requests keep coming back while you type.
- Typing in a large file no longer pegs the CPU and stalls completion. Semantic highlighting recomputed every token's position by rescanning the file from the beginning, so a large file took many seconds at full CPU to highlight. Editors request highlighting on every keystroke, so this ran continuously while typing and starved completion, hover, and other requests until they appeared to hang. Highlighting a large file is now effectively instant, and the same speedup applies to the document outline and code folding, which used the same per-position rescan.
- The first use of a global helper function no longer stalls. Functions defined in Composer "files" autoload entries and guarded by
if (! function_exists(...))(such as Laravel'sapp(),session(), androute()) were parsed on demand the first time one was used, which meant the first completion, hover, or go-to-definition involving such a helper blocked while the server parsed every autoload file in turn. These files are now parsed up front during indexing, so the first lookup is instant. - Framework global helpers loaded outside Composer autoload are now indexed. Some frameworks ship their global function aliases in a
*_global.phpfile that sits beside an autoloaded helper file but is pulled in by the framework's own bootstrap rather than Composer'sfilesautoload, so it never appears in the autoload manifest. CakePHP is the canonical case: helpers like__(),h(), andenv()live in such a sibling and were reported as unknown functions on every call. These sibling helper files are now indexed too, so the globals resolve. Contributed by @dereuromark in #175. - Classes defined inside conditional blocks are now fully resolved. A class declared inside an
if/elseversion guard (the DoctrineServiceEntityRepositorypattern, where a base class is defined differently per ORM version) was previously discovered by name only, so its parent and@extendsgenerics were dropped. Such classes now carry their full inheritance, so member completion, hover, go-to-definition, and generic type resolution work both on them and inside their own methods. When the same class name appears in more than one branch, the first declaration wins. Contributed by @MrSrsen in #154. - Editing a base class stays responsive in large projects. Changing a class that many others extend used to invalidate the resolved-class cache by rescanning every cached class on each edit, which briefly stalled large projects with deep class hierarchies. Invalidation now touches only the classes that actually depend on the edited one.
- Completion latency stays flat during sustained fast typing. Concurrent requests resolving the same classes contended on a single lock guarding the resolved-class cache, so completion latency crept upward for as long as a typing burst continued. The cache now allows parallel reads, so the many lookups in flight at once no longer serialize behind one another, and when a request first loads a vendor class the work to record it in the shared index is prepared before the index is locked, so other requests no longer wait on it.
- The server no longer freezes and stops responding. Editors cancel in-flight requests constantly (every cursor move supersedes the previous hover and highlight), and a burst of cancellations, such as when the editor regains focus after being in the background, could wedge the server so that it went completely silent and had to be restarted. Cancelled requests are now handled cleanly.
- Returning to a backgrounded editor stays responsive. When an editor regains focus it re-reports every file in the workspace as changed in one large batch. Processing that batch could stall the server while it re-read thousands of files from disk. The batch is now handled off the main loop and skips files that were never loaded, so the editor stays responsive.
- Inherited members no longer briefly flagged as unknown after opening a project. A method or property inherited from a vendor base class (for example the base methods of a framework controller) could be reported as an unknown member right after a file opened, even though hover resolved it correctly, and the error went away when the file was closed and reopened. Such members now resolve as soon as indexing finishes.
- Named arguments are matched to parameters by name. Calls that pass arguments by name (
f(c: 3)) are now bound to the parameters they actually target instead of by their position in the call. Conditional return types resolve correctly when the deciding argument is passed by name out of order, a "missing required argument" error is now reported when a named argument fills an optional parameter but leaves a required one unsupplied, and pass-by-reference type inference seeds the right variable. - Argument-count false positives. Extra arguments to a class with no constructor are no longer flagged (PHP accepts them), and namespaced calls to overloaded built-ins written with a leading backslash (
\mt_rand()) are no longer measured against the wrong minimum. @varannotations no longer leak between functions. A/** @var T $x */annotation in one function used to suppress "undefined variable" warnings for that name everywhere in the file; it is now scoped to the function it appears in.- Type resolution through chained and untyped access. Null-safe call chains such as
$a->b?->c()resolve through the full receiver. Array access on a value of unknown type resolves tomixed, so$x = $arr['key'] ?? 5no longer produces spurious type errors.foreachelement types resolve through interfaces that reach a known iterable several hops away. Nested array-shape narrowing ($a["x"]["y"]) no longer targets the wrong key. selfreferences inside class-level attributes resolve. Aself::,static::, orparent::reference inside an attribute attached to a class (for example#[Route(name: self::ROUTE)]) is now resolved against the class it decorates, so the referenced constant or member is no longer reported as unresolvable.@methodtags override inherited methods of the same name. A@methodannotation on a class now takes precedence over a method inherited from a more distant ancestor. The common repository pattern, where a base repository declares@method Entity|null findOneBy(...)while its vendor parent returns a genericobject, now resolves to the concrete entity type, so members accessed on the result are no longer flagged as unverifiable.??=keeps the resolved type. After$x ??= new Foo(), the variable resolves toFoo(or the union of its existing non-null type and the assigned value), so property and method access on$xis no longer reported as unresolvable.- Generics with fewer arguments than parameters.
@extends Collection<User>againstCollection<TKey, TValue>now bindsUserto the value parameter, so inherited element types resolve correctly. - Nullable generic return types resolve through inheritance. A method whose native return hint is nullable (
object|null) and whose docblock returns a template (@return ?T) now resolves to the bound type, so a repository'sfind()returnsEntity|nullinstead of the bareobject|null. Contributed by @MrSrsen in #152. - Conditional
is nullreturn types resolve consistently regardless of how the call site is parsed, and an explicitly passednullnow selects the null branch. - Go-to-definition, rename, and highlight accuracy. References in
@seetags to qualified names likeApp\Foo::bar()now land on the correct location, and renaming a property selects the whole$nameinstead of$nam. @phpstan-require-extendsand@phpstan-require-implementsnavigation. Class and interface names in these trait constraint tags now support go-to-definition and hover, and an import used only by such a tag is no longer flagged as unused. Contributed by @calebdw in #172.- Renaming variables captured by nested closures and arrow functions. Renaming or finding references to a variable used inside deeply nested arrow functions (
fn () => fn () => $var) or closures withuse ($var)now updates every occurrence, whether the rename is triggered on the declaration or from deep inside the nesting. Contributed by @calebdw in #145. - Variables inside dynamic property accesses are tracked. A variable used as a dynamic property selector (
$message->{$attribute}) now counts as a use, so it is no longer wrongly reported as unused, find-references includes it, and renaming the variable updates the selector along with its other occurrences. Contributed by @calebdw in #174. - Member rename stays scoped to the declaration it targets. Renaming a method or property no longer touches same-named members on unrelated classes. A private method rename updates only that method and its real usages, calls on a receiver whose type cannot be resolved are left alone, renaming one implementation of an interface no longer renames sibling implementations, and renaming a child override stays on the child branch. Renaming a parent or interface declaration still updates the inherited overrides and implemented usages. Contributed by @calebdw in #160.
- Find references on a constructor lists every call site. Finding references to a
__constructdeclaration now reports thenew ClassName(...)instantiations,#[ClassName(...)]attribute usages, and explicit delegation calls written asparent::__construct(),self::__construct(), orClass::__construct(), including for subclasses that inherit the constructor (and excluding subclasses that override it). Attribute classes that are never written asneware now found. Contributed by @RemcoSmitsDev in #155. - Positions on lines with multibyte characters. Signature help, go-to-definition on virtual properties, named-argument completion, unused-import removal, and the
@phpstan-ignorequickfix placed cursors and edits at the wrong column on lines containing multibyte characters; they now use the correct UTF-16 columns. Type strings containing*wildcards or variance annotations are also no longer mangled. - Unused-import hint location. When two imports share a name prefix (
use App\Foo;anduse App\FooBar;), the "unused import" dimming now lands on the correct statement. - Document outline ranges. Methods, properties, constants, and functions in the outline and breadcrumbs now report a range covering the whole declaration, with the name nested inside, as editors expect for folding and breadcrumb extent.
- Stale vendor symbols after
composer update. Functions and constants removed from the vendor tree are now purged from the indexes, so completion and go-to-definition stop offering symbols that no longer exist. - Type hierarchy locates the class name even when the
classkeyword and the name are on separate lines. - Edits on Windows (CRLF) files land correctly. Rename, remove-unused-import, and the PHPStan return-type quickfix computed line offsets assuming single-byte line endings, so on files with
\r\nterminators the edits drifted one byte per preceding line and could corrupt the file. Offsets now account for the real terminator. - Malformed
@methodtags no longer crash requests. A docblock with a degenerate@methodsignature (such as@method >()) could panic completion, hover, and go-to-definition. Such tags are now parsed gracefully and simply produce no virtual method. - Code lens navigation. Code lenses now work in Zed, Neovim, Emacs, and other editors. Previously the click command used a VS Code-specific API that other editors ignored.
@mixinwith union types.@mixin Foo|Barnow correctly exposes members from all classes in the union. Previously only single-class mixins were recognized.throw newcompletion no longer offers non-instantiable types. Interfaces, abstract classes, traits, and enums are now filtered out, matching the behavior ofnewcompletion. Thethrow newpath also now filters to Throwable descendants only.- Unified class name completion architecture.
throw newandcatch()completion now use the samebuild_class_name_completionspipeline asnew,extends,implements, etc.throw newuses aThrowNewcontext (instantiable + Throwable) andcatch()/@throwsuses aCatchcontext (class or interface + Throwable). This gives both contexts the same affinity scoring, FQN shortening via use-map, namespace segment drill-down, deprecation flags, and consistent filtering. The separatebuild_catch_class_name_completionsfunction has been removed. - Consolidated class completion passes. The previous 5-pass architecture (use-map, same-namespace, fqn_uri_index, fqn_uri_index duplicate, stub_index) has been simplified to 2 passes (fqn_uri_index + stub_index) with an inline
classifyclosure that determines tier ('0'use-imported,'1'same/sub-namespace,'2'everything else) per candidate. The redundant pass 4 (identical to pass 3) is eliminated, and tier assignment is now based on proximity checks rather than which data source produced the item. - Analysis deadlock. Lazily-parsed vendor files acquired two internal locks in the opposite order from the editor's file-change handler, causing a deadlock when both ran concurrently.
- External tool diagnostics on large files. PHPStan, Mago, and PHPCS diagnostics no longer time out on files that produce a large report. Their output is now read while the tool is still running, so a report bigger than the operating system's pipe buffer can no longer stall the tool and force a timeout.
- Promote to constructor property. Promoting a parameter whose property is declared together with others on one line (
private int $a, $b;) no longer deletes the sibling properties. The action is now offered only when the property is declared on its own. get_defined_vars()counts as using every variable in scope. A function or method that callsget_defined_vars()(for example to build a debug dump) no longer reports its local variables as unused, since the call reads all of them. Variables local to a nested closure or arrow function are still checked. Contributed by @calebdw in #158.
- Improved LSP responsiveness. File parsing (
update_ast) and diagnostics now run in background tasks, preventing interactive requests (completion, hover) from being blocked by full-file parses during typing. Contributed by @MingJen in #118. - Member completion caching. Unfiltered member lists are cached per-target to speed up subsequent completions during keyword entry. Contributed by @MingJen in #118.
- Laravel startup performance. Common Laravel builder classes are warmed in the background at startup to eliminate the first-access penalty on Eloquent completions. Contributed by @MingJen in #118.
- Faster code actions. Requesting code actions (the lightbulb menu) now parses the file once and shares the result across every refactoring, instead of re-parsing it for each one.
0.8.0 - 2026-05-14
- Blade template support. Completion, hover, go-to-definition, diagnostics, semantic tokens, and inlay hints work inside
.blade.phpfiles. Contributed by @MingJen in #100. - Blade keyword highlighting. Blade directives, echo delimiters, PHP keywords, cast types, comments, and PHPDoc tags inside
.blade.phpfiles now receive semantic tokens for proper syntax coloring. - Blade view directive navigation. Go-to-definition works on view names inside Blade directives (
@include,@extends,@includeIf,@includeWhen,@includeUnless,@includeFirst,@component,@each), jumping to the referenced template file. - Replace FQCN with import. A refactoring code action on any fully-qualified class name (
\Foo\Bar) inserts ausestatement and replaces all occurrences of the same FQCN throughout the file with the short name. Detects existing imports and short-name conflicts. A separate "Replace all FQCNs with imports" action appears when the file contains multiple distinct FQCNs, replacing all of them at once (skipping those with import conflicts). - Broader type narrowing.
instanceof, type-guard functions,in_array()strict mode,assert(),@phpstan-assert-if-true/-if-false, and compound&&/||conditions now narrow types in if/else branches, guard clauses, while-loop bodies, ternary expressions, andmatch(true)arms. - Argument type mismatch diagnostics. Flags function and method calls where an argument's resolved type is incompatible with the declared parameter type.
- Invalid class-like kind diagnostics. Flags class-like names used in positions where their kind is guaranteed to fail at runtime:
newon abstract classes, interfaces, traits, or enums;extendson a final class, interface, or trait;implementswith a non-interface; traitusewith a non-trait;instanceofwith a trait;catchwith a non-Throwable type; and traits in type-hint positions. - Unused variable diagnostics. Variables assigned but never read are flagged with hint severity and rendered as dimmed text. Variables named
$_or prefixed with$_are exempt. - Mago diagnostic proxy. Mago lint and analyze diagnostics are surfaced as LSP diagnostics with quick-fix code actions. Configurable under
[mago]in.phpantom.toml. - Laravel Pint formatting. Projects with
laravel/pintinrequire-devautomatically use Pint for formatting via stdin. Configurable under[formatting]in.phpantom.tomlwithpint = "path"orpint = ""to disable. - PHPCS diagnostic proxy. PHP_CodeSniffer violations are surfaced as LSP diagnostics with severity mapping. Configurable under
[phpcs]in.phpantom.toml. - Return type inference from method bodies. Methods without a declared return type or
@returndocblock now have their return type inferred fromreturnstatements, improving completion, hover, and diagnostics for untyped code. - Closure and arrow function parameter inference. Untyped closure parameters are inferred from the enclosing call's callable signature, including through method chains that return
static. Generic type substitution flows through to inferred parameters. - Closure and arrow function inlay hints. When a closure or arrow function is passed to a callable-typed parameter, inlay hints show inferred parameter types and the return type derived from the enclosing callable signature.
- Generics.
@mixintags referencing a template parameter now resolve through the template bound.new $var()where$varisclass-string<T>resolves toT. SPL collection classes now carry@templateparameters so iteration methods resolve to concrete type arguments. - Namespace renaming. Renaming a namespace segment updates all declarations, use statements, and fully-qualified references across the workspace. When a PSR-4 autoload mapping exists, the corresponding directory is moved automatically.
- Linked editing ranges. Place the cursor on a variable and all occurrences within its scope enter linked editing mode, updating every occurrence as you type.
- Import all missing classes. A bulk code action that imports every unresolved class name in the file at once. Ambiguous names are left for manual resolution.
- Context-aware import candidate filtering. Import class actions now filter candidates by syntactic context (only interfaces after
implements, only traits afteruse, etc.). - Convert to instance variable. A code action that promotes a local variable inside a method to a class property, rewriting all references to
$this->prop(orself::$propin static methods). - Laravel view, route, and translation key navigation. Go to Definition works for Blade view names (
view('...')), route names (route('...')), and translation keys (__('...'),trans(...),Lang::get(...)). Contributed by @MingJen in #101. - Laravel config and env key navigation. Go to Definition and Find All References work for config keys and env variables (
config('app.name'),env('APP_KEY')). Contributed by @MingJen in #93. - Untyped property type inference from constructor. Properties without type declarations are resolved by inspecting the constructor body for assignments and promoted parameter defaults. Contributed by @lucasacoutinho in #81.
- Binary expression type inference. Hover and variable resolution now show result types for all binary operators (
int + int→int,int + float→float,int / int→int|float). Compound assignments update the variable's type accordingly. - Nested array shape inference from multi-level key assignments. Assignments like
$b['a']['b'] = 'x'now produce a nested array shape type (array{a: array{b: string}}), enabling array key completion for incrementally built arrays. - Loop type propagation. Variables assigned late in loop bodies are now visible from the start on subsequent iterations.
globalkeyword variable resolution. Variables imported withglobal $varnow resolve to their top-level type, enabling completion, hover, and go-to-definition.array_reduce,array_sum, andarray_productreturn type inference.array_reduce()resolves to the type of its initial value argument.array_sum()andarray_product()resolve toint|float.- Machine-readable CLI output. Both
analyzeandfixaccept a--formatflag withtable,github, andjsonoptions. WhenGITHUB_ACTIONSis set, table output automatically includes GitHub annotations. - Magic property diagnostics. New
report-magic-propertiesoption under[diagnostics]in.phpantom.toml. When enabled, classes with__getthat also have virtual properties (from@propertydocblock tags, Laravel Eloquent column inference, or other providers) will flag unknown property access instead of silently allowing it. - Inline diagnostic suppression.
// @phpantom-ignore codeon the same line or the line above suppresses the specified diagnostic. Multiple codes can be comma-separated. A bare// @phpantom-ignoresuppresses all diagnostics on the target line. - Find references and rename for PHPDoc virtual members.
@property,@property-read,@property-write, and@methoddeclarations in docblocks are now included in find-references and rename results alongside their runtime usages, including when the subject has a nullable or union type (e.g.Foo|nullfrom->first()). Contributed by @AbyssWaIker in #115.
- Find References performance and freshness. Project-wide Find References now avoids more unnecessary file work while still returning references through aliased class and function imports, and it refreshes newly added workspace PHP files on later searches. Contributed by @MingJen in #116.
- Incremental text sync. The server now uses incremental document sync, receiving only changed ranges from the editor instead of the full file content on every keystroke.
- LSP responsiveness. Hover, go-to-definition, signature help, code actions, rename, and other handlers now run on background threads. Slow requests no longer block other requests or cancellations.
- Faster analysis. Analysis time cut significantly on large projects.
- Reduced redundant file parsing. Concurrent threads resolving the same vendor class no longer parse the file in parallel; the second thread waits for the first to finish.
- Unified first-class callable resolution. First-class callable return type inference (
$fn = $obj->method(...)) now uses the shared call return type pipeline, improving accuracy for chained calls and generic substitutions. - Editing responsiveness. Classes evicted from the cache after a file edit are now eagerly re-populated in dependency order.
- Diagnostic delivery model. Editors that support pull diagnostics now get diagnostics on first file open without waiting for a debounce timer. Updates from external tools no longer re-run the entire native diagnostic pipeline.
- Virtual member resolution. Mixins and virtual accessors are now resolved completely on every class, eliminating cases where they were missing after edits.
- Diagnostic code identifiers. All diagnostic codes now use a consistent
snake_casenoun-phrase scheme:unknown_variable,type_mismatch_argument,argument_count_mismatch,deprecated_usage,missing_implementation. Users with editor filters matching on these codes will need to update them. - Lower memory usage for lazily-loaded files. Vendor and stub files no longer store per-file import tables and namespace maps after parsing, and go-to-implementation uses a dedicated reverse-inheritance index instead of scanning all parsed files.
- Lower memory usage for variable type tracking.
- Faster variable name completion. Variable name suggestions now use the precomputed symbol map instead of re-parsing the file. Foreach iteration variables correctly persist after the loop (matching PHP semantics),
@vardocblock variable names are included, andunset()removes variables from suggestions. - Faster go-to-definition for variables. Variable definition lookup no longer re-parses the file as a fallback; the precomputed symbol map handles all cases.
- Updated embedded phpstorm-stubs.
throw newcompletion missing vendor classes. Classes whose Throwable ancestry could not be immediately verified (e.g. vendor classes not yet parsed) were silently excluded fromthrow newandcatchcompletion, even though later heuristic-based sections should have included them.- Stale mixin members after editing. Mixin class resolution (e.g.
@mixin Builder) is now invalidated when any file changes, so newly added or removed methods on mixin targets appear immediately without restarting the server. - Version-gated stub constants now filtered. Constants with
@removedtags (e.g.MCRYPT_ENCRYPT, removed in PHP 7.2) are now excluded from completion and resolution when the project targets a newer PHP version. Previously only classes and functions were filtered. - Go-to-definition. Fixed a potential deadlock when navigating to a vendor class that hadn't been parsed yet.
- LSP no longer freezes under heavy editor activity. Server-to-client requests (diagnostic refresh, progress token creation) could deadlock the service loop when the editor was simultaneously sending bursts of open/close/hover messages. All server-to-client requests are now either fire-and-forget or time-bounded, long-running handlers are cancellation-safe, and the process exits cleanly if the service loop ever terminates unexpectedly.
- Rename class preserves
self,static, andparentkeywords. Renaming a class no longer replaces occurrences ofself::,static::, orparent::with the new class name. - Rename propagates into closures and arrow functions. Renaming a variable now follows explicit
use ($var)captures into closure bodies and implicit captures into arrow function bodies, instead of leaving those occurrences unchanged. - Spurious function auto-imports. Import statements like
use function is_array;were misidentified as function declarations, polluting the completion list with phantom entries that inserted incorrect imports. - Duplicate
use functioninsertion. Accepting a function completion no longer inserts ause functionstatement when the exact import already exists in the file. - Function import conflict handling. When a different function with the same short name is already imported, completing a namespaced function now inserts the fully-qualified name instead of the ambiguous short name.
- False-positive unused variable diagnostics. Variables passed to
compact(), by-reference out-parameters (e.g.preg_match($p, $s, $matches)), and variables used only viaglobalare no longer incorrectly flagged. - False-positive type mismatch diagnostics. Bare
arrayreturn values passed to typed array parameters, properties narrowed viainstanceof, type alias parameters, and use-map shadowing no longer trigger incorrect type errors. - Functions inside
if (!function_exists(...))guards. Function bodies nested inside conditional blocks no longer produce false-positive unresolved-member-access errors. - Standalone
@varcompletion. Variables typed only via a standalone/** @var Type $var */docblock now resolve for member completion and go-to-definition. @vardocblocks with additional tags. Extra tags like@psalm-suppressin the same docblock no longer corrupt the type string.- Foreach
@varannotations for key and value variables. Multi-line docblocks with multiple@vartags before aforeachnow correctly override both key and value types. - Foreach element type from untyped arrays. Variables in a
foreachover barearraynow resolve tomixedinstead of empty. - Foreach narrowing with break in else. The variable state from break paths is now included in the post-loop type.
- Foreach target type after non-empty literal array. The pre-loop sentinel value no longer survives as a possible post-loop type.
- Foreach over
::classliteral arrays resolves static access.$className::CONSTand$className::method()no longer produce unresolved-member diagnostics. - Hover on reassigned variable shows post-assignment type. Hovering on the left-hand side of a reassignment now shows the type produced by the assignment.
- Multi-namespace class resolution. Short class names now resolve against the correct namespace for the current scope.
- Multi-namespace variable isolation. Variable resolution now only considers the namespace block containing the cursor.
- Multi-namespace function return type resolution. Function return types are now resolved against the function's own namespace.
- Multi-namespace static call class resolution.
ClassName::method()now resolves against the correct namespace block. - Short class name resolution in type hints. The resolver now prefers the class in the same namespace as the owning type before falling back to first-match.
- Class loader global fallback. Unqualified class names in namespaced code now fall back to global scope lookup when the namespace-qualified name doesn't exist.
- Template inference through stub interfaces.
@template-implementson stub-loaded interfaces now correctly propagates substituted return types to child methods. - Generic method return types from
@varannotations. Method calls on variables annotated with a generic type now correctly substitute class-level template parameters into the return type. - Template union inference from multiple arguments. When multiple arguments bind to the same
@template T, the resolved type is now the union of all inferred types instead of only the first. - Template param inference from type bounds. Nested template params are now inferred from concrete generic arguments when a template parameter has a generic bound.
- Method-level
@templatewithkey-ofbound. Passing a string literal to a method with@template K as key-of<TData>now resolves the return type to the specific array shape value type. __getmagic method template resolution. Property access on a class whose__getuseskey-of<T>bounds now infers the concrete type from the property name.- Magic
__getproperty access. Accessing undefined properties on objects with a__getmethod now resolves to the method's declared return type. - Magic
__callmethod return type. Calling undefined methods on objects with a__callmethod now resolves to__call's declared return type. - SoapClient arbitrary methods. Calling any method on
SoapClientno longer produces false-positive "unknown member" diagnostics. - Literal
true/falsepreserved in template inference. Passingtrueorfalseto a generic constructor now keeps the precise type instead of widening tobool. @psalm-methodoverrides@method. The vendor-prefixed tag now takes priority when both are present.@psalm-param/@phpstan-parampriority over@param.@phpstan-paramtakes precedence over@psalm-param, which takes precedence over@param, matching PHPStan and Psalm behaviour.@psalm-if-this-istemplate inference. Method-level template parameters are now inferred by matching the receiver's concrete type against the annotation's type pattern.self::classandstatic::classin template arguments. Passing these to aclass-string<T>parameter now correctly resolves T to the enclosing class.staticreturn type through first-class callables.self::method(...)()and similar patterns now preservestaticin the return type.- Interface method return type inheritance. Template-substituted return types from interfaces are now propagated to overriding methods without a return type.
- Property
self/statictype resolution. Properties with@var self|nullorstaticnow resolve to the owning class name in hover. - Trait
selfreturn type resolution through inheritance. Trait methods with return typeselfnow resolve to the declaring class, not the calling subclass. - Conditional return type resolution for scalar arguments.
$param is stringconditions in@returnannotations now resolve correctly for literal values. - SPL iterator generic type propagation. Decorator iterators like
CachingIteratorandLimitIteratornow propagate the wrapped iterator's generic type parameters. ArrayIteratorconstructor generic inference.new ArrayIterator($typedArray)now infers key and value types from the array argument.range()return type inference.range()now returnslist<string>for string arguments andlist<int|float>otherwise, instead of barearray.(object)cast type inference. Casting now resolves to an object shape matching the operand's structure instead of barestdClass.- ArrayAccess array-access assignment.
$obj[$key] = $valonArrayAccessobjects no longer overwrites the variable's generic type with an array type. - Static method calls on class-string unions.
$variable::method()where$variableholds a union of class-strings now resolves through all possible classes. - Array shape keys with special characters. Keys containing backslashes or newlines are now properly quoted and escaped in type display.
- Implement methods: no invalid generic return type hints. The "Implement missing methods" code action no longer emits generic docblock syntax as a native PHP return type hint.
- Composer
filesautoload packages now indexed. Vendor packages using"autoload": {"files": [...]}now have their classes discovered correctly. - Classmap collision resolution. When two files declare the same class name, the file matching PSR-4 naming convention is now preferred.
- Eloquent
$datesandwhere{Property}go-to-definition. Go-to-definition now works for properties backed by the$datesarray and dynamicwhere{Property}()methods. - Type hierarchy registration. Dynamic registration is now gated on client capability, preventing errors in unsupported editors.
- False-positive diagnostics on startup. Files opened while the project was still indexing could produce spurious "class not found" errors. Diagnostics are now deferred until initialization completes.
- Analyzer and LSP no longer hang on files with deeply nested loops.
- Infinite loop on array key reassignment patterns. Files containing
$arr['key'] = f($arr['key'])no longer hang the analyzer. - Chained calls with complex arguments resolve the correct return type. Calling
redirect($string . $var)->with(...)now resolves toRedirectResponseas expected. Complex argument expressions (concatenation, method calls, etc.) were previously serialized as empty, causing conditional return types to take the wrong branch. - Stack overflow on large codebases and large files. The
analyzecommand no longer crashes with stack overflows on large files. - Non-deterministic diagnostic counts eliminated. Projects with heavy use of generics no longer see false positives that vary between runs.
- Pull-diagnostic reliability. Editors that support pull diagnostics no longer show duplicate or stale diagnostics.
- Hover scales linearly on large files. Hover requests no longer take O(n²) time on files with many method calls.
analyzeandfixcommands run at consistent speed regardless of invocation style.- Type narrowing. Comprehensive fixes:
is_*()guards correctly narrow multi-member unions;instanceofonmixedorobjectnarrows to the checked type;=== nulland== nullnarrow correctly;assert()narrowing persists through subsequent branches;isset()/empty()stripnullfrom nullable types; property access expressions are narrowed through conditionals; array shape keys are narrowed through guard clauses; OR'dinstanceofchecks resolve to the union of all branches; post-loop narrowing applies the loop condition's inverse; branch merging preserves nullable information correctly. - Generics. Constructor generic inference works through inherited constructors with correct remapping through multi-level
@extendschains. Function-level templates are inferred from arguments extending wrapper classes. Class-level template parameters are preserved through chained method calls. Template parameters fall back to their declared bound when subclasses omit annotations. Method calls on unions of generic types resolve to the union of each branch's return type.key-of<T>,value-of<T>, and indexed access types evaluate to concrete types after template substitution. Array literal arguments infer key and value types separately. - Mixin resolution. Static method calls on instances with
@mixinnow resolve through the mixin.@methodand@propertytags on mixin classes are propagated to the consumer.$thisreturn types on mixin methods resolve to the consumer class. @methodtag resolution. Colon return type syntax, parenthesised return types, and the ambiguous single-staticpattern are now parsed correctly. Template parameters in@methodreturn types are substituted through@extendsand@implementsannotations.- First-class callable invocation return types. Immediately invoking a first-class callable (
Foo::method(...)()) now resolves to the underlying function's return type. - Chained instantiation preserves constructor-inferred generics. Expressions like
(new Box(new Product()))->get()now propagate template arguments to subsequent method calls. @return numericpseudo-type. Functions annotated with@return numericnow resolve correctly instead of falling back tostring.parent::__construct()with@extendsgenerics. No longer produces false-positive type errors for substituted parameter types.- Array access on bare
arrayandmixedtypes. Accessing a key on plainarraynow resolves tomixedinstead of an empty type. - Vendor functions and constants. Functions and constants defined in vendor packages are now indexed at startup, eliminating false-positive diagnostics.
- Use-imported classes no longer shadowed by global-namespace stubs. Fixes Laravel Facade static method resolution.
- Same-name class in a different namespace no longer shadows inherited members.
- Short-name collisions eliminated project-wide. Two unrelated classes sharing a short name are no longer treated as identical.
- Transitive interface inheritance. A class implementing an interface that extends another interface is now correctly recognized as a subtype of the parent interface.
- Conditional return types. Methods with conditional return types now check whether the argument class implements the bound interface, and class names in conditional annotations are resolved through the defining file's use statements.
- Promoted properties. Inline
/** @var */annotations on promoted constructor properties now resolve inside the constructor body. - Backed enums. Accessing
->valueresolves to the specific backing type.@implementsgenerics on enums are resolved correctly. - Class constants. Inherited constants accessed via
self::CONSTorChildClass::CONSTresolve through multi-level inheritance. - Hover / type display.
T[]displays asarray<T>,mixed[]asarray. PHPDoc type aliases are normalized. Methods returningparentresolve to the actual parent class name. - Chain assignments.
$a = $b = new Foo()resolves all variables in the chain. - Destructuring. Array destructuring (
[$a, $b] = $expr,list(), keyed shapes, nested patterns) and foreach destructuring now resolve types correctly. - Variable type resolution. Short class names from
@var,@param, andnew ClassName()are resolved to FQN before entering the type pipeline. - Closure inlay hints. Template parameters in callable signatures are substituted with concrete types inferred from sibling arguments.
- Laravel scopes. Public methods with the
#[Scope]attribute are no longer treated as scopes. - Static methods.
$thisno longer resolves inside static methods. - Hover cache invalidation. Editing a cross-file class's docblock now immediately reflects updated content on hover.
- Foreach type resolution. Nested generic array access, static property iterables, type alias expansion, and by-reference bindings all resolve element types correctly. Loop prescan no longer leaks types into the same-statement RHS.
- Completion in loops and branches. Array shape keys added inside
ifblocks, variables assigned later in loop bodies, and variables on the RHS of reassignments all resolve correctly. - Scope leakage after closures in chained method calls. Variables from the enclosing method are no longer invisible after a closure argument.
- Docblock
@paramannotations no longer leak across sibling methods or closures. class-string<T>parameter completion. Parameters typed asclass-string<T>resolve to the bound class for member access.- Inherited parameter types propagate to child methods.
- False positive type error for closures passed to callable parameters.
\Closureis now recognised as a subtype ofcallable. - Union-typed method calls no longer lose resolution on second occurrence.
- Fluent method chains in namespaced classes. Methods returning
staticorselfresolve correctly across namespaces. - False-positive undefined variable diagnostics. By-reference parameters, nested array access assignments, and
$this-prefixed variable names no longer produce false positives. - Auto-import formatting. Missing blank line before first import and bulk "remove unused imports" in braced namespaces are fixed.
- Exception types in
catchclauses matched correctly across namespaces. - Nested
match(true)expressions no longer produce incorrect diagnostics. - Lowercase built-in class names recognized as subtypes of
object. - False "class not found" for global-namespace classes loaded via Composer's
filesautoloading. - False-positive type errors on generic class methods. Template parameters are now substituted into method parameter types before checking argument compatibility.
0.7.0 - 2026-04-08
@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 (contributed by @calebdw in #54). 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. Contributed by @markkimsal in #67.--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.- Zed extension setup instructions. Contributed by @daronspence in #47.
- SETUP.md improvements. Contributed by @mattsches in #61.
- 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.
- 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.
- 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>.
0.6.0 - 2026-03-26
- Semantic Tokens. Type-aware syntax highlighting that goes beyond what a TextMate grammar can achieve. Classes, interfaces, enums, traits, methods, properties, parameters, variables, functions, constants, and template parameters all get distinct token types. Modifiers convey declaration sites, static access, readonly, deprecated, and abstract status.
- PHPStan diagnostics. PHPStan errors appear inline as you edit. Auto-detects
vendor/bin/phpstanor$PATH. Runs in the background without blocking native diagnostics. Configurable via[phpstan]in.phpantom.toml(command,memory-limit,timeout). "Ignore PHPStan error" and "Remove unnecessary @phpstan-ignore" code actions manage inline ignore comments. - Formatting. Built-in PHP formatting (PER-CS 2.0 style). Formatting works out of the box without any external tools. Projects that depend on php-cs-fixer or PHP_CodeSniffer in their
composer.jsonrequire-devautomatically use those tools instead (both can run in sequence). Per-tool command overrides and disable switches in[formatting]in.phpantom.toml. - Inlay hints. Parameter name and by-reference indicators appear at call sites. Hints are suppressed when the argument already makes the parameter obvious: variable names matching the parameter, property accesses with a matching trailing identifier, string literals whose content matches, well-known single-parameter functions like
countandstrlen, and spread arguments. Named arguments never receive a redundant hint. - PHPDoc block generation. Typing
/**above any declaration generates a docblock skeleton. Tags are only emitted when the native type hint needs enrichment. Properties and constants always get@var. Class-likes with templated parents or interfaces get@extends/@implementstags. Uncaught exceptions get@throwswith auto-import. Works both via completion and on-type formatting. - Syntax error diagnostic. Parse errors from the Mago parser now appear as Error-severity diagnostics instantly as you type.
- Implementation error diagnostic. Concrete classes that fail to implement all required methods from their interfaces or abstract parents are now flagged with an Error-severity diagnostic on the class name. The existing "Implement missing methods" quick-fix appears inline alongside the error.
- Argument count diagnostic. Flags function and method calls that pass too few arguments. The "too many arguments" check is off by default (PHP silently ignores extra arguments) and can be enabled with
extra-arguments = truein the[diagnostics]section of.phpantom.toml. - Completion item documentation. Selecting a completion item in the popup now shows rich documentation including the full typed signature, description, deprecation notice, and parameter details. Previously only the class name was shown.
- Method commit characters. Typing
(while a method completion is highlighted auto-accepts it and begins the argument list. - Document Symbols. The outline sidebar and breadcrumbs now show classes, interfaces, traits, enums, methods, properties, constants, and standalone functions with correct nesting, icons, visibility detail, and deprecation tags.
- Workspace Symbols. "Go to Symbol in Workspace" (Ctrl+T / Cmd+T) searches across all indexed files including vendor classes. Results include namespace context and deprecation markers, sorted by relevance.
- Type Hierarchy. "Show Type Hierarchy" on any class, interface, trait, or enum reveals its supertypes and subtypes with full up-and-down navigation through the inheritance tree, including cross-file resolution and transitive relationships.
- Code Lens. Clickable annotations above methods that override a parent class method or implement an interface method. Clicking navigates to the prototype declaration.
- Update docblock. Code action on a function or method whose existing docblock is out of sync with its signature. Adds missing
@paramtags, removes stale ones, reorders to match the signature, fixes contradicted types, and removes redundant@return void. Refinement types and unrelated tags are preserved. Only triggers on the signature or the preceding docblock, not inside the function body. - Change visibility. Code action on any method, property, constant, or promoted constructor parameter offers to change its visibility (
public,protected,private). Only triggers on the declaration signature, not inside the body. @throwscode actions. Quick-fixes for adding missing and removing unnecessary@throwstags, triggered by PHPStan diagnostics. Adding inserts the tag and auseimport when needed. Removing cleans up orphaned blank lines and deletes the entire docblock when it would be empty. The diagnostic disappears on the next keystroke without waiting for the next PHPStan run.- File rename on class rename. Renaming a class whose file follows PSR-4 naming now also renames the file to match. The file is only renamed when it contains a single class-like declaration and the editor supports file rename operations.
- Folding Ranges. AST-aware code folding for class bodies, method/function bodies, closures, arrays, argument/parameter lists, control flow blocks, doc comments, and consecutive single-line comment groups.
- Selection Ranges. Smart select / expand selection returns AST-aware nested ranges from innermost to outermost.
- Document Links.
require/includepaths are now Ctrl+Clickable. Path resolution supports string literals,__DIR__concatenation,dirname(__DIR__),dirname(__FILE__), and nesteddirnamewith levels. - Analyze command.
phpantom_lsp analyzescans a Composer project and reports PHPantom's own diagnostics in a PHPStan-like table format. Useful for measuring type coverage across an entire codebase without opening files one by one. Accepts an optional path argument to limit the scan to a single file or directory. Output includes diagnostic identifiers and supports--severityfiltering and--no-colourfor CI. - Null-coalesce (
??) type refinement. When the left-hand side of??is provably non-nullable (e.g.new Foo(),clone $x, a literal), the right-hand side is recognized as dead code and the result resolves to the LHS type only. When the LHS is nullable (e.g. a?Fooreturn type),nullis stripped from the LHS and the result is the union of the non-null LHS with the RHS. @mixingeneric substitution. When a class declares@mixin Foo<T>, the generic arguments are now preserved and substituted into the mixin's members, including through multi-level inheritance chains.- PHPDoc
@varcompletion. Inline@varabove variable assignments sorts first and pre-fills the inferred type when available. Template parameters from@templateenrich@param,@return, and@vartype hints. @seeand@linkimprovements.@seereferences in docblocks now work with go-to-definition (class, member, and function forms). Hover popups show all@linkand@seeURLs as clickable links. Deprecation diagnostics include@seetargets when the@deprecateddocblock references them.- Progress indicators. Go to Implementation and Find References now show a progress indicator in the editor while scanning.
- Phar archive class resolution. Classes inside
.phararchives (e.g. PHPStan'sphpstan.phar) are now discovered and indexed automatically. No PHP runtime needed. Only uncompressed phars are supported (the format used by PHPStan and most other phar-distributed tools). - PSR-0 autoload support. Packages that use the legacy PSR-0 autoloading standard are now discovered automatically.
- Global config. Settings from a global
.phpantom.tomlin the user's config directory (typically~/.config/phpantom_lsp/.phpantom.toml) are now loaded as defaults. Project-level configs take precedence. Contributed by @calebdw in #39. - Config schema. A JSON schema for
.phpantom.tomlis now bundled, enabling autocompletion and validation in editors that support TOML schemas. Contributed by @calebdw in #38.
- Pull diagnostics. Diagnostics are now delivered via the LSP 3.17 pull model when the editor supports it. The editor requests diagnostics only for visible files, and cross-file invalidation no longer recomputes every open tab. Clients without pull support fall back to the previous push model automatically.
- Hover type accuracy. Hover now resolves variable types through the same pipeline as completion, so all narrowing features (instanceof, assert, custom type guards, in_array) apply. When the cursor is inside a specific if/else branch, hover shows only the type visible in that branch. Complex expressions like null-coalesce chains, array shapes, empty arrays, and unresolved symbols all display correctly.
- Version-aware stub types. Built-in function signatures that changed across PHP versions (e.g.
int|falsein 7.x becomingintin 8.0) now show the correct type for your project's PHP version. This eliminates false-positive diagnostics and incorrect completions from stale type annotations. - Completion labels. Method and function completion items now show only parameter names in the label (e.g.
setName($name)) with the return type displayed inline (e.g.: User). Properties and constants show just the type hint. The previousClass: ClassNamedetail line has been removed; class context is available in the documentation panel when the item is highlighted. - Completion sort order. Member completion items are now sorted by kind (constants, then properties, then methods) before alphabetical order within each group. Union-type completions apply the same kind-based ordering within both the intersection and branch-only tiers.
- Class name completion ranking. Completions now rank by match quality first (exact match, then starts-with, then substring), so typing
OrderputsOrderaboveOrderLineaboveCheckOrderFlowJobregardless of where the class comes from. Within each match quality group, use-imported and same-namespace classes appear first, followed by everything else sorted by namespace affinity (classes from heavily-imported namespaces rank higher). - Use-import completion. Same-namespace classes no longer appear in
usestatement completions (PHP auto-resolves them without an import). Classes that are already imported are filtered out. Namespace affinity still ranks the remaining candidates. - Deprecation tags. Completion items use the modern
tags: [DEPRECATED]field instead of the legacydeprecatedboolean. Both convey the same strikethrough rendering in editors. - Import class code action ordering. The "Import Class" code action now sorts candidates by namespace affinity (derived from existing imports) instead of alphabetically, so the most likely namespace appears first.
- Cross-file resolution. Completion, hover, and go-to-definition no longer fail when one reference uses a leading backslash and another does not.
- Embedded stubs track upstream master. The bundled phpstorm-stubs are now pulled from the
masterbranch instead of the latest GitHub release, matching what PHPStan does. This brings in upstream fixes and new PHP version annotations weeks or months before a formal release.
- CLI analyze performance. Single-file analysis is up to 5.8× faster. Full-project analysis of ~2 500 files is up to 10× faster.
- Diagnostic performance on large files. Unknown-member diagnostics on files with many member accesses are up to 7× faster.
- Position encoding. All LSP position conversions now correctly count UTF-16 code units, matching the LSP specification. Files containing emoji or supplementary Unicode characters no longer produce incorrect positions.
- Rename and find references for parameters. Renaming a parameter in a function, method, or closure now correctly updates all usages in the body and the
@paramtag in the docblock. Previously, parameters were scoped incorrectly because they sit physically before the opening{of the body, causing rename and find references to miss body usages when triggered from the parameter (and vice versa). Document highlight is also fixed. - Rename updates imports. Renaming a class now updates
usestatement FQNs, preserves explicit aliases, and introduces an alias when the new name collides with an existing import. - False-positive diagnostics for
$thisinside traits. Accessing host-class members via$this->,self::,static::, orparent::inside a trait method no longer produces "not found" warnings, including chain expressions and accesses inside closures or arrow functions nested within trait methods. - False-positive diagnostics for same-named variables in different methods. Diagnostic resolution is now scoped to the enclosing function/method/closure body, so two methods using a variable like
$orderresolve it independently. - False positive on namespaced constants. Standalone namespaced constant references (e.g.
\PHPStan\PHP_VERSION_ID) no longer produce a spurious "Class not found" diagnostic. Previously the symbol map classified them as class references instead of constant references. - Diagnostic deduplication. Multiple diagnostics on the same span or line are no longer collapsed into one. If PHPStan reports five issues on a line, all five are shown. When PHPantom and PHPStan both flag the same issue, the more precise native diagnostic wins.
- Diagnostics. Enums that implement interfaces are now checked for missing methods. Scalar member access errors detect method-return chains where an intermediate call returns a scalar type. By-reference
@paramannotations no longer produce a false "unknown class" diagnostic. - Removed PHP symbols in stubs. Functions, methods, and classes annotated with
@removed X.Yin phpstorm-stubs are now filtered out when the target PHP version is at or above the removal version. Previously symbols likemysql_tablename(removed in PHP 7.0) andeach(removed in PHP 8.0) appeared in completions and resolved without warnings. - Hover on union member access. Hovering over a method, property, or constant on a union type (e.g.
$ambiguous->turnOff()where$ambiguousisLamp|Faucet) now shows hover information from all branches that declare the member, separated by a horizontal rule. Previously only the first matching branch was shown. When both branches inherit the member from the same declaring class, the hover is deduplicated to a single entry. - Hover on inherited members. Hovering over an inherited method, property, or constant now shows the declaring class in the code block (e.g.
class Model { public static function find(...) }) instead of the class it was accessed on. PreviouslyUser::find()would incorrectly showclass Usereven thoughfind()is declared onModel. - Constant type inference. Variables assigned from global constants (
$a = MY_CONST) or class constants without type hints ($b = Config::TIMEOUT) now resolve to the type implied by the constant's initializer value. Integer, float, string, bool, null, and array literals are all recognised. Typed class constants (public const string NAME = '...') continue to use their declared type hint. - Variable type after reassignment. When a method parameter is reassigned mid-body (e.g.
$file = $result->getFile()), subsequent member accesses now resolve against the new type instead of the original parameter type. - Variable assignments inside foreach loops. Variables conditionally reassigned inside a
foreachbody are now visible after the loop. - Variable-to-variable type propagation. Assignments like
$found = $pennow resolve$foundto the type of$pen. This also eliminates false-positive diagnostics when the initial assignment was$found = nulland a later reassignment provided the real type. - Variable type inside self-referencing assignment RHS. In
$request = new Foo(arg: $request->uuid), the$requestreference inside the constructor arguments now correctly resolves to the original type instead of the type being assigned. - Variable resolution inside anonymous classes. Variables inside anonymous class methods (e.g. closure parameters in
return new class extends Migration { ... }) now resolve correctly. Previously, anonymous class bodies were invisible to the variable resolution pipeline because they appear as expressions inside statements rather than top-level class declarations. - Closure and arrow function variable scope. Variable name completion now correctly respects PHP scoping rules for anonymous functions and arrow functions. Parameters and
use-captured variables are visible inside closures. Arrow function parameters are visible inside the arrow body while the enclosing scope's variables remain accessible. - Function return type resolution across files. Standalone functions that declare return types using short names from their own
useimports now resolve correctly in consuming files. Function parameter types and@throwstypes are also resolved. - Native type override compatibility. A docblock type only overrides a native type hint when it is a compatible refinement (e.g.
class-string<Foo>can refinestring, butarray<int>no longer incorrectly overridesstring). - PHPStan pseudo-type recognition. Types like
non-positive-int,non-negative-int,non-zero-int,lowercase-string,truthy-string,callable-object, and many other PHPStan pseudo-types are now recognized across the entire pipeline. - Nullable and generic types in class lookup. Variables typed as
?ClassNameorCollection<Item>now resolve correctly across all code paths. - Generic substitution through transitive interface chains. When a class implements an interface that itself extends another generic interface, template parameters are now substituted at each level instead of propagating raw template parameter names.
- Generic shape substitution. Template parameters inside array shapes (
array{data: T}) and object shapes (object{name: T}) are now correctly substituted when inherited through@extends. - Type narrowing with same-named classes from different namespaces. instanceof narrowing now correctly distinguishes classes that share a short name but live in different namespaces (e.g.
Contracts\ProvidervsConcrete\Provider). - Guard clause narrowing across instanceof branches. After
if ($x instanceof Y) { return; }, subsequentinstanceofchecks on the same variable no longer incorrectly resolve toY. instanceof self/static/parentnarrowing. Type narrowing withinstanceof self,instanceof static, andinstanceof parentnow works correctly in all contexts (assert, if-blocks, guard clauses, compound conditions).- Type narrowing inside
returnstatements.instanceofchecks in&&chains and ternary conditions now narrow the variable type when the expression is the operand of areturnstatement. - Inline array access on method returns. Expressions like
$c->items()[0]->getLabel()now resolve the element type correctly for both completion and diagnostics. - Array shape bracket access. Variables assigned from string-key bracket access on array shapes (
$name = $data['name']) now resolve to the correct value type. Chained access ($first = $result['items'][0]) walks through shape keys and generic element types in sequence. - Ternary and null-coalesce member access. Accessing a member on a ternary or null-coalesce expression (e.g.
($a ?: $b)->property,($x ?? $y)->method()) now resolves correctly for hover, go-to-definition, and diagnostics. - Null-safe method chain resolution. Null-safe method calls (
$obj?->method()) now resolve the return type correctly for variable type inference, including cross-file chains. - Clone expressions.
(clone $var)->now resolves to the same type as$var, providing correct completion, hover, and diagnostics. self::/static::/parent::in member access chains. Expressions likeself::Active->valueinside an enum method now resolve correctly. Previously,self,static, andparentwere only recognized as bare subjects, not when followed by::MemberNamein a chain.- Inherited methods missing through deep stub chains. Methods are now found on classes that inherit through multi-level chains where intermediate classes live in stubs.
- Interface constants through multi-extends chains. Constants defined on parent interfaces are now found when an interface extends multiple other interfaces.
- Double parentheses when completing calls. Completing a function, constructor, or static method name when parentheses already follow the cursor (e.g.
array_m|(),new Gadge|(),throw new Excepti|()) no longer inserts a second pair of parentheses. Previously only->and::method calls were handled. - Namespace alias completion. Typing a class name through a namespace alias (e.g.
OA\Rewithuse OpenApi\Attributes as OA) now correctly suggests classes under the aliased namespace. - Catch clause completion. Throwable interfaces and abstract exception classes now appear in catch clause completions.
- Type-hint and PHPDoc completion. Traits are now excluded from completions in parameter types, return types, property types, and PHPDoc type tags.
@throwscontinues to use Throwable-filtered completion. - Trait alias go-to-definition. Clicking a trait alias (e.g.
$this->__foo()fromuse Foo { foo as __foo; }) now jumps to the trait method instead of the class's own same-named method. - Self-referential array key assignments no longer crash. Patterns like
$numbers['price'] = $numbers['price']->add(...)no longer cause a stack overflow during hover or completion. - Eloquent
morphedByManyrelationships. The inverse side of polymorphic many-to-many relationships is now recognised. Virtual properties and_countproperties are synthesized for models using this relationship type. - Virtual property merging. Native type hints are now considered when determining virtual property specificity, preventing properties with native PHP type declarations from being incorrectly overridden by less specific virtual properties.
0.5.0 - 2026-03-12
- Diagnostics. Unknown classes, unknown members, and unknown functions are flagged with appropriate severity. An opt-in unresolved member access diagnostic is available via
.phpantom.toml. - Find References. Locate every usage of a symbol across the project. Supports classes, methods, properties, constants, functions, and variables. Variable references are scoped to the enclosing function or closure. Member references are scoped to the class hierarchy, so unrelated classes sharing a method name are excluded.
- Rename. Rename variables, classes, methods, properties, functions, and constants across the workspace. Variable renames are scoped to their enclosing function or closure.
- Deprecation support.
@deprecatedtags and#[Deprecated]attributes surface in hover, completion strikethrough, and diagnostics. A quick-fix code action rewrites deprecated calls when areplacementtemplate is available. - Document highlighting. Placing the cursor on a symbol highlights all occurrences in the current file. Variables are scoped to their enclosing function or closure with write vs. read distinction.
- Implement missing methods. Code action that generates method stubs when a class is missing required interface or abstract method implementations.
- Project configuration.
.phpantom.tomlfor per-project settings: PHP version override, diagnostic toggles, and indexing strategy. Runphpantom --initto generate a default config. - Reverse go-to-implementation. Go-to-implementation on a concrete method jumps to the interface or abstract class that declares the prototype, and vice versa.
- Go to Type Definition. Jump from a variable, property, method call, or function call to the class declaration of its resolved type. Union types produce multiple locations.
- Self-generated classmap. PHPantom works without
composer dump-autoload -o. Missing or incomplete classmaps are supplemented by scanning autoload directories. Non-Composer projects are supported by scanning all PHP files. - Monorepo support. Discovers subdirectories that are independent Composer projects and processes each through the full pipeline.
@implementsgeneric resolution.@implements Interface<ConcreteType>substitutes template parameters on the interface's methods and properties. Foreach iteration on generic iterable interfaces resolves value and key types.- Interface template inheritance. Implementing classes inherit
@templateparameters, bindings, conditional return types, and type assertions from their interfaces. - Function-level
@templatewith generic return types. Functions that use@templateparameters inside generic return types now resolve concrete types from call-site arguments. - Generic
@phpstan-assertwithclass-string<T>. Assertion methods that accept aclass-string<T>parameter resolve the narrowed type from the call-site argument. - Property-level narrowing.
if ($this->prop instanceof Foo)narrows$this->propin then/else bodies and after guard clauses. - Inline
&&short-circuit narrowing. The right-hand side of&&now sees the narrowed type from the left-hand side. - Compound negated guard clause narrowing.
if (!$x instanceof A && !$x instanceof B) { return; }narrows$xtoA|Bin the surviving code. - Closure variable scope isolation. Variables outside a closure are no longer offered as completions unless captured via
use(). - Pipe operator (PHP 8.5).
$input |> trim(...) |> createDate(...)resolves through the chain. - AST-based array type inference. Array shape keys, element access, spread elements, and push-style assignments all resolve through an AST walker.
new $classStringVarand$classStringVar::method(). Class-string variables resolve fornewand static member access.- Invoked closure and arrow function return types.
(fn(): Foo => ...)()and(function(): Bar { ... })()resolve to their return type. - Docblock navigation. Go-to-definition and hover work on class names inside callable types, array/object shape value types, and object shape properties.
- GTD from parameter and property variables. Clicking a parameter or property at its definition site jumps to the type hint class.
- PHP version-aware stubs. Detects the target PHP version from
composer.jsonand filters built-in stub signatures accordingly. @param-closure-this.$thisinside a closure resolves to the type declared by@param-closure-thison the receiving parameter.- Non-Composer function and constant discovery. Cross-file function completion, go-to-definition, and constant resolution for projects without
composer.json. - Indexing progress indicator. The editor shows a progress bar during workspace initialization, including per-subproject progress in monorepos.
- Pass-by-reference parameter type inference. After calling a function with a typed
&$varparameter, the variable acquires that type. iterator_to_array()element type. Resolves the element type from the iterator's generic annotation.- Enum case properties.
$case->nameand$case->valueresolve on enum case variables. - Inline
@varon promoted constructor properties. Overrides the native type hint, matching existing@paramsupport. --versionand--helpCLI flags. Contributed by @calebdw in #7.
- Resolution engine rewritten on AST. Variable type inference, call return types, and go-to-definition all run through the AST walker for better accuracy.
- Hover redesigned. Short names with
namespaceline, actual default values,@linkURLs, precise token highlighting, constructor signatures onnew,@templatedetails, enum case listing, trait member listing, origin indicators, and deprecated explanations. - Signature help enriched. Compact parameter list with native types, per-parameter
@paramdescriptions, default values, and attribute parenthesis support. - Faster resolution and lower memory usage.
- Parallel workspace indexing. File parsing, PSR-4 scanning, and vendor scanning run across all CPU cores.
.gitignorerules are respected. - Two-phase diagnostic publishing. Cheap diagnostics (unused imports, deprecation) publish immediately; expensive diagnostics (unknown classes/members/functions) arrive in a second pass.
- Merged classmap + self-scan pipeline. Composer classmaps and self-scanning work together instead of being mutually exclusive. Stale classmaps are supplemented automatically.
- Automatic stub fetching. The build script downloads phpstorm-stubs automatically when missing. Composer is no longer needed to build PHPantom. Contributed by @calebdw in #16.
- Feature comparison table corrected. Phactor capabilities updated in the README. Contributed by @dantleech in #10.
- Cross-file inheritance from global-scope classes imported via
use. - Inherited
@methodand@propertytags across files. - Diagnostics refresh across open files when a class signature changes.
- Variable types resolve through ternary, elvis, null-coalesce, and match assignments.
instanceofnarrowing no longer widens specific types.- Elseif chain narrowing and sequential assert narrowing.
@phpstan-typealiases in foreach,list(), and key types.- False-positive unknown-class warnings on PHPStan type syntax.
- Go-to-implementation no longer produces false positives across namespaces.
__invoke()return type resolution. Works with chaining, foreach, and parenthesized invocations.- Enum
from()andtryFrom()chaining. static/self/$thisin method return types used as iterable expressions.- Mixed
->then::accessor chains. - Inline
(new Foo)->method()chaining. ?->null-safe chain resolution.- Array function resolution for
array_pop,array_filter,array_values,end,array_map. - Inline
@varannotations no longer leak across scopes. - Literal string conditional return types.
- Class constant and enum case assignment resolution.
- Go-to-definition on trait
asalias andinsteadofdeclarations. - Inline array-element function calls resolve correctly in diagnostics.
end($obj->items)->method()no longer produces a false diagnostic. - Double-negated
instanceofnarrowing. - Self-referential array key assignments no longer crash.
0.4.0 - 2026-03-01
- Signature help. Parameter hints in function/method calls with active parameter highlighting.
- Hover. Type, signature, and docblock in a Markdown popup for all symbol kinds.
- Closure and callable inference. Untyped closure parameters inferred from the callable signature. First-class callable syntax resolves return types.
- Laravel Eloquent. Relationships, scopes, Builder forwarding, factories, custom collections, casts, accessors, mutators,
$attributes, and$visible. - Type narrowing.
in_array()with strict mode, early return guards,instanceofin ternaries and with interfaces. - Anonymous class support.
$this->resolves inside anonymous classes with full inheritance support. - Context-aware completions.
extends,implements,useinside class body, union member sorting, namespace segments, string literal suppression. - Additional resolution. Multi-line chains, nested array keys, generator yield types, conditional return types with template substitution, switch/unset variable tracking.
- Transitive interface go-to-implementation.
- Visibility filtering, scope isolation, static call chains,
staticreturn type, trait resolution, mixin fluent chains, go-to-definition accuracy, import handling, UTF-8 boundaries, and parenthesized RHS expressions.
0.3.0 - 2026-02-21
- Go-to-implementation. Interface/abstract class to all concrete implementations.
- Method-level
@template. InfersTfrom the call-site argument. @phpstan-type/@psalm-typealiases and@phpstan-import-type.- Array function type preservation.
array_filter,array_map,array_pop,current, etc. - Early return narrowing. Guard clauses narrow types for subsequent code.
- Callable variable invocation.
$fn()->resolves return types. - Additional resolution. Spread operators, trait
insteadof/as, chained assignments, destructuring, foreach on function returns, type hint completion, try-catch suggestions.
- PHPDoc type parsing and internal stability fixes.
0.2.0 - 2026-02-18
- Generics. Class-level
@templatewith@extendssubstitution. Method-levelclass-string<T>. Generic trait substitution. - Array shapes and object shapes. Key completion from literals, incremental assignments, destructuring, element access.
- Foreach type resolution. Generic iterables, array shapes,
Collection<User>,Generator<int, Item>,IteratorAggregate. - Expression type inference. Ternary, null-coalescing, and match expressions.
- Additional completions. Named arguments, variable name suggestions, standalone functions,
define()constants, PHPDoc tags, deprecated members, promoted property types, property chaining,require_oncediscovery, go-to type definition.
@mixincontext for return types, global class imports, namespace resolution, and aliased class go-to-definition.
0.1.0 - 2026-02-16
Initial release.
- Completion. Methods, properties, and constants via
->,?->, and::with visibility filtering. - Type resolution. Inheritance merging,
self/static/parent, union types, nullsafe chains. - PHPDoc support.
@return,@property,@method,@mixin, conditional return types, inline@var. - Type narrowing.
instanceof,is_a(),@phpstan-assert. - Enum support. Case completion and
UnitEnum/BackedEnuminterface members. - Go-to-definition. Classes, methods, properties, constants, functions,
newexpressions, variables. - Class name completion with auto-import.
- PSR-4 lazy loading and Composer classmap support.
- Embedded phpstorm-stubs.
- Zed editor extension.