Skip to content

Commit b31841e

Browse files
committed
docs: update roadmap and changelog for Laravel and performance work
1 parent 944ea44 commit b31841e

4 files changed

Lines changed: 7 additions & 117 deletions

File tree

docs/CHANGELOG.md

Lines changed: 4 additions & 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+
- **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()`, and `newModelQuery()` return the custom builder type with correct generic model substitution.
1213
- **Blade template support.** Completion, hover, go-to-definition, diagnostics, semantic tokens, and inlay hints work inside `.blade.php` files. Contributed by @MingJen in https://github.com/AJenbo/phpantom_lsp/pull/100.
1314
- **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.
1415
- **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.
@@ -44,6 +45,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4445

4546
### Changed
4647

48+
- **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.
49+
- **Member completion caching.** Unfiltered member lists are cached per-target to speed up subsequent completions during keyword entry.
50+
- **Laravel startup performance.** Common Laravel builder classes are warmed in the background at startup to eliminate the first-access penalty on Eloquent completions.
4751
- **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 https://github.com/AJenbo/phpantom_lsp/pull/116.
4852
- **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.
4953
- **Replace FQCN with import.** Now replaces all occurrences of the same FQCN throughout the file in one action, not just the one under the cursor. A new "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).

docs/todo.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ within the same impact tier.
3030

3131
> **Note:** F1 (Workspace symbol search), F2 (Document symbols), A8
3232
> (Implement interface methods), A9 (Auto import), D1 (Unknown class
33-
> diagnostic), and D3 (Unknown member diagnostic) were originally
34-
> planned here but have already shipped.
33+
> diagnostic), D3 (Unknown member diagnostic), and L4 (Custom Eloquent
34+
> builders) were originally planned here but have already shipped.
3535
3636
## Sprint 6 — 1.0 release, editor plugins & type intelligence
3737

@@ -151,8 +151,7 @@ unlikely to move the needle for most users.
151151
| S4 | Named argument awareness in active parameter | Low-Medium | Medium |
152152
| S5 | Language construct signature help and hover | Low | Low |
153153
| | **[Laravel](todo/laravel.md)** | | |
154-
| L4 | Custom Eloquent builders (`HasBuilder` / `#[UseEloquentBuilder]`) | Medium | Medium |
155-
| L3 | `$dates` array (deprecated) | Low-Medium | Low |
154+
| L3 | `$dates` array (deprecated) | Low-Medium | Low |
156155
| L6 | Factory `has*`/`for*` relationship methods | Low-Medium | Medium |
157156
| L7 | `$pivot` property on BelongsToMany | Medium | Medium-High |
158157
| L8 | `withSum`/`withAvg`/`withMin`/`withMax` aggregate properties | Low-Medium | Medium-High |
@@ -164,7 +163,6 @@ unlikely to move the needle for most users.
164163
| | **[Performance](todo/performance.md) / [Eager Resolution](todo/eager-resolution.md)** | | |
165164
| ER5 | [Mago-style separated metadata](todo/eager-resolution.md#er5--mago-style-separated-metadata) | High | High |
166165
| P14 | [Eager docblock parsing into structured fields](todo/performance.md#p14-eager-docblock-parsing-into-structured-fields) | Medium | Medium |
167-
| P9 | [`resolved_class_cache` generic-arg specialisation](todo/performance.md#p9-resolved_class_cache-generic-arg-specialisation) | Medium | Medium |
168166
| P11 | [Uncached base-resolution in `build_scope_methods_for_builder`](todo/performance.md#p11-uncached-base-resolution-in-build_scope_methods_for_builder) | Low-Medium | Low |
169167
| P3 | Parallel pre-filter in `find_implementors` | Low-Medium | Medium |
170168
| P4 | `memmem` for block comment terminator search | Low | Low |
@@ -173,7 +171,6 @@ unlikely to move the needle for most users.
173171
| P7 | `diag_pending_uris` uses `Vec::contains` for dedup | Low | Low |
174172
| P8 | `find_class_in_ast_map` linear fallback scan | Low | Low |
175173
| P12 | [`find_or_load_function` Phase 1.75 serial bottleneck](todo/performance.md#p12-find_or_load_function-phase-175-serial-bottleneck) | Low | Low |
176-
| P17 | [`mago-names` resolution on the parse hot path](todo/performance.md#p17-mago-names-resolution-on-the-parse-hot-path) | Medium | Low |
177174
| P18 | [Subtype result caching](todo/performance.md#p18-subtype-result-caching) (per-request HashMap for hierarchy walks) | Medium | Low |
178175
| P19 | [Arena reuse on the parse hot path](todo/performance.md#p19-arena-reuse-on-the-parse-hot-path) (thread-local `Bump::reset()` instead of `Bump::new()`) | Medium | Low |
179176
| P20 | [Content-hash gated resolution cache persistence](todo/performance.md#p20-content-hash-gated-resolution-cache-persistence) | Medium | Medium |

docs/todo/laravel.md

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -238,45 +238,6 @@ today and what is still missing.
238238

239239
---
240240

241-
#### L4. Custom Eloquent builders (`HasBuilder` / `#[UseEloquentBuilder]`)
242-
243-
**Impact: High · Effort: Medium**
244-
245-
Custom builders are the recommended pattern for complex query scoping
246-
in modern Laravel. Without this, users get zero completions for
247-
builder-specific methods via static model calls.
248-
249-
Laravel 11+ introduced the `HasBuilder` trait and
250-
`#[UseEloquentBuilder(UserBuilder::class)]` attribute to let models
251-
declare a custom builder class. When present, `User::query()` and
252-
all static builder-forwarded calls should resolve to the custom
253-
builder instead of the base `Illuminate\Database\Eloquent\Builder`.
254-
255-
```php
256-
/** @extends Builder<User> */
257-
class UserBuilder extends Builder {
258-
/** @return $this */
259-
public function active(): static { ... }
260-
}
261-
262-
class User extends Model {
263-
/** @use HasBuilder<UserBuilder> */
264-
use HasBuilder;
265-
}
266-
267-
User::query()->active()->get(); // active() should resolve on UserBuilder
268-
```
269-
270-
Larastan handles this via `BuilderHelper::determineBuilderName()`,
271-
which inspects `newEloquentBuilder()`'s return type or the
272-
`#[UseEloquentBuilder]` attribute to find the custom builder class.
273-
274-
**Where to change:** In `build_builder_forwarded_methods`, before
275-
loading the standard `Eloquent\Builder`, check whether the model
276-
declares a custom builder via `@use HasBuilder<X>` in `use_generics`
277-
or a `newEloquentBuilder()` method with a non-default return type.
278-
If found, load and resolve that builder class instead.
279-
280241
#### L5. `abort_if`/`abort_unless` type narrowing
281242

282243
**Impact: High · Effort: Medium**

docs/todo/performance.md

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -190,36 +190,6 @@ window.
190190

191191
---
192192

193-
## P9. `resolved_class_cache` generic-arg specialisation
194-
195-
**Impact: Medium · Effort: Medium**
196-
197-
The resolved-class cache is keyed by `(FQN, Vec<String>)`. Every
198-
distinct generic instantiation of the same class (e.g.
199-
`Builder<User>`, `Builder<Order>`, `Builder<Product>`) triggers a
200-
full `resolve_class_fully` call, even though the base resolution
201-
(inheritance merging, trait merging, virtual member injection) is
202-
identical. Only the final generic substitution differs.
203-
204-
In a Laravel codebase with hundreds of Eloquent models, this means
205-
`Builder` is fully resolved hundreds of times, once per model.
206-
207-
### Fix
208-
209-
Cache the base-resolved class (before generic substitution)
210-
separately, keyed by FQN alone. When a generic instantiation is
211-
requested, look up the base-resolved class and apply
212-
`apply_substitution` on top. The substitution step is cheap
213-
(tree walk) compared to the full resolution (inheritance walking,
214-
trait merging, virtual member providers).
215-
216-
This requires splitting `resolve_class_fully` into two stages:
217-
base resolution (cached by FQN) and generic specialisation (cached
218-
by `(FQN, Vec<String>)` as today, but with a much cheaper miss
219-
path).
220-
221-
---
222-
223193
## P11. Uncached base-resolution in `build_scope_methods_for_builder`
224194

225195
**Impact: Low-Medium · Effort: Low**
@@ -506,48 +476,6 @@ essentially free for both eager and deferred indexing paths.
506476

507477
---
508478

509-
## P17. `mago-names` resolution on the parse hot path
510-
511-
**Impact: Medium · Effort: Low**
512-
513-
The `mago-names` name resolver runs synchronously inside
514-
`update_ast_inner`, adding a full AST walk plus an owned `HashMap`
515-
copy on every `didChange` event. Measured regression from `6a0737a`
516-
("Migrate to use mago-names"):
517-
518-
| Benchmark | Before | After | Δ |
519-
| ---------------- | ------ | ----- | ---- |
520-
| with_narrowing | 12 ms | 15 ms | +25% |
521-
| 5_methods_chain | 8 ms | 10 ms | +25% |
522-
| carbon_class | 250 ms | 340 ms | +36% |
523-
| large_file | 150 ms | 210 ms | +40% |
524-
525-
The resolved names are currently consumed only by diagnostics (which
526-
run asynchronously) and `FileContext::resolve_name_at()`. Nothing on
527-
the completion hot path requires this data to be computed eagerly.
528-
529-
### Fix
530-
531-
Defer name resolution out of `update_ast_inner`. Options:
532-
533-
- **Lazy resolution:** compute `OwnedResolvedNames` on first access
534-
per file version, invalidate on the next `update_ast`. Moves the
535-
cost off the typing hot path entirely.
536-
- **Diagnostic-worker resolution:** run the resolver in the
537-
diagnostic worker clone of `Backend`, since diagnostics are the
538-
primary consumer.
539-
540-
### When to implement
541-
542-
Low priority. The `mago-names` migration is complete, but the
543-
`use_map` is still used by several consumers. Further refactoring
544-
(migrating more consumers to byte-offset lookups, eventually
545-
removing `use_map`) will change the access patterns. Optimizing
546-
now would likely be reworked. Revisit once `use_map` usage is
547-
significantly reduced.
548-
549-
---
550-
551479
## P18. Subtype result caching
552480

553481
**Impact: Medium · Effort: Low**

0 commit comments

Comments
 (0)