Skip to content

Commit 06f8eb9

Browse files
committed
Migrate more code to new Mago packages
1 parent e7c65cb commit 06f8eb9

7 files changed

Lines changed: 1059 additions & 1017 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4545
- **Property hover now shows effective types as a `var` detail line.** Property hovers now mirror method hovers by displaying the resolved/effective property type above the PHP snippet as `**var**`, while the snippet itself shows only the native PHP property declaration. This keeps docblock-inferred, virtual, and schema-derived property types out of the generated signature block. Contributed by @calebdw.
4646
- **Updated the bundled mago toolchain to 1.43.0.** The parser, docblock parser, formatter, and supporting crates are refreshed to the latest upstream release. Contributed by @enwi in https://github.com/PHPantom-dev/phpantom_lsp/pull/234.
4747
- **PHPDoc comments and the types inside them are now parsed by one unified parser.** Docblock parsing moves to `mago-phpdoc-syntax`, replacing the two frozen crates PHPantom used before. Tags written with a `@psalm-` or `@phpstan-` prefix are now recognized as the same tag as their unprefixed form throughout, so a vendor-prefixed variant reliably takes precedence over the plain one, and spellings such as `@phpstan-extends`, `@phpstan-sealed` and `@template-extends` are understood in every place the plain spelling was. Variance annotations (`covariant`, `contravariant`) in generic arguments are parsed directly rather than stripped beforehand, which makes docblock go-to-definition and rename land on the right text in types that use them. A tag indented with more than one space after the `*` is no longer dropped, and a docblock you are still typing (a bare `@`, a half-written type, no closing `*/`) now yields the tags above the cursor instead of nothing, so `@param`, `@return` and `@throws` completion keeps working mid-edit.
48+
- **PHPDoc tags are read from the parsed grammar instead of being scanned again as text.** Every tag the PHPDoc parser understands now hands PHPantom the pieces it already identified: the type, the variable it applies to, the description that follows, a `@method` signature's parameters and template parameters, a `@template` bound and default, an alias definition. PHPantom previously split those back out of the raw comment text with its own scanner, which disagreed with the real grammar in places. Types written across several lines are now handled the same everywhere, so a multi-line `@property`, `@mixin`, or `@template` bound resolves like its single-line form, and trailing prose no longer leaks into a `@phpstan-type` alias definition or a `@method` parameter type. Tags the grammar cannot parse still fall back to the old scan, so half-typed and non-standard annotations keep working.
4849
- **Lower memory use in the cross-file reference index.** The index backing Find References and the reference-count inlay hints now keeps only the distinct files and counts each symbol actually needs, instead of one entry per matching location plus never-read position data. On large projects this removes millions of short-lived allocations and shrinks the index to a fraction of its previous size, with no change to Find References or inlay hint results.
4950
- **Lower memory use for method lookups.** Each resolved class's method name index is now a sorted list searched with binary search instead of a hash map, using about a third of the memory for the same lookup speed. On large Laravel projects, where the resolved-class cache holds thousands of these indexes, this measurably shrinks total memory use.
5051
- **Lower memory use for member access spans.** The subject text recorded for every `->`/`::` access (e.g. `$this` in `$this->save()`) no longer allocates a string when it is a plain slice of the source, which covers the vast majority of accesses in typical PHP code. It now reuses the file's own bytes instead, with an allocation only for the rarer cases (chained calls, `new` expressions) where the recorded text differs from the source.

docs/todo.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ unlikely to move the needle for most users.
196196
| E6 | Stub install prompt for non-Composer projects | Low | Low |
197197
| E7 | [Stub-based framework patches](todo/external-stubs.md#e7-stub-based-framework-patches) | Medium | Medium |
198198
| | **[Performance](todo/performance.md)** | | |
199-
| P44 | [Consume the PHPDoc CST directly instead of re-parsing tag text](todo/performance.md#p44-consume-the-phpdoc-cst-directly-instead-of-re-parsing-tag-text) | Medium | High |
199+
| P45 | [Emit docblock symbol spans from the PHPDoc CST](todo/performance.md#p45-emit-docblock-symbol-spans-from-the-phpdoc-cst) | Low-Medium | Medium-High |
200+
| P46 | [`mago-phpdoc-syntax` cannot parse `@method static (…) name()`](todo/performance.md#p46-mago-phpdoc-syntax-cannot-parse-method-static--name) | Low | Low |
200201
| P30 | [Evaluate migrating parse/resolve/docblock pipeline to `mago-hir`](todo/performance.md#p30-evaluate-migrating-parseresolvedocblock-pipeline-to-mago-hir) (parked — re-evaluated at mago 1.45.0, still no `mago-hir` consumers upstream) | Medium-High | High |
201202
| P43 | [`init_single_project` is the longest single-threaded stretch of a run](todo/performance.md#p43-init_single_project-is-the-longest-single-threaded-stretch-of-a-run) | Medium-High | Medium |
202203
| P16 | [Pre-parsed stub format (eliminate raw PHP embedding)](todo/performance.md#p16-pre-parsed-stub-format-eliminate-raw-php-embedding) | High | Medium-High |

docs/todo/performance.md

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -678,38 +678,62 @@ body-return inference, since fixed by memoization).
678678

679679
---
680680

681-
## P44. Consume the PHPDoc CST directly instead of re-parsing tag text
682-
683-
**Impact: Medium · Effort: High**
684-
685-
`src/docblock/parser.rs` adapts `mago-phpdoc-syntax` by reducing each tag
686-
to its `TagKind`, its vendor prefix, and the *raw source text* of its
687-
value. Everything downstream then re-parses that text: `tags.rs` splits
688-
the type token off the front with `split_type_token`, `templates.rs`
689-
pulls the name and `of` bound out of a `@template` value by whitespace,
690-
`virtual_members.rs` hand-parses `@method` signatures (parameter list,
691-
defaults, inline `<T of Bound>` templates) and `@property` declarations,
692-
and `symbol_map/docblock.rs` re-scans the same text a third time to emit
693-
navigable spans.
694-
695-
The parser already produced all of that structurally on the way in:
696-
`ParamTagValue` carries a `&Type` plus the `Variable`, `TemplateTagValue`
697-
carries the name, variance, bound and default, `MethodTagValue` carries
698-
the return type, visibility, name, template list and a full
699-
`MethodParameterList` with per-parameter types, defaults and variadic
700-
flags, and `AssertTagValue` carries the negation/exactness flags, the
701-
pattern and the subject. Reading those fields instead of re-deriving them
702-
from a string would delete a large amount of hand-rolled scanning, remove
703-
one full parse per tag from the hot path, and fix the class of bug where
704-
our splitter and the real grammar disagree.
705-
706-
The reason it was not done as part of the migration is the shape of the
707-
boundary, not the parsing: `DocblockInfo` is owned (it must outlive the
708-
per-call arena), so exposing the CST means either interning the pieces we
709-
need into owned structures per tag kind, or keeping the arena alive for as
710-
long as the extracted data. The first is mechanical but touches every
711-
extractor in `docblock/`; the second changes the ownership model of every
712-
caller. Sized for several sprint items.
681+
## P45. Emit docblock symbol spans from the PHPDoc CST
682+
683+
**Impact: Low-Medium · Effort: Medium-High**
684+
685+
`src/symbol_map/docblock.rs` is the last consumer that re-derives tag
686+
structure from raw text. The extractors in `docblock/` now read the
687+
parsed grammar (see the `TagValueInfo` snapshot on `TagInfo`), but the
688+
symbol map needs a *byte offset in the file* for every identifier inside
689+
a type, not just the type's text, so it still finds the type itself by
690+
hand: `emit_type_first_tag` strips `@assert` modifiers and calls
691+
`split_type_token`, `extract_method_tag_symbols` re-implements the
692+
`[static] Return name(params)` split with its own paren-depth scan and a
693+
heuristic for telling a return type from a method name,
694+
`extract_template_tag_symbols` re-finds the `of` bound, and
695+
`extract_property_tag_symbols` re-splits type from variable.
696+
697+
The CST has a span on every type, identifier and variable, which would
698+
replace all of that guessing. The obstacle is multi-line types:
699+
`join_multiline_type` builds an offset map so that a position inside the
700+
joined type string can be mapped back to the file, and the folded text
701+
kept in `TagValueInfo` cannot be mapped that way. Recording each type's
702+
span alongside its text is enough to anchor the *start* of a type
703+
precisely; the per-identifier offsets inside a folded multi-line type
704+
still need the offset map, or a walk over the CST type tree in place of
705+
`emit_type_spans`.
706+
707+
Walking the CST directly is the endgame: it deletes `join_multiline_type`,
708+
`emit_type_spans_from_ast` and the offset-map machinery outright, since
709+
every identifier already carries its own span. That is the larger of the
710+
two options and is what makes this Medium-High rather than Medium.
711+
712+
---
713+
714+
## P46. `mago-phpdoc-syntax` cannot parse `@method static (…) name()`
715+
716+
**Impact: Low · Effort: Low (upstream)**
717+
718+
The PHPDoc grammar reads `static` followed by `(` as a method literally
719+
named `static` whose parameter list follows, because
720+
`@method static(int $x)` is how such a method would be written and the
721+
two are indistinguishable once whitespace is discarded. A parenthesised
722+
return type after the `static` modifier therefore fails to parse and the
723+
whole tag is dropped:
724+
725+
```php
726+
/**
727+
* @method static (string|int)[] getArray()
728+
* @method static (callable(): string) getCallable()
729+
*/
730+
```
731+
732+
Both forms appear in Psalm's own magic-method test suite.
733+
`recover_static_method_tag` in `src/docblock/virtual_members.rs` works
734+
around it by re-parsing the value without the modifier and reapplying
735+
`static` afterwards. Report it upstream; remove the workaround once the
736+
grammar disambiguates on the whitespace before the `(`.
713737

714738
---
715739

0 commit comments

Comments
 (0)