Skip to content

Commit 36383e4

Browse files
committed
Implement basic hover
1 parent e703c22 commit 36383e4

14 files changed

Lines changed: 1862 additions & 189 deletions

docs/ARCHITECTURE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ src/
6767
│ ├── catch_completion.rs # Smart exception type completion inside catch() clauses
6868
│ ├── type_hint_completion.rs # Type completion in parameter lists, return types, properties
6969
│ └── use_edit.rs # Use-statement insertion helpers
70+
├── hover/
71+
│ └── mod.rs # Hover handler: symbol-map dispatch, type/signature/docblock formatting
7072
├── definition/
7173
│ ├── mod.rs # Submodule declarations
7274
│ ├── resolve.rs # Core go-to-definition: symbol-map dispatch + text-based fallback
@@ -79,6 +81,7 @@ tests/
7981
├── common/mod.rs # Shared test helpers and minimal PHP stubs
8082
├── completion_*.rs # Completion integration tests (by feature area)
8183
├── definition_*.rs # Go-to-definition integration tests
84+
├── hover.rs # Hover integration tests
8285
├── implementation.rs # Go-to-implementation integration tests
8386
├── docblock_*.rs # Docblock parsing and type tests
8487
├── parser.rs # PHP parser / AST extraction tests

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- **Hover.** Hovering over any symbol now shows its type, signature, and docblock description in a Markdown popup. Supported symbols include variables (with inferred types), methods (with full parameter list and return type), properties, class constants, class/interface/trait/enum references (with FQN, parent, interfaces, and docblock summary), function calls, and `$this`/`self`/`static`/`parent` keywords. Works across files via the same resolution pipeline that powers completion and go-to-definition. Deprecated members are marked with `@deprecated`.
1213
- **Closure parameter inference.** When a closure or arrow function is passed to a method or function whose parameter is typed as `callable(T): R` or `Closure(T): R`, untyped closure parameters are now inferred from the callable signature. For example, `$users->map(fn($u) => $u->name)` infers `$u` as `User` when `map` declares `@param callable(TValue): TMapValue` and generic substitution has resolved `TValue` to `User`. Works with instance methods, static methods, null-safe calls, standalone functions, `$this->method()` receivers, and closures at any argument position. Explicit type hints on closure parameters always take precedence. Template parameters inside callable signatures (e.g. `callable(TValue): void`) are substituted during generic resolution so the inferred types are concrete.
1314
- **Factory support.** `User::factory()->create()` and `->make()` now resolve to the model class. When a model uses the `HasFactory` trait with an explicit `@use HasFactory<UserFactory>` annotation, the generics system handles resolution. When the annotation is absent, the naming convention is used as a fallback: `App\Models\User` maps to `Database\Factories\UserFactory`, and subdirectories are preserved (`App\Models\Admin\SuperUser` maps to `Database\Factories\Admin\SuperUserFactory`). Factory chain methods that return `static` (e.g. `count()`, `state()`) continue the chain on the factory, while `create()` and `make()` return the model. The convention also works in reverse: a factory class extending `Factory` without `@extends Factory<Model>` resolves `TModel` from its own class name. Both directions are implemented as fallbacks that defer to explicit generics when present.
1415
- **`newCollection()` override detection.** Eloquent models that override the `newCollection()` method now resolve to the custom collection class declared in the method's return type. This is the third detection mechanism alongside `#[CollectedBy]` and `@use HasCollection<X>`. Priority order: attribute, trait, method override. Works with short names resolved via `use` imports and fully-qualified return types. The standard `Collection` return type is correctly ignored. Custom collection methods appear after `->get()`, on relationship properties, and in builder chains.

docs/todo.md

Lines changed: 61 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,7 @@ Each item carries two ratings:
2020

2121
## Critical Impact
2222

23-
### 1. Hover (`textDocument/hover`)
24-
**Impact: Critical · Effort: Low**
25-
26-
No hover support at all. Users can't see inferred types, docblock descriptions,
27-
or method signatures by hovering. Most of the infrastructure already exists
28-
(type resolution, class loading, docblocks) — wiring it into a hover handler
29-
would be relatively straightforward and extremely high-impact.
30-
31-
---
32-
33-
### 2. Signature Help (`textDocument/signatureHelp`)
23+
### 1. Signature Help (`textDocument/signatureHelp`)
3424
**Impact: Critical · Effort: Medium**
3525

3626
No parameter hints shown while typing function/method arguments. Named arg
@@ -46,7 +36,7 @@ users rely on it constantly when calling unfamiliar APIs.
4636

4737
## High Impact
4838

49-
### 3. `in_array` strict-mode type narrowing
39+
### 2. `in_array` strict-mode type narrowing
5040
**Impact: High · Effort: Low**
5141

5242
When `in_array($needle, $haystack, true)` is used as a condition,
@@ -68,7 +58,7 @@ See `InArrayFunctionTypeSpecifyingExtension` in PHPStan.
6858

6959
---
7060

71-
### 4. Pipe operator (PHP 8.5)
61+
### 3. Pipe operator (PHP 8.5)
7262
**Impact: High · Effort: Low**
7363

7464
PHP 8.5 introduced the pipe operator (`|>`):
@@ -94,7 +84,7 @@ callable syntax (`htmlspecialchars(...)`), reuse the existing
9484

9585
---
9686

97-
### 5. Function-level `@template` generic resolution
87+
### 4. Function-level `@template` generic resolution
9888
**Impact: High · Effort: Medium**
9989

10090
`MethodInfo` has `template_params` and `template_bindings` fields that
@@ -195,7 +185,7 @@ manifestation of this gap.
195185

196186
---
197187

198-
### 6. Parse and resolve `($param is T ? A : B)` return types
188+
### 5. Parse and resolve `($param is T ? A : B)` return types
199189
**Impact: High · Effort: Medium**
200190

201191
PHPStan's stubs use conditional return type syntax in docblocks:
@@ -234,7 +224,7 @@ extend the existing `ConditionalReturnType` infrastructure.
234224

235225
---
236226

237-
### 7. Warn when composer.json is missing or classmap is not optimized
227+
### 6. Warn when composer.json is missing or classmap is not optimized
238228
**Impact: High · Effort: Medium**
239229

240230
PHPantom relies on Composer artifacts (`vendor/composer/autoload_classmap.php`,
@@ -309,7 +299,7 @@ For the non-optimized classmap case, offer action buttons:
309299

310300
---
311301

312-
### 8. Find References (`textDocument/references`)
302+
### 7. Find References (`textDocument/references`)
313303
**Impact: High · Effort: Medium-High**
314304

315305
Can't find all usages of a symbol. The precomputed `SymbolMap` (built
@@ -333,7 +323,7 @@ variable within its scope" without re-parsing.
333323

334324
## Medium-High Impact
335325

336-
### 9. File system watching for vendor and project changes
326+
### 8. File system watching for vendor and project changes
337327
**Impact: Medium-High · Effort: Medium**
338328

339329
PHPantom loads Composer artifacts (classmap, PSR-4 mappings, autoload
@@ -391,7 +381,7 @@ supports glob patterns like `**/vendor/composer/autoload_*.php`.
391381

392382
## Medium Impact
393383

394-
### 10. No reverse jump: implementation → interface method declaration
384+
### 9. No reverse jump: implementation → interface method declaration
395385
**Impact: Medium · Effort: Low**
396386

397387
Go-to-implementation lets you jump from an interface method to its concrete
@@ -408,7 +398,7 @@ target.
408398

409399
---
410400

411-
### 11. `BackedEnum::from()` / `::tryFrom()` return type refinement
401+
### 10. `BackedEnum::from()` / `::tryFrom()` return type refinement
412402
**Impact: Medium · Effort: Low**
413403

414404
When calling `MyEnum::from($value)` or `MyEnum::tryFrom($value)`,
@@ -425,14 +415,14 @@ See `BackedEnumFromMethodDynamicReturnTypeExtension` in PHPStan.
425415

426416
---
427417

428-
### 12. Document Symbols (`textDocument/documentSymbol`)
418+
### 11. Document Symbols (`textDocument/documentSymbol`)
429419
**Impact: Medium · Effort: Low**
430420

431421
No outline view. Editors can't show a file's class/method/property structure.
432422

433423
---
434424

435-
### 13. Workspace Symbols (`workspace/symbol`)
425+
### 12. Workspace Symbols (`workspace/symbol`)
436426
**Impact: Medium · Effort: Low-Medium**
437427

438428
Can't search for classes/functions across the project. The `ast_map`
@@ -444,7 +434,7 @@ LSP `Location`s.
444434

445435
---
446436

447-
### 14. No go-to-definition for built-in (stub) functions and constants
437+
### 13. No go-to-definition for built-in (stub) functions and constants
448438
**Impact: Medium · Effort: Medium**
449439

450440
Clicking on a built-in function name like `array_map`, `strlen`, or
@@ -466,7 +456,7 @@ limitation.
466456

467457
---
468458

469-
### 15. Property hooks (PHP 8.4)
459+
### 14. Property hooks (PHP 8.4)
470460
**Impact: Medium · Effort: Medium**
471461

472462
PHP 8.4 introduced property hooks (`get` / `set`):
@@ -501,7 +491,7 @@ scopes, and parse the set-visibility modifier into a new
501491

502492
---
503493

504-
### 16. Narrow types of `&$var` parameters after function calls
494+
### 15. Narrow types of `&$var` parameters after function calls
505495
**Impact: Medium · Effort: Medium**
506496

507497
When a function takes a parameter by reference, the variable's type
@@ -530,7 +520,7 @@ extension) or use a built-in map for known functions.
530520

531521
---
532522

533-
### 17. SPL iterator generic stubs
523+
### 16. SPL iterator generic stubs
534524
**Impact: Medium · Effort: Medium**
535525

536526
PHPStan's `iterable.stub` provides full `@template TKey` /
@@ -551,7 +541,7 @@ certainly missing these generic annotations. We should either:
551541

552542
---
553543

554-
### 18. Partial result streaming via `$/progress`
544+
### 17. Partial result streaming via `$/progress`
555545
**Impact: Medium · Effort: Medium-High**
556546

557547
The LSP spec (3.17) allows requests that return arrays — such as
@@ -631,7 +621,7 @@ developer arrive before vendor matches, even within a single phase.
631621

632622
---
633623

634-
### 19. Rename (`textDocument/rename`)
624+
### 18. Rename (`textDocument/rename`)
635625
**Impact: Medium · Effort: Medium-High**
636626

637627
No rename refactoring support. Rename builds on find-references (§8) —
@@ -646,7 +636,7 @@ position without text scanning.
646636

647637
---
648638

649-
### 20. Array functions needing new code paths
639+
### 19. Array functions needing new code paths
650640
**Impact: Medium · Effort: High**
651641

652642
These functions have return type semantics that don't fit into either
@@ -685,7 +675,7 @@ These functions have return type semantics that don't fit into either
685675

686676
## Low-Medium Impact
687677

688-
### 21. Asymmetric visibility (PHP 8.4)
678+
### 20. Asymmetric visibility (PHP 8.4)
689679
**Impact: Low-Medium · Effort: Low**
690680

691681
Separate from property hooks, PHP 8.4 allows asymmetric visibility on
@@ -715,7 +705,7 @@ is just to store the value; context-aware filtering can follow later.
715705

716706
---
717707

718-
### 22. `str_contains` / `str_starts_with` / `str_ends_with` → non-empty-string narrowing
708+
### 21. `str_contains` / `str_starts_with` / `str_ends_with` → non-empty-string narrowing
719709
**Impact: Low-Medium · Effort: Low**
720710

721711
When `str_contains($haystack, $needle)` appears in a condition and
@@ -732,7 +722,7 @@ See `StrContainingTypeSpecifyingExtension` in PHPStan.
732722

733723
---
734724

735-
### 23. `count` / `sizeof` comparison → non-empty-array narrowing
725+
### 22. `count` / `sizeof` comparison → non-empty-array narrowing
736726
**Impact: Low-Medium · Effort: Low**
737727

738728
`if (count($arr) > 0)` or `if (count($arr) >= 1)` narrows `$arr` to
@@ -750,7 +740,7 @@ branches in `TypeSpecifier::specifyTypesInCondition`.
750740

751741
## Low Impact
752742

753-
### 24. Short-name collisions in `find_implementors`
743+
### 23. Short-name collisions in `find_implementors`
754744
**Impact: Low · Effort: Low**
755745

756746
`class_implements_or_extends` matches interfaces by both short name and
@@ -766,7 +756,7 @@ before comparison.
766756

767757
---
768758

769-
### 25. Fiber type resolution
759+
### 24. Fiber type resolution
770760
**Impact: Low · Effort: Low**
771761

772762
`Generator<TKey, TValue, TSend, TReturn>` has dedicated support for
@@ -781,7 +771,7 @@ Generator extraction in `docblock/types.rs`.
781771

782772
---
783773

784-
### 26. Non-empty-string propagation through string functions
774+
### 25. Non-empty-string propagation through string functions
785775
**Impact: Low · Effort: Low**
786776

787777
PHPStan tracks `non-empty-string` through string-manipulating
@@ -799,7 +789,7 @@ See `NonEmptyStringFunctionsReturnTypeExtension` in PHPStan.
799789

800790
---
801791

802-
### 27. `Closure::bind()` / `Closure::fromCallable()` return type preservation
792+
### 26. `Closure::bind()` / `Closure::fromCallable()` return type preservation
803793
**Impact: Low · Effort: Low-Medium**
804794

805795
PHPStan preserves the closure's type through `Closure::bind()` and
@@ -812,7 +802,7 @@ See `ClosureBindDynamicReturnTypeExtension` and
812802

813803
---
814804

815-
### 28. Remove deprecated text-search fallbacks
805+
### 27. Remove deprecated text-search fallbacks
816806
**Impact: Low · Effort: Medium**
817807

818808
The go-to-definition subsystem now uses the precomputed `SymbolMap` as
@@ -844,7 +834,7 @@ would let that deprecated function be removed entirely.
844834

845835
---
846836

847-
### 29. Non-array functions with dynamic return types
837+
### 28. Non-array functions with dynamic return types
848838
**Impact: Low · Effort: High**
849839

850840
PHPStan also provides dynamic return type extensions for many non-array
@@ -875,22 +865,22 @@ return types (less impactful for class-based completion).
875865

876866
---
877867

878-
### 30. Diagnostics
868+
### 29. Diagnostics
879869
**Impact: Low (large scope) · Effort: Very High**
880870

881871
No error reporting (undefined methods, type mismatches, etc.).
882872

883873
---
884874

885-
### 31. Code Actions
875+
### 30. Code Actions
886876
**Impact: Low · Effort: Very High**
887877

888878
No quick fixes or refactoring suggestions. No `codeActionProvider` in
889879
`ServerCapabilities`, no `textDocument/codeAction` handler, and no
890880
`WorkspaceEdit` generation infrastructure beyond trivial `TextEdit`s for
891881
use-statement insertion.
892882

893-
#### 31a. Extract Function refactoring
883+
#### 30a. Extract Function refactoring
894884

895885
Select a range of statements inside a method/function and extract them into a
896886
new function. The LSP would need to:
@@ -924,34 +914,33 @@ new function. The LSP would need to:
924914

925915
| # | Item | Impact | Effort |
926916
|---|---|---|---|
927-
| 1 | Hover | **Critical** | Low |
928-
| 2 | Signature Help | **Critical** | Medium |
929-
| 3 | `in_array` strict-mode type narrowing | High | Low |
930-
| 4 | Pipe operator (PHP 8.5) | High | Low |
931-
| 5 | Function-level `@template` generic resolution | High | Medium |
932-
| 6 | Conditional return type syntax | High | Medium |
933-
| 7 | Composer environment warnings | High | Medium |
934-
| 8 | Find References | High | Medium-High |
935-
| 9 | File system watching | Medium-High | Medium |
936-
| 10 | Reverse jump: impl → interface | Medium | Low |
937-
| 11 | `BackedEnum::from()` refinement | Medium | Low |
938-
| 12 | Document Symbols | Medium | Low |
939-
| 13 | Workspace Symbols | Medium | Low-Medium |
940-
| 14 | Built-in stub go-to-definition | Medium | Medium |
941-
| 15 | Property hooks (PHP 8.4) | Medium | Medium |
942-
| 16 | Parameter out types (by-reference) | Medium | Medium |
943-
| 17 | SPL iterator generic stubs | Medium | Medium |
944-
| 18 | Partial result streaming | Medium | Medium-High |
945-
| 19 | Rename | Medium | Medium-High |
946-
| 20 | Array functions (new code paths) | Medium | High |
947-
| 21 | Asymmetric visibility (PHP 8.4) | Low-Medium | Low |
948-
| 22 | `str_contains` / `str_starts_with` narrowing | Low-Medium | Low |
949-
| 23 | `count` / `sizeof` → non-empty-array | Low-Medium | Low |
950-
| 24 | Short-name collisions | Low | Low |
951-
| 25 | Fiber type resolution | Low | Low |
952-
| 26 | Non-empty-string propagation | Low | Low |
953-
| 27 | `Closure::bind()` preservation | Low | Low-Medium |
954-
| 28 | Remove deprecated text-search fallbacks | Low | Medium |
955-
| 29 | Non-array dynamic return types | Low | High |
956-
| 30 | Diagnostics | Low | Very High |
957-
| 31 | Code Actions / Extract Function | Low | Very High |
917+
| 1 | Signature Help | **Critical** | Medium |
918+
| 2 | `in_array` strict-mode type narrowing | High | Low |
919+
| 3 | Pipe operator (PHP 8.5) | High | Low |
920+
| 4 | Function-level `@template` generic resolution | High | Medium |
921+
| 5 | Conditional return type syntax | High | Medium |
922+
| 6 | Composer environment warnings | High | Medium |
923+
| 7 | Find References | High | Medium-High |
924+
| 8 | File system watching | Medium-High | Medium |
925+
| 9 | Reverse jump: impl → interface | Medium | Low |
926+
| 10 | `BackedEnum::from()` refinement | Medium | Low |
927+
| 11 | Document Symbols | Medium | Low |
928+
| 12 | Workspace Symbols | Medium | Low-Medium |
929+
| 13 | Built-in stub go-to-definition | Medium | Medium |
930+
| 14 | Property hooks (PHP 8.4) | Medium | Medium |
931+
| 15 | Parameter out types (by-reference) | Medium | Medium |
932+
| 16 | SPL iterator generic stubs | Medium | Medium |
933+
| 17 | Partial result streaming | Medium | Medium-High |
934+
| 18 | Rename | Medium | Medium-High |
935+
| 19 | Array functions (new code paths) | Medium | High |
936+
| 20 | Asymmetric visibility (PHP 8.4) | Low-Medium | Low |
937+
| 21 | `str_contains` / `str_starts_with` narrowing | Low-Medium | Low |
938+
| 22 | `count` / `sizeof` → non-empty-array | Low-Medium | Low |
939+
| 23 | Short-name collisions | Low | Low |
940+
| 24 | Fiber type resolution | Low | Low |
941+
| 25 | Non-empty-string propagation | Low | Low |
942+
| 26 | `Closure::bind()` preservation | Low | Low-Medium |
943+
| 27 | Remove deprecated text-search fallbacks | Low | Medium |
944+
| 28 | Non-array dynamic return types | Low | High |
945+
| 29 | Diagnostics | Low | Very High |
946+
| 30 | Code Actions / Extract Function | Low | Very High |

example.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,12 @@
114114
$cls::TYPE_ADMIN; // class constant
115115
$cls::$defaultRole; // static property
116116

117-
$ref = self::class; // also works with self::class / static::class
117+
class ClassTest {
118+
function test()
119+
{
120+
return self::class; // also works with self::class / static::class
121+
}
122+
}
118123

119124

120125
// ── Static & Enum Completion ────────────────────────────────────────────────

0 commit comments

Comments
 (0)