Skip to content

0.8.0

Latest

Choose a tag to compare

@AJenbo AJenbo released this 14 May 12:23
· 13 commits to main since this release

Added

  • Blade template support. Completion, hover, go-to-definition, diagnostics, semantic tokens, and inlay hints work inside .blade.php files. Contributed by @MingJen in #100.
  • Blade keyword highlighting. Blade directives, echo delimiters, PHP keywords, cast types, comments, and PHPDoc tags inside .blade.php files 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 a use statement 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, and match(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: new on abstract classes, interfaces, traits, or enums; extends on a final class, interface, or trait; implements with a non-interface; trait use with a non-trait; instanceof with a trait; catch with 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/pint in require-dev automatically use Pint for formatting via stdin. Configurable under [formatting] in .phpantom.toml with pint = "path" or pint = "" 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 @return docblock now have their return type inferred from return statements, 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. @mixin tags referencing a template parameter now resolve through the template bound. new $var() where $var is class-string<T> resolves to T. SPL collection classes now carry @template parameters 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 after use, 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 (or self::$prop in 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 + intint, int + floatfloat, int / intint|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.
  • global keyword variable resolution. Variables imported with global $var now resolve to their top-level type, enabling completion, hover, and go-to-definition.
  • array_reduce, array_sum, and array_product return type inference. array_reduce() resolves to the type of its initial value argument. array_sum() and array_product() resolve to int|float.
  • Machine-readable CLI output. Both analyze and fix accept a --format flag with table, github, and json options. When GITHUB_ACTIONS is set, table output automatically includes GitHub annotations.
  • Magic property diagnostics. New report-magic-properties option under [diagnostics] in .phpantom.toml. When enabled, classes with __get that also have virtual properties (from @property docblock tags, Laravel Eloquent column inference, or other providers) will flag unknown property access instead of silently allowing it.
  • Inline diagnostic suppression. // @phpantom-ignore code on the same line or the line above suppresses the specified diagnostic. Multiple codes can be comma-separated. A bare // @phpantom-ignore suppresses all diagnostics on the target line.
  • Find references and rename for PHPDoc virtual members. @property, @property-read, @property-write, and @method declarations 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|null from ->first()). Contributed by @AbyssWaIker in #115.

Changed

  • 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_case noun-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), @var docblock variable names are included, and unset() 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.

Fixed

  • throw new completion missing vendor classes. Classes whose Throwable ancestry could not be immediately verified (e.g. vendor classes not yet parsed) were silently excluded from throw new and catch completion, 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 @removed tags (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, and parent keywords. Renaming a class no longer replaces occurrences of self::, static::, or parent:: 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 function insertion. Accepting a function completion no longer inserts a use function statement 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 via global are no longer incorrectly flagged.
  • False-positive type mismatch diagnostics. Bare array return values passed to typed array parameters, properties narrowed via instanceof, 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 @var completion. Variables typed only via a standalone /** @var Type $var */ docblock now resolve for member completion and go-to-definition.
  • @var docblocks with additional tags. Extra tags like @psalm-suppress in the same docblock no longer corrupt the type string.
  • Foreach @var annotations for key and value variables. Multi-line docblocks with multiple @var tags before a foreach now correctly override both key and value types.
  • Foreach element type from untyped arrays. Variables in a foreach over bare array now resolve to mixed instead 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 ::class literal arrays resolves static access. $className::CONST and $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-implements on stub-loaded interfaces now correctly propagates substituted return types to child methods.
  • Generic method return types from @var annotations. 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 @template with key-of bound. 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.
  • __get magic method template resolution. Property access on a class whose __get uses key-of<T> bounds now infers the concrete type from the property name.
  • Magic __get property access. Accessing undefined properties on objects with a __get method now resolves to the method's declared return type.
  • Magic __call method return type. Calling undefined methods on objects with a __call method now resolves to __call's declared return type.
  • SoapClient arbitrary methods. Calling any method on SoapClient no longer produces false-positive "unknown member" diagnostics.
  • Literal true/false preserved in template inference. Passing true or false to a generic constructor now keeps the precise type instead of widening to bool.
  • @psalm-method overrides @method. The vendor-prefixed tag now takes priority when both are present.
  • @psalm-param/@phpstan-param priority over @param. @phpstan-param takes precedence over @psalm-param, which takes precedence over @param, matching PHPStan and Psalm behaviour.
  • @psalm-if-this-is template inference. Method-level template parameters are now inferred by matching the receiver's concrete type against the annotation's type pattern.
  • self::class and static::class in template arguments. Passing these to a class-string<T> parameter now correctly resolves T to the enclosing class.
  • static return type through first-class callables. self::method(...)() and similar patterns now preserve static in 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/static type resolution. Properties with @var self|null or static now resolve to the owning class name in hover.
  • Trait self return type resolution through inheritance. Trait methods with return type self now resolve to the declaring class, not the calling subclass.
  • Conditional return type resolution for scalar arguments. $param is string conditions in @return annotations now resolve correctly for literal values.
  • SPL iterator generic type propagation. Decorator iterators like CachingIterator and LimitIterator now propagate the wrapped iterator's generic type parameters.
  • ArrayIterator constructor generic inference. new ArrayIterator($typedArray) now infers key and value types from the array argument.
  • range() return type inference. range() now returns list<string> for string arguments and list<int|float> otherwise, instead of bare array.
  • (object) cast type inference. Casting now resolves to an object shape matching the operand's structure instead of bare stdClass.
  • ArrayAccess array-access assignment. $obj[$key] = $val on ArrayAccess objects no longer overwrites the variable's generic type with an array type.
  • Static method calls on class-string unions. $variable::method() where $variable holds 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 files autoload 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 $dates and where{Property} go-to-definition. Go-to-definition now works for properties backed by the $dates array and dynamic where{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 to RedirectResponse as 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 analyze command 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.
  • analyze and fix commands run at consistent speed regardless of invocation style.
  • Type narrowing. Comprehensive fixes: is_*() guards correctly narrow multi-member unions; instanceof on mixed or object narrows to the checked type; === null and == null narrow correctly; assert() narrowing persists through subsequent branches; isset()/empty() strip null from nullable types; property access expressions are narrowed through conditionals; array shape keys are narrowed through guard clauses; OR'd instanceof checks 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 @extends chains. 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 @mixin now resolve through the mixin. @method and @property tags on mixin classes are propagated to the consumer. $this return types on mixin methods resolve to the consumer class.
  • @method tag resolution. Colon return type syntax, parenthesised return types, and the ambiguous single-static pattern are now parsed correctly. Template parameters in @method return types are substituted through @extends and @implements annotations.
  • 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 numeric pseudo-type. Functions annotated with @return numeric now resolve correctly instead of falling back to string.
  • parent::__construct() with @extends generics. No longer produces false-positive type errors for substituted parameter types.
  • Array access on bare array and mixed types. Accessing a key on plain array now resolves to mixed instead 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 ->value resolves to the specific backing type. @implements generics on enums are resolved correctly.
  • Class constants. Inherited constants accessed via self::CONST or ChildClass::CONST resolve through multi-level inheritance.
  • Hover / type display. T[] displays as array<T>, mixed[] as array. PHPDoc type aliases are normalized. Methods returning parent resolve 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, and new 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. $this no 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 if blocks, 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 @param annotations no longer leak across sibling methods or closures.
  • class-string<T> parameter completion. Parameters typed as class-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. \Closure is now recognised as a subtype of callable.
  • Union-typed method calls no longer lose resolution on second occurrence.
  • Fluent method chains in namespaced classes. Methods returning static or self resolve 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 catch clauses 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 files autoloading.
  • False-positive type errors on generic class methods. Template parameters are now substituted into method parameter types before checking argument compatibility.

New Contributors

Full Changelog: 0.7.0...0.8.0