Skip to content

Commit 14a5b31

Browse files
committed
Update todo
1 parent 2d7f9af commit 14a5b31

1 file changed

Lines changed: 165 additions & 22 deletions

File tree

docs/todo.md

Lines changed: 165 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,170 @@
11
# PHPantom — Remaining Work
22

3-
> Last updated: 2026-02-20
3+
> Last updated: 2026-02-21
4+
5+
---
6+
7+
## Completion Gaps
8+
9+
### 16. Multi-line method chain completion
10+
**Priority: High**
11+
12+
Subject extraction in `completion/target.rs` and `subject_extraction.rs`
13+
operates on the current line only. Chains that span multiple lines produce
14+
no completions:
15+
16+
```php
17+
$this->getRepository()
18+
->findAll()
19+
->filter(fn($u) => $u->active)
20+
-> // ← cursor here: no completion
21+
```
22+
23+
`extract_completion_target` sees only whitespace and `->` on the cursor
24+
line and `extract_arrow_subject` finds nothing meaningful to the left.
25+
This is extremely common in Laravel/Symfony code with fluent builders,
26+
query chains, and collection pipelines.
27+
28+
The same limitation affects go-to-definition: `extract_member_access_context`
29+
in `definition/member.rs` also works on the current line, so Ctrl-clicking
30+
a method in a multi-line chain cannot resolve the owning class.
31+
32+
**Fix:** before extracting the subject, collapse continuation lines around
33+
the cursor. Lines that start with `->` or `?->` (after optional
34+
whitespace) should be joined with the preceding line, then the extraction
35+
runs on the flattened text.
36+
37+
### 17. Switch statement variable type tracking
38+
**Priority: Medium**
39+
40+
`walk_statements_for_assignments` in `completion/variable_resolution.rs`
41+
handles `If`, `Foreach`, `While`, `For`, `DoWhile`, `Try`, `Block`, and
42+
`Expression`, but `Statement::Switch` falls into the `_ => {}` catch-all
43+
and is silently skipped. Variables assigned inside switch cases are
44+
invisible to the type resolver:
45+
46+
```php
47+
switch ($type) {
48+
case 'user':
49+
$result = new User();
50+
break;
51+
case 'admin':
52+
$result = new Admin();
53+
break;
54+
}
55+
$result-> // ← no completion
56+
```
57+
58+
The variable *name* collector in `completion/variable_completion.rs`
59+
already handles `Statement::Switch`, so `$result` appears in variable
60+
name suggestions, but its type is not resolved.
61+
62+
**Fix:** add a `Statement::Switch` arm to `walk_statements_for_assignments`
63+
that iterates the switch cases and recurses into each case's statement
64+
list with `conditional = true` (same pattern as `if` branches).
65+
66+
### 18. `?->` chaining loses intermediate segments
67+
**Priority: Medium**
68+
69+
In `extract_arrow_subject` (`subject_extraction.rs`), when a `?->` is
70+
encountered mid-chain, the code calls `extract_simple_variable` instead
71+
of recursing with `extract_arrow_subject`. The `->` path recurses
72+
correctly, but `?->` does not:
73+
74+
```php
75+
$user->getAddress()?->getCity()-> // extracts "$user?->getCity", loses "->getAddress()"
76+
```
77+
78+
**Fix:** change the `?->` branch to call `extract_arrow_subject(chars, inner_arrow)`
79+
instead of `extract_simple_variable(chars, inner_arrow)`, mirroring what
80+
the `->` branch does.
81+
82+
### 19. `static` return type not resolved to concrete class at call sites
83+
**Priority: Medium**
84+
85+
When a method declares `@return static` (common in builder/factory
86+
patterns), `type_hint_to_classes` resolves `static` to
87+
`owning_class_name` — the class that *declares* the method, not the
88+
class it is called on:
89+
90+
```php
91+
class Builder {
92+
/** @return static */
93+
public function configure(): static { return $this; }
94+
}
95+
class AppBuilder extends Builder {}
96+
97+
$builder = new AppBuilder();
98+
$builder->configure()-> // resolves to Builder, not AppBuilder
99+
```
100+
101+
The resolution works correctly when the subject is `$this` or `self`,
102+
but when the method return type is `static` and the call is on a variable
103+
typed as a subclass, the declaring class is used instead of the
104+
variable's concrete type.
105+
106+
**Fix:** when `resolve_method_return_types_with_args` encounters a
107+
`static` (or `$this`) return type, substitute the caller's class name
108+
(the class the subject resolved to) rather than the class that declares
109+
the method.
110+
111+
### 15. `unset()` tracking
112+
**Priority: Medium**
113+
114+
`unset($var)` removes a variable from scope, and `unset($arr['key'])` removes
115+
a key from an array shape. Neither is tracked today.
116+
117+
- **Variable scope.** After `unset($x)`, the variable `$x` should no longer
118+
appear in variable name suggestions, and `$x->` should not resolve to the
119+
type it had before the `unset`.
120+
- **Array shape keys.** After `unset($config['host'])`, the key `host` should
121+
no longer appear in `$config['` key completions, and the inferred shape
122+
should reflect its removal.
123+
124+
Both cases require the assignment/variable scanner in
125+
`completion/variable_resolution.rs` to recognise `unset(...)` statements
126+
and update its tracking accordingly.
127+
128+
### 20. Non-`$this` property access in text-based assignment path
129+
**Priority: Low**
130+
131+
In `extract_raw_type_from_assignment_text` (`completion/resolver.rs`),
132+
property access on the RHS is only handled for `$this->propName`. When
133+
the RHS is `$otherVar->propName`, it falls through to `None`:
134+
135+
```php
136+
$user = getUser();
137+
$address = $user->address; // text-based path returns None
138+
$address-> // ← no completion (unless the AST-based path catches it)
139+
```
140+
141+
The AST-based path in `resolve_rhs_expression` handles this correctly,
142+
so the gap only surfaces in the text-based fallback used for intermediate
143+
chained assignments and some edge cases.
144+
145+
**Fix:** after the `$this->propName` check, add a branch that resolves
146+
`$var->propName` by first resolving `$var`'s type via
147+
`extract_raw_type_from_assignment_text` (recursively), then looking up
148+
the property on the resulting class.
149+
150+
---
151+
152+
## Go-to-Definition Gaps
153+
154+
### 21. No reverse jump: implementation → interface method declaration
155+
**Priority: Medium**
156+
157+
Go-to-implementation lets you jump from an interface method to its concrete
158+
implementations, but there is no way to jump from a concrete implementation
159+
*back* to the interface or abstract method it satisfies. For example,
160+
clicking `handle()` in a class that `implements Handler` cannot jump to
161+
`Handler::handle()`.
162+
163+
This would be a natural extension of `find_declaring_class` in
164+
`definition/member.rs`: when the cursor is on a method *definition* (not
165+
a call), check whether any implemented interface or parent abstract class
166+
declares a method with the same name, and offer that as a definition
167+
target.
4168

5169
---
6170

@@ -34,27 +198,6 @@ before comparison.
34198

35199
---
36200

37-
## Completion Gaps
38-
39-
### 15. `unset()` tracking
40-
**Priority: Medium**
41-
42-
`unset($var)` removes a variable from scope, and `unset($arr['key'])` removes
43-
a key from an array shape. Neither is tracked today.
44-
45-
- **Variable scope.** After `unset($x)`, the variable `$x` should no longer
46-
appear in variable name suggestions, and `$x->` should not resolve to the
47-
type it had before the `unset`.
48-
- **Array shape keys.** After `unset($config['host'])`, the key `host` should
49-
no longer appear in `$config['` key completions, and the inferred shape
50-
should reflect its removal.
51-
52-
Both cases require the assignment/variable scanner in
53-
`completion/variable_resolution.rs` to recognise `unset(...)` statements
54-
and update its tracking accordingly.
55-
56-
---
57-
58201
## Missing LSP Features
59202

60203
### 6. Hover (`textDocument/hover`)

0 commit comments

Comments
 (0)