Skip to content

Commit 5bc0637

Browse files
committed
Update docs
1 parent c062cac commit 5bc0637

7 files changed

Lines changed: 321 additions & 374 deletions

File tree

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ PHPantom focuses on completion and go-to-definition and aims to do them really w
3737
- **Array shape inference from code.** `$config = ['host' => 'localhost', 'port' => 3306]` offers key completion with no annotation. Incremental `$config['key'] = ...` assignments extend the shape. Nested access chains resolve through shapes and generics. `array_filter`, `array_map`, `array_pop`, `current`, etc. preserve the element type instead of losing it to `array`.
3838
- **Guard clause stacking.** Early return narrows subsequent code. Multiple guards stack to whittle a union down. Works in ternaries, `match(true)`, with `is_a()`, `assert()`.
3939
- **Generic collection foreach.** Iterating `Collection<User>`, `Generator<int, Item>`, or a class with `@implements IteratorAggregate<int, User>` resolves the loop variable to the element type. Keys too.
40-
- **Generics.** Class-level `@template` with substitution through inheritance (`@extends Base<User>`).
40+
- **Generics.** `@template` with type substitution through inheritance chains and at call sites.
4141
- **Everything else you'd expect.** `foreach`, `clone`, `$arr[] = new Foo()`, destructuring with named keys, chained method calls in assignments.
4242

4343
## Project Awareness
@@ -75,6 +75,14 @@ See **[docs/CONTRIBUTING.md](docs/CONTRIBUTING.md)**.
7575
7676
For details on how symbol resolution, stub loading, and inheritance merging work, see **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**.
7777
78+
## Changelog
79+
80+
See **[docs/CHANGELOG.md](docs/CHANGELOG.md)** for a detailed history of each release.
81+
82+
## Roadmap
83+
84+
See **[docs/todo.md](docs/todo.md)** for the backlog of known gaps, missing LSP features, and planned improvements.
85+
7886
## Acknowledgements
7987
8088
PHPantom stands on the shoulders of:

docs/CHANGELOG.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Changelog
2+
3+
All notable changes to PHPantom will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.3.0] - 2026-02-21
11+
12+
### Added
13+
14+
- **Go-to-implementation.** Jump from an interface or abstract class (or a method call typed as one) to all concrete implementations. Scans open files, class index, classmap, embedded stubs, and PSR-4 directories in five phases.
15+
- **Method-level `@template` (general case).** When a method declares `@template T` with `@param T $model` and `@return Collection<T>`, the resolver infers `T` from the actual argument at the call site. Works with inline chains, static methods, `new` expressions, and cross-file resolution.
16+
- **`@phpstan-type` / `@psalm-type` aliases.** Local type aliases defined on a class are expanded during resolution, including `@phpstan-import-type` for importing aliases from other classes.
17+
- **Array function type preservation.** `array_filter`, `array_map`, `array_pop`, `current`, and similar functions preserve the element type instead of losing it to `array`.
18+
- **Spread operator type tracking.** `$all = [...$users, ...$admins]` resolves to the union of element types from all spread sources.
19+
- **Callable/closure variable invocation.** `$fn()->` resolves the return type when `$fn` holds a closure, arrow function, or a variable annotated as `Closure(...): T`.
20+
- **Early return narrowing.** Guard clauses (`if (!$x instanceof Foo) return;`) narrow the type for subsequent code. Multiple guards stack. Works in ternaries and `match(true)`.
21+
- **`instanceof` narrowing to interface and abstract types.**
22+
- **`instanceof` narrowing inside ternary expressions.**
23+
- **Trait `insteadof` / `as` conflict resolution.** Visibility changes and method aliasing via `as`, exclusion via `insteadof`.
24+
- **Generics tracked through loop iterations.**
25+
- **Yield type resolution.**
26+
- **Chained method calls in variable assignment.** `$x = $this->foo()->bar()` resolves through the full chain.
27+
- **Named key destructuring from array shapes.** `['name' => $name] = $shape` resolves `$name` to the correct type.
28+
- **Type hint completion in function/method signatures.**
29+
- **Variable and clone assignment type tracking.**
30+
- **Iterate directly on function return values.** `foreach (getUsers() as $user)` resolves `$user`.
31+
- **User constant completion.**
32+
- **Required argument completion.**
33+
- **Contextual try-catch completion.** Exception suggestions are scoped to what the `try` block can actually throw.
34+
- **Void detection for `@return` PHPDoc suggestions.**
35+
36+
### Fixed
37+
38+
- More robust PHPDoc type parsing.
39+
- Fixed false positive type lookups for internal stubs.
40+
- Fixed crash in variable resolver.
41+
- Fixed incorrect method resolution.
42+
- Fixed finding definitions inside comments.
43+
- Fixed use of incorrect import map for name resolution.
44+
- Fixed completion suggestions being too aggressive or appearing in comments.
45+
46+
## [0.2.0] - 2026-02-18
47+
48+
### Added
49+
50+
- **Generics.** Class-level `@template` with `@extends` substitution through inheritance chains. Method-level `class-string<T>` pattern. Generic trait substitution.
51+
- **Array shapes.** `['key' => Type]` literals offer key completion with no annotation needed. Incremental assignments extend the shape.
52+
- **Object shapes.**
53+
- **Array growth tracking.** `$arr[] = new Foo()` and `$arr['key'] = $value` build up the shape incrementally.
54+
- **Array destructuring.** `[$a, $b] = $pair` resolves element types.
55+
- **Array element access.** `$arr[0]->` resolves the element type.
56+
- **Foreach key type resolution.** Keys from generic iterables and array shapes.
57+
- **Iterable value type resolution.** Foreach on `Collection<User>`, `Generator<int, Item>`, and `@implements IteratorAggregate<int, User>`.
58+
- **Ternary and null-coalescing type resolution.**
59+
- **Match expression type inference.**
60+
- **Named argument completion.**
61+
- **Variable name suggestions.**
62+
- **Standalone function completion.**
63+
- **`define()` constant completion.**
64+
- **Smart PHPDoc tag completion.** Tags filtered to context (`@var` only in property docblocks, `@param` only when there are undocumented parameters). `@throws` detects uncaught exceptions. `@param` pre-fills name and type. Already-documented tags are not suggested again.
65+
- **Deprecated member detection.**
66+
- **Promoted property type via `@param`.**
67+
- **Property chaining.**
68+
- **`require_once` function discovery.**
69+
- **Go-to type definition from property.**
70+
71+
### Fixed
72+
73+
- Fixed `@mixin` context for return types.
74+
- Fixed import of global classes and namespace context.
75+
- Fixed go-to-definition for aliased classes.
76+
77+
## [0.1.0] - 2026-02-16
78+
79+
Initial release.
80+
81+
### Added
82+
83+
- **Completion.** Methods, properties, and constants via `->`, `?->`, and `::`. Context-aware visibility filtering. Alphabetically ordered results.
84+
- **Class inheritance.** Parent classes, interfaces, and traits with correct member merging.
85+
- **`self::`, `static::`, `parent::` resolution.**
86+
- **PHPDoc support.** `@return` type resolution, `@property` virtual properties, `@method` virtual methods, `@mixin` class merging.
87+
- **Conditional return types.** `@return ($param is class-string<T> ? T : mixed)` and similar PHPStan-style conditional types.
88+
- **Inline `@var` annotations.** `/** @var User $user */` resolves the variable type.
89+
- **Enum support.** Case completion, implicit `UnitEnum`/`BackedEnum` interface members.
90+
- **Type narrowing.** `instanceof`, `is_a()`, and `@phpstan-assert` annotations.
91+
- **Nullsafe operator.** `?->` completion.
92+
- **Class name completion with auto-import.** Suggests class names and inserts the `use` statement.
93+
- **Union type inference.**
94+
- **Go-to-definition.** Classes, interfaces, traits, enums, methods, properties, constants, standalone functions, `new` expressions, and variable assignments.
95+
- **PSR-4 lazy loading** via Composer.
96+
- **Composer classmap support.**
97+
- **Embedded phpstorm-stubs.** Standard library type information bundled in the binary.
98+
- **Namespace aliasing and prefix imports.**
99+
- **Zed editor extension.**
100+
101+
[Unreleased]: https://github.com/AJenbo/phpantom_lsp/compare/0.3.0...HEAD
102+
[0.3.0]: https://github.com/AJenbo/phpantom_lsp/compare/0.2.0...0.3.0
103+
[0.2.0]: https://github.com/AJenbo/phpantom_lsp/compare/0.1.0...0.2.0
104+
[0.1.0]: https://github.com/AJenbo/phpantom_lsp/commits/0.1.0

docs/todo.md

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# PHPantom — Remaining Work
2+
3+
> Last updated: 2026-02-20
4+
5+
---
6+
7+
## Go-to-Implementation Gaps
8+
9+
### 5a. Transitive interface inheritance
10+
**Priority: Medium**
11+
12+
If `InterfaceB extends InterfaceA` and `ClassC implements InterfaceB`,
13+
go-to-implementation on `InterfaceA` will not find `ClassC`. The
14+
transitive check in `class_implements_or_extends` walks `parent_class`
15+
chains but does not walk interface-extends chains.
16+
17+
**Fix:** in `class_implements_or_extends`, when checking a class's
18+
`interfaces` list, load each interface via `class_loader` and recursively
19+
check whether it extends the target interface (with a depth bound).
20+
21+
### 5b. Short-name collisions in `find_implementors`
22+
**Priority: Low**
23+
24+
`class_implements_or_extends` matches interfaces by both short name and
25+
FQN (`iface_short == target_short || iface == target_fqn`). Two
26+
interfaces in different namespaces with the same short name (e.g.
27+
`App\Logger` and `Vendor\Logger`) could produce false positives.
28+
Similarly, `seen_names` in `find_implementors` deduplicates by short
29+
name, so two classes with the same short name in different namespaces
30+
could shadow each other.
31+
32+
**Fix:** always compare fully-qualified names by resolving both sides
33+
before comparison.
34+
35+
---
36+
37+
## Missing LSP Features
38+
39+
### 6. Hover (`textDocument/hover`)
40+
**Priority: High**
41+
42+
No hover support at all. Users can't see inferred types, docblock descriptions,
43+
or method signatures by hovering. Most of the infrastructure already exists
44+
(type resolution, class loading, docblocks) — wiring it into a hover handler
45+
would be relatively straightforward and high-impact.
46+
47+
---
48+
49+
### 7. Signature Help (`textDocument/signatureHelp`)
50+
**Priority: Medium**
51+
52+
No parameter hints shown while typing function/method arguments. Named arg
53+
completion partially fills this role, but proper signature help is more
54+
ergonomic.
55+
56+
---
57+
58+
### 8. Document Symbols (`textDocument/documentSymbol`)
59+
**Priority: Medium**
60+
61+
No outline view. Editors can't show a file's class/method/property structure.
62+
63+
---
64+
65+
### 9. Find References (`textDocument/references`)
66+
**Priority: Medium**
67+
68+
Can't find all usages of a symbol.
69+
70+
---
71+
72+
### 10. Rename (`textDocument/rename`)
73+
**Priority: Low**
74+
75+
No rename refactoring support.
76+
77+
---
78+
79+
### 11. Workspace Symbols (`workspace/symbol`)
80+
**Priority: Low**
81+
82+
Can't search for classes/functions across the project.
83+
84+
---
85+
86+
### 12. Diagnostics
87+
**Priority: Low** (large scope)
88+
89+
No error reporting (undefined methods, type mismatches, etc.).
90+
91+
---
92+
93+
### 13. Code Actions
94+
**Priority: Low**
95+
96+
No quick fixes or refactoring suggestions. No `codeActionProvider` in
97+
`ServerCapabilities`, no `textDocument/codeAction` handler, and no
98+
`WorkspaceEdit` generation infrastructure beyond trivial `TextEdit`s for
99+
use-statement insertion.
100+
101+
#### 13a. Extract Function refactoring
102+
103+
Select a range of statements inside a method/function and extract them into a
104+
new function. The LSP would need to:
105+
106+
1. **Scope analysis** — determine which variables are read in the selection but
107+
defined before it (→ parameters) and which are written in the selection but
108+
read after it (→ return values).
109+
2. **Statement boundary validation** — reject selections that split an
110+
expression or cross control-flow boundaries in invalid ways.
111+
3. **Type annotation** — use variable type resolution to generate parameter and
112+
return type hints on the new function.
113+
4. **Code generation** — produce a `WorkspaceEdit` that replaces the selection
114+
with a call and inserts the new function definition nearby.
115+
116+
**Prerequisites (build these first):**
117+
118+
| Feature | What it contributes |
119+
|---|---|
120+
| Hover (§6) | "Resolve type at arbitrary position" — needed to type params |
121+
| Document Symbols (§8) | AST range → symbol mapping — needed to find enclosing function and valid insertion points |
122+
| Find References (§9) | Variable usage tracking across a scope — the same "which variables are used where" analysis |
123+
| Simple code actions (add use stmt, implement interface) | Builds the code action + `WorkspaceEdit` plumbing |
124+
125+
---
126+
127+
## Performance / UX Ideas
128+
129+
### 14. Partial result streaming via `$/progress`
130+
**Priority: Medium** (cross-cutting optimisation)
131+
132+
The LSP spec (3.17) allows requests that return arrays — such as
133+
`textDocument/implementation`, `textDocument/references`,
134+
`workspace/symbol`, and even `textDocument/completion` — to stream
135+
incremental batches of results via `$/progress` notifications when both
136+
sides negotiate a `partialResultToken`. The final RPC response then
137+
carries `null` (all items were already sent through progress).
138+
139+
This would let PHPantom deliver the *first* useful results almost
140+
instantly instead of blocking until every source has been scanned.
141+
142+
#### Streaming between existing phases
143+
144+
`find_implementors` already runs five sequential phases (see
145+
`docs/ARCHITECTURE.md` § Go-to-Implementation):
146+
147+
1. **Phase 1 — ast_map** (already-parsed classes in memory) — essentially
148+
free. Flush results immediately.
149+
2. **Phase 2 — class_index** (FQN → URI entries not yet in ast_map) —
150+
loads individual files. Flush after each batch.
151+
3. **Phase 3 — classmap files** (Composer classmap, user + vendor mixed)
152+
— iterates unique file paths, applies string pre-filter, parses
153+
matches. This is the widest phase and the best candidate for
154+
within-phase streaming (see below).
155+
4. **Phase 4 — embedded stubs** (string pre-filter → lazy parse) — flush
156+
after stubs are checked.
157+
5. **Phase 5 — PSR-4 directory walk** (user code only, catches files not
158+
in the classmap) — disk I/O + parse per file, good candidate for
159+
per-file streaming.
160+
161+
Each phase boundary is a natural point to flush a `$/progress` batch,
162+
so the editor starts populating the results list while heavier phases
163+
are still running.
164+
165+
#### Prioritising user code within Phase 3
166+
167+
Phase 3 iterates the Composer classmap, which contains both user and
168+
vendor entries. Currently they are processed in arbitrary order. A
169+
simple optimisation: partition classmap file paths into user paths
170+
(under PSR-4 roots from `composer.json` `autoload` / `autoload-dev`)
171+
and vendor paths (everything else, typically under `vendor/`), then
172+
process user paths first. This way the results most relevant to the
173+
developer arrive before vendor matches, even within a single phase.
174+
175+
#### Granularity options
176+
177+
- **Per-phase batches** (simplest) — one `$/progress` notification at
178+
each of the five phase boundaries listed above.
179+
- **Per-file streaming** — within Phases 3 and 5, emit results as each
180+
file is parsed from disk instead of waiting for the entire phase to
181+
finish. Phase 3 can iterate hundreds of classmap files and Phase 5
182+
recursively walks PSR-4 directories, so per-file flushing would
183+
significantly improve perceived latency for large projects.
184+
- **Adaptive batching** — collect results for a short window (e.g. 50 ms)
185+
then flush, balancing notification overhead against latency.
186+
187+
#### Applicable requests
188+
189+
| Request | Benefit |
190+
|---|---|
191+
| `textDocument/implementation` | Already scans five phases; each phase's matches can be streamed |
192+
| `textDocument/references` (§9) | Will need full-project scanning; streaming is essential |
193+
| `workspace/symbol` (§11) | Searches every known class/function; early batches feel instant |
194+
| `textDocument/completion` | Less critical (usually fast), but long chains through vendor code could benefit |
195+
196+
#### Implementation sketch
197+
198+
1. Check whether the client sent a `partialResultToken` in the request
199+
params.
200+
2. If yes, create a `$/progress` sender. After each scan phase (or
201+
per-file, depending on granularity), send a
202+
`ProgressParams { token, value: [items...] }` notification.
203+
3. Return `null` as the final response.
204+
4. If no token was provided, fall back to the current behaviour: collect
205+
everything, return once.

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ impl Backend {
160160
fn defaults() -> Self {
161161
Self {
162162
name: "PHPantom".to_string(),
163-
version: "0.2.0".to_string(),
163+
version: env!("CARGO_PKG_VERSION").to_string(),
164164
open_files: Arc::new(Mutex::new(HashMap::new())),
165165
ast_map: Arc::new(Mutex::new(HashMap::new())),
166166
client: None,

tests/server_lifecycle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ async fn test_initialize_server_info() {
1212

1313
let server_info = result.server_info.expect("server_info should be present");
1414
assert_eq!(server_info.name, "PHPantom");
15-
assert_eq!(server_info.version, Some("0.2.0".to_string()));
15+
assert_eq!(server_info.version, Some(env!("CARGO_PKG_VERSION").to_string()));
1616
}
1717

1818
#[tokio::test]

0 commit comments

Comments
 (0)