You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+7-5Lines changed: 7 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -567,7 +567,7 @@ At resolution time, `merge_traits_into` loads the `UnitEnum` or `BackedEnum` stu
567
567
568
568
PSR-4 mappings come exclusively from the project's own `composer.json`. Vendor PSR-4 (`vendor/composer/autoload_psr4.php`) is not loaded. The Composer classmap is the sole source of truth for vendor code.
569
569
570
-
**Design principle:**if the classmap is missing or stale, vendor classes fail to resolve visibly rather than being silently papered over by PSR-4. This makes the problem obvious to the user (fix: run `composer dump-autoload`). User PSR-4 roots are walked by Go-to-implementation (Phase 5) and Find References because user files change between `dump-autoload` runs.
570
+
**Design principle:** vendor PSR-4 mappings are not loaded separately. The Composer classmap (augmented by the self-scanner, see below) is the sole source of truth for vendor code. User PSR-4 roots are walked by Go-to-implementation (Phase 5) and Find References because user files change between `dump-autoload` runs.
571
571
572
572
### Self-Generated Classmap
573
573
@@ -577,16 +577,18 @@ Scanning is parallelised using a two-phase approach: directory walks collect fil
577
577
578
578
The indexing strategy is configurable via `[indexing] strategy` in `.phpantom.toml`:
579
579
580
-
-**`"composer"`** (default) — use Composer's classmap when available and complete; fall back to self-scan when it is missing or incomplete (i.e. the project's own PSR-4 namespaces have no entries in the classmap).
-**`"composer"`** (default) — merged classmap + self-scan. Load Composer's classmap (if it exists) as a skip set, then self-scan all PSR-4 and vendor directories for anything the classmap missed. Whatever the classmap already covers is a free performance win; whatever it's missing, we find ourselves. No completeness heuristic needed.
581
+
-**`"self"`** — always self-scan, ignoring Composer's classmap entirely. Equivalent to the merged approach with an empty skip set.
582
582
-**`"full"`** — same as `"self"` for now; reserved for future background indexing.
583
-
-**`"none"`** — no proactive scanning; uses Composer's classmap if present but never falls back to self-scan.
583
+
-**`"none"`** — no proactive scanning; uses Composer's classmap if present but never self-scans to fill gaps.
584
+
585
+
The merged pipeline works in three steps: (1) load `autoload_classmap.php` into a `HashMap<String, PathBuf>`, (2) collect the classmap's file paths into a `HashSet<PathBuf>` skip set, (3) self-scan all PSR-4 and vendor directories, skipping files already in the skip set. The result is a merged index: classmap entries for everything Composer already knew about, plus self-scanned entries for everything it missed. When the classmap is complete (the common case), the self-scanner walks directories but skips every file, finishing almost instantly. When the classmap is empty or absent, it falls back to a full self-scan. When the classmap is partial (e.g. vendor classes only), vendor files are skipped and only user code is scanned. Every state of the classmap helps.
584
586
585
587
When self-scanning with a `composer.json` present, the scanner reads `autoload.psr-4`, `autoload-dev.psr-4`, `autoload.classmap`, and `autoload-dev.classmap` to determine which directories to walk. PSR-4 directories are filtered: only classes whose FQN matches the namespace prefix plus the relative file path are included. Vendor packages are discovered from `vendor/composer/installed.json` (both Composer 1 and 2 formats); the JSON packages array is borrowed rather than cloned to avoid allocating a copy of the entire vendor manifest. All directory walkers (full-scan, PSR-4 scanner, vendor package scanner, and go-to-implementation file collector) use the `ignore` crate for gitignore-aware traversal. Hidden directories are skipped automatically, and `.gitignore` rules are respected at every level. When no `composer.json` exists at all, the scanner falls back to walking all `.php` files under the workspace root.
586
588
587
589
The result is a `HashMap<String, PathBuf>` in the same format as the existing `Backend.classmap`. Everything downstream (resolution, diagnostics, go-to-definition) works unchanged.
588
590
589
-
**Redundant I/O elimination:**`init_single_project` parses `composer.json` once and passes the pre-parsed `serde_json::Value` to both `build_self_scan_composer` and `is_classmap_incomplete_with_json`. Previously each function re-read and re-parsed the file independently.
591
+
**Redundant I/O elimination:**`init_single_project` parses `composer.json` once and passes the pre-parsed `serde_json::Value` to `build_self_scan_composer`. Previously each function re-read and re-parsed the file independently.
590
592
591
593
**Vendor dir detection:** the `config.vendor-dir` setting is read from `composer.json` once during `initialized` (via `parse_composer_json`, which returns both the PSR-4 mappings and the vendor dir name). The absolute vendor directory path is cached on `Backend.vendor_dir_paths` and a `file://` URI prefix is stored in `Backend.vendor_uri_prefixes` for fast vendor-file detection at runtime. Both fields are `Vec`s to support monorepo workspaces with multiple subprojects (see below). For single-project workspaces, each collection has exactly one entry.
Copy file name to clipboardExpand all lines: docs/CHANGELOG.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -52,9 +52,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
52
52
-**Two-phase diagnostic publishing.** Cheap diagnostics (unused import dimming, deprecated strikethrough) are published immediately. Expensive diagnostics (unknown classes, unknown members, unknown functions) arrive in a second pass. Previously all diagnostics waited for the slowest collector to finish, so unused imports could take several seconds to grey out in large files.
53
53
-**Concurrent read access to shared state.** All read-heavy maps now use `parking_lot::RwLock` instead of `std::sync::Mutex`, allowing multiple requests to read in parallel.
54
54
-**Parallel workspace indexing.** Find References and other workspace-wide operations now parse files across multiple CPU cores. The workspace walk respects `.gitignore` rules instead of hardcoding directory names to skip. Self-scan classmap building, PSR-4 directory scanning, and vendor package scanning now read and scan files in parallel across all CPU cores. The byte-level PHP scanner uses `memchr` SIMD acceleration to skip comments, strings, and heredocs instead of scanning byte-by-byte. Redundant `composer.json` reads during initialization have been eliminated.
55
+
-**Merged classmap + self-scan pipeline.** The Composer classmap and the self-scanner are no longer mutually exclusive. PHPantom always loads the Composer classmap (when present) and then self-scans all PSR-4 and vendor directories, skipping files the classmap already covers. Stale or incomplete classmaps are no longer a problem: whatever Composer already knew about is a free performance win, and whatever it missed is discovered automatically. The fragile completeness heuristic that decided whether to trust the classmap has been removed. Monorepo subprojects use the same merged pipeline.
55
56
56
57
### Fixed
57
58
59
+
-**Parallel file scanner panics no longer crash the server.** If a thread panics during workspace indexing (e.g. due to a malformed PHP file), the panic is caught and logged instead of killing the entire LSP process. Previously a single thread failure during class, PSR-4, or full-symbol scanning would propagate the panic and silently terminate PHP intelligence in the editor.
58
60
-**Type alias array shape diagnostics no longer fire on object values.** When a method returns a `@phpstan-type` alias that expands to an array shape containing object values (e.g. `array{pen: Pen}`), accessing a method on the object value no longer triggers a false diagnostic. Type aliases are now expanded before walking array shape segments in the resolution pipeline.
59
61
-**Inline array-element function calls resolve correctly in diagnostics.**`end($obj->items)->method()` no longer produces a false "unknown member" diagnostic. Previously the argument text for property access expressions was lost during symbol map extraction, causing the resolver to fall back to the native `mixed|false` return type instead of extracting the element type from the array's generic annotation.
60
62
-**Eloquent Builder scope chain diagnostics no longer flicker.** Scope methods on Builder chains (e.g. `Bakery::where(...)->fresh()->get()`) no longer produce false diagnostics that flip on and off between edits. The diagnostic now checks the pre-resolved class returned by the completion pipeline before re-resolving through the cache, preventing stale cache entries from hiding context-specific virtual members.
0 commit comments